forked from baron/baron-sso
패키징 개선
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"baron-sso-backend/internal/service"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -59,6 +60,9 @@ type worksmobileSyncConfig struct {
|
||||
ComparisonOutput string
|
||||
AlignBaronFromWorksOutput string
|
||||
AlignBaronFromWorksExclude string
|
||||
ImportFromWorksEmails string
|
||||
PatchWorksUserNameEmail string
|
||||
PatchWorksUserName string
|
||||
InspectOutput string
|
||||
CredentialBatchID string
|
||||
Process bool
|
||||
@@ -202,6 +206,28 @@ func runWorksmobileSync(args []string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if config.ImportFromWorksEmails != "" {
|
||||
kratosAdmin := service.NewKratosAdminService()
|
||||
syncService.SetIdentityServices(service.NewIdentityWriteService(kratosAdmin, nil), kratosAdmin)
|
||||
worksmobileUserIDs, err := resolveWorksmobileUserIDsByEmail(ctx, newWorksmobileAdminClient(), config.ImportFromWorksEmails)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := syncService.ImportUsersFromWorks(ctx, root.ID, worksmobileUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(encoded))
|
||||
}
|
||||
if config.PatchWorksUserNameEmail != "" {
|
||||
if err := patchWorksmobileUserName(ctx, newWorksmobileAdminClient(), config.PatchWorksUserNameEmail, config.PatchWorksUserName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if config.Process {
|
||||
return processWorksmobileOutbox(ctx, db, outboxRepo, config)
|
||||
}
|
||||
@@ -256,6 +282,9 @@ func resolveWorksmobileSyncConfig(args []string) (worksmobileSyncConfig, error)
|
||||
fs.StringVar(&config.ComparisonOutput, "comparison-output", "", "output CSV path for current Worksmobile user comparison rows whose status is needs_update")
|
||||
fs.StringVar(&config.AlignBaronFromWorksOutput, "align-baron-from-works-output", "", "output CSV path for one-time Baron user updates from current Worksmobile needs_update rows")
|
||||
fs.StringVar(&config.AlignBaronFromWorksExclude, "align-baron-from-works-exclude", "", "comma-separated emails or local-parts to exclude from --align-baron-from-works-output")
|
||||
fs.StringVar(&config.ImportFromWorksEmails, "import-from-works-emails", "", "comma-separated Worksmobile emails to import into Baron and patch Worksmobile externalKey")
|
||||
fs.StringVar(&config.PatchWorksUserNameEmail, "patch-works-user-name-email", "", "Worksmobile email to patch userName by PATCH-only")
|
||||
fs.StringVar(&config.PatchWorksUserName, "patch-works-user-name", "", "display name for --patch-works-user-name-email")
|
||||
fs.StringVar(&config.InspectOutput, "inspect-output", "", "output CSV path for inspect/undelete commands")
|
||||
fs.StringVar(&config.CredentialBatchID, "credential-batch-id", "", "credential batch id for regenerated user password rows")
|
||||
fs.BoolVar(&config.Process, "process", false, "process ready Worksmobile outbox jobs")
|
||||
@@ -267,8 +296,11 @@ 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.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.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.ImportFromWorksEmails == "" && config.PatchWorksUserNameEmail == "" && !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, --import-from-works-emails, --patch-works-user-name-email, or --process")
|
||||
}
|
||||
if config.PatchWorksUserNameEmail != "" && strings.TrimSpace(config.PatchWorksUserName) == "" {
|
||||
return config, fmt.Errorf("--patch-works-user-name is required with --patch-works-user-name-email")
|
||||
}
|
||||
if config.ResetPendingUsersPassword != "" && config.ResetPendingUsersResultOutput == "" {
|
||||
return config, fmt.Errorf("--reset-pending-users-result-output is required with --reset-pending-users-password")
|
||||
@@ -306,6 +338,119 @@ func resolveWorksmobileSyncConfig(args []string) (worksmobileSyncConfig, error)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func resolveWorksmobileUserIDsByEmail(ctx context.Context, client service.WorksmobileDirectoryClient, rawEmails string) ([]string, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("worksmobile client is not configured")
|
||||
}
|
||||
targetEmails := splitCommaSeparatedValues(rawEmails)
|
||||
if len(targetEmails) == 0 {
|
||||
return nil, errors.New("--import-from-works-emails requires at least one email")
|
||||
}
|
||||
remoteUsers, err := client.ListUsers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteByEmail := make(map[string]service.WorksmobileRemoteUser, len(remoteUsers))
|
||||
for _, remote := range remoteUsers {
|
||||
email := strings.ToLower(strings.TrimSpace(remote.Email))
|
||||
if email == "" {
|
||||
continue
|
||||
}
|
||||
remoteByEmail[email] = remote
|
||||
}
|
||||
userIDs := make([]string, 0, len(targetEmails))
|
||||
for _, targetEmail := range targetEmails {
|
||||
remote, ok := remoteByEmail[strings.ToLower(targetEmail)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("worksmobile user not found by email: %s", targetEmail)
|
||||
}
|
||||
if id := strings.TrimSpace(remote.ID); id != "" {
|
||||
userIDs = append(userIDs, id)
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("worksmobile user id is empty for email: %s", targetEmail)
|
||||
}
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
func splitCommaSeparatedValues(raw string) []string {
|
||||
parts := strings.Split(raw, ",")
|
||||
values := make([]string, 0, len(parts))
|
||||
seen := map[string]bool{}
|
||||
for _, part := range parts {
|
||||
value := strings.TrimSpace(part)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(value)
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
values = append(values, value)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func patchWorksmobileUserName(ctx context.Context, client service.WorksmobileDirectoryClient, email string, displayName string) error {
|
||||
if client == nil {
|
||||
return errors.New("worksmobile client is not configured")
|
||||
}
|
||||
email = strings.ToLower(strings.TrimSpace(email))
|
||||
displayName = strings.TrimSpace(displayName)
|
||||
if email == "" || displayName == "" {
|
||||
return errors.New("email and display name are required")
|
||||
}
|
||||
remoteUsers, err := client.ListUsers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var target *service.WorksmobileRemoteUser
|
||||
for i := range remoteUsers {
|
||||
if strings.EqualFold(strings.TrimSpace(remoteUsers[i].Email), email) {
|
||||
target = &remoteUsers[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == nil {
|
||||
return fmt.Errorf("worksmobile user not found by email: %s", email)
|
||||
}
|
||||
if err := client.UpdateUserOnly(ctx, service.WorksmobileUserPayload{
|
||||
DomainID: target.DomainID,
|
||||
Email: strings.TrimSpace(target.Email),
|
||||
UserExternalKey: strings.TrimSpace(target.ExternalID),
|
||||
UserName: adminctlWorksmobileUserNameFromDisplayName(displayName),
|
||||
CellPhone: strings.TrimSpace(target.CellPhone),
|
||||
EmployeeNumber: strings.TrimSpace(target.EmployeeNumber),
|
||||
Locale: "ko_KR",
|
||||
Task: strings.TrimSpace(target.Task),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("worksmobile user name patched: email=%s display_name=%s\n", email, displayName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func adminctlWorksmobileUserNameFromDisplayName(name string) service.WorksmobileUserName {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" || strings.ContainsAny(name, " \t\r\n") {
|
||||
return service.WorksmobileUserName{LastName: name}
|
||||
}
|
||||
runes := []rune(name)
|
||||
if len(runes) < 2 || len(runes) > 4 {
|
||||
return service.WorksmobileUserName{LastName: name}
|
||||
}
|
||||
for _, r := range runes {
|
||||
if r < '가' || r > '힣' {
|
||||
return service.WorksmobileUserName{LastName: name}
|
||||
}
|
||||
}
|
||||
return service.WorksmobileUserName{
|
||||
LastName: string(runes[:1]),
|
||||
FirstName: string(runes[1:]),
|
||||
}
|
||||
}
|
||||
|
||||
func enqueueWorksmobileOrgUnits(ctx context.Context, db *gorm.DB, syncService service.WorksmobileAdminService, rootID string) (int, int, int, error) {
|
||||
tenantIDs, err := activeTenantSubtreeIDs(ctx, db, rootID)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user