Files
opencloud/services/graph/pkg/identity/ldap_education_school.go
T
Florian Schade ad06a192d8 enhancement: add graph beta listPermissions endpoint (#7753)
* enhancement: add graph beta listPermissions endpoint

besides the new api endpoint it includes several utilities to simplify the graph api development.

* resolve drive and item id from the request path
* generic pointer and value utilities
* space root detection

* update GetDriveAndItemIDParam signature to return a error

* move errorcode package

* enhancement: add generic error code handling

* fix: rebase
2023-11-28 17:06:04 +01:00

778 lines
24 KiB
Go

package identity
import (
"context"
"errors"
"fmt"
"time"
"github.com/go-ldap/ldap/v3"
"github.com/gofrs/uuid"
libregraph "github.com/owncloud/libre-graph-api-go"
"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/errorcode"
)
type educationConfig struct {
schoolBaseDN string
schoolFilter string
schoolObjectClass string
schoolScope int
// memberOfSchoolAttribute defines the AttributeType on the user/group objects
// which contains the school Id to which the user/group is assigned
memberOfSchoolAttribute string
schoolAttributeMap schoolAttributeMap
userObjectClass string
userAttributeMap educationUserAttributeMap
classObjectClass string
classAttributeMap educationClassAttributeMap
}
type schoolAttributeMap struct {
displayName string
schoolNumber string
id string
terminationDate string
}
type schoolUpdateOperation uint8
const (
tooManyValues schoolUpdateOperation = iota
schoolUnchanged
schoolRenamed
schoolPropertiesUpdated
)
const ldapDateFormat = "20060102150405Z0700"
var (
errNotSet = errors.New("Attribute not set")
errSchoolNameExists = errorcode.New(errorcode.NameAlreadyExists, "A school with that name is already present")
errSchoolNumberExists = errorcode.New(errorcode.NameAlreadyExists, "A school with that number is already present")
)
func defaultEducationConfig() educationConfig {
return educationConfig{
schoolObjectClass: "ocEducationSchool",
schoolScope: ldap.ScopeWholeSubtree,
memberOfSchoolAttribute: "ocMemberOfSchool",
schoolAttributeMap: newSchoolAttributeMap(),
userObjectClass: "ocEducationUser",
userAttributeMap: newEducationUserAttributeMap(),
classObjectClass: "ocEducationClass",
classAttributeMap: newEducationClassAttributeMap(),
}
}
func newEducationConfig(config config.LDAP) (educationConfig, error) {
if config.EducationResourcesEnabled {
var err error
eduCfg := defaultEducationConfig()
eduCfg.schoolBaseDN = config.EducationConfig.SchoolBaseDN
if config.EducationConfig.SchoolSearchScope != "" {
if eduCfg.schoolScope, err = stringToScope(config.EducationConfig.SchoolSearchScope); err != nil {
return educationConfig{}, fmt.Errorf("error configuring school search scope: %w", err)
}
}
if config.EducationConfig.SchoolFilter != "" {
eduCfg.schoolFilter = config.EducationConfig.SchoolFilter
}
if config.EducationConfig.SchoolObjectClass != "" {
eduCfg.schoolObjectClass = config.EducationConfig.SchoolObjectClass
}
// Attribute mapping config
if config.EducationConfig.SchoolNameAttribute != "" {
eduCfg.schoolAttributeMap.displayName = config.EducationConfig.SchoolNameAttribute
}
if config.EducationConfig.SchoolNumberAttribute != "" {
eduCfg.schoolAttributeMap.schoolNumber = config.EducationConfig.SchoolNumberAttribute
}
if config.EducationConfig.SchoolIDAttribute != "" {
eduCfg.schoolAttributeMap.id = config.EducationConfig.SchoolIDAttribute
}
return eduCfg, nil
}
return educationConfig{}, nil
}
func newSchoolAttributeMap() schoolAttributeMap {
return schoolAttributeMap{
displayName: "ou",
schoolNumber: "ocEducationSchoolNumber",
id: "owncloudUUID",
terminationDate: "ocEducationSchoolTerminationTimestamp",
}
}
// CreateEducationSchool creates the supplied school in the identity backend.
func (i *LDAP) CreateEducationSchool(ctx context.Context, school libregraph.EducationSchool) (*libregraph.EducationSchool, error) {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("CreateEducationSchool")
if !i.writeEnabled {
return nil, ErrReadOnly
}
// Check that the school number is not already used
_, err := i.getSchoolByNumber(school.GetSchoolNumber())
switch err {
case nil:
logger.Debug().Err(errSchoolNumberExists).Str("schoolNumber", school.GetSchoolNumber()).Msg("duplicate school number")
return nil, errSchoolNumberExists
case ErrNotFound:
break
default:
logger.Error().Err(err).Str("schoolNumber", school.GetSchoolNumber()).Msg("error looking up school by number")
return nil, errorcode.New(errorcode.GeneralException, "error looking up school by number")
}
attributeTypeAndValue := ldap.AttributeTypeAndValue{
Type: i.educationConfig.schoolAttributeMap.displayName,
Value: school.GetDisplayName(),
}
dn := fmt.Sprintf("%s,%s",
attributeTypeAndValue.String(),
i.educationConfig.schoolBaseDN,
)
ar := ldap.NewAddRequest(dn, nil)
ar.Attribute(i.educationConfig.schoolAttributeMap.displayName, []string{school.GetDisplayName()})
ar.Attribute(i.educationConfig.schoolAttributeMap.schoolNumber, []string{school.GetSchoolNumber()})
if !i.useServerUUID {
ar.Attribute(i.educationConfig.schoolAttributeMap.id, []string{uuid.Must(uuid.NewV4()).String()})
}
objectClasses := []string{"organizationalUnit", i.educationConfig.schoolObjectClass, "top"}
ar.Attribute("objectClass", objectClasses)
if err := i.conn.Add(ar); err != nil {
var lerr *ldap.Error
logger.Debug().Err(err).Msg("error adding school")
if errors.As(err, &lerr) {
if lerr.ResultCode == ldap.LDAPResultEntryAlreadyExists {
err = errSchoolNameExists
}
}
return nil, err
}
// Read back school from LDAP to get the generated UUID
e, err := i.getSchoolByDN(ar.DN)
if err != nil {
return nil, err
}
return i.createSchoolModelFromLDAP(e), nil
}
// UpdateEducationSchoolOperation contains the logic for which update operation to apply to a school
func (i *LDAP) updateEducationSchoolOperation(
schoolUpdate libregraph.EducationSchool,
currentSchool libregraph.EducationSchool,
) schoolUpdateOperation {
providedDisplayName, displayNameIsSet := schoolUpdate.GetDisplayNameOk()
if displayNameIsSet {
if *providedDisplayName == "" || *providedDisplayName == currentSchool.GetDisplayName() {
// The school name hasn't changed
displayNameIsSet = false
}
}
var propertiesUpdated bool
switch {
case schoolUpdate.HasSchoolNumber():
if schoolUpdate.GetSchoolNumber() != "" && schoolUpdate.GetSchoolNumber() != currentSchool.GetSchoolNumber() {
propertiesUpdated = true
}
case schoolUpdate.HasTerminationDate():
if schoolUpdate.GetTerminationDate() != currentSchool.GetTerminationDate() {
propertiesUpdated = true
}
}
if propertiesUpdated && displayNameIsSet {
return tooManyValues
}
if displayNameIsSet {
return schoolRenamed
}
if propertiesUpdated {
return schoolPropertiesUpdated
}
return schoolUnchanged
}
// updateDisplayName updates the school OU in the identity backend
func (i *LDAP) updateDisplayName(ctx context.Context, dn string, providedDisplayName string) error {
logger := i.logger.SubloggerWithRequestID(ctx)
attributeTypeAndValue := ldap.AttributeTypeAndValue{
Type: i.educationConfig.schoolAttributeMap.displayName,
Value: providedDisplayName,
}
mrdn := ldap.NewModifyDNRequest(dn, attributeTypeAndValue.String(), true, "")
i.logger.Debug().Str("backend", "ldap").
Str("dn", mrdn.DN).
Str("newrdn", mrdn.NewRDN).
Msg("updateDisplayName")
if err := i.conn.ModifyDN(mrdn); err != nil {
var lerr *ldap.Error
logger.Debug().Err(err).Msg("error updating school name")
if errors.As(err, &lerr) {
if lerr.ResultCode == ldap.LDAPResultEntryAlreadyExists {
err = errSchoolNameExists
}
}
return err
}
return nil
}
// updateSchoolProperties updates the properties (other that displayName) of a school.
// It checks if a school number is already taken, before updating the school number
func (i *LDAP) updateSchoolProperties(ctx context.Context, dn string, currentSchool, updatedSchool libregraph.EducationSchool) error {
logger := i.logger.SubloggerWithRequestID(ctx)
mr := ldap.NewModifyRequest(dn, nil)
if updatedSchoolNumber, ok := updatedSchool.GetSchoolNumberOk(); ok {
if *updatedSchoolNumber != "" && currentSchool.GetSchoolNumber() != *updatedSchoolNumber {
_, err := i.getSchoolByNumber(*updatedSchoolNumber)
if err == nil {
return errSchoolNumberExists
}
mr.Replace(i.educationConfig.schoolAttributeMap.schoolNumber, []string{*updatedSchoolNumber})
}
}
if updatedTerminationDate, ok := updatedSchool.GetTerminationDateOk(); ok {
if updatedTerminationDate == nil && currentSchool.HasTerminationDate() {
// Delete the termination date
mr.Delete(i.educationConfig.schoolAttributeMap.terminationDate, []string{})
}
if updatedTerminationDate != nil && *updatedTerminationDate != currentSchool.GetTerminationDate() {
ldapDateTime := updatedTerminationDate.UTC().Format(ldapDateFormat)
mr.Replace(i.educationConfig.schoolAttributeMap.terminationDate, []string{ldapDateTime})
}
}
if err := i.conn.Modify(mr); err != nil {
logger.Debug().Err(err).Msg("error updating school number")
return err
}
return nil
}
// UpdateEducationSchool updates the supplied school in the identity backend
func (i *LDAP) UpdateEducationSchool(ctx context.Context, numberOrID string, school libregraph.EducationSchool) (*libregraph.EducationSchool, error) {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("UpdateEducationSchool")
if !i.writeEnabled {
return nil, ErrReadOnly
}
e, err := i.getSchoolByNumberOrID(numberOrID)
if err != nil {
return nil, err
}
currentSchool := i.createSchoolModelFromLDAP(e)
switch i.updateEducationSchoolOperation(school, *currentSchool) {
case tooManyValues:
return nil, fmt.Errorf("school name and school number cannot be updated in the same request")
case schoolUnchanged:
logger.Debug().Str("backend", "ldap").Msg("UpdateEducationSchool: Nothing changed")
return currentSchool, nil
case schoolRenamed:
if err := i.updateDisplayName(ctx, e.DN, school.GetDisplayName()); err != nil {
return nil, err
}
case schoolPropertiesUpdated:
if err := i.updateSchoolProperties(ctx, e.DN, *currentSchool, school); err != nil {
return nil, err
}
}
// Read back school from LDAP
e, err = i.getSchoolByNumberOrID(i.getID(e))
if err != nil {
return nil, err
}
return i.createSchoolModelFromLDAP(e), nil
}
// DeleteEducationSchool deletes a given school, identified by id
func (i *LDAP) DeleteEducationSchool(ctx context.Context, id string) error {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("DeleteEducationSchool")
if !i.writeEnabled {
return ErrReadOnly
}
e, err := i.getSchoolByNumberOrID(id)
if err != nil {
return err
}
dr := ldap.DelRequest{DN: e.DN}
if err = i.conn.Del(&dr); err != nil {
return err
}
// TODO update any users that are member of this school
return nil
}
// GetEducationSchool implements the EducationBackend interface for the LDAP backend.
func (i *LDAP) GetEducationSchool(ctx context.Context, numberOrID string) (*libregraph.EducationSchool, error) {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("GetEducationSchool")
e, err := i.getSchoolByNumberOrID(numberOrID)
if err != nil {
return nil, err
}
return i.createSchoolModelFromLDAP(e), nil
}
// GetEducationSchools implements the EducationBackend interface for the LDAP backend.
func (i *LDAP) GetEducationSchools(ctx context.Context) ([]*libregraph.EducationSchool, error) {
var filter string
filter = fmt.Sprintf("(objectClass=%s)", i.educationConfig.schoolObjectClass)
if i.educationConfig.schoolFilter != "" {
filter = fmt.Sprintf("(&%s%s)", i.educationConfig.schoolFilter, filter)
}
searchRequest := ldap.NewSearchRequest(
i.educationConfig.schoolBaseDN,
i.educationConfig.schoolScope,
ldap.NeverDerefAliases, 0, 0, false,
filter,
i.getEducationSchoolAttrTypes(),
nil,
)
i.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("GetEducationSchools")
res, err := i.conn.Search(searchRequest)
if err != nil {
return nil, errorcode.New(errorcode.ItemNotFound, err.Error())
}
schools := make([]*libregraph.EducationSchool, 0, len(res.Entries))
for _, e := range res.Entries {
school := i.createSchoolModelFromLDAP(e)
// Skip invalid LDAP entries
if school == nil {
continue
}
schools = append(schools, school)
}
return schools, nil
}
// GetEducationSchoolUsers implements the EducationBackend interface for the LDAP backend.
func (i *LDAP) GetEducationSchoolUsers(ctx context.Context, schoolNumberOrID string) ([]*libregraph.EducationUser, error) {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("GetEducationSchoolUsers")
entries, err := i.getEducationSchoolEntries(
schoolNumberOrID, i.userFilter, i.educationConfig.userObjectClass, i.userBaseDN, i.userScope, i.getEducationUserAttrTypes(), logger,
)
if err != nil {
return nil, err
}
users := make([]*libregraph.EducationUser, 0, len(entries))
for _, e := range entries {
u := i.createEducationUserModelFromLDAP(e)
// Skip invalid LDAP users
if u == nil {
continue
}
users = append(users, u)
}
return users, nil
}
// AddUsersToEducationSchool adds new members (reference by a slice of IDs) to supplied school in the identity backend.
func (i *LDAP) AddUsersToEducationSchool(ctx context.Context, schoolNumberOrID string, memberIDs []string) error {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("AddUsersToEducationSchool")
schoolEntry, err := i.getSchoolByNumberOrID(schoolNumberOrID)
if err != nil {
return err
}
if schoolEntry == nil {
return ErrNotFound
}
schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id)
userEntries := make([]*ldap.Entry, 0, len(memberIDs))
for _, memberID := range memberIDs {
user, err := i.getEducationUserByNameOrID(memberID)
if err != nil {
i.logger.Warn().Str("userid", memberID).Msg("User does not exist")
return errorcode.New(errorcode.ItemNotFound, fmt.Sprintf("user '%s' not found", memberID))
}
userEntries = append(userEntries, user)
}
for _, userEntry := range userEntries {
currentSchools := userEntry.GetEqualFoldAttributeValues(i.educationConfig.memberOfSchoolAttribute)
found := false
for _, currentSchool := range currentSchools {
if currentSchool == schoolID {
found = true
break
}
}
if !found {
mr := ldap.ModifyRequest{DN: userEntry.DN}
mr.Add(i.educationConfig.memberOfSchoolAttribute, []string{schoolID})
if err := i.conn.Modify(&mr); err != nil {
return err
}
}
}
return nil
}
// RemoveUserFromEducationSchool removes a single member (by ID) from a school
func (i *LDAP) RemoveUserFromEducationSchool(ctx context.Context, schoolNumberOrID string, memberID string) error {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("RemoveUserFromEducationSchool")
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.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
}
// 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,
)
if err != nil {
return nil, err
}
classes := make([]*libregraph.EducationClass, 0, len(entries))
for _, e := range entries {
class := i.createEducationClassModelFromLDAP(e)
// Skip invalid LDAP classes
if class == nil {
continue
}
classes = append(classes, class)
}
return classes, 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)
class, err := i.getEducationClassByID(memberID, false)
if err != nil {
i.logger.Warn().Str("userid", memberID).Msg("Class does not exist")
return err
}
currentSchools := class.GetEqualFoldAttributeValues(i.educationConfig.memberOfSchoolAttribute)
for _, currentSchool := range currentSchools {
if currentSchool == schoolID {
mr := ldap.ModifyRequest{DN: class.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) {
filter := fmt.Sprintf("(objectClass=%s)", i.educationConfig.schoolObjectClass)
if i.educationConfig.schoolFilter != "" {
filter = fmt.Sprintf("(&%s(%s))", filter, i.educationConfig.schoolFilter)
}
return i.getEntryByDN(dn, i.getEducationSchoolAttrTypes(), filter)
}
func (i *LDAP) getSchoolByNumberOrID(numberOrID string) (*ldap.Entry, error) {
numberOrID = ldap.EscapeFilter(numberOrID)
filter := fmt.Sprintf(
"(|(%s=%s)(%s=%s))",
i.educationConfig.schoolAttributeMap.id,
numberOrID,
i.educationConfig.schoolAttributeMap.schoolNumber,
numberOrID,
)
return i.getSchoolByFilter(filter)
}
func (i *LDAP) getSchoolByNumber(schoolNumber string) (*ldap.Entry, error) {
schoolNumber = ldap.EscapeFilter(schoolNumber)
filter := fmt.Sprintf(
"(%s=%s)",
i.educationConfig.schoolAttributeMap.schoolNumber,
schoolNumber,
)
return i.getSchoolByFilter(filter)
}
func (i *LDAP) getSchoolByFilter(filter string) (*ldap.Entry, error) {
filter = fmt.Sprintf("(&%s(objectClass=%s)%s)",
i.educationConfig.schoolFilter,
i.educationConfig.schoolObjectClass,
filter,
)
searchRequest := ldap.NewSearchRequest(
i.educationConfig.schoolBaseDN,
i.educationConfig.schoolScope,
ldap.NeverDerefAliases, 1, 0, false,
filter,
i.getEducationSchoolAttrTypes(),
nil,
)
i.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("getSchoolByFilter")
res, err := i.conn.Search(searchRequest)
if err != nil {
var errmsg string
if lerr, ok := err.(*ldap.Error); ok {
if lerr.ResultCode == ldap.LDAPResultSizeLimitExceeded {
errmsg = fmt.Sprintf("too many results searching for school '%s'", filter)
i.logger.Debug().Str("backend", "ldap").Err(lerr).
Str("schoolfilter", filter).Msg("too many results searching for school")
}
}
return nil, errorcode.New(errorcode.ItemNotFound, errmsg)
}
if len(res.Entries) == 0 {
return nil, ErrNotFound
}
return res.Entries[0], nil
}
func (i *LDAP) createSchoolModelFromLDAP(e *ldap.Entry) *libregraph.EducationSchool {
if e == nil {
return nil
}
displayName := i.getDisplayName(e)
id := i.getID(e)
schoolNumber := i.getSchoolNumber(e)
t, err := i.getTerminationDate(e)
if err != nil && !errors.Is(err, errNotSet) {
i.logger.Error().Err(err).Str("dn", e.DN).Msg("Error reading termination date for LDAP entry")
}
if id != "" && displayName != "" && schoolNumber != "" {
school := libregraph.NewEducationSchool()
school.SetDisplayName(displayName)
school.SetSchoolNumber(schoolNumber)
school.SetId(id)
if t != nil {
school.SetTerminationDate(*t)
}
return school
}
i.logger.Warn().Str("dn", e.DN).Str("id", id).Str("displayName", displayName).Str("schoolNumber", schoolNumber).Msg("Invalid School. Missing required attribute")
return nil
}
func (i *LDAP) getSchoolNumber(e *ldap.Entry) string {
schoolNumber := e.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.schoolNumber)
return schoolNumber
}
func (i *LDAP) getID(e *ldap.Entry) string {
id := e.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id)
return id
}
func (i *LDAP) getDisplayName(e *ldap.Entry) string {
displayName := e.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.displayName)
return displayName
}
func (i *LDAP) getTerminationDate(e *ldap.Entry) (*time.Time, error) {
dateString := e.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.terminationDate)
if dateString == "" {
return nil, errNotSet
}
t, err := time.Parse(ldapDateFormat, dateString)
if err != nil {
err = fmt.Errorf("Error parsing LDAP date: '%s': %w", dateString, err)
return nil, err
}
return &t, nil
}
func (i *LDAP) getEducationSchoolAttrTypes() []string {
return []string{
i.educationConfig.schoolAttributeMap.displayName,
i.educationConfig.schoolAttributeMap.id,
i.educationConfig.schoolAttributeMap.schoolNumber,
i.educationConfig.schoolAttributeMap.terminationDate,
}
}