Skip to content

Commit

Permalink
graph: Add config knob to set a minimal grace period for schoolTermin…
Browse files Browse the repository at this point in the history
…ation

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.
  • Loading branch information
rhafer committed Jul 12, 2023
1 parent 0bc36f1 commit 51bc49d
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 0 deletions.
2 changes: 2 additions & 0 deletions services/graph/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
33 changes: 33 additions & 0 deletions services/graph/pkg/service/v0/educationschools.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions services/graph/pkg/service/v0/educationschools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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)
Expand All @@ -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),
)
})

Expand Down

0 comments on commit 51bc49d

Please sign in to comment.