1
0
forked from baron/baron-sso

profile 클레임 구조 확장

This commit is contained in:
2026-06-17 11:50:34 +09:00
parent fd05c049d3
commit efab2a7291
3 changed files with 111 additions and 33 deletions

View File

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

View File

@@ -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"])

View File

@@ -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"
}
}
```