148 lines
5.4 KiB
Go
148 lines
5.4 KiB
Go
|
|
//go:build e2e
|
|||
|
|
|
|||
|
|
package e2e
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"net/http"
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"github.com/stretchr/testify/require"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// TestJourney_TenantAdminCustomRole 模擬 Tenant Admin 為一個新工種「qa_engineer」
|
|||
|
|
// 從零建出可用角色的流程:
|
|||
|
|
// 建 Role → 從 catalog 取 perm id → 全量 PUT 權限 → 設 IdP Mapping → 指派給 user
|
|||
|
|
// → 用該 user 視角看 /permissions/me 確認權限生效 → 撤銷 → 刪 mapping → 刪 role
|
|||
|
|
//
|
|||
|
|
// 整段都用 seed owner token;no-role user 用來驗證「被指派後拿到權限」。
|
|||
|
|
func TestJourney_TenantAdminCustomRole(t *testing.T) {
|
|||
|
|
j := NewJourney(t, "J-2", "Tenant Admin 從零建立 qa_engineer 角色 → 指派 → 驗證 → 撤銷")
|
|||
|
|
defer j.Summary()
|
|||
|
|
|
|||
|
|
owner := NewClient(t)
|
|||
|
|
noRole := NewNoRoleClient(t)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
roleKey = "journey_qa_engineer"
|
|||
|
|
externalKey = fmt.Sprintf("journey-qa-group-%s", noRole.Fixture.UID)
|
|||
|
|
roleID string
|
|||
|
|
permissionID string
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 清掉殘留(前一輪可能 fail 沒走到清理 step)
|
|||
|
|
t.Cleanup(func() {
|
|||
|
|
_, _ = owner.Do(t, http.MethodDelete, "/api/v1/permissions/role-mappings", map[string]string{
|
|||
|
|
"external_source": "zitadel",
|
|||
|
|
"external_key": externalKey,
|
|||
|
|
}, true)
|
|||
|
|
if roleID != "" {
|
|||
|
|
_, _ = owner.Do(t, http.MethodDelete, "/api/v1/permissions/users/"+noRole.Fixture.UID+"/roles/"+roleID, nil, true)
|
|||
|
|
_, _ = owner.Do(t, http.MethodDelete, "/api/v1/permissions/roles/"+roleID, nil, true)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
j.Step("1", "POST /permissions/roles — 建立 qa_engineer 角色", func(t *testing.T) {
|
|||
|
|
env := owner.DoExpectOK(t, http.MethodPost, "/api/v1/permissions/roles", map[string]string{
|
|||
|
|
"key": roleKey,
|
|||
|
|
"display_name": "QA Engineer",
|
|||
|
|
"status": "open",
|
|||
|
|
}, true)
|
|||
|
|
var role struct {
|
|||
|
|
ID string `json:"id"`
|
|||
|
|
Key string `json:"key"`
|
|||
|
|
}
|
|||
|
|
require.NoError(t, json.Unmarshal(env.Data, &role))
|
|||
|
|
require.Equal(t, roleKey, role.Key)
|
|||
|
|
require.NotEmpty(t, role.ID)
|
|||
|
|
roleID = role.ID
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
j.Step("2", "GET /permissions/catalog — 從平台 catalog 撈第一個 leaf permission id", func(t *testing.T) {
|
|||
|
|
permissionID = firstCatalogPermissionID(t, owner)
|
|||
|
|
require.NotEmpty(t, permissionID)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
j.Step("3", "PUT /permissions/roles/:id/permissions — 把選到的 permission 灌進 role", func(t *testing.T) {
|
|||
|
|
require.NotEmpty(t, roleID)
|
|||
|
|
require.NotEmpty(t, permissionID)
|
|||
|
|
owner.DoExpectOK(t, http.MethodPut, "/api/v1/permissions/roles/"+roleID+"/permissions", map[string][]string{
|
|||
|
|
"permission_ids": {permissionID},
|
|||
|
|
}, true)
|
|||
|
|
|
|||
|
|
// 讀回比對:parent closure 會把祖先一起加進去,所以集合 >= 1 + 必含 permissionID
|
|||
|
|
env := owner.DoExpectOK(t, http.MethodGet, "/api/v1/permissions/roles/"+roleID+"/permissions", nil, true)
|
|||
|
|
var list struct {
|
|||
|
|
Permissions []struct {
|
|||
|
|
ID string `json:"id"`
|
|||
|
|
} `json:"permissions"`
|
|||
|
|
}
|
|||
|
|
require.NoError(t, json.Unmarshal(env.Data, &list))
|
|||
|
|
found := false
|
|||
|
|
for _, p := range list.Permissions {
|
|||
|
|
if p.ID == permissionID {
|
|||
|
|
found = true
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
require.True(t, found)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
j.Step("4", "PUT /permissions/role-mappings — 設定 zitadel:qa-group → qa_engineer 對映", func(t *testing.T) {
|
|||
|
|
env := owner.DoExpectOK(t, http.MethodPut, "/api/v1/permissions/role-mappings", map[string]string{
|
|||
|
|
"external_source": "zitadel",
|
|||
|
|
"external_key": externalKey,
|
|||
|
|
"internal_role_key": roleKey,
|
|||
|
|
}, true)
|
|||
|
|
var mapping struct {
|
|||
|
|
ExternalKey string `json:"external_key"`
|
|||
|
|
InternalRoleKey string `json:"internal_role_key"`
|
|||
|
|
}
|
|||
|
|
require.NoError(t, json.Unmarshal(env.Data, &mapping))
|
|||
|
|
require.Equal(t, externalKey, mapping.ExternalKey)
|
|||
|
|
require.Equal(t, roleKey, mapping.InternalRoleKey)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
j.Step("5", "POST /permissions/users/:uid/roles — 把 qa_engineer 手動指派給 no-role user", func(t *testing.T) {
|
|||
|
|
require.NotEmpty(t, roleID)
|
|||
|
|
owner.DoExpectOK(t, http.MethodPost, "/api/v1/permissions/users/"+noRole.Fixture.UID+"/roles", map[string]string{
|
|||
|
|
"role_id": roleID,
|
|||
|
|
}, true)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
j.Step("6", "GET /permissions/me (no-role user 視角) — 確認新角色 + 權限都拿到了", func(t *testing.T) {
|
|||
|
|
env := noRole.DoExpectOK(t, http.MethodGet, "/api/v1/permissions/me?include_tree=false", nil, true)
|
|||
|
|
var data struct {
|
|||
|
|
Roles []string `json:"roles"`
|
|||
|
|
Permissions map[string]string `json:"permissions"`
|
|||
|
|
}
|
|||
|
|
require.NoError(t, json.Unmarshal(env.Data, &data))
|
|||
|
|
require.Contains(t, data.Roles, roleKey, "no-role user should now have qa_engineer")
|
|||
|
|
require.NotEmpty(t, data.Permissions, "expected non-empty permissions after role assignment")
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
j.Step("7", "DELETE /permissions/users/:uid/roles/:id — 撤銷指派", func(t *testing.T) {
|
|||
|
|
require.NotEmpty(t, roleID)
|
|||
|
|
owner.DoExpectOK(t, http.MethodDelete, "/api/v1/permissions/users/"+noRole.Fixture.UID+"/roles/"+roleID, nil, true)
|
|||
|
|
|
|||
|
|
env := noRole.DoExpectOK(t, http.MethodGet, "/api/v1/permissions/me?include_tree=false", nil, true)
|
|||
|
|
var data struct {
|
|||
|
|
Roles []string `json:"roles"`
|
|||
|
|
}
|
|||
|
|
require.NoError(t, json.Unmarshal(env.Data, &data))
|
|||
|
|
require.NotContains(t, data.Roles, roleKey)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
j.Step("8", "DELETE /permissions/role-mappings + /roles — 收尾刪 mapping + role", func(t *testing.T) {
|
|||
|
|
owner.DoExpectOK(t, http.MethodDelete, "/api/v1/permissions/role-mappings", map[string]string{
|
|||
|
|
"external_source": "zitadel",
|
|||
|
|
"external_key": externalKey,
|
|||
|
|
}, true)
|
|||
|
|
require.NotEmpty(t, roleID)
|
|||
|
|
owner.DoExpectOK(t, http.MethodDelete, "/api/v1/permissions/roles/"+roleID, nil, true)
|
|||
|
|
// 標記已清,t.Cleanup 那邊就不會再重複打
|
|||
|
|
roleID = ""
|
|||
|
|
})
|
|||
|
|
}
|