diff --git a/tests/integration/multi-instance/multi_instance_test.go b/tests/integration/multi-instance/multi_instance_test.go index 8c21d7c6..408765b2 100644 --- a/tests/integration/multi-instance/multi_instance_test.go +++ b/tests/integration/multi-instance/multi_instance_test.go @@ -485,7 +485,7 @@ func (e *env) loginAlice(t *testing.T) *userClient { }, nil) apiKey := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "mi-acceptance") - // IAM U4 把建项目改为系统动作 project.create:由引导管理员经真实 API 授予 alice。 + // 建项目需角色上的 project.create:由引导管理员经真实 API 建角色并绑定 alice。 e.grantAliceProjectCreate(t) uc := &userClient{ api: &http.Client{Timeout: httpWait, Transport: bearerRT{key: apiKey}}, @@ -498,44 +498,53 @@ func (e *env) loginAlice(t *testing.T) *userClient { return uc } -// grantAliceProjectCreate 以 mi-root 身份经 admin API 为 alice 追加系统授权 -// project.create(保留其已有授权;PUT 为整体替换,按 authorityEpoch 乐观并发)。 -// mi-root 由 serve 启动时的 SUPER_ADMIN_* 建立,可直接为其发 API key。 +// grantAliceProjectCreate 以 mi-root 身份经 admin API 为 alice 授予项目访问: +// project.create 只由已启用账号所绑定的已启用角色上的单一布尔决定,管理员 +// 身份或系统授权都不再隐含它。故新建角色、写入 project.create +// (allowed=capAllowed=true,按 roleVersion 乐观并发),再按 alice 的 +// stateVersion 绑定该角色。mi-root 由 serve 启动时的 SUPER_ADMIN_* 建立, +// 可直接为其发 API key。 func (e *env) grantAliceProjectCreate(t *testing.T) { t.Helper() rootKey := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", "mi-root", "--name", "mi-acceptance-root") root := &http.Client{Timeout: httpWait, Transport: bearerRT{key: rootKey}} var users []struct { - UserID string `json:"userId"` - Owner string `json:"owner"` + UserID string `json:"userId"` + Owner string `json:"owner"` + StateVersion int64 `json:"stateVersion"` } e.do(t, root, "GET", "/api/admin/users", nil, &users) - userID := "" + userID, stateVersion := "", int64(0) for _, u := range users { if u.Owner == aliceOwner { - userID = u.UserID + userID, stateVersion = u.UserID, u.StateVersion } } if userID == "" { t.Fatalf("user %s not found in /api/admin/users", aliceOwner) } - var grants struct { - Actions []string `json:"actions"` - AuthorityEpoch int64 `json:"authorityEpoch"` + var role struct { + RoleID string `json:"roleId"` + RoleVersion int64 `json:"roleVersion"` } - e.do(t, root, "GET", "/api/admin/users/"+userID+"/system-grants", nil, &grants) - actions := grants.Actions - has := false - for _, a := range actions { - has = has || a == "project.create" + e.do(t, root, "POST", "/api/admin/roles", map[string]any{ + "name": "mi-project-access", + "description": "multi-instance acceptance: project.create", + }, &role) + if role.RoleID == "" { + t.Fatal("POST /api/admin/roles returned no roleId") } - if !has { - actions = append(actions, "project.create") - } - e.do(t, root, "PUT", "/api/admin/users/"+userID+"/system-grants", map[string]any{ - "expectedAuthorityEpoch": grants.AuthorityEpoch, - "idempotencyKey": "mi-alice-project-create", - "actions": actions, + e.do(t, root, "PUT", "/api/admin/roles/"+role.RoleID+"/permissions", map[string]any{ + "expectedRoleVersion": role.RoleVersion, + "idempotencyKey": "mi-role-project-create", + "changes": []map[string]any{ + {"actionId": "project.create", "allowed": true, "capAllowed": true}, + }, + }, nil) + e.do(t, root, "PUT", "/api/admin/users/"+userID+"/role", map[string]any{ + "roleId": role.RoleID, + "expectedStateVersion": stateVersion, + "idempotencyKey": "mi-alice-project-access-role", }, nil) }