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 authored and nabim777 committed Jul 12, 2023
1 parent 0bc36f1 commit 373367c
Show file tree
Hide file tree
Showing 4 changed files with 65 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
17 changes: 17 additions & 0 deletions tests/acceptance/features/apiGraph/fullSearch.feature
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,20 @@ Feature: full text search
| old |
| new |
| spaces |


Scenario Outline: search files using context
Given using <dav-path-version> DAV path
And user "Alice" has uploaded file with content "hello world" to "file1.txt"
And user "Alice" has uploaded file with content "Namaste nepal" to "file2.txt"
And user "Alice" has uploaded file with content "hello nepal" to "file3.txt"
When user "Alice" searches for "Content:hello" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should contain only these files:
| file1.txt |
| file3.txt |
Examples:
| dav-path-version |
| old |
| new |
| spaces |

0 comments on commit 373367c

Please sign in to comment.