From 9b8adb65ed60e4b62b4777847c7ca0895a4b845b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Franke?= Date: Thu, 26 Jan 2023 14:29:04 +0100 Subject: [PATCH] Add LDAP entries for adding classes to schools. --- .../pkg/identity/ldap_education_class.go | 22 +-- .../pkg/identity/ldap_education_school.go | 185 +++++++++++++++--- .../graph/pkg/identity/ldap_education_user.go | 24 ++- 3 files changed, 182 insertions(+), 49 deletions(-) diff --git a/services/graph/pkg/identity/ldap_education_class.go b/services/graph/pkg/identity/ldap_education_class.go index f19af0963..0d2d96afa 100644 --- a/services/graph/pkg/identity/ldap_education_class.go +++ b/services/graph/pkg/identity/ldap_education_class.go @@ -341,16 +341,14 @@ func (i *LDAP) getEducationClassLDAPDN(class libregraph.EducationClass) string { return fmt.Sprintf("ocEducationExternalId=%s,%s", oldap.EscapeDNAttributeValue(class.GetExternalId()), i.groupBaseDN) } -// getEducationClassByID looks up a class by id (and be ID or externalID) -func (i *LDAP) getEducationClassByID(id string, requestMembers bool) (*ldap.Entry, error) { - id = ldap.EscapeFilter(id) - filter := fmt.Sprintf("(|(%s=%s)(%s=%s))", - i.groupAttributeMap.id, id, - i.educationConfig.classAttributeMap.externalID, id) - return i.getEducationClassByFilter(filter, requestMembers) -} - -func (i *LDAP) getEducationClassByFilter(filter string, requestMembers bool) (*ldap.Entry, error) { - filter = fmt.Sprintf("(&%s(objectClass=%s)%s)", i.groupFilter, i.educationConfig.classObjectClass, filter) - return i.searchLDAPEntryByFilter(i.groupBaseDN, i.getEducationClassAttrTypes(requestMembers), filter) +func (i *LDAP) getEducationClassByID(nameOrID string, requestMembers bool) (*ldap.Entry, error) { + return i.getEducationObjectByNameOrID( + nameOrID, + i.groupAttributeMap.name, + i.groupAttributeMap.id, + i.groupFilter, + i.educationConfig.classObjectClass, + i.groupBaseDN, + i.getEducationClassAttrTypes(requestMembers), + ) } diff --git a/services/graph/pkg/identity/ldap_education_school.go b/services/graph/pkg/identity/ldap_education_school.go index 6be681fc0..48f89a593 100644 --- a/services/graph/pkg/identity/ldap_education_school.go +++ b/services/graph/pkg/identity/ldap_education_school.go @@ -10,6 +10,7 @@ import ( "github.com/gofrs/uuid" libregraph "github.com/owncloud/libre-graph-api-go" oldap "github.com/owncloud/ocis/v2/ocis-pkg/ldap" + "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/graph/pkg/config" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ) @@ -352,42 +353,16 @@ func (i *LDAP) GetEducationSchoolUsers(ctx context.Context, schoolNumberOrID str logger := i.logger.SubloggerWithRequestID(ctx) logger.Debug().Str("backend", "ldap").Msg("GetEducationSchoolUsers") - schoolEntry, err := i.getSchoolByNumberOrID(schoolNumberOrID) + entries, err := i.getEducationSchoolEntries( + schoolNumberOrID, i.userFilter, i.educationConfig.userObjectClass, i.userBaseDN, i.userScope, i.getUserAttrTypes(), logger, + ) if err != nil { return nil, err } - if schoolEntry == nil { - return nil, ErrNotFound - } - schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id) - schoolID = ldap.EscapeFilter(schoolID) - idFilter := fmt.Sprintf("(%s=%s)", i.educationConfig.memberOfSchoolAttribute, schoolID) - userFilter := fmt.Sprintf("(&%s(objectClass=%s)%s)", i.userFilter, i.educationConfig.userObjectClass, idFilter) + users := make([]*libregraph.EducationUser, 0, len(entries)) - searchRequest := ldap.NewSearchRequest( - i.userBaseDN, - i.userScope, - ldap.NeverDerefAliases, 0, 0, false, - userFilter, - i.getEducationUserAttrTypes(), - nil, - ) - logger.Debug().Str("backend", "ldap"). - Str("base", searchRequest.BaseDN). - Str("filter", searchRequest.Filter). - Int("scope", searchRequest.Scope). - Int("sizelimit", searchRequest.SizeLimit). - Interface("attributes", searchRequest.Attributes). - Msg("GetEducationUsers") - res, err := i.conn.Search(searchRequest) - if err != nil { - return nil, errorcode.New(errorcode.ItemNotFound, err.Error()) - } - - users := make([]*libregraph.EducationUser, 0, len(res.Entries)) - - for _, e := range res.Entries { + for _, e := range entries { u := i.createEducationUserModelFromLDAP(e) // Skip invalid LDAP users if u == nil { @@ -479,6 +454,154 @@ func (i *LDAP) RemoveUserFromEducationSchool(ctx context.Context, schoolNumberOr return nil } +// GetEducationSchoolClasses implements the EducationBackend interface for the LDAP backend. +func (i *LDAP) GetEducationSchoolClasses(ctx context.Context, schoolNumberOrID string) ([]*libregraph.EducationClass, error) { + logger := i.logger.SubloggerWithRequestID(ctx) + logger.Debug().Str("backend", "ldap").Msg("GetEducationSchoolClasses") + + entries, err := i.getEducationSchoolEntries( + schoolNumberOrID, i.groupFilter, i.educationConfig.classObjectClass, i.groupBaseDN, i.groupScope, i.getEducationClassAttrTypes(false), logger, // TODO: Add attributes + ) + if err != nil { + return nil, err + } + + users := make([]*libregraph.EducationClass, 0, len(entries)) + + for _, e := range entries { + u := i.createEducationClassModelFromLDAP(e) + // Skip invalid LDAP classes + if u == nil { + continue + } + users = append(users, u) + } + return users, nil +} + +func (i *LDAP) getEducationSchoolEntries( + schoolNumberOrID, filter, objectClass, baseDN string, + scope int, + attributes []string, + logger log.Logger, +) ([]*ldap.Entry, error) { + schoolEntry, err := i.getSchoolByNumberOrID(schoolNumberOrID) + if err != nil { + return nil, err + } + + if schoolEntry == nil { + return nil, ErrNotFound + } + + schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id) + schoolID = ldap.EscapeFilter(schoolID) + idFilter := fmt.Sprintf("(%s=%s)", i.educationConfig.memberOfSchoolAttribute, schoolID) + searchFilter := fmt.Sprintf("(&%s(objectClass=%s)%s)", filter, objectClass, idFilter) + + searchRequest := ldap.NewSearchRequest( + baseDN, + scope, + ldap.NeverDerefAliases, 0, 0, false, + searchFilter, + attributes, + nil, + ) + logger.Debug().Str("backend", "ldap"). + Str("base", searchRequest.BaseDN). + Str("filter", searchRequest.Filter). + Int("scope", searchRequest.Scope). + Int("sizelimit", searchRequest.SizeLimit). + Interface("attributes", searchRequest.Attributes). + Msg("GetEducationClasses") + res, err := i.conn.Search(searchRequest) + if err != nil { + return nil, errorcode.New(errorcode.ItemNotFound, err.Error()) + } + return res.Entries, nil +} + +// AddClassesToEducationSchool adds new members (reference by a slice of IDs) to supplied school in the identity backend. +func (i *LDAP) AddClassesToEducationSchool(ctx context.Context, schoolNumberOrID string, memberIDs []string) error { + logger := i.logger.SubloggerWithRequestID(ctx) + logger.Debug().Str("backend", "ldap").Msg("AddClassesToEducationSchool") + + schoolEntry, err := i.getSchoolByNumberOrID(schoolNumberOrID) + if err != nil { + return err + } + + if schoolEntry == nil { + return ErrNotFound + } + + schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id) + + classEntries := make([]*ldap.Entry, 0, len(memberIDs)) + for _, memberID := range memberIDs { + class, err := i.getEducationClassByID(memberID, false) + if err != nil { + i.logger.Warn().Str("userid", memberID).Msg("Class does not exist") + return err + } + classEntries = append(classEntries, class) + } + + for _, classEntry := range classEntries { + currentSchools := classEntry.GetEqualFoldAttributeValues(i.educationConfig.memberOfSchoolAttribute) + found := false + for _, currentSchool := range currentSchools { + if currentSchool == schoolID { + found = true + break + } + } + if !found { + mr := ldap.ModifyRequest{DN: classEntry.DN} + mr.Add(i.educationConfig.memberOfSchoolAttribute, []string{schoolID}) + if err := i.conn.Modify(&mr); err != nil { + return err + } + } + } + + return nil +} + +// RemoveClassFromEducationSchool removes a single member (by ID) from a school +func (i *LDAP) RemoveClassFromEducationSchool(ctx context.Context, schoolNumberOrID string, memberID string) error { + logger := i.logger.SubloggerWithRequestID(ctx) + logger.Debug().Str("backend", "ldap").Msg("RemoveClassFromEducationSchool") + + schoolEntry, err := i.getSchoolByNumberOrID(schoolNumberOrID) + if err != nil { + return err + } + + if schoolEntry == nil { + return ErrNotFound + } + + schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id) + user, err := i.getEducationClassByID(memberID, false) + if err != nil { + i.logger.Warn().Str("userid", memberID).Msg("Class 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) { attrs := []string{ i.educationConfig.schoolAttributeMap.displayName, diff --git a/services/graph/pkg/identity/ldap_education_user.go b/services/graph/pkg/identity/ldap_education_user.go index 9fec76a2e..3df8dffba 100644 --- a/services/graph/pkg/identity/ldap_education_user.go +++ b/services/graph/pkg/identity/ldap_education_user.go @@ -265,12 +265,24 @@ func (i *LDAP) getEducationUserByDN(dn string) (*ldap.Entry, error) { } func (i *LDAP) getEducationUserByNameOrID(nameOrID string) (*ldap.Entry, error) { - nameOrID = ldap.EscapeFilter(nameOrID) - filter := fmt.Sprintf("(|(%s=%s)(%s=%s))", i.userAttributeMap.userName, nameOrID, i.userAttributeMap.id, nameOrID) - return i.getEducationUserByFilter(filter) + return i.getEducationObjectByNameOrID( + nameOrID, + i.userAttributeMap.userName, + i.userAttributeMap.id, + i.userFilter, + i.educationConfig.userObjectClass, + i.userBaseDN, + i.getEducationUserAttrTypes(), + ) } -func (i *LDAP) getEducationUserByFilter(filter string) (*ldap.Entry, error) { - filter = fmt.Sprintf("(&%s(objectClass=%s)%s)", i.userFilter, i.educationConfig.userObjectClass, filter) - return i.searchLDAPEntryByFilter(i.userBaseDN, i.getEducationUserAttrTypes(), filter) +func (i *LDAP) getEducationObjectByNameOrID(nameOrID, nameAttribute, idAttribute, objectFilter, objectClass, baseDN string, attributes []string) (*ldap.Entry, error) { + nameOrID = ldap.EscapeFilter(nameOrID) + filter := fmt.Sprintf("(|(%s=%s)(%s=%s))", nameAttribute, nameOrID, idAttribute, nameOrID) + return i.getEducationObjectByFilter(filter, baseDN, objectFilter, objectClass, attributes) +} + +func (i *LDAP) getEducationObjectByFilter(filter, baseDN, objectFilter, objectClass string, attributes []string) (*ldap.Entry, error) { + filter = fmt.Sprintf("(&%s(objectClass=%s)%s)", objectFilter, objectClass, filter) + return i.searchLDAPEntryByFilter(baseDN, attributes, filter) }