package handler import ( "baron-sso-backend/internal/domain" "errors" "sort" "strings" "github.com/gofiber/fiber/v2" ) const ( clientTenantAccessRestrictedKey = "tenant_access_restricted" clientAllowedTenantsKey = "allowed_tenants" ) func normalizeClientTenantAccessMetadata(metadata map[string]interface{}) (map[string]interface{}, error) { if metadata == nil { metadata = map[string]interface{}{} } restricted := readMetadataBoolValue(metadata, clientTenantAccessRestrictedKey) allowedTenants := normalizeMetadataStringSlice(metadata[clientAllowedTenantsKey]) ownerTenantID := normalizeMetadataString(metadata["tenant_id"]) if len(allowedTenants) > 0 { restricted = true } if !restricted { delete(metadata, clientAllowedTenantsKey) metadata[clientTenantAccessRestrictedKey] = false return metadata, nil } if ownerTenantID != "" { allowedTenants = append(allowedTenants, ownerTenantID) } allowedTenants = uniqueSortedStrings(allowedTenants) if len(allowedTenants) == 0 { return nil, errors.New("allowed_tenants is required when tenant_access_restricted is enabled") } metadata[clientTenantAccessRestrictedKey] = true metadata[clientAllowedTenantsKey] = allowedTenants return metadata, nil } func clientTenantAccessRestricted(metadata map[string]interface{}) bool { if metadata == nil { return false } if readMetadataBoolValue(metadata, clientTenantAccessRestrictedKey) { return true } return len(normalizeMetadataStringSlice(metadata[clientAllowedTenantsKey])) > 0 } func clientAllowedTenants(metadata map[string]interface{}) []string { if metadata == nil { return nil } if !clientTenantAccessRestricted(metadata) { return nil } return uniqueSortedStrings(normalizeMetadataStringSlice(metadata[clientAllowedTenantsKey])) } func normalizeMetadataStringSlice(raw any) []string { switch value := raw.(type) { case []string: return uniqueSortedStrings(value) case []any: items := make([]string, 0, len(value)) for _, item := range value { if s, ok := item.(string); ok { items = append(items, s) } } return uniqueSortedStrings(items) default: return nil } } func normalizeMetadataString(raw any) string { s, ok := raw.(string) if !ok { return "" } return strings.TrimSpace(s) } func uniqueSortedStrings(values []string) []string { if len(values) == 0 { return nil } seen := make(map[string]struct{}, len(values)) out := make([]string, 0, len(values)) for _, value := range values { trimmed := strings.TrimSpace(value) if trimmed == "" { continue } if _, ok := seen[trimmed]; ok { continue } seen[trimmed] = struct{}{} out = append(out, trimmed) } sort.Strings(out) return out } func clientTenantAccessAllowed(profile *domain.UserProfileResponse, client domain.HydraClient) bool { if !clientTenantAccessRestricted(client.Metadata) { return true } allowed := clientAllowedTenants(client.Metadata) if len(allowed) == 0 { return false } keys := manageableTenantKeysFromProfile(profile) if len(keys) == 0 { return false } for _, tenantID := range allowed { if _, ok := keys[strings.ToLower(strings.TrimSpace(tenantID))]; ok { return true } } return false } func tenantNotAllowedError(c *fiber.Ctx) error { return errorJSONCode(c, fiber.StatusForbidden, "tenant_not_allowed", "허용되지 않은 테넌트입니다.") } func isClientTenantAccessAllowed(profile *domain.UserProfileResponse, client domain.HydraClient) bool { if profile == nil { return false } return clientTenantAccessAllowed(profile, client) }