forked from baron/baron-sso
chore: snapshot local state before dev merge
This commit is contained in:
@@ -107,49 +107,69 @@ type WorksmobileComparison struct {
|
||||
}
|
||||
|
||||
type WorksmobileComparisonItem struct {
|
||||
ResourceType string `json:"resourceType"`
|
||||
BaronID string `json:"baronId,omitempty"`
|
||||
BaronSlug string `json:"baronSlug,omitempty"`
|
||||
BaronName string `json:"baronName,omitempty"`
|
||||
BaronEmail string `json:"baronEmail,omitempty"`
|
||||
BaronPhone string `json:"baronPhone,omitempty"`
|
||||
BaronEmployeeNumber string `json:"baronEmployeeNumber,omitempty"`
|
||||
BaronPrimaryOrgID string `json:"baronPrimaryOrgId,omitempty"`
|
||||
BaronPrimaryOrgSlug string `json:"baronPrimaryOrgSlug,omitempty"`
|
||||
BaronPrimaryOrgName string `json:"baronPrimaryOrgName,omitempty"`
|
||||
BaronParentID string `json:"baronParentId,omitempty"`
|
||||
BaronParentSlug string `json:"baronParentSlug,omitempty"`
|
||||
BaronParentName string `json:"baronParentName,omitempty"`
|
||||
WorksmobileID string `json:"worksmobileId,omitempty"`
|
||||
ExternalKey string `json:"externalKey,omitempty"`
|
||||
WorksmobileName string `json:"worksmobileName,omitempty"`
|
||||
WorksmobileEmail string `json:"worksmobileEmail,omitempty"`
|
||||
WorksmobilePhone string `json:"worksmobilePhone,omitempty"`
|
||||
WorksmobileEmployeeNumber string `json:"worksmobileEmployeeNumber,omitempty"`
|
||||
WorksmobileAccountStatus string `json:"worksmobileAccountStatus,omitempty"`
|
||||
WorksmobileLevelID string `json:"worksmobileLevelId,omitempty"`
|
||||
WorksmobileLevelName string `json:"worksmobileLevelName,omitempty"`
|
||||
WorksmobileTask string `json:"worksmobileTask,omitempty"`
|
||||
WorksmobileDomainID int64 `json:"worksmobileDomainId,omitempty"`
|
||||
WorksmobileDomainName string `json:"worksmobileDomainName,omitempty"`
|
||||
WorksmobilePrimaryOrgID string `json:"worksmobilePrimaryOrgId,omitempty"`
|
||||
WorksmobilePrimaryOrgName string `json:"worksmobilePrimaryOrgName,omitempty"`
|
||||
WorksmobilePrimaryOrgPositionID string `json:"worksmobilePrimaryOrgPositionId,omitempty"`
|
||||
WorksmobilePrimaryOrgPositionName string `json:"worksmobilePrimaryOrgPositionName,omitempty"`
|
||||
WorksmobilePrimaryOrgIsManager *bool `json:"worksmobilePrimaryOrgIsManager,omitempty"`
|
||||
BaronParentWorksmobileID string `json:"baronParentWorksmobileId,omitempty"`
|
||||
BaronParentWorksmobileName string `json:"baronParentWorksmobileName,omitempty"`
|
||||
BaronParentWorksmobileEmail string `json:"baronParentWorksmobileEmail,omitempty"`
|
||||
WorksmobileParentID string `json:"worksmobileParentId,omitempty"`
|
||||
WorksmobileParentName string `json:"worksmobileParentName,omitempty"`
|
||||
WorksmobileParentEmail string `json:"worksmobileParentEmail,omitempty"`
|
||||
WorksmobileParentExternalKey string `json:"worksmobileParentExternalKey,omitempty"`
|
||||
WorksmobileJobStatus string `json:"worksmobileJobStatus,omitempty"`
|
||||
WorksmobileJobRetryCount int `json:"worksmobileJobRetryCount,omitempty"`
|
||||
WorksmobileLastError string `json:"worksmobileLastError,omitempty"`
|
||||
WorksmobileLastAttemptAt string `json:"worksmobileLastAttemptAt,omitempty"`
|
||||
UpdateReasons []string `json:"updateReasons,omitempty"`
|
||||
Status string `json:"status"`
|
||||
ResourceType string `json:"resourceType"`
|
||||
BaronID string `json:"baronId,omitempty"`
|
||||
BaronSlug string `json:"baronSlug,omitempty"`
|
||||
BaronName string `json:"baronName,omitempty"`
|
||||
BaronEmail string `json:"baronEmail,omitempty"`
|
||||
BaronPhone string `json:"baronPhone,omitempty"`
|
||||
BaronEmployeeNumber string `json:"baronEmployeeNumber,omitempty"`
|
||||
BaronGrade string `json:"baronGrade,omitempty"`
|
||||
BaronPrimaryOrgID string `json:"baronPrimaryOrgId,omitempty"`
|
||||
BaronPrimaryOrgSlug string `json:"baronPrimaryOrgSlug,omitempty"`
|
||||
BaronPrimaryOrgName string `json:"baronPrimaryOrgName,omitempty"`
|
||||
BaronParentID string `json:"baronParentId,omitempty"`
|
||||
BaronParentSlug string `json:"baronParentSlug,omitempty"`
|
||||
BaronParentName string `json:"baronParentName,omitempty"`
|
||||
WorksmobileID string `json:"worksmobileId,omitempty"`
|
||||
ExternalKey string `json:"externalKey,omitempty"`
|
||||
WorksmobileName string `json:"worksmobileName,omitempty"`
|
||||
WorksmobileEmail string `json:"worksmobileEmail,omitempty"`
|
||||
WorksmobilePhone string `json:"worksmobilePhone,omitempty"`
|
||||
WorksmobileEmployeeNumber string `json:"worksmobileEmployeeNumber,omitempty"`
|
||||
WorksmobileAccountStatus string `json:"worksmobileAccountStatus,omitempty"`
|
||||
WorksmobileLevelID string `json:"worksmobileLevelId,omitempty"`
|
||||
WorksmobileLevelName string `json:"worksmobileLevelName,omitempty"`
|
||||
WorksmobileTask string `json:"worksmobileTask,omitempty"`
|
||||
WorksmobileDomainID int64 `json:"worksmobileDomainId,omitempty"`
|
||||
WorksmobileDomainName string `json:"worksmobileDomainName,omitempty"`
|
||||
WorksmobilePrimaryOrgID string `json:"worksmobilePrimaryOrgId,omitempty"`
|
||||
WorksmobilePrimaryOrgName string `json:"worksmobilePrimaryOrgName,omitempty"`
|
||||
WorksmobilePrimaryOrgPositionID string `json:"worksmobilePrimaryOrgPositionId,omitempty"`
|
||||
WorksmobilePrimaryOrgPositionName string `json:"worksmobilePrimaryOrgPositionName,omitempty"`
|
||||
WorksmobilePrimaryOrgIsManager *bool `json:"worksmobilePrimaryOrgIsManager,omitempty"`
|
||||
BaronParentWorksmobileID string `json:"baronParentWorksmobileId,omitempty"`
|
||||
BaronParentWorksmobileName string `json:"baronParentWorksmobileName,omitempty"`
|
||||
BaronParentWorksmobileEmail string `json:"baronParentWorksmobileEmail,omitempty"`
|
||||
WorksmobileParentID string `json:"worksmobileParentId,omitempty"`
|
||||
WorksmobileParentName string `json:"worksmobileParentName,omitempty"`
|
||||
WorksmobileParentEmail string `json:"worksmobileParentEmail,omitempty"`
|
||||
WorksmobileParentExternalKey string `json:"worksmobileParentExternalKey,omitempty"`
|
||||
WorksmobileJobStatus string `json:"worksmobileJobStatus,omitempty"`
|
||||
WorksmobileJobRetryCount int `json:"worksmobileJobRetryCount,omitempty"`
|
||||
WorksmobileLastError string `json:"worksmobileLastError,omitempty"`
|
||||
WorksmobileLastAttemptAt string `json:"worksmobileLastAttemptAt,omitempty"`
|
||||
UserMemberships []WorksmobileUserMembershipComparison `json:"userMemberships,omitempty"`
|
||||
UpdateReasons []string `json:"updateReasons,omitempty"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type WorksmobileUserMembershipComparison struct {
|
||||
BaronOrgID string `json:"baronOrgId,omitempty"`
|
||||
BaronOrgSlug string `json:"baronOrgSlug,omitempty"`
|
||||
BaronOrgName string `json:"baronOrgName,omitempty"`
|
||||
BaronGrade string `json:"baronGrade,omitempty"`
|
||||
BaronPrimary bool `json:"baronPrimary,omitempty"`
|
||||
WorksmobileDomainID int64 `json:"worksmobileDomainId,omitempty"`
|
||||
WorksmobileDomainName string `json:"worksmobileDomainName,omitempty"`
|
||||
WorksmobileOrgID string `json:"worksmobileOrgId,omitempty"`
|
||||
WorksmobileOrgName string `json:"worksmobileOrgName,omitempty"`
|
||||
WorksmobileLevelID string `json:"worksmobileLevelId,omitempty"`
|
||||
WorksmobileLevelName string `json:"worksmobileLevelName,omitempty"`
|
||||
WorksmobileOrgPositionID string `json:"worksmobileOrgPositionId,omitempty"`
|
||||
WorksmobileOrgIsManager *bool `json:"worksmobileOrgIsManager,omitempty"`
|
||||
WorksmobilePrimary bool `json:"worksmobilePrimary,omitempty"`
|
||||
GradeNeedsUpdate bool `json:"gradeNeedsUpdate,omitempty"`
|
||||
}
|
||||
|
||||
type worksmobileSyncService struct {
|
||||
@@ -362,7 +382,7 @@ func (s *worksmobileSyncService) GetComparison(ctx context.Context, tenantID str
|
||||
tenantIDs = append(tenantIDs, tenant.ID)
|
||||
}
|
||||
}
|
||||
users, err := s.comparisonUsers(ctx, tenantIDs)
|
||||
users, err := s.comparisonUsers(ctx, tenantIDs, tenantByID)
|
||||
if err != nil {
|
||||
return WorksmobileComparison{}, err
|
||||
}
|
||||
@@ -383,7 +403,7 @@ func (s *worksmobileSyncService) GetComparison(ctx context.Context, tenantID str
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *worksmobileSyncService) comparisonUsers(ctx context.Context, tenantIDs []string) ([]domain.User, error) {
|
||||
func (s *worksmobileSyncService) comparisonUsers(ctx context.Context, tenantIDs []string, tenantByID map[string]domain.Tenant) ([]domain.User, error) {
|
||||
if s.identityMirror != nil {
|
||||
status, err := s.identityMirror.GetIdentityCacheStatus(ctx)
|
||||
if err == nil &&
|
||||
@@ -394,32 +414,100 @@ func (s *worksmobileSyncService) comparisonUsers(ctx context.Context, tenantIDs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return worksmobileUsersFromIdentityMirror(identities, tenantIDs), nil
|
||||
return worksmobileUsersFromIdentityMirror(identities, tenantIDs, tenantByID), nil
|
||||
}
|
||||
}
|
||||
return s.userRepo.FindByTenantIDs(ctx, tenantIDs)
|
||||
}
|
||||
|
||||
func worksmobileUsersFromIdentityMirror(identities []KratosIdentity, tenantIDs []string) []domain.User {
|
||||
func worksmobileUsersFromIdentityMirror(identities []KratosIdentity, tenantIDs []string, tenantMaps ...map[string]domain.Tenant) []domain.User {
|
||||
allowed := make(map[string]bool, len(tenantIDs))
|
||||
for _, tenantID := range tenantIDs {
|
||||
allowed[strings.TrimSpace(tenantID)] = true
|
||||
tenantID = strings.TrimSpace(tenantID)
|
||||
if tenantID == "" {
|
||||
continue
|
||||
}
|
||||
allowed[strings.ToLower(tenantID)] = true
|
||||
if len(tenantMaps) > 0 {
|
||||
if tenant, ok := tenantMaps[0][tenantID]; ok {
|
||||
if slug := strings.TrimSpace(tenant.Slug); slug != "" {
|
||||
allowed[strings.ToLower(slug)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users := make([]domain.User, 0, len(identities))
|
||||
for _, identity := range identities {
|
||||
tenantID := traitString(identity.Traits, "tenant_id")
|
||||
if tenantID == "" || !allowed[tenantID] {
|
||||
if !worksmobileIdentityMirrorMatchesTenant(identity.Traits, allowed) {
|
||||
continue
|
||||
}
|
||||
user := worksmobileUserFromIdentity(identity)
|
||||
if user.TenantID == nil || strings.TrimSpace(*user.TenantID) == "" {
|
||||
if tenantID := worksmobileIdentityMirrorTenantID(identity.Traits, allowed); tenantID != "" {
|
||||
user.TenantID = &tenantID
|
||||
}
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func worksmobileIdentityMirrorMatchesTenant(traits map[string]any, allowed map[string]bool) bool {
|
||||
for _, key := range identityMirrorTenantKeys(traits) {
|
||||
if allowed[strings.ToLower(strings.TrimSpace(key))] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func worksmobileIdentityMirrorTenantID(traits map[string]any, allowed map[string]bool) string {
|
||||
appointments := identityMirrorAppointments(traits["additionalAppointments"])
|
||||
if len(appointments) == 0 {
|
||||
if metadata, ok := traits["metadata"].(map[string]any); ok {
|
||||
appointments = identityMirrorAppointments(metadata["additionalAppointments"])
|
||||
}
|
||||
}
|
||||
for _, appointment := range appointments {
|
||||
if !metadataBool(domain.JSONMap(appointment), "isPrimary", "primary") {
|
||||
continue
|
||||
}
|
||||
if tenantID := worksmobileIdentityMirrorAllowedAppointmentTenantID(appointment, allowed); tenantID != "" {
|
||||
return tenantID
|
||||
}
|
||||
}
|
||||
for _, appointment := range appointments {
|
||||
if tenantID := worksmobileIdentityMirrorAllowedAppointmentTenantID(appointment, allowed); tenantID != "" {
|
||||
return tenantID
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func worksmobileIdentityMirrorAllowedAppointmentTenantID(appointment map[string]any, allowed map[string]bool) string {
|
||||
tenantID := strings.TrimSpace(identityMirrorAnyString(appointment["tenantId"]))
|
||||
if tenantID == "" {
|
||||
tenantID = strings.TrimSpace(identityMirrorAnyString(appointment["tenant_id"]))
|
||||
}
|
||||
if tenantID != "" && allowed[strings.ToLower(tenantID)] {
|
||||
return tenantID
|
||||
}
|
||||
tenantSlug := strings.TrimSpace(identityMirrorAnyString(appointment["tenantSlug"]))
|
||||
if tenantSlug == "" {
|
||||
tenantSlug = strings.TrimSpace(identityMirrorAnyString(appointment["slug"]))
|
||||
}
|
||||
if tenantSlug != "" && allowed[strings.ToLower(tenantSlug)] {
|
||||
return tenantID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func worksmobileUserFromIdentity(identity KratosIdentity) domain.User {
|
||||
metadata := domain.JSONMap{}
|
||||
for key, value := range identity.Traits {
|
||||
if key == "grade" {
|
||||
continue
|
||||
}
|
||||
metadata[key] = value
|
||||
}
|
||||
tenantID := traitString(identity.Traits, "tenant_id")
|
||||
@@ -438,7 +526,6 @@ func worksmobileUserFromIdentity(identity KratosIdentity) domain.User {
|
||||
Role: domain.NormalizeRole(traitString(identity.Traits, "role")),
|
||||
AffiliationType: traitString(identity.Traits, "affiliationType"),
|
||||
Department: traitString(identity.Traits, "department"),
|
||||
Grade: traitString(identity.Traits, "grade"),
|
||||
Position: traitString(identity.Traits, "position"),
|
||||
JobTitle: traitString(identity.Traits, "jobTitle"),
|
||||
Metadata: metadata,
|
||||
@@ -595,10 +682,8 @@ func (s *worksmobileSyncService) EnqueueOrgUnitDelete(ctx context.Context, tenan
|
||||
return nil, errors.New("worksmobile orgunit not found")
|
||||
}
|
||||
tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
|
||||
if tenant, ok := findWorksmobileOrgUnitTenantByRemoteLocalPart(*target, scopeTenants, tenantByID); ok {
|
||||
return s.enqueueOrgUnitUpsert(ctx, root, tenant, scopeTenants)
|
||||
}
|
||||
if isProtectedWorksmobileRemoteOrgUnit(*root, scopeTenants, *target) {
|
||||
_, matchedLocalPart := findWorksmobileOrgUnitTenantByRemoteLocalPart(*target, scopeTenants, tenantByID)
|
||||
if !matchedLocalPart && isProtectedWorksmobileRemoteOrgUnit(*root, scopeTenants, *target) {
|
||||
return nil, errors.New("protected worksmobile domain root orgunit cannot be deleted")
|
||||
}
|
||||
item := &domain.WorksmobileOutbox{
|
||||
@@ -1622,7 +1707,9 @@ func compareWorksmobileUsersWithRemoteGroups(localUsers []domain.User, remoteUse
|
||||
remote, matched = remoteByEmail[strings.ToLower(strings.TrimSpace(user.Email))]
|
||||
}
|
||||
updateReasons := []string(nil)
|
||||
gradeComparison := worksmobileUserGradeComparison{}
|
||||
if matched {
|
||||
gradeComparison = worksmobileCompareUserGrade(user, remote, localTenants, remoteOrgUnitByExternalID)
|
||||
updateReasons = worksmobileUserUpdateReasons(user, remote, localTenants, remoteOrgUnitByExternalID)
|
||||
}
|
||||
needsUpdate := len(updateReasons) > 0
|
||||
@@ -1630,6 +1717,10 @@ func compareWorksmobileUsersWithRemoteGroups(localUsers []domain.User, remoteUse
|
||||
matchedRemoteIDs[remote.ID] = true
|
||||
continue
|
||||
}
|
||||
baronGrade, _ := worksmobileUserComparisonGradeWithTenant(user, remote, localTenants, remoteOrgUnitByExternalID)
|
||||
if strings.TrimSpace(gradeComparison.LocalGrade) != "" {
|
||||
baronGrade = gradeComparison.LocalGrade
|
||||
}
|
||||
item := WorksmobileComparisonItem{
|
||||
ResourceType: "USER",
|
||||
BaronID: user.ID,
|
||||
@@ -1637,6 +1728,7 @@ func compareWorksmobileUsersWithRemoteGroups(localUsers []domain.User, remoteUse
|
||||
BaronEmail: user.Email,
|
||||
BaronPhone: user.Phone,
|
||||
BaronEmployeeNumber: metadataEmployeeNumber(user.Metadata),
|
||||
BaronGrade: baronGrade,
|
||||
BaronPrimaryOrgID: worksmobileUserPrimaryOrgID(user),
|
||||
BaronPrimaryOrgSlug: worksmobileUserPrimaryOrgSlug(user, localTenants),
|
||||
BaronPrimaryOrgName: worksmobileUserPrimaryOrgName(user, localTenants),
|
||||
@@ -1665,6 +1757,10 @@ func compareWorksmobileUsersWithRemoteGroups(localUsers []domain.User, remoteUse
|
||||
item.WorksmobileAccountStatus = worksmobileRemoteAccountStatus(remote)
|
||||
item.WorksmobileLevelID = remote.LevelID
|
||||
item.WorksmobileLevelName = remote.LevelName
|
||||
if gradeComparison.NeedsUpdate {
|
||||
item.WorksmobileLevelID = gradeComparison.RemoteLevelID
|
||||
item.WorksmobileLevelName = gradeComparison.RemoteLevelName
|
||||
}
|
||||
item.WorksmobileTask = remote.Task
|
||||
item.WorksmobileDomainID = remote.DomainID
|
||||
item.WorksmobileDomainName = remote.DomainName
|
||||
@@ -1673,6 +1769,7 @@ func compareWorksmobileUsersWithRemoteGroups(localUsers []domain.User, remoteUse
|
||||
item.WorksmobilePrimaryOrgPositionID = remote.PrimaryOrgUnitPositionID
|
||||
item.WorksmobilePrimaryOrgPositionName = remote.PrimaryOrgUnitPositionName
|
||||
item.WorksmobilePrimaryOrgIsManager = remote.PrimaryOrgUnitIsManager
|
||||
item.UserMemberships = worksmobileUserMembershipComparisons(user, remote, localTenants, remoteOrgUnitByExternalID, gradeComparison)
|
||||
matchedRemoteIDs[remote.ID] = true
|
||||
}
|
||||
result = append(result, item)
|
||||
@@ -1779,6 +1876,9 @@ func worksmobileUserUpdateReasons(user domain.User, remote WorksmobileRemoteUser
|
||||
if worksmobileUserEmployeeNumberNeedsUpdate(user, remote) {
|
||||
reasons = append(reasons, "employee_number")
|
||||
}
|
||||
if worksmobileUserGradeNeedsUpdate(user, remote, localTenants, remoteOrgUnitByExternalID) {
|
||||
reasons = append(reasons, "grade")
|
||||
}
|
||||
if worksmobileUserOrganizationsNeedUpdate(user, remote, localTenants, remoteOrgUnitByExternalID) {
|
||||
reasons = append(reasons, "organization")
|
||||
}
|
||||
@@ -1823,6 +1923,229 @@ func worksmobileUserEmployeeNumberNeedsUpdate(user domain.User, remote Worksmobi
|
||||
return localEmployeeNumber != remoteEmployeeNumber
|
||||
}
|
||||
|
||||
func worksmobileUserGradeNeedsUpdate(user domain.User, remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant, remoteOrgUnitByExternalID map[string]WorksmobileRemoteGroup) bool {
|
||||
return worksmobileCompareUserGrade(user, remote, localTenants, remoteOrgUnitByExternalID).NeedsUpdate
|
||||
}
|
||||
|
||||
func worksmobileUserComparisonGradeWithTenant(user domain.User, remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant, remoteOrgUnitByExternalID map[string]WorksmobileRemoteGroup) (string, string) {
|
||||
comparison := worksmobileCompareUserGrade(user, remote, localTenants, remoteOrgUnitByExternalID)
|
||||
if strings.TrimSpace(comparison.LocalGrade) != "" {
|
||||
return comparison.LocalGrade, comparison.TenantID
|
||||
}
|
||||
tenantID := worksmobileRemotePrimaryTenantID(remote, localTenants, remoteOrgUnitByExternalID)
|
||||
if tenantID == "" {
|
||||
tenantID = worksmobileUserComparisonTenantID(user, localTenants)
|
||||
}
|
||||
if tenantID == "" {
|
||||
return "", ""
|
||||
}
|
||||
for _, appointment := range worksmobileAppointmentsFromMetadata(user.Metadata) {
|
||||
if strings.TrimSpace(appointment.TenantID) != tenantID {
|
||||
continue
|
||||
}
|
||||
grade := normalizeWorksmobileGradeForTenant(appointment.Grade, tenantID, localTenants)
|
||||
if grade == "" {
|
||||
return "", tenantID
|
||||
}
|
||||
return grade, tenantID
|
||||
}
|
||||
return "", tenantID
|
||||
}
|
||||
|
||||
type worksmobileUserGradeComparison struct {
|
||||
NeedsUpdate bool
|
||||
TenantID string
|
||||
LocalGrade string
|
||||
RemoteLevelID string
|
||||
RemoteLevelName string
|
||||
}
|
||||
|
||||
type worksmobileRemoteOrganizationLevel struct {
|
||||
levelID string
|
||||
levelName string
|
||||
primary bool
|
||||
}
|
||||
|
||||
func worksmobileCompareUserGrade(user domain.User, remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant, remoteOrgUnitByExternalID map[string]WorksmobileRemoteGroup) worksmobileUserGradeComparison {
|
||||
if localTenants == nil {
|
||||
return worksmobileUserGradeComparison{}
|
||||
}
|
||||
remoteLevelsByTenant := worksmobileRemoteOrganizationLevelsByTenant(remote, localTenants, remoteOrgUnitByExternalID)
|
||||
if len(remoteLevelsByTenant) == 0 {
|
||||
return worksmobileUserGradeComparison{}
|
||||
}
|
||||
fallback := worksmobileUserGradeComparison{}
|
||||
for _, appointment := range worksmobileGradeComparisonAppointments(user, localTenants) {
|
||||
tenantID := strings.TrimSpace(appointment.TenantID)
|
||||
localGrade := normalizeWorksmobileGradeForTenant(appointment.Grade, tenantID, localTenants)
|
||||
if tenantID == "" || localGrade == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := localTenants[tenantID]; !ok {
|
||||
continue
|
||||
}
|
||||
remoteLevel, ok := remoteLevelsByTenant[tenantID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
comparison := worksmobileUserGradeComparison{
|
||||
TenantID: tenantID,
|
||||
LocalGrade: localGrade,
|
||||
RemoteLevelID: strings.TrimSpace(remoteLevel.levelID),
|
||||
RemoteLevelName: strings.TrimSpace(remoteLevel.levelName),
|
||||
}
|
||||
if fallback.LocalGrade == "" || remoteLevel.primary {
|
||||
fallback = comparison
|
||||
}
|
||||
if comparison.RemoteLevelName == "" && comparison.RemoteLevelID == "" {
|
||||
comparison.NeedsUpdate = true
|
||||
return comparison
|
||||
}
|
||||
if !worksmobileRemoteLevelMatchesLocalGrade(localGrade, tenantID, comparison.RemoteLevelID, comparison.RemoteLevelName, localTenants) {
|
||||
comparison.NeedsUpdate = true
|
||||
return comparison
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func worksmobileRemoteLevelMatchesLocalGrade(localGrade, tenantID, remoteLevelID, remoteLevelName string, localTenants map[string]domain.Tenant) bool {
|
||||
localGrade = strings.TrimSpace(localGrade)
|
||||
remoteLevelID = strings.TrimSpace(remoteLevelID)
|
||||
remoteLevelName = strings.TrimSpace(remoteLevelName)
|
||||
if localGrade == "" {
|
||||
return remoteLevelID == "" && remoteLevelName == ""
|
||||
}
|
||||
if remoteLevelName == localGrade || remoteLevelID == localGrade {
|
||||
return true
|
||||
}
|
||||
expectedLevelID := worksmobileLevelIDForTenant(localGrade, tenantID, localTenants)
|
||||
return WorksmobileLevelIdentifierMatchesRemote(expectedLevelID, remoteLevelID, remoteLevelName)
|
||||
}
|
||||
|
||||
func worksmobileGradeComparisonAppointments(user domain.User, tenantByID map[string]domain.Tenant) []worksmobileAppointment {
|
||||
appointments := worksmobileAppointmentsFromMetadata(user.Metadata)
|
||||
hasGPDTDCGrade := false
|
||||
for _, appointment := range appointments {
|
||||
if strings.TrimSpace(appointment.Grade) == "" {
|
||||
continue
|
||||
}
|
||||
if worksmobileTenantIsGPDTDCDescendant(appointment.TenantID, tenantByID) {
|
||||
hasGPDTDCGrade = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasGPDTDCGrade {
|
||||
return appointments
|
||||
}
|
||||
filtered := make([]worksmobileAppointment, 0, len(appointments))
|
||||
for _, appointment := range appointments {
|
||||
if worksmobileTenantIsGPDTDCDescendant(appointment.TenantID, tenantByID) {
|
||||
filtered = append(filtered, appointment)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func worksmobileRemoteOrganizationLevelsByTenant(remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant, remoteOrgUnitByExternalID map[string]WorksmobileRemoteGroup) map[string]worksmobileRemoteOrganizationLevel {
|
||||
result := map[string]worksmobileRemoteOrganizationLevel{}
|
||||
if localTenants == nil {
|
||||
return result
|
||||
}
|
||||
organizations := remote.Organizations
|
||||
if len(organizations) == 0 {
|
||||
organizations = worksmobileRemoteUserLegacyOrganizations(remote, remoteOrgUnitByExternalID)
|
||||
} else {
|
||||
organizations = worksmobileRemoteUserOrganizationsForCompare(remote, remoteOrgUnitByExternalID)
|
||||
}
|
||||
for _, organization := range organizations {
|
||||
levelID := strings.TrimSpace(organization.LevelID)
|
||||
levelName := strings.TrimSpace(organization.LevelName)
|
||||
if levelID == "" && levelName == "" && (len(organizations) == 1 || organization.Primary) {
|
||||
levelID = strings.TrimSpace(remote.LevelID)
|
||||
levelName = strings.TrimSpace(remote.LevelName)
|
||||
}
|
||||
for _, orgUnit := range organization.OrgUnits {
|
||||
tenantID := worksmobileOrgUnitLocalExternalKey(orgUnit.OrgUnitID)
|
||||
if tenantID == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := localTenants[tenantID]; !ok {
|
||||
continue
|
||||
}
|
||||
current := worksmobileRemoteOrganizationLevel{
|
||||
levelID: levelID,
|
||||
levelName: levelName,
|
||||
primary: organization.Primary || orgUnit.Primary,
|
||||
}
|
||||
existing, ok := result[tenantID]
|
||||
if !ok || (!existing.primary && current.primary) {
|
||||
result[tenantID] = current
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func worksmobileRemotePrimaryTenantID(remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant, remoteOrgUnitByExternalID map[string]WorksmobileRemoteGroup) string {
|
||||
if localTenants == nil {
|
||||
return ""
|
||||
}
|
||||
for _, orgUnitID := range worksmobileRemotePrimaryOrgUnitIDs(remote) {
|
||||
canonicalOrgUnitID := worksmobileCanonicalRemoteOrgUnitID(orgUnitID, remoteOrgUnitByExternalID)
|
||||
tenantID := worksmobileOrgUnitLocalExternalKey(canonicalOrgUnitID)
|
||||
if tenantID == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := localTenants[tenantID]; ok {
|
||||
return tenantID
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func worksmobileIsResearchGrade(values ...string) bool {
|
||||
for _, value := range values {
|
||||
normalized := strings.ToLower(strings.TrimSpace(value))
|
||||
if normalized == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(normalized, "연구원") ||
|
||||
strings.Contains(normalized, "선임") ||
|
||||
strings.Contains(normalized, "책임") ||
|
||||
strings.Contains(normalized, "수석") ||
|
||||
strings.Contains(normalized, "research") ||
|
||||
strings.Contains(normalized, "principal") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func worksmobileTenantIsGPDTDCDescendant(tenantID string, tenantByID map[string]domain.Tenant) bool {
|
||||
tenantID = strings.TrimSpace(tenantID)
|
||||
if tenantID == "" || tenantByID == nil {
|
||||
return false
|
||||
}
|
||||
visited := map[string]bool{}
|
||||
currentID := tenantID
|
||||
for currentID != "" {
|
||||
if visited[currentID] {
|
||||
return false
|
||||
}
|
||||
visited[currentID] = true
|
||||
tenant, ok := tenantByID[currentID]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if worksmobileTenantDomainIDEnvKey(tenant) == "GPDTDC_DOMAIN_ID" && isWorksmobileDomainRootTenant(tenant) {
|
||||
return true
|
||||
}
|
||||
currentID = worksmobileTenantParentID(tenant)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func worksmobileUserOrganizationsNeedUpdate(user domain.User, remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant, remoteOrgUnitByExternalID map[string]WorksmobileRemoteGroup) bool {
|
||||
if localTenants == nil {
|
||||
return false
|
||||
@@ -1911,9 +2234,11 @@ func worksmobileRemoteUserLegacyOrganizations(remote WorksmobileRemoteUser, remo
|
||||
}
|
||||
return []WorksmobileUserOrganization{
|
||||
{
|
||||
DomainID: remote.DomainID,
|
||||
Email: strings.TrimSpace(remote.Email),
|
||||
Primary: true,
|
||||
DomainID: remote.DomainID,
|
||||
Email: strings.TrimSpace(remote.Email),
|
||||
Primary: true,
|
||||
LevelID: strings.TrimSpace(remote.LevelID),
|
||||
LevelName: strings.TrimSpace(remote.LevelName),
|
||||
OrgUnits: []WorksmobileUserOrgUnit{
|
||||
{
|
||||
OrgUnitID: worksmobileCanonicalRemoteOrgUnitID(strings.TrimSpace(remote.PrimaryOrgUnitID), remoteOrgUnitByExternalID),
|
||||
@@ -2009,20 +2334,17 @@ type worksmobileComparableOrgUnit struct {
|
||||
func worksmobileUserOrganizationsEqual(expected []WorksmobileUserOrganization, remote []WorksmobileUserOrganization) bool {
|
||||
expectedUnits := flattenExpectedWorksmobileUserOrganizations(expected)
|
||||
remoteUnits := flattenRemoteWorksmobileUserOrganizations(remote)
|
||||
if len(expectedUnits) != len(remoteUnits) {
|
||||
if len(expectedUnits) == 0 {
|
||||
return len(remoteUnits) == 0
|
||||
}
|
||||
if len(remoteUnits) == 0 {
|
||||
return false
|
||||
}
|
||||
for key, expectedUnit := range expectedUnits {
|
||||
remoteUnit, ok := remoteUnits[key]
|
||||
for key, remoteUnit := range remoteUnits {
|
||||
expectedUnit, ok := expectedUnits[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if expectedUnit.organizationPrimary != remoteUnit.organizationPrimary {
|
||||
return false
|
||||
}
|
||||
if expectedUnit.unitPrimary != remoteUnit.unitPrimary {
|
||||
return false
|
||||
}
|
||||
if expectedUnit.comparePosition && strings.TrimSpace(expectedUnit.positionID) != strings.TrimSpace(remoteUnit.positionID) {
|
||||
return false
|
||||
}
|
||||
@@ -2090,6 +2412,145 @@ func flattenRemoteWorksmobileUserOrganizations(organizations []WorksmobileUserOr
|
||||
return result
|
||||
}
|
||||
|
||||
type worksmobileRemoteMembershipDetail struct {
|
||||
domainID int64
|
||||
domainName string
|
||||
orgUnitID string
|
||||
orgUnitName string
|
||||
levelID string
|
||||
levelName string
|
||||
positionID string
|
||||
manager *bool
|
||||
primary bool
|
||||
}
|
||||
|
||||
func worksmobileUserMembershipComparisons(user domain.User, remote WorksmobileRemoteUser, localTenants map[string]domain.Tenant, remoteOrgUnitByExternalID map[string]WorksmobileRemoteGroup, gradeComparison worksmobileUserGradeComparison) []WorksmobileUserMembershipComparison {
|
||||
if localTenants == nil {
|
||||
return nil
|
||||
}
|
||||
tenantID := worksmobileUserComparisonTenantID(user, localTenants)
|
||||
if tenantID == "" {
|
||||
return nil
|
||||
}
|
||||
tenant, ok := localTenants[tenantID]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
expectedOrganizations, _, err := buildWorksmobileUserOrganizations(user, tenant, localTenants, worksmobileComparisonRootConfig(localTenants))
|
||||
if err != nil || len(expectedOrganizations) == 0 {
|
||||
return nil
|
||||
}
|
||||
remoteOrganizations := remote.Organizations
|
||||
if len(remoteOrganizations) == 0 {
|
||||
remoteOrganizations = worksmobileRemoteUserLegacyOrganizations(remote, remoteOrgUnitByExternalID)
|
||||
} else {
|
||||
remoteOrganizations = worksmobileRemoteUserOrganizationsForCompare(remote, remoteOrgUnitByExternalID)
|
||||
}
|
||||
remoteMemberships := worksmobileRemoteMembershipDetailsByKey(remote, remoteOrganizations, remoteOrgUnitByExternalID)
|
||||
appointments := worksmobileAppointmentsByTenantID(user.Metadata)
|
||||
result := make([]WorksmobileUserMembershipComparison, 0)
|
||||
for _, organization := range expectedOrganizations {
|
||||
for _, orgUnit := range organization.OrgUnits {
|
||||
baronOrgID := worksmobileOrgUnitLocalExternalKey(orgUnit.OrgUnitID)
|
||||
if baronOrgID == "" {
|
||||
continue
|
||||
}
|
||||
baronTenant, ok := localTenants[baronOrgID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item := WorksmobileUserMembershipComparison{
|
||||
BaronOrgID: baronOrgID,
|
||||
BaronOrgSlug: strings.TrimSpace(baronTenant.Slug),
|
||||
BaronOrgName: strings.TrimSpace(baronTenant.Name),
|
||||
BaronPrimary: organization.Primary || orgUnit.Primary,
|
||||
}
|
||||
if appointment, ok := appointments[baronOrgID]; ok {
|
||||
item.BaronGrade = normalizeWorksmobileGradeForTenant(appointment.Grade, baronOrgID, localTenants)
|
||||
}
|
||||
key := worksmobileComparableOrgUnitKey(organization.DomainID, orgUnit.OrgUnitID)
|
||||
if remoteMembership, ok := remoteMemberships[key]; ok {
|
||||
item.WorksmobileDomainID = remoteMembership.domainID
|
||||
item.WorksmobileDomainName = remoteMembership.domainName
|
||||
item.WorksmobileOrgID = remoteMembership.orgUnitID
|
||||
item.WorksmobileOrgName = remoteMembership.orgUnitName
|
||||
item.WorksmobileLevelID = remoteMembership.levelID
|
||||
item.WorksmobileLevelName = remoteMembership.levelName
|
||||
item.WorksmobileOrgPositionID = remoteMembership.positionID
|
||||
item.WorksmobileOrgIsManager = remoteMembership.manager
|
||||
item.WorksmobilePrimary = remoteMembership.primary
|
||||
}
|
||||
item.GradeNeedsUpdate = gradeComparison.NeedsUpdate && strings.TrimSpace(gradeComparison.TenantID) == baronOrgID
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func worksmobileAppointmentsByTenantID(metadata domain.JSONMap) map[string]worksmobileAppointment {
|
||||
result := map[string]worksmobileAppointment{}
|
||||
for _, appointment := range worksmobileAppointmentsFromMetadata(metadata) {
|
||||
tenantID := strings.TrimSpace(appointment.TenantID)
|
||||
if tenantID == "" {
|
||||
continue
|
||||
}
|
||||
result[tenantID] = appointment
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func worksmobileRemoteMembershipDetailsByKey(remote WorksmobileRemoteUser, organizations []WorksmobileUserOrganization, remoteOrgUnitByExternalID map[string]WorksmobileRemoteGroup) map[string]worksmobileRemoteMembershipDetail {
|
||||
result := map[string]worksmobileRemoteMembershipDetail{}
|
||||
for _, organization := range organizations {
|
||||
domainID := organization.DomainID
|
||||
if domainID == 0 {
|
||||
domainID = remote.DomainID
|
||||
}
|
||||
domainName := strings.TrimSpace(remote.DomainName)
|
||||
levelID := strings.TrimSpace(organization.LevelID)
|
||||
levelName := strings.TrimSpace(organization.LevelName)
|
||||
if levelID == "" && levelName == "" && (len(organizations) == 1 || organization.Primary) {
|
||||
levelID = strings.TrimSpace(remote.LevelID)
|
||||
levelName = strings.TrimSpace(remote.LevelName)
|
||||
}
|
||||
for _, orgUnit := range organization.OrgUnits {
|
||||
key := worksmobileComparableOrgUnitKey(domainID, orgUnit.OrgUnitID)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
localExternalKey := worksmobileOrgUnitLocalExternalKey(orgUnit.OrgUnitID)
|
||||
orgUnitID := strings.TrimSpace(orgUnit.OrgUnitID)
|
||||
orgUnitName := ""
|
||||
if localExternalKey != "" {
|
||||
if remoteGroup, ok := remoteOrgUnitByExternalID[localExternalKey]; ok {
|
||||
if strings.TrimSpace(remoteGroup.ID) != "" {
|
||||
orgUnitID = strings.TrimSpace(remoteGroup.ID)
|
||||
}
|
||||
orgUnitName = strings.TrimSpace(remoteGroup.DisplayName)
|
||||
if domainName == "" {
|
||||
domainName = strings.TrimSpace(remoteGroup.DomainName)
|
||||
}
|
||||
}
|
||||
}
|
||||
if orgUnitName == "" && worksmobileOrgUnitIDContains([]string{remote.PrimaryOrgUnitID}, orgUnitID) {
|
||||
orgUnitName = strings.TrimSpace(remote.PrimaryOrgUnitName)
|
||||
}
|
||||
result[key] = worksmobileRemoteMembershipDetail{
|
||||
domainID: domainID,
|
||||
domainName: domainName,
|
||||
orgUnitID: orgUnitID,
|
||||
orgUnitName: orgUnitName,
|
||||
levelID: levelID,
|
||||
levelName: levelName,
|
||||
positionID: strings.TrimSpace(orgUnit.PositionID),
|
||||
manager: orgUnit.IsManager,
|
||||
primary: organization.Primary || orgUnit.Primary,
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func worksmobileComparableOrgUnitKey(domainID int64, orgUnitID string) string {
|
||||
orgUnitID = strings.TrimSpace(orgUnitID)
|
||||
if domainID == 0 || orgUnitID == "" {
|
||||
@@ -2167,10 +2628,7 @@ func worksmobileOrgUnitLocalExternalKey(orgUnitID string) string {
|
||||
}
|
||||
|
||||
func worksmobileUserPrimaryOrgID(user domain.User) string {
|
||||
if user.TenantID == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(*user.TenantID)
|
||||
return worksmobileUserComparisonPrimaryTenantID(user)
|
||||
}
|
||||
|
||||
func worksmobileUserPrimaryOrgName(user domain.User, localTenants map[string]domain.Tenant) string {
|
||||
|
||||
Reference in New Issue
Block a user