forked from baron/baron-sso
profile 클레임 구조 확장
This commit is contained in:
@@ -1040,7 +1040,7 @@ func normalizePhoneForLoginID(phone string) string {
|
|||||||
return domain.NormalizePhoneNumber(phone)
|
return domain.NormalizePhoneNumber(phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOidcClaimsFromTraits(traits map[string]any, scopes []string, tenantID string) map[string]any {
|
func buildOidcClaimsFromTraits(traits map[string]any, scopes []string, tenantID string, profileStatus string) map[string]any {
|
||||||
claims := map[string]any{}
|
claims := map[string]any{}
|
||||||
if traits == nil {
|
if traits == nil {
|
||||||
return claims
|
return claims
|
||||||
@@ -1089,31 +1089,27 @@ func buildOidcClaimsFromTraits(traits map[string]any, scopes []string, tenantID
|
|||||||
|
|
||||||
if _, ok := scopeSet["profile"]; ok {
|
if _, ok := scopeSet["profile"]; ok {
|
||||||
profile := map[string]any{}
|
profile := map[string]any{}
|
||||||
names := map[string]any{}
|
displayProfileName := displayName
|
||||||
for _, key := range []string{
|
if displayProfileName != "" {
|
||||||
"name",
|
profile["name"] = displayProfileName
|
||||||
"displayname",
|
|
||||||
"preferred_username",
|
|
||||||
"given_name",
|
|
||||||
"family_name",
|
|
||||||
"middle_name",
|
|
||||||
"nickname",
|
|
||||||
} {
|
|
||||||
if value := getString(key); value != "" {
|
|
||||||
names[key] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(names) > 0 {
|
if primaryEmail != "" {
|
||||||
profile["names"] = names
|
profile["email"] = primaryEmail
|
||||||
}
|
}
|
||||||
|
if secondaryEmails := collectStringValues(traits, "sub_email", "secondary_emails", "additional_emails", "aliasEmails", "worksmobileAliasEmails"); len(secondaryEmails) > 0 {
|
||||||
emails := collectEmailList(traits, primaryEmail)
|
profile["secondary_emails"] = secondaryEmails
|
||||||
if len(emails) > 0 {
|
|
||||||
profile["emails"] = emails
|
|
||||||
}
|
}
|
||||||
if phone := getString("phone_number"); phone != "" {
|
if phone := getString("phone_number"); phone != "" {
|
||||||
profile["phones"] = []string{phone}
|
profile["phones"] = []string{phone}
|
||||||
}
|
}
|
||||||
|
if employeeID := getString("employee_id"); employeeID != "" {
|
||||||
|
profile["employee_id"] = employeeID
|
||||||
|
}
|
||||||
|
if trimmedStatus := strings.TrimSpace(profileStatus); trimmedStatus != "" {
|
||||||
|
if normalizedStatus := strings.TrimSpace(domain.NormalizeUserStatus(trimmedStatus)); normalizedStatus != "" {
|
||||||
|
profile["status"] = normalizedStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(profile) > 0 {
|
if len(profile) > 0 {
|
||||||
claims["profile"] = profile
|
claims["profile"] = profile
|
||||||
}
|
}
|
||||||
@@ -1218,13 +1214,63 @@ func withRefreshTokenExpiryClaim(claims map[string]any, issuedAt time.Time) map[
|
|||||||
return claims
|
return claims
|
||||||
}
|
}
|
||||||
|
|
||||||
func composeOIDCSessionClaims(client domain.HydraClient, traits map[string]any, scopes []string, tenantID string, sessionID string) map[string]any {
|
func composeOIDCSessionClaims(client domain.HydraClient, traits map[string]any, scopes []string, tenantID string, sessionID string, profileStatus string) map[string]any {
|
||||||
claims := buildOidcClaimsFromTraits(traits, scopes, tenantID)
|
claims := buildOidcClaimsFromTraits(traits, scopes, tenantID, profileStatus)
|
||||||
claims = applyConfiguredIDTokenClaims(claims, client.Metadata)
|
claims = applyConfiguredIDTokenClaims(claims, client.Metadata)
|
||||||
claims = withRefreshTokenExpiryClaim(claims, time.Now())
|
claims = withRefreshTokenExpiryClaim(claims, time.Now())
|
||||||
return withOidcSessionMetadata(claims, sessionID)
|
return withOidcSessionMetadata(claims, sessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectStringValues(traits map[string]any, keys ...string) []string {
|
||||||
|
values := make([]string, 0)
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
add := func(raw string) {
|
||||||
|
value := strings.TrimSpace(raw)
|
||||||
|
if value == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := seen[value]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen[value] = struct{}{}
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
for _, key := range keys {
|
||||||
|
raw, ok := traits[key]
|
||||||
|
if !ok || raw == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch value := raw.(type) {
|
||||||
|
case string:
|
||||||
|
add(value)
|
||||||
|
case []string:
|
||||||
|
for _, item := range value {
|
||||||
|
add(item)
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
for _, item := range value {
|
||||||
|
add(fmt.Sprint(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AuthHandler) resolveProfileStatus(ctx context.Context, subject string) string {
|
||||||
|
if h == nil || h.UserRepo == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
subject = strings.TrimSpace(subject)
|
||||||
|
if subject == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
user, err := h.UserRepo.FindByID(ctx, subject)
|
||||||
|
if err != nil || user == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return domain.NormalizeUserStatus(user.Status)
|
||||||
|
}
|
||||||
|
|
||||||
func applyGlobalCustomClaims(baseClaims map[string]any, traits map[string]any) map[string]any {
|
func applyGlobalCustomClaims(baseClaims map[string]any, traits map[string]any) map[string]any {
|
||||||
if baseClaims == nil {
|
if baseClaims == nil {
|
||||||
baseClaims = map[string]any{}
|
baseClaims = map[string]any{}
|
||||||
@@ -6298,6 +6344,7 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
|||||||
consentRequest.RequestedScope,
|
consentRequest.RequestedScope,
|
||||||
representativeTenantIDFromTraits(identity.Traits),
|
representativeTenantIDFromTraits(identity.Traits),
|
||||||
currentSessionID,
|
currentSessionID,
|
||||||
|
h.resolveProfileStatus(c.Context(), consentRequest.Subject),
|
||||||
)
|
)
|
||||||
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
||||||
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||||
@@ -6336,6 +6383,7 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
|||||||
consentRequest.RequestedScope,
|
consentRequest.RequestedScope,
|
||||||
representativeTenantIDFromTraits(identity.Traits),
|
representativeTenantIDFromTraits(identity.Traits),
|
||||||
currentSessionID,
|
currentSessionID,
|
||||||
|
h.resolveProfileStatus(c.Context(), consentRequest.Subject),
|
||||||
)
|
)
|
||||||
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
||||||
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||||
@@ -6527,6 +6575,7 @@ func (h *AuthHandler) AcceptConsentRequest(c *fiber.Ctx) error {
|
|||||||
consentRequest.RequestedScope,
|
consentRequest.RequestedScope,
|
||||||
representativeTenantIDFromTraits(identity.Traits),
|
representativeTenantIDFromTraits(identity.Traits),
|
||||||
currentSessionID,
|
currentSessionID,
|
||||||
|
h.resolveProfileStatus(c.Context(), consentRequest.Subject),
|
||||||
)
|
)
|
||||||
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
sessionClaims = h.withHanmacFamilyTenantClaims(c.Context(), sessionClaims, identity.Traits, consentRequest.RequestedScope)
|
||||||
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
sessionClaims = h.withRPUserMetadataClaims(c.Context(), sessionClaims, consentRequest.Client, consentRequest.Subject)
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ func TestBuildOidcClaimsFromTraits_DynamicClaims(t *testing.T) {
|
|||||||
"email": "user@baron.com",
|
"email": "user@baron.com",
|
||||||
"name": "홍길동",
|
"name": "홍길동",
|
||||||
"phone_number": "+821012345678",
|
"phone_number": "+821012345678",
|
||||||
|
"employee_id": "EMP-001",
|
||||||
|
"sub_email": []any{"alias1@baron.com", "alias2@baron.com"},
|
||||||
"tenant_id": "primary-tenant-999", // Added primary tenant
|
"tenant_id": "primary-tenant-999", // Added primary tenant
|
||||||
"tenant-1": map[string]any{
|
"tenant-1": map[string]any{
|
||||||
"department": "개발팀",
|
"department": "개발팀",
|
||||||
@@ -86,13 +88,18 @@ func TestBuildOidcClaimsFromTraits_DynamicClaims(t *testing.T) {
|
|||||||
scopes := []string{"openid", "profile"}
|
scopes := []string{"openid", "profile"}
|
||||||
|
|
||||||
t.Run("No tenantID", func(t *testing.T) {
|
t.Run("No tenantID", func(t *testing.T) {
|
||||||
claims := buildOidcClaimsFromTraits(traits, scopes, "")
|
claims := buildOidcClaimsFromTraits(traits, scopes, "", "leave_of_absence")
|
||||||
assert.Equal(t, "user@baron.com", claims["email"])
|
assert.Equal(t, "user@baron.com", claims["email"])
|
||||||
assert.Equal(t, "홍길동", claims["name"])
|
assert.Equal(t, "홍길동", claims["name"])
|
||||||
assert.Equal(t, "primary-tenant-999", claims["tenant_id"])
|
assert.Equal(t, "primary-tenant-999", claims["tenant_id"])
|
||||||
assert.Nil(t, claims["department"])
|
assert.Nil(t, claims["department"])
|
||||||
assert.Nil(t, claims["grade"])
|
assert.Nil(t, claims["grade"])
|
||||||
profile := claims["profile"].(map[string]any)
|
profile := claims["profile"].(map[string]any)
|
||||||
|
assert.Equal(t, "홍길동", profile["name"])
|
||||||
|
assert.Equal(t, "user@baron.com", profile["email"])
|
||||||
|
assert.Equal(t, "EMP-001", profile["employee_id"])
|
||||||
|
assert.Equal(t, []string{"alias1@baron.com", "alias2@baron.com"}, profile["secondary_emails"])
|
||||||
|
assert.Equal(t, "temporary_leave", profile["status"])
|
||||||
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
||||||
|
|
||||||
assert.Nil(t, claims["tenants"])
|
assert.Nil(t, claims["tenants"])
|
||||||
@@ -102,13 +109,18 @@ func TestBuildOidcClaimsFromTraits_DynamicClaims(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("With tenant-1", func(t *testing.T) {
|
t.Run("With tenant-1", func(t *testing.T) {
|
||||||
claims := buildOidcClaimsFromTraits(traits, scopes, "tenant-1")
|
claims := buildOidcClaimsFromTraits(traits, scopes, "tenant-1", "leave_of_absence")
|
||||||
assert.Equal(t, "user@baron.com", claims["email"])
|
assert.Equal(t, "user@baron.com", claims["email"])
|
||||||
assert.Equal(t, "홍길동", claims["name"])
|
assert.Equal(t, "홍길동", claims["name"])
|
||||||
assert.Equal(t, "tenant-1", claims["tenant_id"])
|
assert.Equal(t, "tenant-1", claims["tenant_id"])
|
||||||
assert.Nil(t, claims["department"])
|
assert.Nil(t, claims["department"])
|
||||||
assert.Nil(t, claims["grade"])
|
assert.Nil(t, claims["grade"])
|
||||||
profile := claims["profile"].(map[string]any)
|
profile := claims["profile"].(map[string]any)
|
||||||
|
assert.Equal(t, "홍길동", profile["name"])
|
||||||
|
assert.Equal(t, "user@baron.com", profile["email"])
|
||||||
|
assert.Equal(t, "EMP-001", profile["employee_id"])
|
||||||
|
assert.Equal(t, []string{"alias1@baron.com", "alias2@baron.com"}, profile["secondary_emails"])
|
||||||
|
assert.Equal(t, "temporary_leave", profile["status"])
|
||||||
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
||||||
|
|
||||||
assert.Nil(t, claims["tenants"])
|
assert.Nil(t, claims["tenants"])
|
||||||
@@ -118,13 +130,18 @@ func TestBuildOidcClaimsFromTraits_DynamicClaims(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("With tenant-2", func(t *testing.T) {
|
t.Run("With tenant-2", func(t *testing.T) {
|
||||||
claims := buildOidcClaimsFromTraits(traits, scopes, "tenant-2")
|
claims := buildOidcClaimsFromTraits(traits, scopes, "tenant-2", "leave_of_absence")
|
||||||
assert.Equal(t, "user@baron.com", claims["email"])
|
assert.Equal(t, "user@baron.com", claims["email"])
|
||||||
assert.Equal(t, "홍길동", claims["name"])
|
assert.Equal(t, "홍길동", claims["name"])
|
||||||
assert.Equal(t, "tenant-2", claims["tenant_id"])
|
assert.Equal(t, "tenant-2", claims["tenant_id"])
|
||||||
assert.Nil(t, claims["department"])
|
assert.Nil(t, claims["department"])
|
||||||
assert.Nil(t, claims["grade"])
|
assert.Nil(t, claims["grade"])
|
||||||
profile := claims["profile"].(map[string]any)
|
profile := claims["profile"].(map[string]any)
|
||||||
|
assert.Equal(t, "홍길동", profile["name"])
|
||||||
|
assert.Equal(t, "user@baron.com", profile["email"])
|
||||||
|
assert.Equal(t, "EMP-001", profile["employee_id"])
|
||||||
|
assert.Equal(t, []string{"alias1@baron.com", "alias2@baron.com"}, profile["secondary_emails"])
|
||||||
|
assert.Equal(t, "temporary_leave", profile["status"])
|
||||||
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
||||||
|
|
||||||
assert.Nil(t, claims["tenants"])
|
assert.Nil(t, claims["tenants"])
|
||||||
@@ -132,13 +149,18 @@ func TestBuildOidcClaimsFromTraits_DynamicClaims(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("With non-existent tenant", func(t *testing.T) {
|
t.Run("With non-existent tenant", func(t *testing.T) {
|
||||||
claims := buildOidcClaimsFromTraits(traits, scopes, "tenant-3")
|
claims := buildOidcClaimsFromTraits(traits, scopes, "tenant-3", "leave_of_absence")
|
||||||
assert.Equal(t, "user@baron.com", claims["email"])
|
assert.Equal(t, "user@baron.com", claims["email"])
|
||||||
assert.Equal(t, "홍길동", claims["name"])
|
assert.Equal(t, "홍길동", claims["name"])
|
||||||
assert.Equal(t, "tenant-3", claims["tenant_id"])
|
assert.Equal(t, "tenant-3", claims["tenant_id"])
|
||||||
assert.Nil(t, claims["department"])
|
assert.Nil(t, claims["department"])
|
||||||
assert.Nil(t, claims["grade"])
|
assert.Nil(t, claims["grade"])
|
||||||
profile := claims["profile"].(map[string]any)
|
profile := claims["profile"].(map[string]any)
|
||||||
|
assert.Equal(t, "홍길동", profile["name"])
|
||||||
|
assert.Equal(t, "user@baron.com", profile["email"])
|
||||||
|
assert.Equal(t, "EMP-001", profile["employee_id"])
|
||||||
|
assert.Equal(t, []string{"alias1@baron.com", "alias2@baron.com"}, profile["secondary_emails"])
|
||||||
|
assert.Equal(t, "temporary_leave", profile["status"])
|
||||||
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
||||||
|
|
||||||
assert.Nil(t, claims["tenants"])
|
assert.Nil(t, claims["tenants"])
|
||||||
@@ -147,11 +169,16 @@ func TestBuildOidcClaimsFromTraits_DynamicClaims(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Tenants scope includes detailed tenant metadata", func(t *testing.T) {
|
t.Run("Tenants scope includes detailed tenant metadata", func(t *testing.T) {
|
||||||
claims := buildOidcClaimsFromTraits(traits, []string{"openid", "profile", "tenants"}, "tenant-1")
|
claims := buildOidcClaimsFromTraits(traits, []string{"openid", "profile", "tenants"}, "tenant-1", "leave_of_absence")
|
||||||
assert.Equal(t, "tenant-1", claims["tenant_id"])
|
assert.Equal(t, "tenant-1", claims["tenant_id"])
|
||||||
assert.Equal(t, "개발팀", claims["department"])
|
assert.Equal(t, "개발팀", claims["department"])
|
||||||
assert.Equal(t, "선임", claims["grade"])
|
assert.Equal(t, "선임", claims["grade"])
|
||||||
profile := claims["profile"].(map[string]any)
|
profile := claims["profile"].(map[string]any)
|
||||||
|
assert.Equal(t, "홍길동", profile["name"])
|
||||||
|
assert.Equal(t, "user@baron.com", profile["email"])
|
||||||
|
assert.Equal(t, "EMP-001", profile["employee_id"])
|
||||||
|
assert.Equal(t, []string{"alias1@baron.com", "alias2@baron.com"}, profile["secondary_emails"])
|
||||||
|
assert.Equal(t, "temporary_leave", profile["status"])
|
||||||
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
assert.Equal(t, []string{"+821012345678"}, profile["phones"])
|
||||||
assert.NotNil(t, claims["tenants"])
|
assert.NotNil(t, claims["tenants"])
|
||||||
assert.Contains(t, claims["joined_tenants"], "tenant-1")
|
assert.Contains(t, claims["joined_tenants"], "tenant-1")
|
||||||
@@ -856,7 +883,7 @@ func TestBuildOidcClaimsFromTraits_IncludesGlobalCustomClaims(t *testing.T) {
|
|||||||
"writePermission": "admin_only",
|
"writePermission": "admin_only",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, []string{"openid", "profile", "email"}, "")
|
}, []string{"openid", "profile", "email"}, "", "")
|
||||||
|
|
||||||
assert.Equal(t, "2026-06-09", claims["contract_date"])
|
assert.Equal(t, "2026-06-09", claims["contract_date"])
|
||||||
assert.Equal(t, "2026-06-09T09:30:00+09:00", claims["approved_at"])
|
assert.Equal(t, "2026-06-09T09:30:00+09:00", claims["approved_at"])
|
||||||
|
|||||||
@@ -238,15 +238,17 @@ Baron은 기본적으로 대표소속 tenant와 전체 소속 tenant 목록을
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"emails": [
|
"email": "hanmac-user@example.com",
|
||||||
"hanmac-user@example.com"
|
"secondary_emails": [
|
||||||
|
"alias1@hanmaceng.co.kr",
|
||||||
|
"alias2@hanmaceng.co.kr"
|
||||||
],
|
],
|
||||||
"phones": [
|
"phones": [
|
||||||
"+821012345678"
|
"+821012345678"
|
||||||
],
|
],
|
||||||
"names": {
|
"name": "한맥 사용자",
|
||||||
"name": "한맥 사용자"
|
"employee_id": "EMP-001",
|
||||||
}
|
"status": "temporary_leave"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user