package service import ( "baron-sso-backend/internal/domain" "baron-sso-backend/internal/repository" "context" "errors" "os" "sort" "strings" ) const HanmacFamilyTenantSlug = "hanmac-family" type WorksmobileSyncer interface { EnqueueTenantUpsertIfInScope(ctx context.Context, tenant domain.Tenant) error EnqueueTenantDeleteIfInScope(ctx context.Context, tenant domain.Tenant) error EnqueueUserUpsertIfInScope(ctx context.Context, user domain.User) error EnqueueUserDeleteIfInScope(ctx context.Context, user domain.User) error } type WorksmobileAdminService interface { GetTenantOverview(ctx context.Context, tenantID string) (WorksmobileTenantOverview, error) GetComparison(ctx context.Context, tenantID string, includeMatched bool) (WorksmobileComparison, error) EnqueueBackfillDryRun(ctx context.Context, tenantID string) (WorksmobileBackfillDryRun, error) EnqueueOrgUnitSync(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error) EnqueueUserSync(ctx context.Context, tenantID, userID string) (*domain.WorksmobileOutbox, error) RetryJob(ctx context.Context, tenantID, jobID string) (*domain.WorksmobileOutbox, error) ListInitialPasswordCredentials(ctx context.Context, tenantID string) ([]WorksmobileInitialPasswordCredential, error) } type WorksmobileConfigSummary struct { Enabled bool `json:"enabled"` DomainMappings map[string]int64 `json:"domainMappings"` TokenConfigured bool `json:"tokenConfigured"` AdminTenantID string `json:"adminTenantId,omitempty"` } type WorksmobileTenantOverview struct { Tenant domain.Tenant `json:"tenant"` Config WorksmobileConfigSummary `json:"config"` RecentJobs []domain.WorksmobileOutbox `json:"recentJobs"` } type WorksmobileBackfillDryRun struct { OrgUnitCount int `json:"orgUnitCount"` UserCount int `json:"userCount"` } type WorksmobileInitialPasswordCredential struct { Email string `json:"email"` InitialPassword string `json:"initialPassword"` Status string `json:"status"` LastError string `json:"lastError,omitempty"` } type WorksmobileComparison struct { Users []WorksmobileComparisonItem `json:"users"` Groups []WorksmobileComparisonItem `json:"groups"` } type WorksmobileComparisonItem struct { ResourceType string `json:"resourceType"` BaronID string `json:"baronId,omitempty"` BaronName string `json:"baronName,omitempty"` BaronEmail string `json:"baronEmail,omitempty"` BaronPrimaryOrgID string `json:"baronPrimaryOrgId,omitempty"` BaronPrimaryOrgName string `json:"baronPrimaryOrgName,omitempty"` BaronParentID string `json:"baronParentId,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"` 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"` WorksmobileParentID string `json:"worksmobileParentId,omitempty"` WorksmobileParentName string `json:"worksmobileParentName,omitempty"` Status string `json:"status"` } type worksmobileSyncService struct { tenantService TenantService userRepo repository.UserRepository outboxRepo repository.WorksmobileOutboxRepository client WorksmobileDirectoryClient } func NewWorksmobileSyncService(tenantService TenantService, userRepo repository.UserRepository, outboxRepo repository.WorksmobileOutboxRepository, client WorksmobileDirectoryClient) *worksmobileSyncService { return &worksmobileSyncService{ tenantService: tenantService, userRepo: userRepo, outboxRepo: outboxRepo, client: client, } } func (s *worksmobileSyncService) GetTenantOverview(ctx context.Context, tenantID string) (WorksmobileTenantOverview, error) { tenant, err := s.tenantService.GetTenant(ctx, tenantID) if err != nil { return WorksmobileTenantOverview{}, err } jobs, _ := s.outboxRepo.ListRecent(ctx, 50) jobs = redactWorksmobileOutboxPayloads(jobs) return WorksmobileTenantOverview{ Tenant: *tenant, Config: WorksmobileConfigSummary{ Enabled: WorksmobileEnabled(tenant.Config), DomainMappings: WorksmobileDomainMappings(tenant.Config), TokenConfigured: worksmobileDirectoryAuthConfigured(), AdminTenantID: strings.TrimSpace(os.Getenv("WORKS_ADMIN_TENANT_ID")), }, RecentJobs: jobs, }, nil } func worksmobileDirectoryAuthConfigured() bool { if strings.TrimSpace(os.Getenv("WORKS_ADMIN_ACCESS_TOKEN")) != "" || strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_ACCESS_TOKEN")) != "" { return true } return strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_ID")) != "" && strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_SECRET")) != "" && strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_SERVICE_ACCOUNT")) != "" && (strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY")) != "" || strings.TrimSpace(os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE")) != "") } func redactWorksmobileOutboxPayloads(jobs []domain.WorksmobileOutbox) []domain.WorksmobileOutbox { for i := range jobs { if jobs[i].Payload != nil { jobs[i].Payload = nil } } return jobs } func (s *worksmobileSyncService) GetComparison(ctx context.Context, tenantID string, includeMatched bool) (WorksmobileComparison, error) { root, err := s.hanmacRoot(ctx, tenantID) if err != nil { return WorksmobileComparison{}, err } if s.client == nil { return WorksmobileComparison{}, errors.New("worksmobile client is not configured") } tenants, err := s.hanmacSubtree(ctx, root.ID) if err != nil { return WorksmobileComparison{}, err } tenantByID := worksmobileTenantByID(tenants) tenantByID[root.ID] = *root tenantIDs := make([]string, 0, len(tenants)) for _, tenant := range tenants { if isWorksmobileUserScopeTenant(tenant) { tenantIDs = append(tenantIDs, tenant.ID) } } users, err := s.userRepo.FindByTenantIDs(ctx, tenantIDs) if err != nil { return WorksmobileComparison{}, err } remoteUsers, err := s.client.ListUsers(ctx) if err != nil { return WorksmobileComparison{}, err } remoteGroups, err := s.client.ListGroups(ctx) if err != nil { return WorksmobileComparison{}, err } return WorksmobileComparison{ Users: compareWorksmobileUsers(users, remoteUsers, includeMatched, tenantByID), Groups: compareWorksmobileGroups(append([]domain.Tenant{*root}, tenants...), remoteGroups, includeMatched), }, nil } func (s *worksmobileSyncService) EnqueueBackfillDryRun(ctx context.Context, tenantID string) (WorksmobileBackfillDryRun, error) { root, err := s.hanmacRoot(ctx, tenantID) if err != nil { return WorksmobileBackfillDryRun{}, err } tenants, err := s.hanmacSubtree(ctx, root.ID) if err != nil { return WorksmobileBackfillDryRun{}, err } orgUnitTenantIDs := make([]string, 0, len(tenants)) userTenantIDs := make([]string, 0, len(tenants)) tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, tenants...)) for _, tenant := range tenants { if isWorksmobileOrgUnitTenant(tenant, tenantByID) { orgUnitTenantIDs = append(orgUnitTenantIDs, tenant.ID) } if isWorksmobileUserScopeTenant(tenant) { userTenantIDs = append(userTenantIDs, tenant.ID) } } users, err := s.userRepo.FindByTenantIDs(ctx, userTenantIDs) if err != nil { return WorksmobileBackfillDryRun{}, err } _ = s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{ ResourceType: domain.WorksmobileResourceOrgUnit, ResourceID: root.ID, Action: domain.WorksmobileActionDryRun, DedupeKey: "backfill:dry-run:" + root.ID, Payload: domain.JSONMap{ "tenantIds": orgUnitTenantIDs, "userCount": len(users), }, }) return WorksmobileBackfillDryRun{OrgUnitCount: len(orgUnitTenantIDs), UserCount: len(users)}, nil } func (s *worksmobileSyncService) EnqueueOrgUnitSync(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error) { root, err := s.hanmacRoot(ctx, tenantID) if err != nil { return nil, err } tenant, err := s.tenantService.GetTenant(ctx, orgUnitID) if err != nil { return nil, err } tenantRoot, ok, err := s.rootForTenant(ctx, *tenant) if err != nil { return nil, err } if !ok || tenantRoot.ID != root.ID { return nil, errors.New("target orgunit is outside hanmac-family subtree") } scopeTenants, err := s.hanmacSubtree(ctx, root.ID) if err != nil { return nil, err } tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...)) if !isWorksmobileOrgUnitTenant(*tenant, tenantByID) { return nil, errors.New("target tenant is not a worksmobile orgunit tenant") } payload, err := BuildWorksmobileOrgUnitPayloadForDomainTenant( *tenant, worksmobileDomainClassificationTenant(*tenant, tenantByID), root.Config, 0, ) if err != nil { return nil, err } payload = normalizeWorksmobileOrgUnitParent(payload, *tenant, tenantByID, root.ID) item := &domain.WorksmobileOutbox{ ResourceType: domain.WorksmobileResourceOrgUnit, ResourceID: tenant.ID, Action: domain.WorksmobileActionUpsert, DedupeKey: "orgunit:upsert:" + tenant.ID, Payload: domain.JSONMap{ "request": payload, "matchLocalPart": tenant.Slug, }, } if err := s.outboxRepo.Create(ctx, item); err != nil { return nil, err } return item, nil } func (s *worksmobileSyncService) EnqueueUserSync(ctx context.Context, tenantID, userID string) (*domain.WorksmobileOutbox, error) { root, err := s.hanmacRoot(ctx, tenantID) if err != nil { return nil, err } user, err := s.userRepo.FindByID(ctx, userID) if err != nil { return nil, err } if user.TenantID == nil { return nil, errors.New("target user has no tenant") } tenant, err := s.tenantService.GetTenant(ctx, *user.TenantID) if err != nil { return nil, err } tenantRoot, ok, err := s.rootForTenant(ctx, *tenant) if err != nil { return nil, err } if !ok || tenantRoot.ID != root.ID { return nil, errors.New("target user is outside hanmac-family subtree") } scopeTenants, err := s.hanmacSubtree(ctx, root.ID) if err != nil { return nil, err } tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...)) payload, err := BuildWorksmobileUserPayloadForDomainTenants( *user, *tenant, tenantByID, root.Config, ) if err != nil { return nil, err } if err := s.validateUserAliasLocalParts(ctx, root, *user, payload); err != nil { return nil, err } action := WorksmobileUserStatusAction(user.Status) item := &domain.WorksmobileOutbox{ ResourceType: domain.WorksmobileResourceUser, ResourceID: user.ID, Action: action, DedupeKey: "user:" + strings.ToLower(action) + ":" + user.ID, Payload: worksmobileUserOutboxPayload(root.ID, payload), } if err := s.outboxRepo.Create(ctx, item); err != nil { return nil, err } return item, nil } func (s *worksmobileSyncService) ListInitialPasswordCredentials(ctx context.Context, tenantID string) ([]WorksmobileInitialPasswordCredential, error) { root, err := s.hanmacRoot(ctx, tenantID) if err != nil { return nil, err } jobs, err := s.outboxRepo.ListRecent(ctx, 1000) if err != nil { return nil, err } credentials := make([]WorksmobileInitialPasswordCredential, 0) seen := map[string]bool{} for _, job := range jobs { if job.ResourceType != domain.WorksmobileResourceUser { continue } if stringValue(job.Payload["tenantRootId"]) != root.ID { continue } email := stringValue(job.Payload["loginEmail"]) password := stringValue(job.Payload["initialPassword"]) if email == "" || password == "" || seen[email] { continue } seen[email] = true credentials = append(credentials, WorksmobileInitialPasswordCredential{ Email: email, InitialPassword: password, Status: job.Status, LastError: job.LastError, }) } return credentials, nil } func (s *worksmobileSyncService) RetryJob(ctx context.Context, tenantID, jobID string) (*domain.WorksmobileOutbox, error) { if _, err := s.hanmacRoot(ctx, tenantID); err != nil { return nil, err } if err := s.outboxRepo.MarkRetry(ctx, jobID); err != nil { return nil, err } return s.outboxRepo.FindByID(ctx, jobID) } func (s *worksmobileSyncService) EnqueueTenantUpsertIfInScope(ctx context.Context, tenant domain.Tenant) error { root, ok, err := s.rootForTenant(ctx, tenant) if err != nil || !ok { return err } scopeTenants, err := s.hanmacSubtree(ctx, root.ID) if err != nil { return err } tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...)) if !isWorksmobileOrgUnitTenant(tenant, tenantByID) { return nil } payload, err := BuildWorksmobileOrgUnitPayloadForDomainTenant( tenant, worksmobileDomainClassificationTenant(tenant, tenantByID), root.Config, 0, ) if err != nil { return err } payload = normalizeWorksmobileOrgUnitParent(payload, tenant, tenantByID, root.ID) return s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{ ResourceType: domain.WorksmobileResourceOrgUnit, ResourceID: tenant.ID, Action: domain.WorksmobileActionUpsert, DedupeKey: "orgunit:upsert:" + tenant.ID, Payload: domain.JSONMap{ "request": payload, "matchLocalPart": tenant.Slug, }, }) } func (s *worksmobileSyncService) EnqueueTenantDeleteIfInScope(ctx context.Context, tenant domain.Tenant) error { root, ok, err := s.rootForTenant(ctx, tenant) if err != nil || !ok { return err } scopeTenants, err := s.hanmacSubtree(ctx, root.ID) if err != nil { return err } tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...)) if !isWorksmobileOrgUnitTenant(tenant, tenantByID) { return nil } return s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{ ResourceType: domain.WorksmobileResourceOrgUnit, ResourceID: tenant.ID, Action: domain.WorksmobileActionDelete, DedupeKey: "orgunit:delete:" + tenant.ID, Payload: domain.JSONMap{"orgUnitExternalKey": tenant.ID}, }) } func (s *worksmobileSyncService) EnqueueUserUpsertIfInScope(ctx context.Context, user domain.User) error { if user.TenantID == nil || *user.TenantID == "" { return nil } tenant, err := s.tenantService.GetTenant(ctx, *user.TenantID) if err != nil { return err } root, ok, err := s.rootForTenant(ctx, *tenant) if err != nil || !ok { return err } scopeTenants, err := s.hanmacSubtree(ctx, root.ID) if err != nil { return err } tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...)) payload, err := BuildWorksmobileUserPayloadForDomainTenants( user, *tenant, tenantByID, root.Config, ) if err != nil { return err } if err := s.validateUserAliasLocalParts(ctx, root, user, payload); err != nil { return err } action := WorksmobileUserStatusAction(user.Status) return s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{ ResourceType: domain.WorksmobileResourceUser, ResourceID: user.ID, Action: action, DedupeKey: "user:" + strings.ToLower(action) + ":" + user.ID, Payload: worksmobileUserOutboxPayload(root.ID, payload), }) } func (s *worksmobileSyncService) EnqueueUserDeleteIfInScope(ctx context.Context, user domain.User) error { if user.TenantID == nil || *user.TenantID == "" { return nil } tenant, err := s.tenantService.GetTenant(ctx, *user.TenantID) if err != nil { return err } _, ok, err := s.rootForTenant(ctx, *tenant) if err != nil || !ok { return err } return s.outboxRepo.Create(ctx, &domain.WorksmobileOutbox{ ResourceType: domain.WorksmobileResourceUser, ResourceID: user.ID, Action: domain.WorksmobileActionDelete, DedupeKey: "user:delete:" + user.ID, Payload: domain.JSONMap{ "userExternalKey": user.ID, "loginEmail": user.Email, }, }) } func (s *worksmobileSyncService) hanmacRoot(ctx context.Context, tenantID string) (*domain.Tenant, error) { tenant, err := s.tenantService.GetTenant(ctx, tenantID) if err != nil { return nil, err } if tenant.Slug != HanmacFamilyTenantSlug || tenant.ParentID != nil { return nil, errors.New("worksmobile is only available for hanmac-family root tenant") } return tenant, nil } func (s *worksmobileSyncService) hanmacSubtree(ctx context.Context, rootID string) ([]domain.Tenant, error) { all, _, err := s.tenantService.ListTenants(ctx, 10000, 0, "") if err != nil { return nil, err } byParent := map[string][]domain.Tenant{} for _, tenant := range all { if tenant.ParentID != nil { byParent[*tenant.ParentID] = append(byParent[*tenant.ParentID], tenant) } } result := []domain.Tenant{} var visit func(id string) visit = func(id string) { for _, child := range byParent[id] { result = append(result, child) visit(child.ID) } } visit(rootID) sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name }) return result, nil } func (s *worksmobileSyncService) rootForTenant(ctx context.Context, tenant domain.Tenant) (*domain.Tenant, bool, error) { current := tenant for current.ParentID != nil && *current.ParentID != "" { parent, err := s.tenantService.GetTenant(ctx, *current.ParentID) if err != nil { return nil, false, err } current = *parent } return ¤t, current.Slug == HanmacFamilyTenantSlug, nil } func (s *worksmobileSyncService) validateUserAliasLocalParts(ctx context.Context, root *domain.Tenant, user domain.User, payload WorksmobileUserPayload) error { if len(payload.AliasEmails) == 0 { return nil } tenants, err := s.hanmacSubtree(ctx, root.ID) if err != nil { return err } tenantByID := make(map[string]domain.Tenant, len(tenants)+1) tenantByID[root.ID] = *root tenantIDs := make([]string, 0, len(tenants)+1) tenantIDs = append(tenantIDs, root.ID) for _, tenant := range tenants { tenantByID[tenant.ID] = tenant if isWorksmobileUserScopeTenant(tenant) { tenantIDs = append(tenantIDs, tenant.ID) } } users, err := s.userRepo.FindByTenantIDs(ctx, tenantIDs) if err != nil { return err } existing := map[string]string{} for _, existingUser := range users { if existingUser.ID == user.ID { continue } addWorksmobileLocalPart(existing, existingUser.Email, existingUser.ID) if existingUser.TenantID == nil { continue } tenant, ok := tenantByID[*existingUser.TenantID] if !ok { continue } for _, alias := range BuildWorksmobileAliasEmails(existingUser, tenant) { addWorksmobileLocalPart(existing, alias, existingUser.ID) } } return ValidateWorksmobileAliasLocalParts(payload.Email, payload.AliasEmails, existing) } func addWorksmobileLocalPart(target map[string]string, email string, owner string) { localPart, err := domain.ExtractNormalizedEmailLocalPart(email) if err == nil && localPart != "" { target[localPart] = owner } } func isWorksmobileOrgUnitTenant(tenant domain.Tenant, tenantByID map[string]domain.Tenant) bool { if tenant.Type == domain.TenantTypeOrganization { return true } if tenant.Type == domain.TenantTypeCompany { return isWorksmobileBarongroupChildCompany(tenant, tenantByID) } return false } func isWorksmobileUserScopeTenant(tenant domain.Tenant) bool { return tenant.Type == domain.TenantTypeCompany || tenant.Type == domain.TenantTypeOrganization || tenant.Type == domain.TenantTypeUserGroup } func worksmobileDomainClassificationTenant(tenant domain.Tenant, tenantByID map[string]domain.Tenant) domain.Tenant { current := tenant for { if current.Type == domain.TenantTypeCompany || len(current.Domains) > 0 { return current } parentID := worksmobileTenantParentID(current) if parentID == "" { return tenant } parent, ok := tenantByID[parentID] if !ok { return tenant } current = parent } } func isWorksmobileBarongroupChildCompany(tenant domain.Tenant, tenantByID map[string]domain.Tenant) bool { if tenant.Type != domain.TenantTypeCompany || tenant.Slug == "baron-group" { return false } parentID := worksmobileTenantParentID(tenant) for parentID != "" { parent, ok := tenantByID[parentID] if !ok { return false } if parent.Slug == "baron-group" { return true } parentID = worksmobileTenantParentID(parent) } return false } func normalizeWorksmobileOrgUnitParent(payload WorksmobileOrgUnitPayload, tenant domain.Tenant, tenantByID map[string]domain.Tenant, rootID string) WorksmobileOrgUnitPayload { if tenant.ParentID != nil && *tenant.ParentID == rootID { payload.ParentOrgUnitID = "" } if tenant.ParentID != nil { if parent, ok := tenantByID[*tenant.ParentID]; ok { if parent.Slug == "baron-group" || !isWorksmobileOrgUnitTenant(parent, tenantByID) { payload.ParentOrgUnitID = "" } } } return payload } func worksmobileUserOutboxPayload(rootID string, payload WorksmobileUserPayload) domain.JSONMap { return domain.JSONMap{ "request": payload, "tenantRootId": rootID, "loginEmail": payload.Email, "initialPassword": payload.PasswordConfig.Password, } } func stringValue(value any) string { switch v := value.(type) { case string: return strings.TrimSpace(v) default: return "" } } func compareWorksmobileUsers(localUsers []domain.User, remoteUsers []WorksmobileRemoteUser, includeMatched bool, localTenants map[string]domain.Tenant) []WorksmobileComparisonItem { remoteByExternalID := map[string]WorksmobileRemoteUser{} remoteByEmail := map[string]WorksmobileRemoteUser{} for _, remote := range remoteUsers { if remote.ExternalID != "" { remoteByExternalID[remote.ExternalID] = remote } if normalizedEmail := strings.ToLower(strings.TrimSpace(remote.Email)); normalizedEmail != "" { remoteByEmail[normalizedEmail] = remote } } localByID := map[string]domain.User{} matchedRemoteIDs := map[string]bool{} result := make([]WorksmobileComparisonItem, 0) for _, user := range localUsers { localByID[user.ID] = user remote, matched := remoteByExternalID[user.ID] if !matched { remote, matched = remoteByEmail[strings.ToLower(strings.TrimSpace(user.Email))] } if matched && !includeMatched { matchedRemoteIDs[remote.ID] = true continue } item := WorksmobileComparisonItem{ ResourceType: "USER", BaronID: user.ID, BaronName: user.Name, BaronEmail: user.Email, BaronPrimaryOrgID: worksmobileUserPrimaryOrgID(user), BaronPrimaryOrgName: worksmobileUserPrimaryOrgName(user, localTenants), Status: "missing_in_worksmobile", } if matched { item.Status = "matched" item.WorksmobileID = remote.ID item.ExternalKey = remote.ExternalID item.WorksmobileName = remote.DisplayName item.WorksmobileEmail = remote.Email item.WorksmobileLevelID = remote.LevelID item.WorksmobileLevelName = remote.LevelName item.WorksmobileTask = remote.Task item.WorksmobileDomainID = remote.DomainID item.WorksmobileDomainName = remote.DomainName item.WorksmobilePrimaryOrgID = remote.PrimaryOrgUnitID item.WorksmobilePrimaryOrgName = remote.PrimaryOrgUnitName item.WorksmobilePrimaryOrgPositionID = remote.PrimaryOrgUnitPositionID item.WorksmobilePrimaryOrgPositionName = remote.PrimaryOrgUnitPositionName item.WorksmobilePrimaryOrgIsManager = remote.PrimaryOrgUnitIsManager matchedRemoteIDs[remote.ID] = true } result = append(result, item) } for _, remote := range remoteUsers { if matchedRemoteIDs[remote.ID] { continue } if remote.ExternalID == "" { result = append(result, WorksmobileComparisonItem{ ResourceType: "USER", WorksmobileID: remote.ID, ExternalKey: remote.ExternalID, WorksmobileName: remote.DisplayName, WorksmobileEmail: remote.Email, WorksmobileLevelID: remote.LevelID, WorksmobileLevelName: remote.LevelName, WorksmobileTask: remote.Task, WorksmobileDomainID: remote.DomainID, WorksmobileDomainName: remote.DomainName, WorksmobilePrimaryOrgID: remote.PrimaryOrgUnitID, WorksmobilePrimaryOrgName: remote.PrimaryOrgUnitName, WorksmobilePrimaryOrgPositionID: remote.PrimaryOrgUnitPositionID, WorksmobilePrimaryOrgPositionName: remote.PrimaryOrgUnitPositionName, WorksmobilePrimaryOrgIsManager: remote.PrimaryOrgUnitIsManager, Status: "missing_external_key", }) continue } if _, ok := localByID[remote.ExternalID]; !ok { result = append(result, WorksmobileComparisonItem{ ResourceType: "USER", WorksmobileID: remote.ID, ExternalKey: remote.ExternalID, WorksmobileName: remote.DisplayName, WorksmobileEmail: remote.Email, WorksmobileLevelID: remote.LevelID, WorksmobileLevelName: remote.LevelName, WorksmobileTask: remote.Task, WorksmobileDomainID: remote.DomainID, WorksmobileDomainName: remote.DomainName, WorksmobilePrimaryOrgID: remote.PrimaryOrgUnitID, WorksmobilePrimaryOrgName: remote.PrimaryOrgUnitName, WorksmobilePrimaryOrgPositionID: remote.PrimaryOrgUnitPositionID, WorksmobilePrimaryOrgPositionName: remote.PrimaryOrgUnitPositionName, WorksmobilePrimaryOrgIsManager: remote.PrimaryOrgUnitIsManager, Status: "missing_in_baron", }) } } return result } func worksmobileUserPrimaryOrgID(user domain.User) string { if user.TenantID == nil { return "" } return strings.TrimSpace(*user.TenantID) } func worksmobileUserPrimaryOrgName(user domain.User, localTenants map[string]domain.Tenant) string { tenantID := worksmobileUserPrimaryOrgID(user) if tenantID == "" { return "" } if tenant, ok := localTenants[tenantID]; ok { return strings.TrimSpace(tenant.Name) } if user.Tenant != nil && user.Tenant.ID == tenantID { return strings.TrimSpace(user.Tenant.Name) } return "" } func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []WorksmobileRemoteGroup, includeMatched bool) []WorksmobileComparisonItem { remoteByExternalID := map[string]WorksmobileRemoteGroup{} remoteByMailLocalPart := map[string]WorksmobileRemoteGroup{} ambiguousMailLocalParts := map[string]bool{} for _, remote := range remoteGroups { if remote.ExternalID != "" { remoteByExternalID[remote.ExternalID] = remote } if remote.ExternalID == "" && remote.MailLocalPart != "" { if _, exists := remoteByMailLocalPart[remote.MailLocalPart]; exists { delete(remoteByMailLocalPart, remote.MailLocalPart) ambiguousMailLocalParts[remote.MailLocalPart] = true continue } if !ambiguousMailLocalParts[remote.MailLocalPart] { remoteByMailLocalPart[remote.MailLocalPart] = remote } } } tenantByID := worksmobileTenantByID(localTenants) localByID := map[string]domain.Tenant{} ignoredLocalByID := map[string]bool{} matchedRemoteIDs := map[string]bool{} result := make([]WorksmobileComparisonItem, 0) for _, tenant := range localTenants { if !isWorksmobileOrgUnitTenant(tenant, tenantByID) { ignoredLocalByID[tenant.ID] = true continue } localByID[tenant.ID] = tenant remote, matched := remoteByExternalID[tenant.ID] if !matched { remote, matched = remoteByMailLocalPart[worksmobileMailLocalPart(tenant.Slug)] } if matched && !includeMatched { matchedRemoteIDs[remote.ID] = true continue } item := WorksmobileComparisonItem{ ResourceType: "GROUP", BaronID: tenant.ID, BaronName: tenant.Name, BaronParentID: worksmobileTenantParentID(tenant), BaronParentName: worksmobileTenantParentName(tenant, tenantByID), Status: "missing_in_worksmobile", } if matched { item.Status = "matched" item.WorksmobileID = remote.ID item.ExternalKey = remote.ExternalID item.WorksmobileName = remote.DisplayName item.WorksmobileEmail = remote.Email item.WorksmobileDomainID = remote.DomainID item.WorksmobileDomainName = remote.DomainName item.WorksmobileParentID = remote.ParentID item.WorksmobileParentName = remote.ParentName matchedRemoteIDs[remote.ID] = true } result = append(result, item) } for _, remote := range remoteGroups { if matchedRemoteIDs[remote.ID] { continue } if remote.ExternalID == "" { result = append(result, WorksmobileComparisonItem{ ResourceType: "GROUP", WorksmobileID: remote.ID, ExternalKey: remote.ExternalID, WorksmobileName: remote.DisplayName, WorksmobileEmail: remote.Email, WorksmobileDomainID: remote.DomainID, WorksmobileDomainName: remote.DomainName, WorksmobileParentID: remote.ParentID, WorksmobileParentName: remote.ParentName, Status: "missing_external_key", }) continue } if ignoredLocalByID[remote.ExternalID] { continue } if _, ok := localByID[remote.ExternalID]; !ok { result = append(result, WorksmobileComparisonItem{ ResourceType: "GROUP", WorksmobileID: remote.ID, ExternalKey: remote.ExternalID, WorksmobileName: remote.DisplayName, WorksmobileEmail: remote.Email, WorksmobileDomainID: remote.DomainID, WorksmobileDomainName: remote.DomainName, WorksmobileParentID: remote.ParentID, WorksmobileParentName: remote.ParentName, Status: "missing_in_baron", }) } } return result } func worksmobileTenantByID(tenants []domain.Tenant) map[string]domain.Tenant { result := make(map[string]domain.Tenant, len(tenants)) for _, tenant := range tenants { result[tenant.ID] = tenant } return result } func worksmobileTenantParentID(tenant domain.Tenant) string { if tenant.ParentID == nil { return "" } return strings.TrimSpace(*tenant.ParentID) } func worksmobileTenantParentName(tenant domain.Tenant, tenantByID map[string]domain.Tenant) string { parentID := worksmobileTenantParentID(tenant) if parentID == "" { return "" } return strings.TrimSpace(tenantByID[parentID].Name) }