forked from baron/baron-sso
RP 관계 범위의 콘솔 접근 허용
This commit is contained in:
@@ -156,11 +156,26 @@ func (m *mockConsentRepo) ListBySubject(ctx context.Context, subject string) ([]
|
||||
}
|
||||
func (m *mockConsentRepo) Delete(ctx context.Context, clientID, subject string) error { return nil }
|
||||
func (m *mockConsentRepo) List(ctx context.Context, clientID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error) {
|
||||
return nil, 0, nil
|
||||
results := make([]domain.ClientConsentWithTenantInfo, 0, len(m.consents))
|
||||
for _, consent := range m.consents {
|
||||
if consent.ClientID == clientID {
|
||||
results = append(results, domain.ClientConsentWithTenantInfo{ClientConsent: consent})
|
||||
}
|
||||
}
|
||||
return results, int64(len(results)), nil
|
||||
}
|
||||
|
||||
func (m *mockConsentRepo) ListByTenant(ctx context.Context, clientID, tenantID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error) {
|
||||
return nil, 0, nil
|
||||
results := make([]domain.ClientConsentWithTenantInfo, 0, len(m.consents))
|
||||
for _, consent := range m.consents {
|
||||
if consent.ClientID == clientID {
|
||||
results = append(results, domain.ClientConsentWithTenantInfo{
|
||||
ClientConsent: consent,
|
||||
TenantID: tenantID,
|
||||
})
|
||||
}
|
||||
}
|
||||
return results, int64(len(results)), nil
|
||||
}
|
||||
|
||||
// --- Mock Secret Repository ---
|
||||
|
||||
@@ -205,6 +205,7 @@ var allowedRelyingPartyOperatorRelations = map[string]struct{}{
|
||||
"consent_viewer": {},
|
||||
"consent_revoker": {},
|
||||
"relationship_viewer": {},
|
||||
"audit_viewer": {},
|
||||
"status_operator": {},
|
||||
}
|
||||
|
||||
@@ -221,6 +222,15 @@ func isDevConsoleRoleAllowed(role string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func isDevConsoleViewerRole(role string) bool {
|
||||
switch normalizeUserRole(role) {
|
||||
case domain.RoleSuperAdmin, domain.RoleTenantAdmin, domain.RoleRPAdmin, domain.RoleUser:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DevHandler) getCurrentProfile(c *fiber.Ctx) *domain.UserProfileResponse {
|
||||
if profile, ok := c.Locals("user_profile").(*domain.UserProfileResponse); ok && profile != nil {
|
||||
return profile
|
||||
@@ -388,6 +398,51 @@ func (h *DevHandler) canManageClientRelations(c *fiber.Ctx, profile *domain.User
|
||||
return canAccessClientByLegacyScope(profile, summary)
|
||||
}
|
||||
|
||||
func (h *DevHandler) auditClientIDsByPermit(c *fiber.Ctx, profile *domain.UserProfileResponse, clientFilter string) map[string]struct{} {
|
||||
ids := make(map[string]struct{})
|
||||
if profile == nil || h.Hydra == nil {
|
||||
return ids
|
||||
}
|
||||
if normalizeUserRole(profile.Role) == domain.RoleSuperAdmin {
|
||||
return ids
|
||||
}
|
||||
|
||||
clientFilter = strings.TrimSpace(clientFilter)
|
||||
if clientFilter != "" {
|
||||
summary, err := h.loadClientSummary(c.Context(), clientFilter)
|
||||
if err == nil && h.canOperateClientByPermit(c, profile, summary, "view_audit_logs") {
|
||||
ids[summary.ID] = struct{}{}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
clients, err := h.Hydra.ListClients(c.Context(), 500, 0)
|
||||
if err != nil {
|
||||
slog.Warn("Failed to list clients for audit permission filtering", "error", err)
|
||||
return ids
|
||||
}
|
||||
for _, client := range clients {
|
||||
if isHiddenSystemClient(client) {
|
||||
continue
|
||||
}
|
||||
summary := h.mapClientSummary(client)
|
||||
if h.canOperateClientByPermit(c, profile, summary, "view_audit_logs") {
|
||||
ids[summary.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func mergeStringSets(dst map[string]struct{}, src map[string]struct{}) map[string]struct{} {
|
||||
if dst == nil {
|
||||
dst = make(map[string]struct{}, len(src))
|
||||
}
|
||||
for key := range src {
|
||||
dst[key] = struct{}{}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func canAccessClientByLegacyScope(profile *domain.UserProfileResponse, summary clientSummary) bool {
|
||||
if profile == nil {
|
||||
return false
|
||||
@@ -938,7 +993,7 @@ func (h *DevHandler) ListClients(c *fiber.Ctx) error {
|
||||
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
|
||||
}
|
||||
role := normalizeUserRole(profile.Role)
|
||||
if !isDevConsoleRoleAllowed(role) {
|
||||
if !isDevConsoleViewerRole(role) {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden")
|
||||
}
|
||||
|
||||
@@ -972,14 +1027,16 @@ func (h *DevHandler) ListClients(c *fiber.Ctx) error {
|
||||
summary := h.mapClientSummary(client)
|
||||
|
||||
// 1. [Security] Filter out 'private' clients if user is not an AppManager
|
||||
if summary.Type == "private" && !isAppManager {
|
||||
canViewByPermit := h.canViewClientByPermit(c, profile, summary)
|
||||
|
||||
if summary.Type == "private" && !isAppManager && !canViewByPermit {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. [Isolation] If not SuperAdmin, only show clients belonging to the same tenant
|
||||
if !isSuperAdmin {
|
||||
clientTenantID, _ := summary.Metadata["tenant_id"].(string)
|
||||
if clientTenantID != userTenantID && !h.canViewClientByPermit(c, profile, summary) {
|
||||
if clientTenantID != userTenantID && !canViewByPermit {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -987,13 +1044,13 @@ func (h *DevHandler) ListClients(c *fiber.Ctx) error {
|
||||
// 3. [Role Scope] RP Admin can only access managed RP IDs unless explicit Keto permit exists
|
||||
if role == domain.RoleRPAdmin && len(allowedClientIDs) > 0 {
|
||||
if _, ok := allowedClientIDs[summary.ID]; !ok {
|
||||
if !h.canViewClientByPermit(c, profile, summary) {
|
||||
if !canViewByPermit {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isSuperAdmin && !canAccessClientByLegacyScope(profile, summary) && !h.canViewClientByPermit(c, profile, summary) {
|
||||
if !isSuperAdmin && !canAccessClientByLegacyScope(profile, summary) && !canViewByPermit {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1163,7 +1220,7 @@ func (h *DevHandler) GetClient(c *fiber.Ctx) error {
|
||||
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
|
||||
}
|
||||
role := normalizeUserRole(profile.Role)
|
||||
if !isDevConsoleRoleAllowed(role) {
|
||||
if !isDevConsoleViewerRole(role) {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden")
|
||||
}
|
||||
|
||||
@@ -1172,7 +1229,7 @@ func (h *DevHandler) GetClient(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// Check permission for private clients
|
||||
if summary.Type == "private" {
|
||||
if summary.Type == "private" && !h.canViewClientByPermit(c, profile, summary) {
|
||||
isAppManager, err := h.checkAppManagerPermission(c)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, "permission check error")
|
||||
@@ -1730,7 +1787,7 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
|
||||
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
|
||||
}
|
||||
role := normalizeUserRole(profile.Role)
|
||||
if !isDevConsoleRoleAllowed(role) {
|
||||
if !isDevConsoleViewerRole(role) {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden")
|
||||
}
|
||||
client, err := h.Hydra.GetClient(c.Context(), clientID)
|
||||
@@ -1741,7 +1798,8 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
summary := h.mapClientSummary(*client)
|
||||
if !canAccessClientByLegacyScope(profile, summary) && !h.canOperateClientByPermit(c, profile, summary, "view_consents") {
|
||||
canViewConsentsByPermit := h.canOperateClientByPermit(c, profile, summary, "view_consents")
|
||||
if !canAccessClientByLegacyScope(profile, summary) && !canViewConsentsByPermit {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden: rp_admin scope does not include this client")
|
||||
}
|
||||
|
||||
@@ -1755,7 +1813,7 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
|
||||
// [Isolation] Get admin tenant ID from locals or header
|
||||
adminTenantID := ""
|
||||
if profile != nil {
|
||||
if role != domain.RoleSuperAdmin && profile.TenantID != nil {
|
||||
if role != domain.RoleSuperAdmin && !canViewConsentsByPermit && profile.TenantID != nil {
|
||||
adminTenantID = *profile.TenantID
|
||||
}
|
||||
}
|
||||
@@ -1813,12 +1871,14 @@ func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
userName := ""
|
||||
identity, err := h.KratosAdmin.GetIdentity(c.Context(), consent.Subject)
|
||||
if err == nil && identity != nil {
|
||||
if name, ok := identity.Traits["name"].(string); ok {
|
||||
userName = name
|
||||
} else if email, ok := identity.Traits["email"].(string); ok {
|
||||
userName = email
|
||||
if h.KratosAdmin != nil {
|
||||
identity, err := h.KratosAdmin.GetIdentity(c.Context(), consent.Subject)
|
||||
if err == nil && identity != nil {
|
||||
if name, ok := identity.Traits["name"].(string); ok {
|
||||
userName = name
|
||||
} else if email, ok := identity.Traits["email"].(string); ok {
|
||||
userName = email
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1854,7 +1914,7 @@ func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
|
||||
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
|
||||
}
|
||||
role := normalizeUserRole(profile.Role)
|
||||
if !isDevConsoleRoleAllowed(role) {
|
||||
if !isDevConsoleViewerRole(role) {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden")
|
||||
}
|
||||
if clientID != "" {
|
||||
@@ -2123,13 +2183,9 @@ func (h *DevHandler) ListAuditLogs(c *fiber.Ctx) error {
|
||||
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
|
||||
}
|
||||
role := normalizeUserRole(profile.Role)
|
||||
if !isDevConsoleRoleAllowed(role) {
|
||||
if !isDevConsoleViewerRole(role) {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden")
|
||||
}
|
||||
allowedClientIDs := managedClientIDsFromProfile(profile)
|
||||
if role == domain.RoleRPAdmin && len(allowedClientIDs) == 0 {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden: rp_admin has no managed clients")
|
||||
}
|
||||
|
||||
limit := c.QueryInt("limit", 50)
|
||||
if limit <= 0 {
|
||||
@@ -2142,11 +2198,21 @@ func (h *DevHandler) ListAuditLogs(c *fiber.Ctx) error {
|
||||
actionFilter := strings.ToUpper(strings.TrimSpace(c.Query("action")))
|
||||
clientFilter := strings.TrimSpace(c.Query("client_id"))
|
||||
statusFilter := strings.ToLower(strings.TrimSpace(c.Query("status")))
|
||||
allowedClientIDs := managedClientIDsFromProfile(profile)
|
||||
allowedClientIDs = mergeStringSets(allowedClientIDs, h.auditClientIDsByPermit(c, profile, clientFilter))
|
||||
if role != domain.RoleSuperAdmin && len(allowedClientIDs) == 0 && (role == domain.RoleRPAdmin || role == domain.RoleUser) {
|
||||
return c.JSON(devAuditListResponse{
|
||||
Items: []domain.AuditLog{},
|
||||
Limit: limit,
|
||||
Cursor: c.Query("cursor"),
|
||||
})
|
||||
}
|
||||
|
||||
tenantFilter := strings.TrimSpace(c.Query("tenant_id"))
|
||||
if tenantFilter == "" {
|
||||
tenantFilter = h.resolveDevTenantScope(c)
|
||||
}
|
||||
if role != domain.RoleSuperAdmin && tenantFilter == "" {
|
||||
if role != domain.RoleSuperAdmin && tenantFilter == "" && len(allowedClientIDs) == 0 {
|
||||
tenantFilter = tenantIDFromProfile(profile)
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +229,54 @@ func TestListClients_Success(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestListClients_UserSeesOnlyClientsAllowedByReBAC(t *testing.T) {
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if r.URL.Path == "/clients" {
|
||||
return httpJSONAny(r, http.StatusOK, []map[string]interface{}{
|
||||
{"client_id": "client-denied", "client_name": "Denied App", "metadata": map[string]interface{}{"tenant_id": "tenant-a", "status": "active"}},
|
||||
{"client_id": "client-allowed", "client_name": "Allowed App", "metadata": map[string]interface{}{"tenant_id": "tenant-b", "status": "active"}},
|
||||
}), nil
|
||||
}
|
||||
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
||||
})
|
||||
|
||||
mockKeto := new(devMockKetoService)
|
||||
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "Tenant", "tenant-a", "view_dev_console").Return(false, nil)
|
||||
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "RelyingParty", "client-denied", "view").Return(false, nil)
|
||||
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "Tenant", "tenant-b", "view_dev_console").Return(false, nil)
|
||||
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "RelyingParty", "client-allowed", "view").Return(true, nil)
|
||||
|
||||
h := &DevHandler{
|
||||
Hydra: &service.HydraAdminService{
|
||||
AdminURL: "http://hydra.test",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
},
|
||||
Keto: mockKeto,
|
||||
}
|
||||
app := fiber.New()
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
tenantID := "tenant-a"
|
||||
c.Locals("user_profile", &domain.UserProfileResponse{
|
||||
ID: "user-1",
|
||||
Role: domain.RoleUser,
|
||||
TenantID: &tenantID,
|
||||
})
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/api/v1/dev/clients", h.ListClients)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients", nil)
|
||||
resp, _ := app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
var result clientListResponse
|
||||
_ = json.NewDecoder(resp.Body).Decode(&result)
|
||||
if assert.Len(t, result.Items, 1) {
|
||||
assert.Equal(t, "client-allowed", result.Items[0].ID)
|
||||
}
|
||||
mockKeto.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestCreateClient_ReservedSystemNameForbidden(t *testing.T) {
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
t.Fatalf("hydra should not be called when reserved system name is rejected")
|
||||
@@ -1310,6 +1358,133 @@ func TestListAuditLogs_RPAdminScope(t *testing.T) {
|
||||
assert.Equal(t, "evt-1", result.Items[0].EventID)
|
||||
}
|
||||
|
||||
func TestListAuditLogs_UserAllowedByRPAuditPermission(t *testing.T) {
|
||||
auditRepo := &mockAuditRepo{
|
||||
logs: []domain.AuditLog{
|
||||
{
|
||||
EventID: "evt-allowed",
|
||||
EventType: "POST /api/v1/dev/clients/client-allowed/secret/rotate",
|
||||
Status: "success",
|
||||
Timestamp: time.Now().UTC(),
|
||||
Details: `{"target_id":"client-allowed","tenant_id":"tenant-a","action":"ROTATE_SECRET"}`,
|
||||
},
|
||||
{
|
||||
EventID: "evt-denied",
|
||||
EventType: "POST /api/v1/dev/clients/client-denied/secret/rotate",
|
||||
Status: "success",
|
||||
Timestamp: time.Now().UTC().Add(-time.Minute),
|
||||
Details: `{"target_id":"client-denied","tenant_id":"tenant-b","action":"ROTATE_SECRET"}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if r.URL.Path == "/clients" {
|
||||
return httpJSONAny(r, http.StatusOK, []map[string]interface{}{
|
||||
{"client_id": "client-allowed", "client_name": "Allowed App", "metadata": map[string]interface{}{"tenant_id": "tenant-a"}},
|
||||
{"client_id": "client-denied", "client_name": "Denied App", "metadata": map[string]interface{}{"tenant_id": "tenant-b"}},
|
||||
}), nil
|
||||
}
|
||||
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
||||
})
|
||||
|
||||
mockKeto := new(devMockKetoService)
|
||||
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "RelyingParty", "client-allowed", "view_audit_logs").Return(true, nil)
|
||||
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "RelyingParty", "client-denied", "view_audit_logs").Return(false, nil)
|
||||
|
||||
h := &DevHandler{
|
||||
Hydra: &service.HydraAdminService{
|
||||
AdminURL: "http://hydra.test",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
},
|
||||
AuditRepo: auditRepo,
|
||||
Keto: mockKeto,
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
tenantID := "tenant-a"
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
c.Locals("user_profile", &domain.UserProfileResponse{
|
||||
ID: "user-1",
|
||||
Role: domain.RoleUser,
|
||||
TenantID: &tenantID,
|
||||
})
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/api/v1/dev/audit-logs", h.ListAuditLogs)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/dev/audit-logs?limit=50", nil)
|
||||
resp, _ := app.Test(req, -1)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result devAuditListResponse
|
||||
_ = json.NewDecoder(resp.Body).Decode(&result)
|
||||
if assert.Len(t, result.Items, 1) {
|
||||
assert.Equal(t, "evt-allowed", result.Items[0].EventID)
|
||||
}
|
||||
mockKeto.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestListConsents_UserAllowedByRPAdminsRelation(t *testing.T) {
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if r.Method == http.MethodGet && r.URL.Path == "/clients/client-1" {
|
||||
return httpJSONAny(r, http.StatusOK, map[string]any{
|
||||
"client_id": "client-1",
|
||||
"client_name": "App One",
|
||||
"metadata": map[string]any{
|
||||
"tenant_id": "tenant-1",
|
||||
"status": "active",
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
||||
})
|
||||
|
||||
mockKeto := new(devMockKetoService)
|
||||
mockKeto.On("CheckPermission", mock.Anything, "User:user-1", "RelyingParty", "client-1", "view_consents").Return(true, nil)
|
||||
|
||||
h := &DevHandler{
|
||||
Hydra: &service.HydraAdminService{
|
||||
AdminURL: "http://hydra.test",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
},
|
||||
ConsentRepo: &mockConsentRepo{
|
||||
consents: []domain.ClientConsent{
|
||||
{
|
||||
ClientID: "client-1",
|
||||
Subject: "subject-1",
|
||||
GrantedScopes: []string{"openid", "profile"},
|
||||
CreatedAt: time.Now().UTC(),
|
||||
},
|
||||
},
|
||||
},
|
||||
Keto: mockKeto,
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
tenantID := "tenant-1"
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
c.Locals("user_profile", &domain.UserProfileResponse{
|
||||
ID: "user-1",
|
||||
Role: domain.RoleUser,
|
||||
TenantID: &tenantID,
|
||||
})
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/api/v1/dev/consents", h.ListConsents)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/dev/consents?client_id=client-1", nil)
|
||||
resp, _ := app.Test(req, -1)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result consentListResponse
|
||||
_ = json.NewDecoder(resp.Body).Decode(&result)
|
||||
if assert.Len(t, result.Items, 1) {
|
||||
assert.Equal(t, "client-1", result.Items[0].ClientID)
|
||||
assert.Equal(t, "subject-1", result.Items[0].Subject)
|
||||
}
|
||||
mockKeto.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestListClientRelations_RPAdminAllowedByViewRelationshipsPermission(t *testing.T) {
|
||||
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if r.Method == http.MethodGet && r.URL.Path == "/clients/client-1" {
|
||||
@@ -1330,7 +1505,7 @@ func TestListClientRelations_RPAdminAllowedByViewRelationshipsPermission(t *test
|
||||
mockKeto.On("ListRelations", mock.Anything, "RelyingParty", "client-1", "config_editor", "").Return([]service.RelationTuple{
|
||||
{Object: "client-1", Relation: "config_editor", SubjectID: "User:user-2"},
|
||||
}, nil)
|
||||
for _, relation := range []string{"admins", "creator", "secret_rotator", "jwks_viewer", "jwks_operator", "consent_viewer", "consent_revoker", "relationship_viewer", "status_operator"} {
|
||||
for _, relation := range []string{"admins", "creator", "secret_rotator", "jwks_viewer", "jwks_operator", "consent_viewer", "consent_revoker", "relationship_viewer", "audit_viewer", "status_operator"} {
|
||||
mockKeto.On("ListRelations", mock.Anything, "RelyingParty", "client-1", relation, "").Return([]service.RelationTuple{}, nil)
|
||||
}
|
||||
mockKratos := new(devMockKratosAdmin)
|
||||
|
||||
Reference in New Issue
Block a user