forked from baron/baron-sso
154 lines
4.1 KiB
Go
154 lines
4.1 KiB
Go
package service
|
|
|
|
import (
|
|
"baron-sso-backend/internal/domain"
|
|
"baron-sso-backend/internal/repository"
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type UserProjectionSyncService struct {
|
|
kratos KratosAdminService
|
|
repo repository.UserProjectionRepository
|
|
}
|
|
|
|
type UserProjectionReconciler interface {
|
|
Reconcile(ctx context.Context) (int, error)
|
|
}
|
|
|
|
func NewUserProjectionSyncService(kratos KratosAdminService, repo repository.UserProjectionRepository) *UserProjectionSyncService {
|
|
return &UserProjectionSyncService{
|
|
kratos: kratos,
|
|
repo: repo,
|
|
}
|
|
}
|
|
|
|
func (s *UserProjectionSyncService) Reconcile(ctx context.Context) (int, error) {
|
|
if s == nil || s.kratos == nil || s.repo == nil {
|
|
return 0, fmt.Errorf("user projection sync dependencies are not configured")
|
|
}
|
|
|
|
identities, err := s.kratos.ListIdentities(ctx)
|
|
if err != nil {
|
|
_ = s.repo.MarkFailed(ctx, err)
|
|
return 0, err
|
|
}
|
|
|
|
users := make([]domain.User, 0, len(identities))
|
|
for _, identity := range identities {
|
|
users = append(users, MapKratosIdentityToLocalUser(identity))
|
|
}
|
|
if err := s.repo.ReplaceAllFromKratos(ctx, users); err != nil {
|
|
_ = s.repo.MarkFailed(ctx, err)
|
|
return 0, err
|
|
}
|
|
return len(users), nil
|
|
}
|
|
|
|
func MapKratosIdentityToLocalUser(identity KratosIdentity) domain.User {
|
|
traits := identity.Traits
|
|
now := time.Now()
|
|
createdAt := identity.CreatedAt
|
|
if createdAt.IsZero() {
|
|
createdAt = now
|
|
}
|
|
updatedAt := identity.UpdatedAt
|
|
if updatedAt.IsZero() {
|
|
updatedAt = now
|
|
}
|
|
|
|
role, ok := domain.NormalizeRoleAlias(kratosProjectionTraitString(traits, "role"))
|
|
if !ok {
|
|
role, ok = domain.NormalizeRoleAlias(kratosProjectionTraitString(traits, "grade"))
|
|
if !ok {
|
|
role = domain.RoleUser
|
|
}
|
|
}
|
|
grade := kratosProjectionTraitString(traits, "grade")
|
|
if _, ok := domain.NormalizeRoleAlias(grade); ok {
|
|
grade = ""
|
|
}
|
|
|
|
user := domain.User{
|
|
ID: identity.ID,
|
|
Email: kratosProjectionTraitString(traits, "email"),
|
|
Name: kratosProjectionTraitString(traits, "name"),
|
|
Phone: domain.NormalizePhoneNumber(kratosProjectionTraitString(traits, "phone_number")),
|
|
Role: role,
|
|
Status: normalizeProjectionStatus(identity.State),
|
|
Department: kratosProjectionTraitString(traits, "department"),
|
|
Grade: grade,
|
|
Position: kratosProjectionTraitString(traits, "position"),
|
|
JobTitle: kratosProjectionTraitString(traits, "jobTitle"),
|
|
AffiliationType: kratosProjectionTraitString(traits, "affiliationType"),
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
Metadata: make(domain.JSONMap),
|
|
}
|
|
if tenantID := kratosProjectionTraitString(traits, "tenant_id"); tenantID != "" {
|
|
user.TenantID = &tenantID
|
|
}
|
|
if relyingPartyID := kratosProjectionTraitString(traits, "relying_party_id"); relyingPartyID != "" {
|
|
user.RelyingPartyID = &relyingPartyID
|
|
}
|
|
|
|
coreTraits := map[string]bool{
|
|
"email": true, "name": true, "phone_number": true,
|
|
"grade": true, "role": true,
|
|
"companyCode": true, "company_code": true, "companyCodes": true,
|
|
"tenant_id": true, "department": true,
|
|
"position": true, "jobTitle": true, "affiliationType": true,
|
|
"relying_party_id": true, "custom_login_ids": true, "id": true,
|
|
}
|
|
for key, value := range traits {
|
|
if !coreTraits[key] {
|
|
user.Metadata[key] = value
|
|
}
|
|
}
|
|
return user
|
|
}
|
|
|
|
func kratosProjectionTraitString(traits map[string]any, key string) string {
|
|
if traits == nil {
|
|
return ""
|
|
}
|
|
value, ok := traits[key]
|
|
if !ok || value == nil {
|
|
return ""
|
|
}
|
|
if str, ok := value.(string); ok {
|
|
return str
|
|
}
|
|
return fmt.Sprint(value)
|
|
}
|
|
|
|
func kratosProjectionTraitStringArray(traits map[string]any, key string) []string {
|
|
if traits == nil {
|
|
return nil
|
|
}
|
|
switch value := traits[key].(type) {
|
|
case []string:
|
|
return value
|
|
case []any:
|
|
items := make([]string, 0, len(value))
|
|
for _, item := range value {
|
|
if str, ok := item.(string); ok && strings.TrimSpace(str) != "" {
|
|
items = append(items, str)
|
|
}
|
|
}
|
|
return items
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func normalizeProjectionStatus(state string) string {
|
|
normalized := domain.NormalizeUserStatus(state)
|
|
if normalized == "" {
|
|
return domain.UserStatusActive
|
|
}
|
|
return normalized
|
|
}
|