forked from baron/baron-sso
offline 스코프 제거, rp_claims 값 표준화
This commit is contained in:
@@ -1202,7 +1202,7 @@ func buildOidcClaimsFromTraits(traits map[string]any, scopes []string, tenantID
|
||||
if includeTenantDetails {
|
||||
// tenant 스코프가 있을 때만 대표소속 namespace metadata를 top-level claim으로 펼칩니다.
|
||||
if namespaced, ok := traits[tenantID].(map[string]any); ok {
|
||||
maps.Copy(claims, namespaced)
|
||||
maps.Copy(claims, sanitizeTenantClaimMetadata(namespaced))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1213,11 +1213,11 @@ func buildOidcClaimsFromTraits(traits map[string]any, scopes []string, tenantID
|
||||
|
||||
// Heuristic: if a trait value is a map, it's treated as namespaced metadata for a tenant
|
||||
for k, v := range traits {
|
||||
if k == "metadata" || k == "global_custom_claims" || k == "global_custom_claim_types" || k == "global_custom_claim_permissions" {
|
||||
if isReservedTenantTraitKey(k) {
|
||||
continue
|
||||
}
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
allTenants[k] = m
|
||||
allTenants[k] = sanitizeTenantClaimMetadata(m)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1272,7 +1272,7 @@ func applyGlobalCustomClaims(baseClaims map[string]any, traits map[string]any) m
|
||||
if key == "" || value == nil {
|
||||
continue
|
||||
}
|
||||
if key == "rp_claims" || key == "rp_profiles" {
|
||||
if isReservedTopLevelCustomClaimKey(key) {
|
||||
continue
|
||||
}
|
||||
if _, exists := baseClaims[key]; exists {
|
||||
@@ -1322,6 +1322,7 @@ func (h *AuthHandler) withHanmacFamilyTenantClaims(ctx context.Context, claims m
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tenantClaim = sanitizeTenantClaimMetadata(tenantClaim)
|
||||
|
||||
tenant, ancestors, inHanmacFamily := h.resolveHanmacFamilyTenantClaimAncestry(ctx, tenantKey)
|
||||
if !inHanmacFamily || tenant == nil {
|
||||
@@ -1613,9 +1614,12 @@ func applyConfiguredIDTokenClaims(baseClaims map[string]any, metadata map[string
|
||||
}
|
||||
|
||||
for _, claim := range normalizedClaims {
|
||||
if claim.Namespace == "rp_claims" && isReservedRPClaimKey(claim.Key) {
|
||||
continue
|
||||
}
|
||||
if claim.Nullable && strings.TrimSpace(claim.Value) == "" {
|
||||
if claim.Namespace == "rp_claims" {
|
||||
rpClaims[claim.Key] = nil
|
||||
rpClaims[claim.Key] = buildRPClaimPayload(nil, claim, nil)
|
||||
continue
|
||||
}
|
||||
if _, exists := baseClaims[claim.Key]; !exists {
|
||||
@@ -1631,7 +1635,7 @@ func applyConfiguredIDTokenClaims(baseClaims map[string]any, metadata map[string
|
||||
}
|
||||
|
||||
if claim.Namespace == "rp_claims" {
|
||||
rpClaims[claim.Key] = value
|
||||
rpClaims[claim.Key] = buildRPClaimPayload(value, claim, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1677,6 +1681,9 @@ func (h *AuthHandler) withRPUserMetadataClaims(ctx context.Context, claims map[s
|
||||
}
|
||||
|
||||
for _, claim := range rpClaimDefinitions {
|
||||
if isReservedRPClaimKey(claim.Key) {
|
||||
continue
|
||||
}
|
||||
raw, ok := row.Metadata[claim.Key]
|
||||
if !ok || raw == nil {
|
||||
continue
|
||||
@@ -1686,7 +1693,7 @@ func (h *AuthHandler) withRPUserMetadataClaims(ctx context.Context, claims map[s
|
||||
slog.Warn("failed to coerce rp user metadata claim", "client_id", clientID, "subject", subject, "key", claim.Key, "value_type", claim.ValueType)
|
||||
continue
|
||||
}
|
||||
rpClaims[claim.Key] = value
|
||||
rpClaims[claim.Key] = buildRPClaimPayload(value, claim, row.Metadata[claim.Key+"_permissions"])
|
||||
}
|
||||
|
||||
if len(rpClaims) > 0 {
|
||||
@@ -1723,6 +1730,92 @@ func extractRPClaimDefinitions(metadata map[string]any) []normalizedIDTokenClaim
|
||||
return definitions
|
||||
}
|
||||
|
||||
func isReservedTopLevelCustomClaimKey(key string) bool {
|
||||
return strings.HasPrefix(strings.TrimSpace(key), "rp_")
|
||||
}
|
||||
|
||||
func isReservedRPClaimKey(key string) bool {
|
||||
key = strings.TrimSpace(key)
|
||||
if strings.HasPrefix(key, "rp_") {
|
||||
return true
|
||||
}
|
||||
switch key {
|
||||
case "", "tenant_id", "tenants", "joined_tenants", "lead_tenants":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isReservedTenantTraitKey(key string) bool {
|
||||
key = strings.TrimSpace(key)
|
||||
if strings.HasPrefix(key, "rp_") {
|
||||
return true
|
||||
}
|
||||
switch key {
|
||||
case "metadata",
|
||||
"global_custom_claims",
|
||||
"global_custom_claim_types",
|
||||
"global_custom_claim_permissions":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isRPClaimRelatedTenantMetadataKey(key string) bool {
|
||||
return strings.HasPrefix(strings.TrimSpace(key), "rp_")
|
||||
}
|
||||
|
||||
func sanitizeTenantClaimMetadata(raw map[string]any) map[string]any {
|
||||
cleaned := make(map[string]any, len(raw))
|
||||
for key, value := range raw {
|
||||
if isRPClaimRelatedTenantMetadataKey(key) {
|
||||
continue
|
||||
}
|
||||
cleaned[key] = sanitizeTenantClaimValue(value)
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
func sanitizeTenantClaimValue(value any) any {
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
return sanitizeTenantClaimMetadata(typed)
|
||||
case []any:
|
||||
items := make([]any, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
items = append(items, sanitizeTenantClaimValue(item))
|
||||
}
|
||||
return items
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func buildRPClaimPayload(value any, claim normalizedIDTokenClaim, rawPermission any) map[string]any {
|
||||
readPermission := normalizeCustomClaimPermission(claim.ReadPermission)
|
||||
writePermission := normalizeCustomClaimPermission(claim.WritePermission)
|
||||
|
||||
if permissions, ok := rawPermission.(map[string]any); ok {
|
||||
if rawRead := readInterfaceString(permissions["readPermission"], ""); rawRead != "" {
|
||||
readPermission = normalizeCustomClaimPermission(rawRead)
|
||||
}
|
||||
if rawWrite := readInterfaceString(permissions["writePermission"], ""); rawWrite != "" {
|
||||
writePermission = normalizeCustomClaimPermission(rawWrite)
|
||||
}
|
||||
}
|
||||
if writePermission == "user_and_admin" {
|
||||
readPermission = "user_and_admin"
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"value": value,
|
||||
"readPermission": readPermission,
|
||||
"writePermission": writePermission,
|
||||
}
|
||||
}
|
||||
|
||||
func coerceRPUserMetadataClaimValue(raw any, valueType string) (any, bool) {
|
||||
switch value := raw.(type) {
|
||||
case string:
|
||||
@@ -1752,6 +1845,12 @@ func coerceRPUserMetadataClaimValue(raw any, valueType string) (any, bool) {
|
||||
return value, true
|
||||
}
|
||||
case float64:
|
||||
if valueType == "date" || valueType == "datetime" {
|
||||
if value == math.Trunc(value) {
|
||||
return value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
if valueType == "float" {
|
||||
return value, true
|
||||
}
|
||||
@@ -1760,6 +1859,12 @@ func coerceRPUserMetadataClaimValue(raw any, valueType string) (any, bool) {
|
||||
}
|
||||
case float32:
|
||||
floatValue := float64(value)
|
||||
if valueType == "date" || valueType == "datetime" {
|
||||
if floatValue == math.Trunc(floatValue) {
|
||||
return floatValue, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
if valueType == "float" {
|
||||
return floatValue, true
|
||||
}
|
||||
@@ -1767,6 +1872,9 @@ func coerceRPUserMetadataClaimValue(raw any, valueType string) (any, bool) {
|
||||
return floatValue, true
|
||||
}
|
||||
case int:
|
||||
if valueType == "date" || valueType == "datetime" {
|
||||
return float64(value), true
|
||||
}
|
||||
if valueType == "number" {
|
||||
return float64(value), true
|
||||
}
|
||||
@@ -1774,6 +1882,9 @@ func coerceRPUserMetadataClaimValue(raw any, valueType string) (any, bool) {
|
||||
return float64(value), true
|
||||
}
|
||||
case int64:
|
||||
if valueType == "date" || valueType == "datetime" {
|
||||
return float64(value), true
|
||||
}
|
||||
if valueType == "number" {
|
||||
return float64(value), true
|
||||
}
|
||||
@@ -1781,6 +1892,10 @@ func coerceRPUserMetadataClaimValue(raw any, valueType string) (any, bool) {
|
||||
return float64(value), true
|
||||
}
|
||||
case json.Number:
|
||||
if valueType == "date" || valueType == "datetime" {
|
||||
parsed, err := value.Int64()
|
||||
return float64(parsed), err == nil
|
||||
}
|
||||
if valueType == "number" {
|
||||
parsed, err := value.Int64()
|
||||
return float64(parsed), err == nil
|
||||
@@ -1795,120 +1910,6 @@ func coerceRPUserMetadataClaimValue(raw any, valueType string) (any, bool) {
|
||||
return parsed, err == nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) withRPProfileClaims(ctx context.Context, claims map[string]any, client domain.HydraClient, subject string) map[string]any {
|
||||
if claims == nil {
|
||||
claims = map[string]any{}
|
||||
}
|
||||
if h == nil || h.RPUserMetadataRepo == nil {
|
||||
return claims
|
||||
}
|
||||
|
||||
clientID := strings.TrimSpace(client.ClientID)
|
||||
subject = strings.TrimSpace(subject)
|
||||
if clientID == "" || subject == "" {
|
||||
return claims
|
||||
}
|
||||
|
||||
claimKeys := extractClaimEnabledCustomUserSchemaKeys(client.Metadata)
|
||||
if len(claimKeys) == 0 {
|
||||
return claims
|
||||
}
|
||||
|
||||
row, err := h.RPUserMetadataRepo.Get(ctx, clientID, subject)
|
||||
if err != nil || row == nil || len(row.Metadata) == 0 {
|
||||
return claims
|
||||
}
|
||||
|
||||
fields := make(map[string]any)
|
||||
for _, key := range claimKeys {
|
||||
raw, ok := row.Metadata[key]
|
||||
if !ok || raw == nil {
|
||||
continue
|
||||
}
|
||||
if value, ok := raw.(string); ok {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
fields[key] = value
|
||||
continue
|
||||
}
|
||||
fields[key] = raw
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return claims
|
||||
}
|
||||
|
||||
profile := map[string]any{
|
||||
"client_id": clientID,
|
||||
"fields": fields,
|
||||
}
|
||||
if existing, ok := claims["rp_profiles"].([]any); ok {
|
||||
claims["rp_profiles"] = append(existing, profile)
|
||||
return claims
|
||||
}
|
||||
if existing, ok := claims["rp_profiles"].([]any); ok {
|
||||
claims["rp_profiles"] = append(existing, profile)
|
||||
return claims
|
||||
}
|
||||
claims["rp_profiles"] = []any{profile}
|
||||
return claims
|
||||
}
|
||||
|
||||
func extractClaimEnabledCustomUserSchemaKeys(metadata map[string]any) []string {
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
rawSchema, ok := metadata["customUserSchema"]
|
||||
if !ok || rawSchema == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var items []any
|
||||
switch schema := rawSchema.(type) {
|
||||
case []any:
|
||||
items = schema
|
||||
case []map[string]any:
|
||||
items = make([]any, 0, len(schema))
|
||||
for _, item := range schema {
|
||||
items = append(items, item)
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(items))
|
||||
seen := make(map[string]struct{})
|
||||
for _, item := range items {
|
||||
field, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
if typed, typedOK := item.(map[string]any); typedOK {
|
||||
field = typed
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
enabled, _ := field["claimEnabled"].(bool)
|
||||
if !enabled {
|
||||
enabled, _ = field["claim_enabled"].(bool)
|
||||
}
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
key, _ := field["key"].(string)
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[key]; exists {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func collectEmailList(traits map[string]any, primaryEmail string) []string {
|
||||
emails := make([]string, 0)
|
||||
seen := make(map[string]struct{})
|
||||
@@ -6196,7 +6197,6 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
||||
)
|
||||
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
||||
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||
sessionClaims = h.withRPProfileClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||
acceptResp, err := h.Hydra.AcceptConsentRequest(c.Context(), challenge, consentRequest, sessionClaims)
|
||||
if err == nil {
|
||||
if err := h.emitRPUsageAuthorizationGranted(c, consentRequest, profile, currentSessionID, true, challenge); err != nil {
|
||||
@@ -6235,7 +6235,6 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
||||
)
|
||||
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
||||
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||
sessionClaims = h.withRPProfileClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||
|
||||
// [Debug] 실제 생성된 클레임 출력 (요청사항 확인용 - 자동 승인 시)
|
||||
appEnv := strings.ToLower(os.Getenv("APP_ENV"))
|
||||
@@ -6427,7 +6426,6 @@ func (h *AuthHandler) AcceptConsentRequest(c *fiber.Ctx) error {
|
||||
)
|
||||
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
||||
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||
sessionClaims = h.withRPProfileClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||
|
||||
// [Debug] 실제 생성된 클레임 출력 (요청사항 확인용)
|
||||
appEnv := strings.ToLower(os.Getenv("APP_ENV"))
|
||||
@@ -8432,7 +8430,7 @@ func buildHydraAuthorizationURL(clientID string, scopes []string, redirectURIs [
|
||||
seen := map[string]struct{}{}
|
||||
for _, scope := range append([]string{"openid"}, scopes...) {
|
||||
scope = strings.TrimSpace(scope)
|
||||
if scope == "" {
|
||||
if scope == "" || isRefreshTokenScopeAlias(scope) {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[scope]; ok {
|
||||
|
||||
Reference in New Issue
Block a user