forked from baron/baron-sso
네이버 계정 정합성 맞춤
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -33,6 +34,7 @@ type WorksmobileDirectoryClient interface {
|
||||
DeleteOrgUnit(ctx context.Context, orgUnitID string) error
|
||||
CreateUser(ctx context.Context, payload WorksmobileUserPayload) error
|
||||
UpsertUser(ctx context.Context, payload WorksmobileUserPayload) error
|
||||
UpdateUserOnly(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
|
||||
@@ -324,17 +326,14 @@ func (c *WorksmobileHTTPClient) DeleteOrgUnit(ctx context.Context, orgUnitID str
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) CreateUser(ctx context.Context, payload WorksmobileUserPayload) error {
|
||||
payload = normalizeWorksmobileUserCreatePayload(payload)
|
||||
return c.sendDirectoryJSON(ctx, http.MethodPost, "/v1.0/users", payload)
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) UpsertUser(ctx context.Context, payload WorksmobileUserPayload) error {
|
||||
err := c.CreateUser(ctx, payload)
|
||||
if apiErr, ok := err.(WorksmobileHTTPError); ok && apiErr.StatusCode == http.StatusConflict {
|
||||
identifier := strings.TrimSpace(payload.Email)
|
||||
if identifier == "" {
|
||||
identifier = strings.TrimSpace(payload.UserExternalKey)
|
||||
}
|
||||
if patchErr := c.PatchUser(ctx, identifier, NewWorksmobileUserPatchPayload(payload)); patchErr != nil {
|
||||
if patchErr := c.updateUserByPatchOnly(ctx, payload); patchErr != nil {
|
||||
return fmt.Errorf("worksmobile user create conflict: %w; patch after conflict failed: %v", err, patchErr)
|
||||
}
|
||||
return nil
|
||||
@@ -342,6 +341,163 @@ func (c *WorksmobileHTTPClient) UpsertUser(ctx context.Context, payload Worksmob
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) UpdateUserOnly(ctx context.Context, payload WorksmobileUserPayload) error {
|
||||
return c.updateUserByPatchOnly(ctx, payload)
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) updateUserByPatchOnly(ctx context.Context, payload WorksmobileUserPayload) error {
|
||||
identifier := strings.TrimSpace(payload.Email)
|
||||
if identifier == "" {
|
||||
identifier = strings.TrimSpace(payload.UserExternalKey)
|
||||
}
|
||||
patchPayload := NewWorksmobileUserPatchPayload(payload)
|
||||
if patchErr := c.PatchUser(ctx, identifier, patchPayload); patchErr != nil {
|
||||
externalKey := strings.TrimSpace(payload.UserExternalKey)
|
||||
if patchAPIError, ok := patchErr.(WorksmobileHTTPError); ok && patchAPIError.StatusCode == http.StatusNotFound && externalKey != "" && externalKey != identifier {
|
||||
if externalKeyPatchErr := c.PatchUser(ctx, externalKey, patchPayload); externalKeyPatchErr == nil {
|
||||
return nil
|
||||
} else {
|
||||
if externalKeyPatchAPIError, ok := externalKeyPatchErr.(WorksmobileHTTPError); ok && externalKeyPatchAPIError.StatusCode == http.StatusNotFound {
|
||||
if lookupPatchErr := c.patchUserByExternalKeyLookup(ctx, externalKey, payload.DomainID, patchPayload); lookupPatchErr == nil {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("patch failed: %w; external key patch failed: %v; external key lookup patch failed: %v", patchErr, externalKeyPatchErr, lookupPatchErr)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("patch failed: %w; external key patch failed: %v", patchErr, externalKeyPatchErr)
|
||||
}
|
||||
}
|
||||
return patchErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) patchUserByExternalKeyLookup(ctx context.Context, externalKey string, requestedDomainID int64, payload WorksmobileUserPatchPayload) error {
|
||||
externalKey = strings.TrimSpace(externalKey)
|
||||
if externalKey == "" {
|
||||
return fmt.Errorf("worksmobile user external key is required")
|
||||
}
|
||||
matches, err := c.findUsersByExternalKey(ctx, externalKey, requestedDomainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return fmt.Errorf("worksmobile user external key match not found after create conflict: %s", externalKey)
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
domainIDs := worksmobileRemoteUserDomainIDs(matches)
|
||||
userIDs := worksmobileRemoteUserIDs(matches)
|
||||
slog.Error(
|
||||
"Worksmobile external key matched multiple users during upsert conflict recovery",
|
||||
"externalKey", externalKey,
|
||||
"requestedDomainID", requestedDomainID,
|
||||
"domainIDs", domainIDs,
|
||||
"userIDs", userIDs,
|
||||
"matchCount", len(matches),
|
||||
)
|
||||
return fmt.Errorf("multiple worksmobile users matched external key: externalKey=%s requestedDomainID=%d domainIDs=%v userIDs=%v", externalKey, requestedDomainID, domainIDs, userIDs)
|
||||
}
|
||||
remote := matches[0]
|
||||
identifiers := compactUniqueStrings(remote.ID, remote.Email, remote.UserName)
|
||||
if len(identifiers) == 0 {
|
||||
return fmt.Errorf("worksmobile user external key match has no patch identifier: %s", externalKey)
|
||||
}
|
||||
var lastErr error
|
||||
for _, identifier := range identifiers {
|
||||
if err := c.PatchUser(ctx, identifier, payload); err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) findUsersByExternalKey(ctx context.Context, externalKey string, requestedDomainID int64) ([]WorksmobileRemoteUser, error) {
|
||||
externalKey = strings.TrimSpace(externalKey)
|
||||
domainIDs := worksmobileExternalKeyLookupDomainIDs(requestedDomainID, c.DomainIDs)
|
||||
if c.directoryAuthConfigured() && len(domainIDs) > 0 {
|
||||
matches := make([]WorksmobileRemoteUser, 0, 1)
|
||||
for _, domainID := range domainIDs {
|
||||
users, err := c.listDirectoryUsers(ctx, []int64{domainID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, user := range users {
|
||||
if user.ExternalID == externalKey {
|
||||
matches = append(matches, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
users, err := c.ListUsers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matches := make([]WorksmobileRemoteUser, 0, 1)
|
||||
for _, user := range users {
|
||||
if user.ExternalID == externalKey {
|
||||
matches = append(matches, user)
|
||||
}
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func worksmobileExternalKeyLookupDomainIDs(requestedDomainID int64, configuredDomainIDs []int64) []int64 {
|
||||
domainIDs := make([]int64, 0, len(configuredDomainIDs)+1)
|
||||
if requestedDomainID > 0 {
|
||||
domainIDs = append(domainIDs, requestedDomainID)
|
||||
}
|
||||
domainIDs = append(domainIDs, configuredDomainIDs...)
|
||||
return uniqueWorksmobileDomainIDs(domainIDs)
|
||||
}
|
||||
|
||||
func worksmobileRemoteUserDomainIDs(users []WorksmobileRemoteUser) []int64 {
|
||||
domainIDs := make([]int64, 0, len(users))
|
||||
for _, user := range users {
|
||||
domainIDs = append(domainIDs, user.DomainID)
|
||||
}
|
||||
return uniqueWorksmobileDomainIDs(domainIDs)
|
||||
}
|
||||
|
||||
func worksmobileRemoteUserIDs(users []WorksmobileRemoteUser) []string {
|
||||
ids := make([]string, 0, len(users))
|
||||
for _, user := range users {
|
||||
if id := strings.TrimSpace(user.ID); id != "" {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
return compactUniqueStrings(ids...)
|
||||
}
|
||||
|
||||
func compactUniqueStrings(values ...string) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
seen := map[string]bool{}
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || seen[value] {
|
||||
continue
|
||||
}
|
||||
seen[value] = true
|
||||
result = append(result, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeWorksmobileUserCreatePayload(payload WorksmobileUserPayload) WorksmobileUserPayload {
|
||||
payload.Email = strings.TrimSpace(payload.Email)
|
||||
payload.PrivateEmail = strings.TrimSpace(payload.PrivateEmail)
|
||||
payload.CellPhone = normalizeWorksmobileOutboundCellPhone(payload.CellPhone)
|
||||
if strings.EqualFold(strings.TrimSpace(payload.PasswordConfig.PasswordCreationType), "ADMIN") &&
|
||||
strings.TrimSpace(payload.PasswordConfig.Password) != "" &&
|
||||
payload.PasswordConfig.ChangePasswordAtNextLogin == nil {
|
||||
changePasswordAtNextLogin := true
|
||||
payload.PasswordConfig.ChangePasswordAtNextLogin = &changePasswordAtNextLogin
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) AddUserAliasEmail(ctx context.Context, userID string, email string) error {
|
||||
userID = strings.TrimSpace(userID)
|
||||
email = strings.TrimSpace(email)
|
||||
@@ -995,7 +1151,22 @@ func NewWorksmobileSCIMUserPayload(payload WorksmobileUserPayload) WorksmobileSC
|
||||
}
|
||||
|
||||
func normalizeWorksmobileOutboundCellPhone(value string) string {
|
||||
return domain.NormalizePhoneNumber(value)
|
||||
normalized := domain.NormalizePhoneNumber(value)
|
||||
if !strings.HasPrefix(normalized, "+82") {
|
||||
if strings.HasPrefix(normalized, "0") {
|
||||
return "+82 " + normalized
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
national := strings.TrimPrefix(normalized, "+82")
|
||||
if national == "" {
|
||||
return normalized
|
||||
}
|
||||
national = strings.TrimLeft(national, "0")
|
||||
if national == "" {
|
||||
return "+82 0"
|
||||
}
|
||||
return "+82 0" + national
|
||||
}
|
||||
|
||||
func NewWorksmobileUserPatchPayload(payload WorksmobileUserPayload) WorksmobileUserPatchPayload {
|
||||
|
||||
Reference in New Issue
Block a user