graph: Add config knob to set a minimal grace period for schoolTermination

When setting a terminationDate on a School, it's possible to configure a grace
period now so that only terminationDate that are at least a certain time in the
future can be set.
We also now forbid to set a terminationDate in the past.
This commit is contained in:
Ralf Haferkamp
2023-07-06 17:28:20 +02:00
committed by Ralf Haferkamp
parent 0bc36f1cf5
commit 51bc49d5b5
3 changed files with 48 additions and 0 deletions

View File

@@ -94,6 +94,8 @@ type LDAPEducationConfig struct {
SchoolNameAttribute string `yaml:"school_name_attribute" env:"GRAPH_LDAP_SCHOOL_NAME_ATTRIBUTE" desc:"LDAP Attribute to use for the name of a school."`
SchoolNumberAttribute string `yaml:"school_number_attribute" env:"GRAPH_LDAP_SCHOOL_NUMBER_ATTRIBUTE" desc:"LDAP Attribute to use for the number of a school."`
SchoolIDAttribute string `yaml:"school_id_attribute" env:"GRAPH_LDAP_SCHOOL_ID_ATTRIBUTE" desc:"LDAP Attribute to use as the unique id for schools. This should be a stable globally unique ID like a UUID."`
SchoolTerminationGraceDays int `yaml:"school_termination_min_grace_days" env:"GRAPH_LDAP_SCHOOL_TERMINATION_MIN_GRACE_DAYS" desc:"When setting a 'terminationDate' for a school, require the date to be at least this number of days in the future."`
}
type Identity struct {

View File

@@ -80,6 +80,16 @@ func (g Graph) PostEducationSchool(w http.ResponseWriter, r *http.Request) {
return
}
// validate terminationDate attribute, needs to be "far enough" in the future, terminationDate can be nil (means
// termination date is to be deleted
if terminationDate, ok := school.GetTerminationDateOk(); ok && terminationDate != nil {
err = g.validateTerminationDate(*terminationDate)
if err != nil {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
}
return
}
if school, err = g.identityEducationBackend.CreateEducationSchool(r.Context(), *school); err != nil {
logger.Debug().Err(err).Interface("school", school).Msg("could not create school: backend error")
errorcode.RenderError(w, r, err)
@@ -126,6 +136,16 @@ func (g Graph) PatchEducationSchool(w http.ResponseWriter, r *http.Request) {
return
}
// validate terminationDate attribute, needs to be "far enough" in the future, terminationDate can be nil (means
// termination date is to be deleted
if terminationDate, ok := school.GetTerminationDateOk(); ok && terminationDate != nil {
err = g.validateTerminationDate(*terminationDate)
if err != nil {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
return
}
}
if school, err = g.identityEducationBackend.UpdateEducationSchool(r.Context(), schoolID, *school); err != nil {
logger.Debug().Err(err).Interface("school", school).Msg("could not update school: backend error")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -566,6 +586,19 @@ func (g Graph) DeleteEducationSchoolClass(w http.ResponseWriter, r *http.Request
render.NoContent(w, r)
}
func (g Graph) validateTerminationDate(terminationDate time.Time) error {
if terminationDate.Before(time.Now()) {
return fmt.Errorf("can not set a termination date in the past")
}
graceDays := g.config.Identity.LDAP.EducationConfig.SchoolTerminationGraceDays
if graceDays != 0 {
if terminationDate.Before(time.Now().Add(time.Duration(graceDays) * 24 * time.Hour)) {
return fmt.Errorf("termination needs to be at least %d day(s) in the future", graceDays)
}
}
return nil
}
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

@@ -74,6 +74,7 @@ var _ = Describe("Schools", func() {
cfg = defaults.FullDefaultConfig()
cfg.Identity.LDAP.CACert = "" // skip the startup checks, we don't use LDAP at all in this tests
cfg.Identity.LDAP.EducationConfig.SchoolTerminationGraceDays = 30
cfg.TokenManager.JWTSecret = "loremipsum"
cfg.Commons = &shared.Commons{}
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
@@ -294,8 +295,17 @@ var _ = Describe("Schools", func() {
schoolUpdate.SetDisplayName("New School Name")
schoolUpdateJson, _ := json.Marshal(schoolUpdate)
schoolUpdatePast := libregraph.NewEducationSchool()
schoolUpdatePast.SetTerminationDate(time.Now().Add(-time.Hour * 1))
schoolUpdatePastJson, _ := json.Marshal(schoolUpdatePast)
schoolUpdateBeforeGrace := libregraph.NewEducationSchool()
schoolUpdateBeforeGrace.SetTerminationDate(time.Now().Add(24 * 10 * time.Hour))
schoolUpdateBeforeGraceJson, _ := json.Marshal(schoolUpdateBeforeGrace)
schoolUpdatePastGrace := libregraph.NewEducationSchool()
schoolUpdatePastGrace.SetTerminationDate(time.Now().Add(24 * 31 * time.Hour))
schoolUpdatePastGraceJson, _ := json.Marshal(schoolUpdatePastGrace)
BeforeEach(func() {
identityEducationBackend.On("UpdateEducationSchool", mock.Anything, mock.Anything, mock.Anything).Return(schoolUpdate, nil)
@@ -315,6 +325,9 @@ var _ = Describe("Schools", func() {
Entry("handles missing or empty school id", "", bytes.NewBufferString(""), http.StatusBadRequest),
Entry("handles malformed school id", "school%id", bytes.NewBuffer(schoolUpdateJson), http.StatusBadRequest),
Entry("updates the school", "school-id", bytes.NewBuffer(schoolUpdateJson), http.StatusOK),
Entry("fails to set a termination date in the past", "school-id", bytes.NewBuffer(schoolUpdatePastJson), http.StatusBadRequest),
Entry("fails to set a termination date before grace period", "school-id", bytes.NewBuffer(schoolUpdateBeforeGraceJson), http.StatusBadRequest),
Entry("succeeds to set a termination date past the grace period", "school-id", bytes.NewBuffer(schoolUpdatePastGraceJson), http.StatusOK),
)
})