1
0
forked from baron/baron-sso

RP 관계 범위의 콘솔 접근 허용

This commit is contained in:
2026-04-20 10:46:17 +09:00
parent 0b8eaec636
commit 51e46a4d00
10 changed files with 376 additions and 109 deletions

View File

@@ -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)
}