Skip to content

Commit

Permalink
refactor: align allowed fields in the SCIM API
Browse files Browse the repository at this point in the history
  • Loading branch information
christiangda committed Oct 6, 2023
1 parent afc1bc4 commit e96d314
Show file tree
Hide file tree
Showing 19 changed files with 574 additions and 282 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"codecov",
"codeql",
"Debugf",
"deepcopy",
"displayname",
"Dockerfiles",
"dwenegar",
Expand Down
5 changes: 3 additions & 2 deletions internal/core/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (ss *SyncService) SyncGroupsAndTheirMembers(ctx context.Context) error {

log.WithFields(log.Fields{
"group_filter": ss.provGroupsFilter,
}).Info("getting groups members from the identity provider")
}).Info("getting users (groups members) from the identity provider")

idpUsersResult, err := ss.prov.GetUsersByGroupsMembers(ctx, idpGroupsMembersResult)
if err != nil {
Expand Down Expand Up @@ -134,7 +134,8 @@ func (ss *SyncService) SyncGroupsAndTheirMembers(ctx context.Context) error {
// - Users emails are equals on both sides, update only the external id (coming from the identity provider)
log.Warn("syncing from scim service, first time syncing")
totalGroupsResult, totalUsersResult, totalGroupsMembersResult, err = scimSync(
ctx, ss.scim,
ctx,
ss.scim,
idpGroupsResult,
idpUsersResult,
idpGroupsMembersResult,
Expand Down
19 changes: 19 additions & 0 deletions internal/deepcopy/deepcopy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package deepcopy

// SliceOfPointers creates a new slice of pointers from the given slice.
// The new slice will have the same length and capacity as the given slice.
// and the values will be copied.
func SliceOfPointers[T any](s []*T) []*T {
newSlice := make([]*T, 0, len(s))

for _, v := range s {
// Create a new struct to hold the copied data
newStruct := new(T)
*newStruct = *v

// Append the new struct to the new slice
newSlice = append(newSlice, newStruct)
}

return newSlice
}
37 changes: 14 additions & 23 deletions internal/idp/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ func buildUser(usr *admin.User) *model.User {
for _, v := range m {
if v.(map[string]interface{})["primary"] != nil {
if v.(map[string]interface{})["primary"].(bool) {
emails = append(emails, model.EmailBuilder().
WithPrimary(v.(map[string]interface{})["primary"].(bool)).
Build())
emails = append(emails,
model.EmailBuilder().
WithPrimary(v.(map[string]interface{})["primary"].(bool)).
Build(),
)

if v.(map[string]interface{})["address"] != nil {
emails[0].Value = v.(map[string]interface{})["address"].(string)
Expand Down Expand Up @@ -90,14 +92,12 @@ func buildUser(usr *admin.User) *model.User {
mainAddress = model.AddressBuilder().
WithFormatted(v.(map[string]interface{})["formatted"].(string)).
WithType(v.(map[string]interface{})["type"].(string)).
WithPrimary(true).
Build()
break
} else if v.(map[string]interface{})["type"].(string) == "home" {
mainAddress = model.AddressBuilder().
WithFormatted(v.(map[string]interface{})["formatted"].(string)).
WithType(v.(map[string]interface{})["type"].(string)).
WithPrimary(true).
Build()
break
}
Expand Down Expand Up @@ -153,15 +153,6 @@ func buildUser(usr *admin.User) *model.User {
mainOrganization.Organization = v.(map[string]interface{})["name"].(string)
}

if v.(map[string]interface{})["primary"] != nil {
mainOrganization.Primary = v.(map[string]interface{})["primary"].(bool)
}

if v.(map[string]interface{})["title"] != nil {
title = v.(map[string]interface{})["title"].(string)
mainOrganization.Title = title
}

var manager *model.Manager
if v.(map[string]interface{})["manager"] != nil {
manager = model.ManagerBuilder().
Expand All @@ -183,7 +174,7 @@ func buildUser(usr *admin.User) *model.User {
displayName = fmt.Sprintf("%s %s", usr.Name.GivenName, usr.Name.FamilyName)
}

createdUser := model.UserBuilder().
userModel := model.UserBuilder().
WithIPID(usr.Id).
WithUserName(usr.PrimaryEmail).
WithDisplayName(displayName).
Expand All @@ -198,33 +189,33 @@ func buildUser(usr *admin.User) *model.User {
Build()

if emails != nil {
createdUser.Emails = emails
userModel.Emails = emails
}

if mainAddress != (model.Address{}) {
createdUser.Addresses = append(createdUser.Addresses, mainAddress)
userModel.Addresses = append(userModel.Addresses, mainAddress)
}

if mainPhone != (model.PhoneNumber{}) {
createdUser.PhoneNumbers = append(createdUser.PhoneNumbers, mainPhone)
userModel.PhoneNumbers = append(userModel.PhoneNumbers, mainPhone)
}

if usr.Name != nil {
createdUser.Name = model.NameBuilder().
userModel.Name = model.NameBuilder().
WithGivenName(usr.Name.GivenName).
WithFamilyName(usr.Name.FamilyName).
WithFormatted(usr.Name.FullName).
Build()
}

if mainOrganization != (model.EnterpriseData{}) {
createdUser.EnterpriseData = &mainOrganization
userModel.EnterpriseData = &mainOrganization
}

// recalculate the hashcode because we have modified the user after building it
createdUser.SetHashCode()
userModel.SetHashCode()

log.Tracef("idp: buildUser() from: %+v, --> to: %+v", convert.ToJSONString(usr), convert.ToJSONString(createdUser))
log.Tracef("idp: buildUser() from: %+v, --> to: %+v", convert.ToJSONString(usr), convert.ToJSONString(userModel))

return createdUser
return userModel
}
4 changes: 0 additions & 4 deletions internal/idp/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,9 @@ func Test_buildUser(t *testing.T) {
WithCostCenter("costCenter").
WithDepartment("department").
WithOrganization("name").
WithPrimary(true).
WithTitle("title").
Build(),
).
WithPreferredLanguage("languageCode").
WithTitle("title").
WithTimezone("").
WithPhoneNumber(
model.PhoneNumberBuilder().
Expand All @@ -102,7 +99,6 @@ func Test_buildUser(t *testing.T) {
model.AddressBuilder().
WithFormatted("formatted work").
WithType("work").
WithPrimary(true).
Build(),
).
Build(),
Expand Down
2 changes: 2 additions & 0 deletions internal/idp/idp.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ func (i *IdentityProvider) GetUsersByGroupsMembers(ctx context.Context, gmr *mod

gu := buildUser(u)

log.Tracef("idp: user: %+v", convert.ToJSONString(gu))

// this is a hack to avoid the second, third, etc repetition of the same user email
primaryEmail := gu.GetPrimaryEmailAddress()
if _, ok := uniqUsers[primaryEmail]; !ok {
Expand Down
19 changes: 12 additions & 7 deletions internal/model/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/gob"
"encoding/json"
"sort"

"github.com/slashdevops/idp-scim-sync/internal/deepcopy"
)

// Group represents a group entity.
Expand Down Expand Up @@ -119,19 +121,22 @@ func (gr *GroupsResult) MarshalJSON() ([]byte, error) {
// this method discards fields that are not used in the hash calculation.
// only fields coming from the Identity Provider are used.
func (gr *GroupsResult) SetHashCode() {
copyResources := make([]*Group, len(gr.Resources))
copy(copyResources, gr.Resources)
// this copy is necessary to avoid changing the original data
// with the sort.Slice function and always be consistent
// when calculating the hash code
c := deepcopy.SliceOfPointers(gr.Resources)

// only these fields are used in the hash calculation
copyStruct := &GroupsResult{
copiedStruct := &GroupsResult{
Items: gr.Items,
Resources: copyResources,
Resources: c,
}

// order the resources by their hash code to be consistency always
sort.Slice(copyStruct.Resources, func(i, j int) bool {
return copyStruct.Resources[i].HashCode < copyStruct.Resources[j].HashCode
// NOTE: review this, it may be a performance issue and may not be necessary
sort.Slice(copiedStruct.Resources, func(i, j int) bool {
return copiedStruct.Resources[i].HashCode < copiedStruct.Resources[j].HashCode
})

gr.HashCode = Hash(copyStruct)
gr.HashCode = Hash(copiedStruct)
}
45 changes: 28 additions & 17 deletions internal/model/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/gob"
"encoding/json"
"sort"

"github.com/slashdevops/idp-scim-sync/internal/deepcopy"
)

// Member represents a member entity.
Expand Down Expand Up @@ -118,16 +120,19 @@ func (mr *MembersResult) UnmarshalBinary(data []byte) error {
// this method discards fields that are not used in the hash calculation.
// only fields coming from the Identity Provider are used.
func (mr *MembersResult) SetHashCode() {
copyResources := make([]*Member, len(mr.Resources))
copy(copyResources, mr.Resources)
// this copy is necessary to avoid changing the original data
// with the sort.Slice function and always be consistent
// when calculating the hash code
c := deepcopy.SliceOfPointers(mr.Resources)

// only these fields are used in the hash calculation
copyStruct := &MembersResult{
Items: mr.Items,
Resources: copyResources,
Resources: c,
}

// order the resources by their hash code to be consistency always
// NOTE: review this, it may be a performance issue and may not be necessary
sort.Slice(copyStruct.Resources, func(i, j int) bool {
return copyStruct.Resources[i].IPID < copyStruct.Resources[j].IPID
})
Expand Down Expand Up @@ -197,23 +202,26 @@ func (gm *GroupMembers) UnmarshalBinary(data []byte) error {
// this method discards fields that are not used in the hash calculation.
// only fields coming from the Identity Provider are used.
func (gm *GroupMembers) SetHashCode() {
copyResources := make([]*Member, len(gm.Resources))
copy(copyResources, gm.Resources)
// this copy is necessary to avoid changing the original data
// with the sort.Slice function and always be consistent
// when calculating the hash code
c := deepcopy.SliceOfPointers(gm.Resources)

// only these fields are used in the hash calculation
copyStruct := &GroupMembers{
copiedStruct := &GroupMembers{
Items: gm.Items,
Group: gm.Group,
Resources: copyResources,
Resources: c,
}

// to order the members of the group we used the email of the members
// because this never could be empty and it is unique
sort.Slice(copyStruct.Resources, func(i, j int) bool {
return copyStruct.Resources[i].Email < copyStruct.Resources[j].Email
// NOTE: review this, it may be a performance issue and may not be necessary
sort.Slice(copiedStruct.Resources, func(i, j int) bool {
return copiedStruct.Resources[i].Email < copiedStruct.Resources[j].Email
})

gm.HashCode = Hash(copyStruct)
gm.HashCode = Hash(copiedStruct)
}

// GroupsMembersResult represents a group members result list entity.
Expand Down Expand Up @@ -273,20 +281,23 @@ func (gmr *GroupsMembersResult) MarshalJSON() ([]byte, error) {
// this method discards fields that are not used in the hash calculation.
// only fields coming from the Identity Provider are used.
func (gmr *GroupsMembersResult) SetHashCode() {
copyResources := make([]*GroupMembers, len(gmr.Resources))
copy(copyResources, gmr.Resources)
// this copy is necessary to avoid changing the original data
// with the sort.Slice function and always be consistent
// when calculating the hash code
c := deepcopy.SliceOfPointers(gmr.Resources)

// only these fields are used in the hash calculation
copyStruct := GroupsMembersResult{
copiedStruct := GroupsMembersResult{
Items: gmr.Items,
Resources: copyResources,
Resources: c,
}

// to order the members of the group we used the email of the members
// because this never could be empty and it is unique
sort.Slice(copyStruct.Resources, func(i, j int) bool {
return copyStruct.Resources[i].HashCode < copyStruct.Resources[j].HashCode
// NOTE: review this, it may be a performance issue and may not be necessary
sort.Slice(copiedStruct.Resources, func(i, j int) bool {
return copiedStruct.Resources[i].HashCode < copiedStruct.Resources[j].HashCode
})

gmr.HashCode = Hash(copyStruct)
gmr.HashCode = Hash(copiedStruct)
}
1 change: 1 addition & 0 deletions internal/model/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ func (s *State) SetHashCode() {
Build()

users = append(users, e)

}
usersResult := UsersResultBuilder().WithResources(users).Build()

Expand Down
5 changes: 2 additions & 3 deletions internal/model/state_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func (b *StateBuilderChoice) WithGroupsMembers(groupsMembers *GroupsMembersResul

// Build returns the State entity.
func (b *StateBuilderChoice) Build() *State {
s := b.s
s.SetHashCode()
return s
b.s.SetHashCode()
return b.s
}
4 changes: 2 additions & 2 deletions internal/model/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func TestState_GobEncode(t *testing.T) {
Active: true,
Name: &Name{FamilyName: "user", GivenName: "1", Formatted: "user 1"},
Emails: []Email{{Value: "[email protected]", Type: "work", Primary: true}},
Addresses: []Address{{Formatted: "address 1", StreetAddress: "street 1", Locality: "locality 1", Region: "region 1", PostalCode: "postalCode 1", Country: "country 1", Primary: true}},
Addresses: []Address{{Formatted: "address 1", StreetAddress: "street 1", Locality: "locality 1", Region: "region 1", PostalCode: "postalCode 1", Country: "country 1"}},
PreferredLanguage: "en",
PhoneNumbers: []PhoneNumber{{Value: "123456789", Type: "work"}},
EnterpriseData: &EnterpriseData{EmployeeNumber: "123456789", CostCenter: "123456789", Organization: "123456789", Division: "123456789", Department: "123456789"},
Expand All @@ -210,7 +210,7 @@ func TestState_GobEncode(t *testing.T) {
Active: true,
Name: &Name{FamilyName: "user", GivenName: "2", Formatted: "user 2"},
Emails: []Email{{Value: "[email protected]", Type: "work", Primary: true}},
Addresses: []Address{{Formatted: "address 2", StreetAddress: "street 2", Locality: "locality 2", Region: "region 2", PostalCode: "postalCode 2", Country: "country 2", Primary: true}},
Addresses: []Address{{Formatted: "address 2", StreetAddress: "street 2", Locality: "locality 2", Region: "region 2", PostalCode: "postalCode 2", Country: "country 2"}},
PreferredLanguage: "en",
PhoneNumbers: []PhoneNumber{{Value: "123456789", Type: "work"}},
EnterpriseData: &EnterpriseData{EmployeeNumber: "123456789", CostCenter: "123456789", Organization: "123456789", Division: "123456789", Department: "123456789"},
Expand Down
Loading

0 comments on commit e96d314

Please sign in to comment.