1
0
forked from baron/baron-sso

offline 스코프 제거, rp_claims 값 표준화

This commit is contained in:
2026-06-11 14:50:26 +09:00
parent f60b15a17b
commit c495e9119b
26 changed files with 1034 additions and 300 deletions

View File

@@ -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 {