diff --git a/compose/service/record.go b/compose/service/record.go index 6d23169fa..842d45f4c 100644 --- a/compose/service/record.go +++ b/compose/service/record.go @@ -1376,6 +1376,10 @@ func (svc record) generalValueSetValidation(m *types.Module, vv types.RecordValu return RecordErrFieldNotFound(aProps.setField(v.Name)) } + if !svc.ac.CanUpdateRecordValue(svc.ctx, field) { + return RecordErrNotAllowedToChangeFieldValue(aProps.setField(v.Name)) + } + if field.IsRef() { if v.Value == "" { return nil diff --git a/compose/service/record_actions.gen.go b/compose/service/record_actions.gen.go index 2dc5a09f9..18818ea3b 100644 --- a/compose/service/record_actions.gen.go +++ b/compose/service/record_actions.gen.go @@ -1284,6 +1284,36 @@ func RecordErrNotAllowedToUndelete(props ...*recordActionProps) *recordError { } +// RecordErrNotAllowedToChangeFieldValue returns "compose:record.notAllowedToChangeFieldValue" audit event as actionlog.Error +// +// +// This function is auto-generated. +// +func RecordErrNotAllowedToChangeFieldValue(props ...*recordActionProps) *recordError { + var e = &recordError{ + timestamp: time.Now(), + resource: "compose:record", + error: "notAllowedToChangeFieldValue", + action: "error", + message: "not allowed to change value of field {field}", + log: "failed to change value of field {field}; insufficient permissions", + severity: actionlog.Error, + props: func() *recordActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + // RecordErrImportSessionAlreadActive returns "compose:record.importSessionAlreadActive" audit event as actionlog.Error // // diff --git a/compose/service/record_actions.yaml b/compose/service/record_actions.yaml index 43efb7b8b..8d34c2930 100644 --- a/compose/service/record_actions.yaml +++ b/compose/service/record_actions.yaml @@ -148,6 +148,11 @@ errors: message: "not allowed to undelete this record" log: "failed to undelete {record}; insufficient permissions" + - error: notAllowedToChangeFieldValue + message: "not allowed to change value of field {field}" + log: "failed to change value of field {field}; insufficient permissions" + + - error: importSessionAlreadActive message: "import session already active" log: "failed to start import session" diff --git a/tests/compose/record_test.go b/tests/compose/record_test.go index 5bae42daf..fe0aee3a5 100644 --- a/tests/compose/record_test.go +++ b/tests/compose/record_test.go @@ -446,3 +446,78 @@ func TestRecordImportImportProgress_sessionNotFound(t *testing.T) { Assert(helpers.AssertError("compose.service.RecordImportSessionNotFound")). End() } + +func TestRecordFieldModulePermissionCheck(t *testing.T) { + h := newHelper(t) + + // make a standad module, and prevent current user to + // read from "name" and update "email" fields + module := h.repoMakeRecordModuleWithFields("record testing module") + h.deny(module.Fields.FindByName("name").PermissionResource(), "record.value.read") + h.deny(module.Fields.FindByName("email").PermissionResource(), "record.value.update") + h.allow(types.ModulePermissionResource.AppendWildcard(), "record.create") + h.allow(types.ModulePermissionResource.AppendWildcard(), "record.update") + + record := h.repoMakeRecord( + module, + &types.RecordValue{Name: "name", Value: "should not be readable"}, + &types.RecordValue{Name: "email", Value: "should not be writable"}, + ) + + // Fetching record should work as before but without read-protected fields + h.apiInit(). + Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + // should not return name + Assert(jsonpath.NotPresent(`$.response.values[? @.name=="name"]`)). + // should return email + Assert(jsonpath.Present(`$.response.values[? @.name=="email"]`)). + End() + + bb := map[string]func() *apitest.Request{ + "update": func() *apitest.Request { + return h.apiInit(). + Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)) + }, + + "create": func() *apitest.Request { + return h.apiInit(). + Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)) + }, + } + + for name, b := range bb { + t.Run(name, func(t *testing.T) { + t.Run("field:email", func(t *testing.T) { + // Try to change email (not writable!), expect error... + b().JSON(fmt.Sprintf(`{"values": [{"name": "email", "value": "changed-email"}]}`)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.Dump). + Assert(helpers.AssertError("not allowed to change value of field email")). + End() + }) + + t.Run("field:name", func(t *testing.T) { + // Try to change name, (not readable), expect it to work + b().JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "changed-name"}]}`)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + }) + + t.Run("field:description", func(t *testing.T) { + // Try to change description, (no perm. rules), expect it to work + b().JSON(fmt.Sprintf(`{"values": [{"name": "description", "value": "changed-description"}]}`)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + }) + }) + } + +}