What this is about
An object is invisible to you when one of the filters of the row level excludes it: it belongs to another person (own data), it does not match your profile (attribute filter), or a custom filter of the project does not let it through.
For an invisible object CDMS answers exactly as for an object that does not exist at all: 404.
Why not 403?
- says: the object exists
- an attacker could try other people's
ids and learn which ones exist - CDMS uses 403 for missing roles: they apply equally to all objects and reveal nothing about a single one
- says nothing about whether the object exists
- someone else's object and a made-up
idlook the same - CDMS uses 404 for invisible rows
Variants
When: POST /read/{id}
-
1CDMSchecks the read role
-
2CDMS→Databasereads the row with
idand all filters -
3CDMSno row → 404
not-found
Result: When reading, the role comes first: without the read role you get 403, even for an invisible object.
When: POST /query
Invisible rows are missing from data and do not count in totalCount. There is no error.
Result: The hit count never reveals how many objects there are in total.
When: reference or list you read along
An invisible single reference comes back as null. In a list, invisible entries are missing.
Result: So null can mean: not set or invisible to you.
When: PUT /update/{id}, PATCH /update/{id}
-
1CDMS→Databasecounts the row with
idand the same filters as for reading -
2CDMS0 → 404
not-found|<Dto>|<id> -
3CDMS1 → only now checks the update role
Result: When writing, visibility comes first: invisible gives 404, even without the role.
When: A child with an id in a nested write
The same filters as reading, applied to the target: if it is invisible to you, the answer is 404 missing-object|<id>|<model> — the same answer an id that never existed gets. Read permission for the target model is required as well.
Result: You can only connect to what you could have looked at. See The four cases.
When: DELETE /delete/{id}
Same as changing: visibility first (404), then the delete role (403). You do not need a read role, but the read filters still apply.
Result: See How a DELETE runs.
When: POST /{id}/rollback/{revision}
Same as changing: visibility first (404), then the rollback role (403). A deleted object no longer exists; that also gives 404. If the rollback re-links a single reference, the old and the new target are checked as for linking.
Result: You can only roll back what you can see now.
When: GET /{id}/file
The download first reads the object like a read. Invisible → 404, before any file flows.
Result: See Downloading.
When: POST /{id}/history
First the two roles (403), then visibility: invisible → 404 missing-object|<id>|<model>. If the object has been deleted, its last state before the deletion counts. On a model with a filter, an unknown id cannot be told apart from a hidden one.
Result: See Reading the history.
Decision table
| Operation | role present | object visible | Answer |
|---|---|---|---|
| read | no | – | 403 missing-permission|<read role> |
| read | yes | no | 404 not-found |
| change, delete, rollback | – | no | 404 not-found|<Dto>|<id> |
| change, delete, rollback | no | yes | 403 missing-permission|<role> |
| any | yes | yes | allowed |
Pitfalls
Where to go next
- Which filters there are: The three levels at a glance
- What is different without strict mode: Strict mode