Add service endpoints.

This commit is contained in:
Daniël Franke
2023-01-27 13:55:00 +01:00
committed by Ralf Haferkamp
parent 9b8adb65ed
commit 20f6a212f3
7 changed files with 251 additions and 3 deletions

View File

@@ -60,6 +60,13 @@ type EducationBackend interface {
// RemoveUserFromEducationSchool removes a single member (by ID) from a school
RemoveUserFromEducationSchool(ctx context.Context, schoolID string, memberID string) error
// GetEducationSchoolClasses lists all classes in a chool
GetEducationSchoolClasses(ctx context.Context, schoolNumberOrID string) ([]*libregraph.EducationClass, error)
// AddClassesToEducationSchool adds new classes (referenced by a slice of IDs) to supplied school in the identity backend.
AddClassesToEducationSchool(ctx context.Context, schoolNumberOrID string, memberIDs []string) error
// RemoveClassFromEducationSchool removes a class from a school.
RemoveClassFromEducationSchool(ctx context.Context, schoolNumberOrID string, memberID string) error
// GetEducationClasses lists all classes
GetEducationClasses(ctx context.Context, queryParam url.Values) ([]*libregraph.EducationClass, error)
// GetEducationClasses reads a given class by id

View File

@@ -40,6 +40,21 @@ func (i *ErrEducationBackend) GetEducationSchoolUsers(ctx context.Context, id st
return nil, errNotImplemented
}
// GetEducationSchoolClasses implements the EducationBackend interface for the ErrEducationBackend backend.
func (i *ErrEducationBackend) GetEducationSchoolClasses(ctx context.Context, schoolNumberOrID string) ([]*libregraph.EducationClass, error) {
return nil, errNotImplemented
}
// AddClassesToEducationSchool implements the EducationBackend interface for the ErrEducationBackend backend.
func (i *ErrEducationBackend) AddClassesToEducationSchool(ctx context.Context, schoolNumberOrID string, memberIDs []string) error {
return errNotImplemented
}
// RemoveClassFromEducationSchool implements the EducationBackend interface for the ErrEducationBackend backend.
func (i *ErrEducationBackend) RemoveClassFromEducationSchool(ctx context.Context, schoolNumberOrID string, memberID string) error {
return errNotImplemented
}
// AddUsersToEducationSchool adds new members (reference by a slice of IDs) to supplied school in the identity backend.
func (i *ErrEducationBackend) AddUsersToEducationSchool(ctx context.Context, schoolID string, memberID []string) error {
return errNotImplemented

View File

@@ -344,8 +344,8 @@ func (i *LDAP) getEducationClassLDAPDN(class libregraph.EducationClass) string {
func (i *LDAP) getEducationClassByID(nameOrID string, requestMembers bool) (*ldap.Entry, error) {
return i.getEducationObjectByNameOrID(
nameOrID,
i.groupAttributeMap.name,
i.groupAttributeMap.id,
i.userAttributeMap.id,
i.educationConfig.classAttributeMap.externalID,
i.groupFilter,
i.educationConfig.classObjectClass,
i.groupBaseDN,

View File

@@ -354,7 +354,7 @@ func (i *LDAP) GetEducationSchoolUsers(ctx context.Context, schoolNumberOrID str
logger.Debug().Str("backend", "ldap").Msg("GetEducationSchoolUsers")
entries, err := i.getEducationSchoolEntries(
schoolNumberOrID, i.userFilter, i.educationConfig.userObjectClass, i.userBaseDN, i.userScope, i.getUserAttrTypes(), logger,
schoolNumberOrID, i.userFilter, i.educationConfig.userObjectClass, i.userBaseDN, i.userScope, i.getEducationUserAttrTypes(), logger,
)
if err != nil {
return nil, err

View File

@@ -17,6 +17,20 @@ type EducationBackend struct {
mock.Mock
}
// AddClassesToEducationSchool provides a mock function with given fields: ctx, schoolNumberOrID, memberIDs
func (_m *EducationBackend) AddClassesToEducationSchool(ctx context.Context, schoolNumberOrID string, memberIDs []string) error {
ret := _m.Called(ctx, schoolNumberOrID, memberIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok {
r0 = rf(ctx, schoolNumberOrID, memberIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// AddUsersToEducationSchool provides a mock function with given fields: ctx, schoolID, memberID
func (_m *EducationBackend) AddUsersToEducationSchool(ctx context.Context, schoolID string, memberID []string) error {
ret := _m.Called(ctx, schoolID, memberID)
@@ -234,6 +248,29 @@ func (_m *EducationBackend) GetEducationSchool(ctx context.Context, nameOrID str
return r0, r1
}
// GetEducationSchoolClasses provides a mock function with given fields: ctx, schoolNumberOrID
func (_m *EducationBackend) GetEducationSchoolClasses(ctx context.Context, schoolNumberOrID string) ([]*libregraph.EducationClass, error) {
ret := _m.Called(ctx, schoolNumberOrID)
var r0 []*libregraph.EducationClass
if rf, ok := ret.Get(0).(func(context.Context, string) []*libregraph.EducationClass); ok {
r0 = rf(ctx, schoolNumberOrID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*libregraph.EducationClass)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, schoolNumberOrID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetEducationSchoolUsers provides a mock function with given fields: ctx, id
func (_m *EducationBackend) GetEducationSchoolUsers(ctx context.Context, id string) ([]*libregraph.EducationUser, error) {
ret := _m.Called(ctx, id)
@@ -326,6 +363,20 @@ func (_m *EducationBackend) GetEducationUsers(ctx context.Context, queryParam ur
return r0, r1
}
// RemoveClassFromEducationSchool provides a mock function with given fields: ctx, schoolNumberOrID, memberID
func (_m *EducationBackend) RemoveClassFromEducationSchool(ctx context.Context, schoolNumberOrID string, memberID string) error {
ret := _m.Called(ctx, schoolNumberOrID, memberID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
r0 = rf(ctx, schoolNumberOrID, memberID)
} else {
r0 = ret.Error(0)
}
return r0
}
// RemoveUserFromEducationSchool provides a mock function with given fields: ctx, schoolID, memberID
func (_m *EducationBackend) RemoveUserFromEducationSchool(ctx context.Context, schoolID string, memberID string) error {
ret := _m.Called(ctx, schoolID, memberID)

View File

@@ -423,6 +423,176 @@ func (g Graph) DeleteEducationSchoolUser(w http.ResponseWriter, r *http.Request)
render.NoContent(w, r)
}
// GetEducationSchoolUsers implements the Service interface.
func (g Graph) GetEducationSchoolClasses(w http.ResponseWriter, r *http.Request) {
logger := g.logger.SubloggerWithRequestID(r.Context())
logger.Info().Msg("calling get school classes")
schoolID := chi.URLParam(r, "schoolID")
schoolID, err := url.PathUnescape(schoolID)
if err != nil {
logger.Debug().Str("id", schoolID).Msg("could not get school users: unescaping school id failed")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
return
}
if schoolID == "" {
logger.Debug().Msg("could not get school users: missing school id")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
return
}
logger.Debug().Str("id", schoolID).Msg("calling get school classes on backend")
classes, err := g.identityEducationBackend.GetEducationSchoolClasses(r.Context(), schoolID)
if err != nil {
logger.Debug().Err(err).Msg("could not get school classes: backend error")
var errcode errorcode.Error
if errors.As(err, &errcode) {
errcode.Render(w, r)
} else {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
}
return
}
render.Status(r, http.StatusOK)
render.JSON(w, r, classes)
}
// PostEducationSchoolUser implements the Service interface.
func (g Graph) PostEducationSchoolClass(w http.ResponseWriter, r *http.Request) {
logger := g.logger.SubloggerWithRequestID(r.Context())
logger.Info().Msg("Calling post school class")
schoolID := chi.URLParam(r, "schoolID")
schoolID, err := url.PathUnescape(schoolID)
if err != nil {
logger.Debug().
Err(err).
Str("id", schoolID).
Msg("could not add class to school: unescaping school id failed")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
return
}
if schoolID == "" {
logger.Debug().Msg("could not add school class: missing school id")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
return
}
memberRef := libregraph.NewMemberReference()
err = json.NewDecoder(r.Body).Decode(memberRef)
if err != nil {
logger.Debug().
Err(err).
Interface("body", r.Body).
Msg("could not add school class: invalid request body")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: %s", err.Error()))
return
}
memberRefURL, ok := memberRef.GetOdataIdOk()
if !ok {
logger.Debug().Msg("could not add school class: @odata.id reference is missing")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "@odata.id reference is missing")
return
}
memberType, id, err := g.parseMemberRef(*memberRefURL)
if err != nil {
logger.Debug().Err(err).Msg("could not add school class: error parsing @odata.id url")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Error parsing @odata.id url")
return
}
// The MS Graph spec allows "directoryObject", "user", "school" and "organizational Contact"
// we restrict this to users for now. Might add Schools as members later
if memberType != "classes" {
logger.Debug().Str("type", memberType).Msg("could not add school class: Only classes are allowed as school members")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Only classes are allowed as school members")
return
}
logger.Debug().Str("memberType", memberType).Str("id", id).Msg("calling add class on backend")
err = g.identityEducationBackend.AddClassesToEducationSchool(r.Context(), schoolID, []string{id})
if err != nil {
logger.Debug().Err(err).Msg("could not add school class: backend error")
var errcode errorcode.Error
if errors.As(err, &errcode) {
errcode.Render(w, r)
} else {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
}
return
}
/* TODO requires reva changes
e := events.SchoolMemberAdded{SchoolID: schoolID, UserID: id}
if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
e.Executant = currentUser.GetId()
}
g.publishEvent(e)
*/
render.Status(r, http.StatusNoContent)
render.NoContent(w, r)
}
// DeleteEducationSchoolUser implements the Service interface.
func (g Graph) DeleteEducationSchoolClass(w http.ResponseWriter, r *http.Request) {
logger := g.logger.SubloggerWithRequestID(r.Context())
logger.Info().Msg("calling delete school class")
schoolID := chi.URLParam(r, "schoolID")
schoolID, err := url.PathUnescape(schoolID)
if err != nil {
logger.Debug().Err(err).Str("id", schoolID).Msg("could not delete school class: unescaping school id failed")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
return
}
if schoolID == "" {
logger.Debug().Msg("could not delete school class: missing school id")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
return
}
classID := chi.URLParam(r, "classID")
classID, err = url.PathUnescape(classID)
if err != nil {
logger.Debug().Err(err).Str("id", classID).Msg("could not delete school class: unescaping class id failed")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping class id failed")
return
}
if classID == "" {
logger.Debug().Msg("could not delete school class: missing class id")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing class id")
return
}
logger.Debug().Str("schoolID", schoolID).Str("userID", classID).Msg("calling delete class on backend")
err = g.identityEducationBackend.RemoveClassFromEducationSchool(r.Context(), schoolID, classID)
if err != nil {
logger.Debug().Err(err).Msg("could not delete school class: backend error")
var errcode errorcode.Error
if errors.As(err, &errcode) {
errcode.Render(w, r)
} else {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
}
return
}
/* TODO requires reva changes
e := events.SchoolMemberRemoved{SchoolID: schoolID, UserID: userID}
if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
e.Executant = currentUser.GetId()
}
g.publishEvent(e)
*/
render.Status(r, http.StatusNoContent)
render.NoContent(w, r)
}
func sortEducationSchools(req *godata.GoDataRequest, schools []*libregraph.EducationSchool) ([]*libregraph.EducationSchool, error) {
if req.Query.OrderBy == nil || len(req.Query.OrderBy.OrderByItems) != 1 {
return schools, nil

View File

@@ -241,6 +241,11 @@ func NewService(opts ...Option) (Graph, error) {
r.Post("/$ref", svc.PostEducationSchoolUser)
r.Delete("/{userID}/$ref", svc.DeleteEducationSchoolUser)
})
r.Route("/classes", func(r chi.Router) {
r.Get("/", svc.GetEducationSchoolClasses)
r.Post("/$ref", svc.PostEducationSchoolClass)
r.Delete("/{classID}/$ref", svc.DeleteEducationSchoolClass)
})
})
})
r.Route("/users", func(r chi.Router) {