1
0
forked from baron/baron-sso

네이버 계정 정합성 맞춤

This commit is contained in:
2026-06-15 19:54:09 +09:00
parent 8e9d015443
commit 4d468cd39f
97 changed files with 5837 additions and 2031 deletions

View File

@@ -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 {