What this is about
Picture a vault vault with notes note. A person should see the notes of a vault when they read the vault. But they should not be able to search all notes through /note/query.
Model roles alone cannot do that: the read role of note always opens the own endpoint of note as well. That is what field roles are for. A field role is attached to a relation, here the field vault.notes, and is named e.g. vault-notes-read.
Two paths to the same child
flowchart LR
P(["Person with vault-read<br/>and vault-notes-read"])
P -->|"POST /vault/read/{id}<br/>response: notes"| V["vault"]
V -->|"through the field notes:<br/>vault-notes-read is enough"| N["note"]
P -.->|"POST /note/query<br/>requires note-read"| X["403"]
| Path | What CDMS checks | Result with vault-read and vault-notes-read |
|---|---|---|
through the vault: POST /vault/read/{id} with notes in the response | role of vault, then field role or read role of note | 200 with notes |
directly: POST /note/query | read role of note | 403 `missing-permission |
When the field role counts
When a request descends through a relation into another model, CDMS checks two things. One of them is enough:
| field role on vault.notes for this operation | role of note for this operation | Result |
|---|---|---|
| yes | – | allowed through the field |
| no | yes | allowed through the role of the child model |
| no | no | 403 missing-permission|vault-notes-<operation> |
| no field role defined on the field | no | 403 missing-permission|<role of note> |
The operation has to match exactly:
| Operation on the child | Field role |
|---|---|
read, including lists in the response | …-read |
| create along with the parent | …-create |
| change along with the parent, by PUT or PATCH | …-update |
| delete along with the parent in a cascade | …-delete |
Variants
When: response names notes
-
1CDMSchecks the read role of
vaultand reads the vault -
2CDMS
vault-notes-readpresent → reads the notes withoutnote-read -
3CDMS→Databasereads only notes that the filters of
notelet through
Result: The filters of the row level still apply. A field role does not make an invisible note visible.
When: POST /vault/create with new notes in notes
-
1CDMSchecks the create role of
vault -
2CDMS
vault-notes-createpresent → creates the notes withoutnote-create -
3CDMSreads the result back. If the
responsenames the notes, the same applies as for reading:vault-notes-readornote-read
Result: Whoever has only vault-notes-create leaves the notes out of the response, otherwise the read-back fails. See Create and read back: STRICT or LENIENT.
When: DELETE /vault/delete/{id}, notes with DELETE flag
-
1CDMSchecks the delete role of
vault -
2CDMS
vault-notes-deletepresent → deletes the notes withoutnote-delete -
3CDMSif a note has dependent children of its own, each of them again needs its own role or a field role on the note's relation
Result: See Dependent objects (cascades).
How far a field role reaches
- One level only.
vault-notes-readapplies to the notes, not to what hangs off a note. For the next level you again need the model’s role or a field role on the note’s relation. - This operation only.
vault-notes-readdoes not allow changing along. For that you needvault-notes-updateornote-update. - Different on simple fields. A role on a simple field such as
note.textopens no way, it protects the value in addition to the role of the model. See Protected values. - Never the direct path. Even with all four
vault-notes-…roles, every endpoint under/note/…returns 403 with the role ofnote.
Where you define field roles
In the hub, on the relation, you mark the operations that should have a field role. The generator builds the name: base role of the model, field name, operation. See How role names are built.
Pitfalls
Where to go next
- The roles of the model itself: Model roles
- Roles on simple fields: Protected values
- The levels in context: The three levels at a glance
- How relations are written: The four cases in nested writing