Update Google SCIM bridge
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -104,17 +104,20 @@ func (s *Bridge) Run(ctx context.Context) (created, updated, deleted, deactivate
|
|||||||
}
|
}
|
||||||
created++
|
created++
|
||||||
} else {
|
} else {
|
||||||
needsUpdate := s.forceUpdate
|
needsUpdate := s.forceUpdate ||
|
||||||
|
existingSCIM.Active != pu.Active ||
|
||||||
if existingSCIM.Active != pu.Active {
|
existingSCIM.DisplayName != pu.DisplayName ||
|
||||||
needsUpdate = true
|
existingSCIM.Title != pu.Title ||
|
||||||
}
|
existingSCIM.GivenName != pu.GivenName ||
|
||||||
if existingSCIM.DisplayName != pu.DisplayName {
|
existingSCIM.FamilyName != pu.FamilyName ||
|
||||||
needsUpdate = true
|
existingSCIM.ExternalID != pu.ExternalID ||
|
||||||
}
|
existingSCIM.Department != pu.Department ||
|
||||||
if existingSCIM.Title != pu.Title {
|
existingSCIM.CostCenter != pu.CostCenter ||
|
||||||
needsUpdate = true
|
existingSCIM.EnterpriseOrganization != pu.EnterpriseOrganization ||
|
||||||
}
|
existingSCIM.Division != pu.Division ||
|
||||||
|
existingSCIM.EmployeeNumber != pu.EmployeeNumber ||
|
||||||
|
existingSCIM.ManagerValue != pu.ManagerValue ||
|
||||||
|
existingSCIM.PreferredLanguage != pu.PreferredLanguage
|
||||||
|
|
||||||
if needsUpdate {
|
if needsUpdate {
|
||||||
if !s.dryRun {
|
if !s.dryRun {
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ type (
|
|||||||
FamilyName string `json:"-"`
|
FamilyName string `json:"-"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
ExternalID string `json:"-"`
|
||||||
|
Department string `json:"-"`
|
||||||
|
CostCenter string `json:"-"`
|
||||||
|
EnterpriseOrganization string `json:"-"`
|
||||||
|
Division string `json:"-"`
|
||||||
|
EmployeeNumber string `json:"-"`
|
||||||
|
ManagerValue string `json:"-"`
|
||||||
|
PreferredLanguage string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
Users []User
|
Users []User
|
||||||
@@ -114,25 +122,7 @@ func (c *Client) listUsersPage(ctx context.Context, startIndex, count int) (User
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) CreateUser(ctx context.Context, user *User) error {
|
func (c *Client) CreateUser(ctx context.Context, user *User) error {
|
||||||
payload := map[string]any{
|
payload := buildUserPayload(user)
|
||||||
"schemas": []string{"urn:ietf:params:scim:schemas:core:2.0:User"},
|
|
||||||
"userName": user.UserName,
|
|
||||||
"name": map[string]string{
|
|
||||||
"givenName": user.GivenName,
|
|
||||||
"familyName": user.FamilyName,
|
|
||||||
"formatted": user.DisplayName,
|
|
||||||
},
|
|
||||||
"displayName": user.DisplayName,
|
|
||||||
"active": user.Active,
|
|
||||||
"emails": []map[string]any{
|
|
||||||
{
|
|
||||||
"value": user.UserName,
|
|
||||||
"type": "work",
|
|
||||||
"primary": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"title": user.Title,
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := json.Marshal(payload)
|
body, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -163,25 +153,7 @@ func (c *Client) CreateUser(ctx context.Context, user *User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) UpdateUser(ctx context.Context, userID string, user *User) error {
|
func (c *Client) UpdateUser(ctx context.Context, userID string, user *User) error {
|
||||||
payload := map[string]any{
|
payload := buildUserPayload(user)
|
||||||
"schemas": []string{"urn:ietf:params:scim:schemas:core:2.0:User"},
|
|
||||||
"userName": user.UserName,
|
|
||||||
"name": map[string]string{
|
|
||||||
"givenName": user.GivenName,
|
|
||||||
"familyName": user.FamilyName,
|
|
||||||
"formatted": user.DisplayName,
|
|
||||||
},
|
|
||||||
"displayName": user.DisplayName,
|
|
||||||
"active": user.Active,
|
|
||||||
"emails": []map[string]any{
|
|
||||||
{
|
|
||||||
"value": user.UserName,
|
|
||||||
"type": "work",
|
|
||||||
"primary": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"title": user.Title,
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := json.Marshal(payload)
|
body, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -211,6 +183,65 @@ func (c *Client) UpdateUser(ctx context.Context, userID string, user *User) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildUserPayload(user *User) map[string]any {
|
||||||
|
schemas := []string{"urn:ietf:params:scim:schemas:core:2.0:User"}
|
||||||
|
|
||||||
|
enterprise := map[string]any{}
|
||||||
|
if user.EmployeeNumber != "" {
|
||||||
|
enterprise["employeeNumber"] = user.EmployeeNumber
|
||||||
|
}
|
||||||
|
if user.Department != "" {
|
||||||
|
enterprise["department"] = user.Department
|
||||||
|
}
|
||||||
|
if user.CostCenter != "" {
|
||||||
|
enterprise["costCenter"] = user.CostCenter
|
||||||
|
}
|
||||||
|
if user.EnterpriseOrganization != "" {
|
||||||
|
enterprise["organization"] = user.EnterpriseOrganization
|
||||||
|
}
|
||||||
|
if user.Division != "" {
|
||||||
|
enterprise["division"] = user.Division
|
||||||
|
}
|
||||||
|
if user.ManagerValue != "" {
|
||||||
|
enterprise["manager"] = map[string]string{"value": user.ManagerValue}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := map[string]any{
|
||||||
|
"schemas": schemas,
|
||||||
|
"userName": user.UserName,
|
||||||
|
"name": map[string]string{
|
||||||
|
"givenName": user.GivenName,
|
||||||
|
"familyName": user.FamilyName,
|
||||||
|
"formatted": user.DisplayName,
|
||||||
|
},
|
||||||
|
"displayName": user.DisplayName,
|
||||||
|
"active": user.Active,
|
||||||
|
"emails": []map[string]any{
|
||||||
|
{
|
||||||
|
"value": user.UserName,
|
||||||
|
"type": "work",
|
||||||
|
"primary": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": user.Title,
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.ExternalID != "" {
|
||||||
|
payload["externalId"] = user.ExternalID
|
||||||
|
}
|
||||||
|
if user.PreferredLanguage != "" {
|
||||||
|
payload["preferredLanguage"] = user.PreferredLanguage
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(enterprise) > 0 {
|
||||||
|
schemas = append(schemas, "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User")
|
||||||
|
payload["schemas"] = schemas
|
||||||
|
payload["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"] = enterprise
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) DeactivateUser(ctx context.Context, userID string) error {
|
func (c *Client) DeactivateUser(ctx context.Context, userID string) error {
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"schemas": []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"},
|
"schemas": []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"},
|
||||||
|
|||||||
@@ -83,47 +83,20 @@ func (p *Provider) ListUsers(ctx context.Context) (scimclient.Users, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var title string
|
user := scimclient.User{
|
||||||
|
|
||||||
if u.Organizations == nil {
|
|
||||||
title = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(u.Organizations)
|
|
||||||
if err != nil {
|
|
||||||
title = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var orgs []admin.UserOrganization
|
|
||||||
if err := json.Unmarshal(data, &orgs); err != nil {
|
|
||||||
title = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer the primary organization's title
|
|
||||||
for _, org := range orgs {
|
|
||||||
if org.Primary && org.Title != "" {
|
|
||||||
title = org.Title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to the first organization with a title
|
|
||||||
for _, org := range orgs {
|
|
||||||
if org.Title != "" {
|
|
||||||
title = org.Title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allUsers = append(
|
|
||||||
allUsers,
|
|
||||||
scimclient.User{
|
|
||||||
UserName: u.PrimaryEmail,
|
UserName: u.PrimaryEmail,
|
||||||
DisplayName: u.Name.FullName,
|
DisplayName: u.Name.FullName,
|
||||||
GivenName: u.Name.GivenName,
|
GivenName: u.Name.GivenName,
|
||||||
FamilyName: u.Name.FamilyName,
|
FamilyName: u.Name.FamilyName,
|
||||||
Active: !u.Suspended && !u.Archived,
|
Active: !u.Suspended && !u.Archived,
|
||||||
Title: title,
|
}
|
||||||
},
|
|
||||||
)
|
p.extractOrganizationFields(u.Organizations, &user)
|
||||||
|
p.extractExternalID(u.ExternalIds, &user)
|
||||||
|
p.extractRelations(u.Relations, &user)
|
||||||
|
p.extractPreferredLanguage(u.Languages, &user)
|
||||||
|
|
||||||
|
allUsers = append(allUsers, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
pageToken = resp.NextPageToken
|
pageToken = resp.NextPageToken
|
||||||
@@ -134,3 +107,122 @@ func (p *Provider) ListUsers(ctx context.Context) (scimclient.Users, error) {
|
|||||||
|
|
||||||
return allUsers, nil
|
return allUsers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) extractOrganizationFields(raw interface{}, user *scimclient.User) {
|
||||||
|
if raw == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var orgs []admin.UserOrganization
|
||||||
|
if err := json.Unmarshal(data, &orgs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var primary *admin.UserOrganization
|
||||||
|
var first *admin.UserOrganization
|
||||||
|
for i := range orgs {
|
||||||
|
if first == nil {
|
||||||
|
first = &orgs[i]
|
||||||
|
}
|
||||||
|
if orgs[i].Primary {
|
||||||
|
primary = &orgs[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
org := primary
|
||||||
|
if org == nil {
|
||||||
|
org = first
|
||||||
|
}
|
||||||
|
if org == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Title = org.Title
|
||||||
|
user.Department = org.Department
|
||||||
|
user.CostCenter = org.CostCenter
|
||||||
|
user.EnterpriseOrganization = org.Name
|
||||||
|
user.Division = org.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) extractExternalID(raw interface{}, user *scimclient.User) {
|
||||||
|
if raw == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ids []admin.UserExternalId
|
||||||
|
if err := json.Unmarshal(data, &ids); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
if id.Type == "organization" && id.Value != "" {
|
||||||
|
user.ExternalID = id.Value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ids) > 0 && ids[0].Value != "" {
|
||||||
|
user.ExternalID = ids[0].Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) extractRelations(raw interface{}, user *scimclient.User) {
|
||||||
|
if raw == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var relations []admin.UserRelation
|
||||||
|
if err := json.Unmarshal(data, &relations); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rel := range relations {
|
||||||
|
if rel.Type == "manager" && rel.Value != "" {
|
||||||
|
user.ManagerValue = rel.Value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) extractPreferredLanguage(raw interface{}, user *scimclient.User) {
|
||||||
|
if raw == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var languages []admin.UserLanguage
|
||||||
|
if err := json.Unmarshal(data, &languages); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lang := range languages {
|
||||||
|
if lang.Preference == "preferred" && lang.LanguageCode != "" {
|
||||||
|
user.PreferredLanguage = lang.LanguageCode
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(languages) > 0 && languages[0].LanguageCode != "" {
|
||||||
|
user.PreferredLanguage = languages[0].LanguageCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user