forked from baron/baron-sso
chore: snapshot local state before dev merge
This commit is contained in:
@@ -43,6 +43,9 @@ type worksmobileSyncConfig struct {
|
||||
CreateUsersResultOutput string
|
||||
CreateUsersLimit int
|
||||
CreateUsersForcePasswordChange bool
|
||||
UpdateUserLevelsCSV string
|
||||
UpdateUserLevelsResultOutput string
|
||||
UpdateUserLevelsLimit int
|
||||
ImportHanmacUsersCSV string
|
||||
ImportHanmacUsersResultOutput string
|
||||
ImportHanmacUsersPassword string
|
||||
@@ -168,6 +171,11 @@ func runWorksmobileSync(args []string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if config.UpdateUserLevelsCSV != "" {
|
||||
if err := updateWorksmobileUserLevelsFromCSV(ctx, db, tenantRepo, userRepo, *root, config.UpdateUserLevelsCSV, config.UpdateUserLevelsResultOutput, config.UpdateUserLevelsLimit, newWorksmobileAdminClient()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if config.ImportHanmacUsersCSV != "" {
|
||||
if err := importHanmacUsersAndCreateWorksmobileAccounts(ctx, db, tenantRepo, userRepo, *root, config.ImportHanmacUsersCSV, config.ImportHanmacUsersResultOutput, config.ImportHanmacUsersPassword, config.ImportHanmacUsersLimit, config.ImportHanmacUsersForcePasswordChange, newWorksmobileAdminClient()); err != nil {
|
||||
return err
|
||||
@@ -232,6 +240,9 @@ func resolveWorksmobileSyncConfig(args []string) (worksmobileSyncConfig, error)
|
||||
fs.StringVar(&config.CreateUsersResultOutput, "create-users-result-output", "", "output CSV path for Worksmobile create results")
|
||||
fs.IntVar(&config.CreateUsersLimit, "create-users-limit", 0, "maximum users to create; 0 means all")
|
||||
fs.BoolVar(&config.CreateUsersForcePasswordChange, "create-users-force-password-change", true, "request password change at next login for --create-users-csv")
|
||||
fs.StringVar(&config.UpdateUserLevelsCSV, "update-user-levels-csv", "", "CSV containing user_id column to patch Worksmobile user levels directly")
|
||||
fs.StringVar(&config.UpdateUserLevelsResultOutput, "update-user-levels-result-output", "", "output CSV path for Worksmobile user level patch results")
|
||||
fs.IntVar(&config.UpdateUserLevelsLimit, "update-user-levels-limit", 0, "maximum users to patch levels; 0 means all")
|
||||
fs.StringVar(&config.ImportHanmacUsersCSV, "import-hanmac-users-csv", "", "CSV containing Hanmac internal users to upsert into Baron and create in Worksmobile")
|
||||
fs.StringVar(&config.ImportHanmacUsersResultOutput, "import-hanmac-users-result-output", "", "output CSV path for Hanmac user import and Worksmobile create results")
|
||||
fs.StringVar(&config.ImportHanmacUsersPassword, "import-hanmac-users-password", "", "initial password for --import-hanmac-users-csv")
|
||||
@@ -256,8 +267,8 @@ func resolveWorksmobileSyncConfig(args []string) (worksmobileSyncConfig, error)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return config, err
|
||||
}
|
||||
if !config.SyncOrgUnits && config.UsersCSV == "" && config.InspectUsersCSV == "" && config.InspectOrgUnitsCSV == "" && config.UpsertOrgUnitID == "" && config.UndeleteUsersCSV == "" && config.RemoveAliasesCSV == "" && config.FindNumberStrippedAliasesOutput == "" && config.DuplicatePhoneCountryCodeOutput == "" && !config.FixDuplicatePhoneCountryCode && config.PendingUsersOutput == "" && config.ResetPendingUsersPassword == "" && config.DeletePendingUsersResultOutput == "" && config.ForceDeleteUsersCSV == "" && config.CreateUsersCSV == "" && config.ImportHanmacUsersCSV == "" && config.RecreatePendingUsersPassword == "" && config.ActivateAllUsersOutput == "" && config.ComparisonOutput == "" && config.AlignBaronFromWorksOutput == "" && !config.Process {
|
||||
return config, fmt.Errorf("nothing to do; pass --orgunits, --users-csv, --inspect-users-csv, --inspect-orgunits-csv, --upsert-orgunit-id, --undelete-users-csv, --remove-aliases-csv, --find-number-stripped-aliases-output, --duplicate-phone-country-code-output, --fix-duplicate-phone-country-code, --pending-users-output, --reset-pending-users-password, --delete-pending-users-result-output, --force-delete-users-csv, --create-users-csv, --import-hanmac-users-csv, --recreate-pending-users-password, --activate-all-users-output, --comparison-output, --align-baron-from-works-output, or --process")
|
||||
if !config.SyncOrgUnits && config.UsersCSV == "" && config.InspectUsersCSV == "" && config.InspectOrgUnitsCSV == "" && config.UpsertOrgUnitID == "" && config.UndeleteUsersCSV == "" && config.RemoveAliasesCSV == "" && config.FindNumberStrippedAliasesOutput == "" && config.DuplicatePhoneCountryCodeOutput == "" && !config.FixDuplicatePhoneCountryCode && config.PendingUsersOutput == "" && config.ResetPendingUsersPassword == "" && config.DeletePendingUsersResultOutput == "" && config.ForceDeleteUsersCSV == "" && config.CreateUsersCSV == "" && config.UpdateUserLevelsCSV == "" && config.ImportHanmacUsersCSV == "" && config.RecreatePendingUsersPassword == "" && config.ActivateAllUsersOutput == "" && config.ComparisonOutput == "" && config.AlignBaronFromWorksOutput == "" && !config.Process {
|
||||
return config, fmt.Errorf("nothing to do; pass --orgunits, --users-csv, --inspect-users-csv, --inspect-orgunits-csv, --upsert-orgunit-id, --undelete-users-csv, --remove-aliases-csv, --find-number-stripped-aliases-output, --duplicate-phone-country-code-output, --fix-duplicate-phone-country-code, --pending-users-output, --reset-pending-users-password, --delete-pending-users-result-output, --force-delete-users-csv, --create-users-csv, --update-user-levels-csv, --import-hanmac-users-csv, --recreate-pending-users-password, --activate-all-users-output, --comparison-output, --align-baron-from-works-output, or --process")
|
||||
}
|
||||
if config.ResetPendingUsersPassword != "" && config.ResetPendingUsersResultOutput == "" {
|
||||
return config, fmt.Errorf("--reset-pending-users-result-output is required with --reset-pending-users-password")
|
||||
@@ -277,6 +288,9 @@ func resolveWorksmobileSyncConfig(args []string) (worksmobileSyncConfig, error)
|
||||
if config.CreateUsersCSV != "" && config.CreateUsersResultOutput == "" {
|
||||
return config, fmt.Errorf("--create-users-result-output is required with --create-users-csv")
|
||||
}
|
||||
if config.UpdateUserLevelsCSV != "" && config.UpdateUserLevelsResultOutput == "" {
|
||||
return config, fmt.Errorf("--update-user-levels-result-output is required with --update-user-levels-csv")
|
||||
}
|
||||
if config.ImportHanmacUsersCSV != "" && config.ImportHanmacUsersPassword == "" {
|
||||
return config, fmt.Errorf("--import-hanmac-users-password is required with --import-hanmac-users-csv")
|
||||
}
|
||||
@@ -1346,6 +1360,134 @@ func createWorksmobileUsersFromCSV(ctx context.Context, db *gorm.DB, tenantRepo
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateWorksmobileUserLevelsFromCSV(ctx context.Context, db *gorm.DB, tenantRepo repository.TenantRepository, userRepo repository.UserRepository, root domain.Tenant, usersCSV string, outputPath string, limit int, client *service.WorksmobileHTTPClient) error {
|
||||
if limit < 0 {
|
||||
return fmt.Errorf("--update-user-levels-limit cannot be negative")
|
||||
}
|
||||
userIDs, err := readWorksmobileUserIDsCSV(usersCSV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if limit > 0 && len(userIDs) > limit {
|
||||
userIDs = userIDs[:limit]
|
||||
}
|
||||
tenantIDs, err := activeTenantSubtreeIDs(ctx, db, root.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tenants, err := tenantRepo.FindByIDs(ctx, tenantIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tenantByID := map[string]domain.Tenant{root.ID: root}
|
||||
for _, tenant := range tenants {
|
||||
tenantByID[tenant.ID] = tenant
|
||||
}
|
||||
users, err := userRepo.FindByIDs(ctx, userIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userByID := map[string]domain.User{}
|
||||
for _, user := range users {
|
||||
userByID[user.ID] = user
|
||||
}
|
||||
|
||||
file, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
writer := csv.NewWriter(file)
|
||||
defer writer.Flush()
|
||||
header := []string{"user_id", "email", "name", "domain_id", "baron_level", "status", "error"}
|
||||
if err := writer.Write(header); err != nil {
|
||||
return err
|
||||
}
|
||||
okCount := 0
|
||||
skippedCount := 0
|
||||
errorCount := 0
|
||||
for _, userID := range userIDs {
|
||||
status := "ok"
|
||||
errorMessage := ""
|
||||
email := ""
|
||||
name := ""
|
||||
domainID := ""
|
||||
levelName := ""
|
||||
user, ok := userByID[userID]
|
||||
if !ok {
|
||||
status = "skipped"
|
||||
errorMessage = "baron user not found"
|
||||
skippedCount++
|
||||
} else {
|
||||
email = strings.TrimSpace(user.Email)
|
||||
name = strings.TrimSpace(user.Name)
|
||||
if user.TenantID == nil {
|
||||
status = "skipped"
|
||||
errorMessage = "baron user has no tenant"
|
||||
skippedCount++
|
||||
} else if !domain.IsWorksProvisionedUserStatus(user.Status) {
|
||||
status = "skipped"
|
||||
errorMessage = "baron user status is excluded from Worksmobile sync"
|
||||
skippedCount++
|
||||
} else {
|
||||
tenant, ok := tenantByID[*user.TenantID]
|
||||
if !ok {
|
||||
status = "skipped"
|
||||
errorMessage = "baron user tenant is outside Worksmobile sync scope"
|
||||
skippedCount++
|
||||
} else {
|
||||
payload, err := service.BuildWorksmobileUserPayloadForDomainTenants(user, tenant, tenantByID, root.Config)
|
||||
if err != nil {
|
||||
status = "skipped"
|
||||
errorMessage = err.Error()
|
||||
skippedCount++
|
||||
} else {
|
||||
levelDomainID := worksmobileUserLevelPatchDomainID(payload)
|
||||
domainID = fmt.Sprint(levelDomainID)
|
||||
levelName = strings.TrimSpace(payload.LevelID)
|
||||
expectedLevelName := service.WorksmobileLevelDisplayNameForIdentifier(levelName)
|
||||
if levelName == "" {
|
||||
status = "skipped"
|
||||
errorMessage = "baron user has no level"
|
||||
skippedCount++
|
||||
} else if err := client.PatchUserOrganizationLevelByName(ctx, payload.Email, levelDomainID, levelName); err != nil {
|
||||
status = "error"
|
||||
errorMessage = err.Error()
|
||||
errorCount++
|
||||
} else if remote, err := client.GetUser(ctx, payload.Email); err != nil {
|
||||
status = "error"
|
||||
errorMessage = err.Error()
|
||||
errorCount++
|
||||
} else if !service.WorksmobileLevelIdentifierMatchesRemote(levelName, remote.LevelID, remote.LevelName) {
|
||||
status = "error"
|
||||
errorMessage = fmt.Sprintf("worksmobile level verification failed: expected_level=%s expected_level_name=%s remote_level_id=%s remote_level_name=%s", levelName, expectedLevelName, strings.TrimSpace(remote.LevelID), strings.TrimSpace(remote.LevelName))
|
||||
errorCount++
|
||||
} else {
|
||||
okCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := writer.Write([]string{userID, email, name, domainID, levelName, status, errorMessage}); err != nil {
|
||||
return err
|
||||
}
|
||||
writer.Flush()
|
||||
if err := writer.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Printf("worksmobile user levels update result written: %s targets=%d ok=%d skipped=%d errors=%d\n", outputPath, len(userIDs), okCount, skippedCount, errorCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
func worksmobileUserLevelPatchDomainID(payload service.WorksmobileUserPayload) int64 {
|
||||
if payload.LevelDomainID > 0 {
|
||||
return payload.LevelDomainID
|
||||
}
|
||||
return payload.DomainID
|
||||
}
|
||||
|
||||
type hanmacWorksmobileImportRow struct {
|
||||
Email string
|
||||
Name string
|
||||
@@ -1698,6 +1840,7 @@ func applyHanmacWorksmobileImportRowToUser(user *domain.User, row hanmacWorksmob
|
||||
"tenantId": tenant.ID,
|
||||
"tenantSlug": tenant.Slug,
|
||||
"tenantName": tenant.Name,
|
||||
"grade": strings.TrimSpace(row.Grade),
|
||||
"isPrimary": true,
|
||||
}}
|
||||
}
|
||||
@@ -2300,6 +2443,7 @@ func exportWorksmobileNeedsUpdateComparison(ctx context.Context, syncService ser
|
||||
"baron_id",
|
||||
"baron_name",
|
||||
"baron_email",
|
||||
"baron_grade",
|
||||
"baron_primary_org_id",
|
||||
"baron_primary_org_slug",
|
||||
"baron_primary_org_name",
|
||||
@@ -2336,6 +2480,7 @@ func exportWorksmobileNeedsUpdateComparison(ctx context.Context, syncService ser
|
||||
item.BaronID,
|
||||
item.BaronName,
|
||||
item.BaronEmail,
|
||||
item.BaronGrade,
|
||||
item.BaronPrimaryOrgID,
|
||||
item.BaronPrimaryOrgSlug,
|
||||
item.BaronPrimaryOrgName,
|
||||
@@ -3061,7 +3206,7 @@ func newWorksmobileAdminClient() *service.WorksmobileHTTPClient {
|
||||
},
|
||||
)
|
||||
client.BaseURL = strings.TrimSpace(getenv("WORKS_ADMIN_API_BASE_URL", ""))
|
||||
client.RateLimiter = service.NewWorksmobileAPIRateLimiter(180, time.Minute)
|
||||
client.RateLimiter = service.NewWorksmobileAPIRateLimiter(240, time.Minute)
|
||||
return client
|
||||
}
|
||||
|
||||
@@ -3071,7 +3216,7 @@ func newWorksmobileSCIMClient() *service.WorksmobileHTTPClient {
|
||||
getenv("WORKS_ADMIN_SCIM_TOKEN", getenv("SAMAN_SCIM_LONGLIVE_TOKEN", "")),
|
||||
)
|
||||
client.BaseURL = strings.TrimSpace(getenv("WORKS_ADMIN_API_BASE_URL", ""))
|
||||
client.RateLimiter = service.NewWorksmobileAPIRateLimiter(180, time.Minute)
|
||||
client.RateLimiter = service.NewWorksmobileAPIRateLimiter(240, time.Minute)
|
||||
return client
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user