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