forked from baron/baron-sso
네이버 웍스 연동기능 개선
This commit is contained in:
@@ -24,6 +24,7 @@ type WorksmobileAdminService interface {
|
||||
GetComparison(ctx context.Context, tenantID string, includeMatched bool) (WorksmobileComparison, error)
|
||||
EnqueueBackfillDryRun(ctx context.Context, tenantID string) (WorksmobileBackfillDryRun, error)
|
||||
EnqueueOrgUnitSync(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error)
|
||||
EnqueueOrgUnitDelete(ctx context.Context, tenantID, worksmobileOrgUnitID string) (*domain.WorksmobileOutbox, error)
|
||||
EnqueueUserSync(ctx context.Context, tenantID, userID string) (*domain.WorksmobileOutbox, error)
|
||||
RetryJob(ctx context.Context, tenantID, jobID string) (*domain.WorksmobileOutbox, error)
|
||||
ListInitialPasswordCredentials(ctx context.Context, tenantID string) ([]WorksmobileInitialPasswordCredential, error)
|
||||
@@ -62,11 +63,14 @@ type WorksmobileComparison struct {
|
||||
type WorksmobileComparisonItem struct {
|
||||
ResourceType string `json:"resourceType"`
|
||||
BaronID string `json:"baronId,omitempty"`
|
||||
BaronSlug string `json:"baronSlug,omitempty"`
|
||||
BaronName string `json:"baronName,omitempty"`
|
||||
BaronEmail string `json:"baronEmail,omitempty"`
|
||||
BaronPrimaryOrgID string `json:"baronPrimaryOrgId,omitempty"`
|
||||
BaronPrimaryOrgSlug string `json:"baronPrimaryOrgSlug,omitempty"`
|
||||
BaronPrimaryOrgName string `json:"baronPrimaryOrgName,omitempty"`
|
||||
BaronParentID string `json:"baronParentId,omitempty"`
|
||||
BaronParentSlug string `json:"baronParentSlug,omitempty"`
|
||||
BaronParentName string `json:"baronParentName,omitempty"`
|
||||
WorksmobileID string `json:"worksmobileId,omitempty"`
|
||||
ExternalKey string `json:"externalKey,omitempty"`
|
||||
@@ -82,8 +86,13 @@ type WorksmobileComparisonItem struct {
|
||||
WorksmobilePrimaryOrgPositionID string `json:"worksmobilePrimaryOrgPositionId,omitempty"`
|
||||
WorksmobilePrimaryOrgPositionName string `json:"worksmobilePrimaryOrgPositionName,omitempty"`
|
||||
WorksmobilePrimaryOrgIsManager *bool `json:"worksmobilePrimaryOrgIsManager,omitempty"`
|
||||
BaronParentWorksmobileID string `json:"baronParentWorksmobileId,omitempty"`
|
||||
BaronParentWorksmobileName string `json:"baronParentWorksmobileName,omitempty"`
|
||||
BaronParentWorksmobileEmail string `json:"baronParentWorksmobileEmail,omitempty"`
|
||||
WorksmobileParentID string `json:"worksmobileParentId,omitempty"`
|
||||
WorksmobileParentName string `json:"worksmobileParentName,omitempty"`
|
||||
WorksmobileParentEmail string `json:"worksmobileParentEmail,omitempty"`
|
||||
WorksmobileParentExternalKey string `json:"worksmobileParentExternalKey,omitempty"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
@@ -243,16 +252,21 @@ func (s *worksmobileSyncService) EnqueueOrgUnitSync(ctx context.Context, tenantI
|
||||
if !isWorksmobileOrgUnitTenant(*tenant, tenantByID) {
|
||||
return nil, errors.New("target tenant is not a worksmobile orgunit tenant")
|
||||
}
|
||||
return s.enqueueOrgUnitUpsert(ctx, root, *tenant, scopeTenants)
|
||||
}
|
||||
|
||||
func (s *worksmobileSyncService) enqueueOrgUnitUpsert(ctx context.Context, root *domain.Tenant, tenant domain.Tenant, scopeTenants []domain.Tenant) (*domain.WorksmobileOutbox, error) {
|
||||
tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
|
||||
payload, err := BuildWorksmobileOrgUnitPayloadForDomainTenant(
|
||||
*tenant,
|
||||
worksmobileDomainClassificationTenant(*tenant, tenantByID),
|
||||
tenant,
|
||||
worksmobileDomainClassificationTenant(tenant, tenantByID),
|
||||
root.Config,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload = normalizeWorksmobileOrgUnitParent(payload, *tenant, tenantByID, root.ID)
|
||||
payload = normalizeWorksmobileOrgUnitParent(payload, tenant, tenantByID, root.ID)
|
||||
item := &domain.WorksmobileOutbox{
|
||||
ResourceType: domain.WorksmobileResourceOrgUnit,
|
||||
ResourceID: tenant.ID,
|
||||
@@ -269,6 +283,62 @@ func (s *worksmobileSyncService) EnqueueOrgUnitSync(ctx context.Context, tenantI
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *worksmobileSyncService) EnqueueOrgUnitDelete(ctx context.Context, tenantID, worksmobileOrgUnitID string) (*domain.WorksmobileOutbox, error) {
|
||||
root, err := s.hanmacRoot(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.client == nil {
|
||||
return nil, errors.New("worksmobile client is not configured")
|
||||
}
|
||||
scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
worksmobileOrgUnitID = strings.TrimSpace(worksmobileOrgUnitID)
|
||||
if worksmobileOrgUnitID == "" {
|
||||
return nil, errors.New("worksmobile orgunit id is required")
|
||||
}
|
||||
groups, err := s.client.ListGroups(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var target *WorksmobileRemoteGroup
|
||||
for i := range groups {
|
||||
if strings.TrimSpace(groups[i].ID) == worksmobileOrgUnitID {
|
||||
target = &groups[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == nil {
|
||||
return nil, errors.New("worksmobile orgunit not found")
|
||||
}
|
||||
tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
|
||||
if tenant, ok := findWorksmobileOrgUnitTenantByRemoteLocalPart(*target, scopeTenants, tenantByID); ok {
|
||||
return s.enqueueOrgUnitUpsert(ctx, root, tenant, scopeTenants)
|
||||
}
|
||||
if isProtectedWorksmobileRemoteOrgUnit(*root, scopeTenants, *target) {
|
||||
return nil, errors.New("protected worksmobile domain root orgunit cannot be deleted")
|
||||
}
|
||||
item := &domain.WorksmobileOutbox{
|
||||
ResourceType: domain.WorksmobileResourceOrgUnit,
|
||||
ResourceID: worksmobileOrgUnitID,
|
||||
Action: domain.WorksmobileActionDelete,
|
||||
DedupeKey: "orgunit:delete:works:" + worksmobileOrgUnitID,
|
||||
Payload: domain.JSONMap{
|
||||
"worksmobileId": worksmobileOrgUnitID,
|
||||
"externalKey": target.ExternalID,
|
||||
"domainId": target.DomainID,
|
||||
"name": target.DisplayName,
|
||||
"email": target.Email,
|
||||
},
|
||||
}
|
||||
if err := s.outboxRepo.Create(ctx, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *worksmobileSyncService) EnqueueUserSync(ctx context.Context, tenantID, userID string) (*domain.WorksmobileOutbox, error) {
|
||||
root, err := s.hanmacRoot(ctx, tenantID)
|
||||
if err != nil {
|
||||
@@ -585,6 +655,53 @@ func addWorksmobileLocalPart(target map[string]string, email string, owner strin
|
||||
}
|
||||
}
|
||||
|
||||
func findWorksmobileOrgUnitTenantByRemoteLocalPart(remote WorksmobileRemoteGroup, localTenants []domain.Tenant, tenantByID map[string]domain.Tenant) (domain.Tenant, bool) {
|
||||
candidates := worksmobileRemoteGroupLocalPartCandidates(remote)
|
||||
for _, tenant := range localTenants {
|
||||
if !isWorksmobileOrgUnitTenant(tenant, tenantByID) {
|
||||
continue
|
||||
}
|
||||
if candidates[normalizeWorksmobileSlugLocalPart(tenant.Slug)] {
|
||||
return tenant, true
|
||||
}
|
||||
}
|
||||
return domain.Tenant{}, false
|
||||
}
|
||||
|
||||
func isProtectedWorksmobileRemoteOrgUnit(root domain.Tenant, localTenants []domain.Tenant, remote WorksmobileRemoteGroup) bool {
|
||||
if strings.TrimSpace(remote.ParentID) == "" {
|
||||
return true
|
||||
}
|
||||
candidates := worksmobileRemoteGroupLocalPartCandidates(remote)
|
||||
if len(candidates) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, tenant := range localTenants {
|
||||
if tenant.ParentID == nil || *tenant.ParentID != root.ID || tenant.Type != domain.TenantTypeCompany {
|
||||
continue
|
||||
}
|
||||
if candidates[normalizeWorksmobileSlugLocalPart(tenant.Slug)] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func worksmobileRemoteGroupLocalPartCandidates(remote WorksmobileRemoteGroup) map[string]bool {
|
||||
result := map[string]bool{}
|
||||
if localPart := normalizeWorksmobileSlugLocalPart(remote.MailLocalPart); localPart != "" {
|
||||
result[localPart] = true
|
||||
}
|
||||
if localPart, err := domain.ExtractNormalizedEmailLocalPart(remote.Email); err == nil && localPart != "" {
|
||||
result[localPart] = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeWorksmobileSlugLocalPart(value string) string {
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
func isWorksmobileOrgUnitTenant(tenant domain.Tenant, tenantByID map[string]domain.Tenant) bool {
|
||||
if tenant.Type == domain.TenantTypeOrganization {
|
||||
return true
|
||||
@@ -703,6 +820,7 @@ func compareWorksmobileUsers(localUsers []domain.User, remoteUsers []Worksmobile
|
||||
BaronName: user.Name,
|
||||
BaronEmail: user.Email,
|
||||
BaronPrimaryOrgID: worksmobileUserPrimaryOrgID(user),
|
||||
BaronPrimaryOrgSlug: worksmobileUserPrimaryOrgSlug(user, localTenants),
|
||||
BaronPrimaryOrgName: worksmobileUserPrimaryOrgName(user, localTenants),
|
||||
Status: "missing_in_worksmobile",
|
||||
}
|
||||
@@ -796,24 +914,30 @@ func worksmobileUserPrimaryOrgName(user domain.User, localTenants map[string]dom
|
||||
return ""
|
||||
}
|
||||
|
||||
func worksmobileUserPrimaryOrgSlug(user domain.User, localTenants map[string]domain.Tenant) string {
|
||||
tenantID := worksmobileUserPrimaryOrgID(user)
|
||||
if tenantID == "" {
|
||||
return ""
|
||||
}
|
||||
if tenant, ok := localTenants[tenantID]; ok {
|
||||
return strings.TrimSpace(tenant.Slug)
|
||||
}
|
||||
if user.Tenant != nil && user.Tenant.ID == tenantID {
|
||||
return strings.TrimSpace(user.Tenant.Slug)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []WorksmobileRemoteGroup, includeMatched bool) []WorksmobileComparisonItem {
|
||||
remoteByExternalID := map[string]WorksmobileRemoteGroup{}
|
||||
remoteByMailLocalPart := map[string]WorksmobileRemoteGroup{}
|
||||
ambiguousMailLocalParts := map[string]bool{}
|
||||
remoteByID := map[string]WorksmobileRemoteGroup{}
|
||||
for _, remote := range remoteGroups {
|
||||
if remote.ID != "" {
|
||||
remoteByID[remote.ID] = remote
|
||||
}
|
||||
if remote.ExternalID != "" {
|
||||
remoteByExternalID[remote.ExternalID] = remote
|
||||
}
|
||||
if remote.ExternalID == "" && remote.MailLocalPart != "" {
|
||||
if _, exists := remoteByMailLocalPart[remote.MailLocalPart]; exists {
|
||||
delete(remoteByMailLocalPart, remote.MailLocalPart)
|
||||
ambiguousMailLocalParts[remote.MailLocalPart] = true
|
||||
continue
|
||||
}
|
||||
if !ambiguousMailLocalParts[remote.MailLocalPart] {
|
||||
remoteByMailLocalPart[remote.MailLocalPart] = remote
|
||||
}
|
||||
}
|
||||
}
|
||||
tenantByID := worksmobileTenantByID(localTenants)
|
||||
localByID := map[string]domain.Tenant{}
|
||||
@@ -827,9 +951,6 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
}
|
||||
localByID[tenant.ID] = tenant
|
||||
remote, matched := remoteByExternalID[tenant.ID]
|
||||
if !matched {
|
||||
remote, matched = remoteByMailLocalPart[worksmobileMailLocalPart(tenant.Slug)]
|
||||
}
|
||||
if matched && !includeMatched {
|
||||
matchedRemoteIDs[remote.ID] = true
|
||||
continue
|
||||
@@ -837,8 +958,10 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
item := WorksmobileComparisonItem{
|
||||
ResourceType: "GROUP",
|
||||
BaronID: tenant.ID,
|
||||
BaronSlug: tenant.Slug,
|
||||
BaronName: tenant.Name,
|
||||
BaronParentID: worksmobileTenantParentID(tenant),
|
||||
BaronParentSlug: worksmobileTenantParentSlug(tenant, tenantByID),
|
||||
BaronParentName: worksmobileTenantParentName(tenant, tenantByID),
|
||||
Status: "missing_in_worksmobile",
|
||||
}
|
||||
@@ -852,6 +975,19 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
item.WorksmobileDomainName = remote.DomainName
|
||||
item.WorksmobileParentID = remote.ParentID
|
||||
item.WorksmobileParentName = remote.ParentName
|
||||
if parentRemote, ok := remoteByExternalID[item.BaronParentID]; ok {
|
||||
item.BaronParentWorksmobileID = parentRemote.ID
|
||||
item.BaronParentWorksmobileName = parentRemote.DisplayName
|
||||
item.BaronParentWorksmobileEmail = parentRemote.Email
|
||||
}
|
||||
if parentRemote, ok := remoteByID[remote.ParentID]; ok {
|
||||
if item.WorksmobileParentName == "" {
|
||||
item.WorksmobileParentName = parentRemote.DisplayName
|
||||
}
|
||||
item.WorksmobileParentEmail = parentRemote.Email
|
||||
item.WorksmobileParentExternalKey = parentRemote.ExternalID
|
||||
}
|
||||
item = fillWorksmobileParentFromBaronParentMatch(item)
|
||||
matchedRemoteIDs[remote.ID] = true
|
||||
}
|
||||
result = append(result, item)
|
||||
@@ -873,6 +1009,14 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
WorksmobileParentName: remote.ParentName,
|
||||
Status: "missing_external_key",
|
||||
})
|
||||
if parentRemote, ok := remoteByID[remote.ParentID]; ok {
|
||||
last := &result[len(result)-1]
|
||||
if last.WorksmobileParentName == "" {
|
||||
last.WorksmobileParentName = parentRemote.DisplayName
|
||||
}
|
||||
last.WorksmobileParentEmail = parentRemote.Email
|
||||
last.WorksmobileParentExternalKey = parentRemote.ExternalID
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ignoredLocalByID[remote.ExternalID] {
|
||||
@@ -891,11 +1035,35 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
WorksmobileParentName: remote.ParentName,
|
||||
Status: "missing_in_baron",
|
||||
})
|
||||
if parentRemote, ok := remoteByID[remote.ParentID]; ok {
|
||||
last := &result[len(result)-1]
|
||||
if last.WorksmobileParentName == "" {
|
||||
last.WorksmobileParentName = parentRemote.DisplayName
|
||||
}
|
||||
last.WorksmobileParentEmail = parentRemote.Email
|
||||
last.WorksmobileParentExternalKey = parentRemote.ExternalID
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fillWorksmobileParentFromBaronParentMatch(item WorksmobileComparisonItem) WorksmobileComparisonItem {
|
||||
if item.WorksmobileParentID == "" || item.WorksmobileParentID != item.BaronParentWorksmobileID {
|
||||
return item
|
||||
}
|
||||
if item.WorksmobileParentName == "" {
|
||||
item.WorksmobileParentName = item.BaronParentWorksmobileName
|
||||
}
|
||||
if item.WorksmobileParentEmail == "" {
|
||||
item.WorksmobileParentEmail = item.BaronParentWorksmobileEmail
|
||||
}
|
||||
if item.WorksmobileParentExternalKey == "" {
|
||||
item.WorksmobileParentExternalKey = item.BaronParentID
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func worksmobileTenantByID(tenants []domain.Tenant) map[string]domain.Tenant {
|
||||
result := make(map[string]domain.Tenant, len(tenants))
|
||||
for _, tenant := range tenants {
|
||||
@@ -918,3 +1086,11 @@ func worksmobileTenantParentName(tenant domain.Tenant, tenantByID map[string]dom
|
||||
}
|
||||
return strings.TrimSpace(tenantByID[parentID].Name)
|
||||
}
|
||||
|
||||
func worksmobileTenantParentSlug(tenant domain.Tenant, tenantByID map[string]domain.Tenant) string {
|
||||
parentID := worksmobileTenantParentID(tenant)
|
||||
if parentID == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(tenantByID[parentID].Slug)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user