1
0
forked from baron/baron-sso

feat: update worksmobile sync and restore planning

This commit is contained in:
2026-06-01 17:01:53 +09:00
parent 6574fb54b9
commit 5c8a338085
36 changed files with 3922 additions and 243 deletions

View File

@@ -30,6 +30,8 @@ type WorksmobileDirectoryClient interface {
DeleteOrgUnit(ctx context.Context, orgUnitID string) error
CreateUser(ctx context.Context, payload WorksmobileUserPayload) error
UpsertUser(ctx context.Context, payload WorksmobileUserPayload) error
AddUserAliasEmail(ctx context.Context, userID string, email string) error
ResetUserPassword(ctx context.Context, userID string, password string) error
DeleteUser(ctx context.Context, userID string) error
SetUserActive(ctx context.Context, userID string, active bool) error
ListUsers(ctx context.Context) ([]WorksmobileRemoteUser, error)
@@ -283,6 +285,45 @@ func (c *WorksmobileHTTPClient) UpsertUser(ctx context.Context, payload Worksmob
return err
}
func (c *WorksmobileHTTPClient) AddUserAliasEmail(ctx context.Context, userID string, email string) error {
userID = strings.TrimSpace(userID)
email = strings.TrimSpace(email)
if userID == "" {
return fmt.Errorf("worksmobile user id is required")
}
if email == "" {
return fmt.Errorf("worksmobile alias email is required")
}
err := c.sendDirectoryJSON(
ctx,
http.MethodPost,
"/v1.0/users/"+url.PathEscape(userID)+"/alias-emails/"+url.PathEscape(email),
nil,
)
if apiErr, ok := err.(WorksmobileHTTPError); ok && apiErr.StatusCode == http.StatusConflict {
return nil
}
return err
}
func (c *WorksmobileHTTPClient) ResetUserPassword(ctx context.Context, userID string, password string) error {
userID = strings.TrimSpace(userID)
password = strings.TrimSpace(password)
if userID == "" {
return fmt.Errorf("worksmobile user id is required")
}
if password == "" {
return fmt.Errorf("worksmobile password is required")
}
payload := map[string]any{
"passwordConfig": WorksmobilePasswordConfig{
PasswordCreationType: "ADMIN",
Password: password,
},
}
return c.sendDirectoryJSON(ctx, http.MethodPatch, "/v1.0/users/"+url.PathEscape(userID), payload)
}
func (c *WorksmobileHTTPClient) PatchUser(ctx context.Context, identifier string, payload WorksmobileUserPatchPayload) error {
identifier = strings.TrimSpace(identifier)
if identifier == "" {
@@ -756,22 +797,23 @@ type WorksmobileOrgUnitPatchPayload struct {
}
type WorksmobileRemoteUser struct {
ID string `json:"id"`
ExternalID string `json:"externalId"`
UserName string `json:"userName"`
Email string `json:"email"`
DisplayName string `json:"displayName"`
LevelID string `json:"levelId"`
LevelName string `json:"levelName"`
Task string `json:"task"`
DomainID int64 `json:"domainId"`
DomainName string `json:"domainName"`
PrimaryOrgUnitID string `json:"primaryOrgUnitId"`
PrimaryOrgUnitName string `json:"primaryOrgUnitName"`
PrimaryOrgUnitPositionID string `json:"primaryOrgUnitPositionId"`
PrimaryOrgUnitPositionName string `json:"primaryOrgUnitPositionName"`
PrimaryOrgUnitIsManager *bool `json:"primaryOrgUnitIsManager,omitempty"`
Active bool `json:"active"`
ID string `json:"id"`
ExternalID string `json:"externalId"`
UserName string `json:"userName"`
Email string `json:"email"`
DisplayName string `json:"displayName"`
LevelID string `json:"levelId"`
LevelName string `json:"levelName"`
Task string `json:"task"`
DomainID int64 `json:"domainId"`
DomainName string `json:"domainName"`
PrimaryOrgUnitID string `json:"primaryOrgUnitId"`
PrimaryOrgUnitName string `json:"primaryOrgUnitName"`
PrimaryOrgUnitPositionID string `json:"primaryOrgUnitPositionId"`
PrimaryOrgUnitPositionName string `json:"primaryOrgUnitPositionName"`
PrimaryOrgUnitIsManager *bool `json:"primaryOrgUnitIsManager,omitempty"`
OrgUnitManagers map[string]*bool `json:"orgUnitManagers,omitempty"`
Active bool `json:"active"`
}
type WorksmobileRemoteGroup struct {
@@ -907,6 +949,7 @@ func parseWorksmobileDirectoryUser(resource map[string]any) WorksmobileRemoteUse
user.PrimaryOrgUnitPositionID = primaryOrgUnit.PositionID
user.PrimaryOrgUnitPositionName = primaryOrgUnit.PositionName
user.PrimaryOrgUnitIsManager = primaryOrgUnit.IsManager
user.OrgUnitManagers = parseWorksmobileOrgUnitManagers(resource)
return user
}
@@ -1029,6 +1072,43 @@ func parseWorksmobilePrimaryOrgUnitDetail(resource map[string]any) worksmobileOr
return worksmobileOrgUnitDetail{}
}
func parseWorksmobileOrgUnitManagers(resource map[string]any) map[string]*bool {
result := map[string]*bool{}
collectWorksmobileOrgUnitManagers(resource["organizations"], result)
collectWorksmobileOrgUnitManagers(resource["orgUnits"], result)
for key, raw := range resource {
if !strings.Contains(strings.ToLower(key), "works") {
continue
}
if values, ok := raw.(map[string]any); ok {
collectWorksmobileOrgUnitManagers(values["organizations"], result)
collectWorksmobileOrgUnitManagers(values["orgUnits"], result)
}
}
if len(result) == 0 {
return nil
}
return result
}
func collectWorksmobileOrgUnitManagers(raw any, result map[string]*bool) {
values, ok := raw.([]any)
if !ok {
return
}
for _, item := range values {
orgUnit, ok := item.(map[string]any)
if !ok {
continue
}
if id := firstStringFromMap(orgUnit, "orgUnitId", "id", "value"); id != "" {
result[id] = boolPointerFromMap(orgUnit, "isManager", "manager")
}
collectWorksmobileOrgUnitManagers(orgUnit["organizations"], result)
collectWorksmobileOrgUnitManagers(orgUnit["orgUnits"], result)
}
}
func parseWorksmobileParentOrgUnit(resource map[string]any) (string, string) {
id := firstStringFromMap(resource, "parentOrgUnitId", "parentId")
name := firstStringFromMap(resource, "parentOrgUnitName", "parentName")