forked from baron/baron-sso
Merge pull request 'feature/df-cosent-skip' (#626) from feature/df-cosent-skip into dev
Reviewed-on: baron/baron-sso#626
This commit is contained in:
@@ -1144,6 +1144,7 @@ func buildOidcClaimsFromTraits(traits map[string]any, scopes []string, tenantID
|
|||||||
|
|
||||||
return claims
|
return claims
|
||||||
}
|
}
|
||||||
|
|
||||||
func withOidcSessionMetadata(claims map[string]any, sessionID string) map[string]any {
|
func withOidcSessionMetadata(claims map[string]any, sessionID string) map[string]any {
|
||||||
if claims == nil {
|
if claims == nil {
|
||||||
claims = map[string]any{}
|
claims = map[string]any{}
|
||||||
@@ -5129,13 +5130,57 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
|||||||
"scopes", consentRequest.RequestedScope,
|
"scopes", consentRequest.RequestedScope,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// [New] 로컬 DB에서 기존 동의 내역 확인 (강제 자동 승인 전략)
|
||||||
|
// Hydra가 skip을 주지 않더라도, 우리 DB에 이미 기록이 있다면 승인 처리함
|
||||||
|
if !consentRequest.Skip && h.ConsentRepo != nil && consentRequest.Subject != "" {
|
||||||
|
existingConsent, err := h.ConsentRepo.Find(c.Context(), consentRequest.Client.ClientID, consentRequest.Subject)
|
||||||
|
if err == nil && existingConsent != nil {
|
||||||
|
// 요청된 스코프가 이미 동의된 스코프 내에 있는지 확인
|
||||||
|
allGranted := true
|
||||||
|
grantedMap := make(map[string]bool)
|
||||||
|
for _, s := range existingConsent.GrantedScopes {
|
||||||
|
grantedMap[s] = true
|
||||||
|
}
|
||||||
|
for _, s := range consentRequest.RequestedScope {
|
||||||
|
if !grantedMap[s] {
|
||||||
|
allGranted = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allGranted {
|
||||||
|
slog.Info("Auto-approving based on local DB consent record", "subject", consentRequest.Subject, "client", consentRequest.Client.ClientID)
|
||||||
|
identity, err := h.KratosAdmin.GetIdentity(c.Context(), consentRequest.Subject)
|
||||||
|
if err == nil && identity != nil {
|
||||||
|
currentSessionID := h.resolveCurrentSessionID(c)
|
||||||
|
var tenantID string
|
||||||
|
if consentRequest.Client.Metadata != nil {
|
||||||
|
if tid, ok := consentRequest.Client.Metadata["tenant_id"].(string); ok {
|
||||||
|
tenantID = tid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessionClaims := withOidcSessionMetadata(
|
||||||
|
buildOidcClaimsFromTraits(identity.Traits, consentRequest.RequestedScope, tenantID),
|
||||||
|
currentSessionID,
|
||||||
|
)
|
||||||
|
acceptResp, err := h.Hydra.AcceptConsentRequest(c.Context(), challenge, consentRequest, sessionClaims)
|
||||||
|
if err == nil {
|
||||||
|
return c.JSON(acceptResp)
|
||||||
|
}
|
||||||
|
slog.Error("failed to force auto-accept based on local DB", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hydra가 이전에 동의한 이력이 있어 skip을 권장하는 경우, 즉시 승인 처리
|
// Hydra가 이전에 동의한 이력이 있어 skip을 권장하는 경우, 즉시 승인 처리
|
||||||
if consentRequest.Skip {
|
if consentRequest.Skip {
|
||||||
identity, err := h.KratosAdmin.GetIdentity(c.Context(), consentRequest.Subject)
|
identity, err := h.KratosAdmin.GetIdentity(c.Context(), consentRequest.Subject)
|
||||||
if err != nil || identity == nil {
|
if err != nil || identity == nil {
|
||||||
slog.Error("failed to load identity for skip consent", "error", err, "subject", consentRequest.Subject)
|
slog.Error("failed to load identity for skip consent", "error", err, "subject", consentRequest.Subject, "client_id", consentRequest.Client.ClientID)
|
||||||
// 신원 정보를 가져오지 못하면 자동 승인을 진행할 수 없으므로 일반 흐름(UI 노출)으로 진행
|
// 신원 정보를 가져오지 못하면 자동 승인을 진행할 수 없으므로 일반 흐름(UI 노출)으로 진행
|
||||||
} else {
|
} else {
|
||||||
|
currentSessionID := h.resolveCurrentSessionID(c)
|
||||||
var tenantID string
|
var tenantID string
|
||||||
if consentRequest.Client.Metadata != nil {
|
if consentRequest.Client.Metadata != nil {
|
||||||
if tid, ok := consentRequest.Client.Metadata["tenant_id"].(string); ok {
|
if tid, ok := consentRequest.Client.Metadata["tenant_id"].(string); ok {
|
||||||
@@ -5145,7 +5190,7 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
sessionClaims := withOidcSessionMetadata(
|
sessionClaims := withOidcSessionMetadata(
|
||||||
buildOidcClaimsFromTraits(identity.Traits, consentRequest.RequestedScope, tenantID),
|
buildOidcClaimsFromTraits(identity.Traits, consentRequest.RequestedScope, tenantID),
|
||||||
h.resolveCurrentSessionID(c),
|
currentSessionID,
|
||||||
)
|
)
|
||||||
|
|
||||||
// [Debug] 실제 생성된 클레임 출력 (요청사항 확인용 - 자동 승인 시)
|
// [Debug] 실제 생성된 클레임 출력 (요청사항 확인용 - 자동 승인 시)
|
||||||
@@ -5158,7 +5203,7 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
acceptResp, err := h.Hydra.AcceptConsentRequest(c.Context(), challenge, consentRequest, sessionClaims)
|
acceptResp, err := h.Hydra.AcceptConsentRequest(c.Context(), challenge, consentRequest, sessionClaims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to auto-accept hydra consent request", "error", err)
|
slog.Error("failed to auto-accept hydra consent request", "error", err, "client_id", consentRequest.Client.ClientID, "subject", consentRequest.Subject)
|
||||||
// 자동 승인 실패 시 일반 흐름으로 진행
|
// 자동 승인 실패 시 일반 흐름으로 진행
|
||||||
} else {
|
} else {
|
||||||
// [New] Sync to local DB even on auto-accept to ensure data consistency
|
// [New] Sync to local DB even on auto-accept to ensure data consistency
|
||||||
@@ -5177,7 +5222,6 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
|||||||
"scopes": consentRequest.RequestedScope,
|
"scopes": consentRequest.RequestedScope,
|
||||||
"client_name": consentRequest.Client.ClientName,
|
"client_name": consentRequest.Client.ClientName,
|
||||||
}
|
}
|
||||||
currentSessionID := h.resolveCurrentSessionID(c)
|
|
||||||
if currentSessionID != "" {
|
if currentSessionID != "" {
|
||||||
detailsMap["session_id"] = currentSessionID
|
detailsMap["session_id"] = currentSessionID
|
||||||
detailsMap["approved_session_id"] = currentSessionID
|
detailsMap["approved_session_id"] = currentSessionID
|
||||||
@@ -5199,7 +5243,7 @@ func (h *AuthHandler) GetConsentRequest(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Consent skipped and auto-accepted", "subject", consentRequest.Subject, "client", consentRequest.Client.ClientID)
|
slog.Info("Consent skipped and auto-accepted", "subject", consentRequest.Subject, "client", consentRequest.Client.ClientID, "session_id", currentSessionID)
|
||||||
return c.JSON(acceptResp)
|
return c.JSON(acceptResp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,4 +265,3 @@ func TestGetConsentRequest_Skip_DynamicClaims(t *testing.T) {
|
|||||||
assert.Equal(t, "Security", capturedClaims["department"])
|
assert.Equal(t, "Security", capturedClaims["department"])
|
||||||
assert.Equal(t, "Officer", capturedClaims["position"])
|
assert.Equal(t, "Officer", capturedClaims["position"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,19 @@ func (m *mockConsentRepo) ListBySubject(ctx context.Context, subject string) ([]
|
|||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
func (m *mockConsentRepo) Delete(ctx context.Context, clientID, subject string) error { return nil }
|
|
||||||
|
func (m *mockConsentRepo) Find(ctx context.Context, clientID, subject string) (*domain.ClientConsent, error) {
|
||||||
|
for _, consent := range m.consents {
|
||||||
|
if consent.ClientID == clientID && consent.Subject == subject {
|
||||||
|
found := consent
|
||||||
|
return &found, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockConsentRepo) Delete(ctx context.Context, subject, clientID string) error { return nil }
|
||||||
|
|
||||||
func (m *mockConsentRepo) List(ctx context.Context, clientID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error) {
|
func (m *mockConsentRepo) List(ctx context.Context, clientID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error) {
|
||||||
results := make([]domain.ClientConsentWithTenantInfo, 0, len(m.consents))
|
results := make([]domain.ClientConsentWithTenantInfo, 0, len(m.consents))
|
||||||
for _, consent := range m.consents {
|
for _, consent := range m.consents {
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ func (h *UserHandler) GetUser(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(h.mapIdentitySummary(c.Context(), *identity))
|
return c.JSON(h.mapIdentitySummary(c.Context(), *identity))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
|
func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
|
||||||
if h.OryProvider == nil || h.KratosAdmin == nil {
|
if h.OryProvider == nil || h.KratosAdmin == nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"baron-sso-backend/internal/domain"
|
"baron-sso-backend/internal/domain"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -13,6 +14,7 @@ type ClientConsentRepository interface {
|
|||||||
List(ctx context.Context, clientID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error)
|
List(ctx context.Context, clientID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error)
|
||||||
ListByTenant(ctx context.Context, clientID, tenantID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error)
|
ListByTenant(ctx context.Context, clientID, tenantID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error)
|
||||||
ListBySubject(ctx context.Context, subject string) ([]domain.ClientConsent, error)
|
ListBySubject(ctx context.Context, subject string) ([]domain.ClientConsent, error)
|
||||||
|
Find(ctx context.Context, clientID, subject string) (*domain.ClientConsent, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientConsentRepo struct {
|
type clientConsentRepo struct {
|
||||||
@@ -23,6 +25,20 @@ func NewClientConsentRepository(db *gorm.DB) ClientConsentRepository {
|
|||||||
return &clientConsentRepo{db: db}
|
return &clientConsentRepo{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *clientConsentRepo) Find(ctx context.Context, clientID, subject string) (*domain.ClientConsent, error) {
|
||||||
|
var consent domain.ClientConsent
|
||||||
|
err := r.db.WithContext(ctx).Unscoped().
|
||||||
|
Where("client_id = ? AND subject = ?", clientID, subject).
|
||||||
|
First(&consent).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &consent, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *clientConsentRepo) Upsert(ctx context.Context, consent *domain.ClientConsent) error {
|
func (r *clientConsentRepo) Upsert(ctx context.Context, consent *domain.ClientConsent) error {
|
||||||
return r.db.WithContext(ctx).Unscoped().
|
return r.db.WithContext(ctx).Unscoped().
|
||||||
Where("client_id = ? AND subject = ?", consent.ClientID, consent.Subject).
|
Where("client_id = ? AND subject = ?", consent.ClientID, consent.Subject).
|
||||||
|
|||||||
@@ -156,4 +156,4 @@
|
|||||||
"authorizer": { "handler": "allow" },
|
"authorizer": { "handler": "allow" },
|
||||||
"mutators": [{ "handler": "noop" }]
|
"mutators": [{ "handler": "noop" }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Reference in New Issue
Block a user