diff --git a/services/graph/pkg/identity/ldap_education_user.go b/services/graph/pkg/identity/ldap_education_user.go index 099cb7912b..1905f81d04 100644 --- a/services/graph/pkg/identity/ldap_education_user.go +++ b/services/graph/pkg/identity/ldap_education_user.go @@ -249,6 +249,7 @@ func (i *LDAP) getEducationUserAttrTypes() []string { i.userAttributeMap.userName, i.educationConfig.userAttributeMap.identities, i.educationConfig.userAttributeMap.primaryRole, + i.educationConfig.memberOfSchoolAttribute, } } diff --git a/services/graph/pkg/identity/ldap_education_user_test.go b/services/graph/pkg/identity/ldap_education_user_test.go index 8d42549dbf..2d1017cff7 100644 --- a/services/graph/pkg/identity/ldap_education_user_test.go +++ b/services/graph/pkg/identity/ldap_education_user_test.go @@ -23,13 +23,26 @@ var eduUserEntry = ldap.NewEntry("uid=user,ou=people,dc=test", "xxx $ http://idpnew $ xxxxx-xxxxx-xxxxx", }, }) +var eduUserEntryWithSchool = ldap.NewEntry("uid=user,ou=people,dc=test", + map[string][]string{ + "uid": {"testuser"}, + "displayname": {"Test User"}, + "mail": {"user@example"}, + "entryuuid": {"abcd-defg"}, + "userClass": {"student"}, + "ocMemberOfSchool": {"abcd-defg"}, + "oCExternalIdentity": { + "$ http://idp $ testuser", + "xxx $ http://idpnew $ xxxxx-xxxxx-xxxxx", + }, + }) var sr1 *ldap.SearchRequest = &ldap.SearchRequest{ BaseDN: "ou=people,dc=test", Scope: 2, SizeLimit: 1, Filter: "(&(objectClass=ocEducationUser)(|(uid=abcd-defg)(entryUUID=abcd-defg)))", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass", "ocMemberOfSchool"}, Controls: []ldap.Control(nil), } var sr2 *ldap.SearchRequest = &ldap.SearchRequest{ @@ -37,7 +50,7 @@ var sr2 *ldap.SearchRequest = &ldap.SearchRequest{ Scope: 2, SizeLimit: 1, Filter: "(&(objectClass=ocEducationUser)(|(uid=xxxx-xxxx)(entryUUID=xxxx-xxxx)))", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass", "ocMemberOfSchool"}, Controls: []ldap.Control(nil), } @@ -120,7 +133,7 @@ func TestGetEducationUsers(t *testing.T) { Scope: 2, SizeLimit: 0, Filter: "(objectClass=ocEducationUser)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass", "ocMemberOfSchool"}, Controls: []ldap.Control(nil), } lm.On("Search", sr).Return(&ldap.SearchResult{Entries: []*ldap.Entry{eduUserEntry}}, nil) diff --git a/services/graph/pkg/identity/ldap_school.go b/services/graph/pkg/identity/ldap_school.go index f40e79f1e1..23ff284cd0 100644 --- a/services/graph/pkg/identity/ldap_school.go +++ b/services/graph/pkg/identity/ldap_school.go @@ -256,9 +256,36 @@ func (i *LDAP) AddUsersToEducationSchool(ctx context.Context, schoolID string, m return nil } -// RemoveMemberFromEducationSchool removes a single member (by ID) from a school -func (i *LDAP) RemoveMemberFromEducationSchool(ctx context.Context, schoolID string, memberID string) error { - return errNotImplemented +// RemoveUserFromEducationSchool removes a single member (by ID) from a school +func (i *LDAP) RemoveUserFromEducationSchool(ctx context.Context, schoolID string, memberID string) error { + logger := i.logger.SubloggerWithRequestID(ctx) + logger.Debug().Str("backend", "ldap").Msg("RemoveUserFromEducationSchool") + + schoolEntry, err := i.getSchoolByID(schoolID) + if err != nil { + return err + } + + if schoolEntry == nil { + return errNotFound + } + user, err := i.getEducationUserByNameOrID(memberID) + if err != nil { + i.logger.Warn().Str("userid", memberID).Msg("User does not exist") + return err + } + currentSchools := user.GetEqualFoldAttributeValues(i.educationConfig.memberOfSchoolAttribute) + for _, currentSchool := range currentSchools { + if currentSchool == schoolID { + mr := ldap.ModifyRequest{DN: user.DN} + mr.Delete(i.educationConfig.memberOfSchoolAttribute, []string{schoolID}) + if err := i.conn.Modify(&mr); err != nil { + return err + } + break + } + } + return nil } func (i *LDAP) getSchoolByDN(dn string) (*ldap.Entry, error) { diff --git a/services/graph/pkg/identity/ldap_school_test.go b/services/graph/pkg/identity/ldap_school_test.go index 3d4d5f8f27..8309db8e83 100644 --- a/services/graph/pkg/identity/ldap_school_test.go +++ b/services/graph/pkg/identity/ldap_school_test.go @@ -168,45 +168,64 @@ func TestGetEducationSchools(t *testing.T) { assert.Nil(t, err) } -func TestAddMembersToEducationSchool(t *testing.T) { +var schoolByIDSearch1 *ldap.SearchRequest = &ldap.SearchRequest{ + BaseDN: "", + Scope: 2, + SizeLimit: 1, + Filter: "(&(objectClass=ocEducationSchool)(owncloudUUID=abcd-defg))", + Attributes: []string{"ou", "owncloudUUID", "ocEducationSchoolNumber"}, + Controls: []ldap.Control(nil), +} +var userByIDSearch1 *ldap.SearchRequest = &ldap.SearchRequest{ + BaseDN: "ou=people,dc=test", + Scope: 2, + SizeLimit: 1, + Filter: "(&(objectClass=ocEducationUser)(|(uid=abcd-defg)(entryUUID=abcd-defg)))", + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass", "ocMemberOfSchool"}, + Controls: []ldap.Control(nil), +} +var userByIDSearch2 *ldap.SearchRequest = &ldap.SearchRequest{ + BaseDN: "ou=people,dc=test", + Scope: 2, + SizeLimit: 1, + Filter: "(&(objectClass=ocEducationUser)(|(uid=does-not-exist)(entryUUID=does-not-exist)))", + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass", "ocMemberOfSchool"}, + Controls: []ldap.Control(nil), +} + +func TestAddUsersToEducationSchool(t *testing.T) { lm := &mocks.Client{} - sr1 := &ldap.SearchRequest{ - BaseDN: "", - Scope: 2, - SizeLimit: 1, - Filter: "(&(objectClass=ocEducationSchool)(owncloudUUID=abcd-defg))", - Attributes: []string{"ou", "owncloudUUID", "ocEducationSchoolNumber"}, - Controls: []ldap.Control(nil), - } - sr2 := &ldap.SearchRequest{ - BaseDN: "ou=people,dc=test", - Scope: 2, - SizeLimit: 1, - Filter: "(&(objectClass=ocEducationUser)(|(uid=abcd-defg)(entryUUID=abcd-defg)))", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass"}, - Controls: []ldap.Control(nil), - } - sr3 := &ldap.SearchRequest{ - BaseDN: "ou=people,dc=test", - Scope: 2, - SizeLimit: 1, - Filter: "(&(objectClass=ocEducationUser)(|(uid=does-not-exist)(entryUUID=does-not-exist)))", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "oCExternalIdentity", "userClass"}, - Controls: []ldap.Control(nil), - } - lm.On("Search", sr1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry, schoolEntry1}}, nil) - lm.On("Search", sr2).Return(&ldap.SearchResult{Entries: []*ldap.Entry{eduUserEntry}}, nil) - lm.On("Search", sr3).Return(&ldap.SearchResult{Entries: []*ldap.Entry{}}, nil) + lm.On("Search", schoolByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry, schoolEntry1}}, nil) + lm.On("Search", userByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{eduUserEntry}}, nil) + lm.On("Search", userByIDSearch2).Return(&ldap.SearchResult{Entries: []*ldap.Entry{}}, nil) lm.On("Modify", mock.Anything).Return(nil) b, err := getMockedBackend(lm, eduConfig, &logger) assert.Nil(t, err) - err = b.AddMembersToEducationSchool(context.Background(), "abcd-defg", []string{"does-not-exist"}) + err = b.AddUsersToEducationSchool(context.Background(), "abcd-defg", []string{"does-not-exist"}) lm.AssertNumberOfCalls(t, "Search", 2) assert.NotNil(t, err) - err = b.AddMembersToEducationSchool(context.Background(), "abcd-defg", []string{"abcd-defg", "does-not-exist"}) + err = b.AddUsersToEducationSchool(context.Background(), "abcd-defg", []string{"abcd-defg", "does-not-exist"}) lm.AssertNumberOfCalls(t, "Search", 5) assert.NotNil(t, err) - err = b.AddMembersToEducationSchool(context.Background(), "abcd-defg", []string{"abcd-defg"}) + err = b.AddUsersToEducationSchool(context.Background(), "abcd-defg", []string{"abcd-defg"}) lm.AssertNumberOfCalls(t, "Search", 7) assert.Nil(t, err) } + +func TestRemoveMemberFromEducationSchoo(t *testing.T) { + lm := &mocks.Client{} + lm.On("Search", schoolByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry, schoolEntry1}}, nil) + lm.On("Search", userByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{eduUserEntryWithSchool}}, nil) + lm.On("Search", userByIDSearch2).Return(&ldap.SearchResult{Entries: []*ldap.Entry{}}, nil) + lm.On("Modify", mock.Anything).Return(nil) + b, err := getMockedBackend(lm, eduConfig, &logger) + assert.Nil(t, err) + err = b.RemoveUserFromEducationSchool(context.Background(), "abcd-defg", "does-not-exist") + lm.AssertNumberOfCalls(t, "Search", 2) + assert.NotNil(t, err) + assert.Equal(t, "itemNotFound", err.Error()) + err = b.RemoveUserFromEducationSchool(context.Background(), "abcd-defg", "abcd-defg") + lm.AssertNumberOfCalls(t, "Search", 4) + lm.AssertNumberOfCalls(t, "Modify", 1) + assert.Nil(t, err) +}