CodamAIDocs
Topicdone

Protected values (roles on simple fields)

A role on a simple field protects a single value. Whoever does not hold it does not see the field, cannot change it and cannot search by it. The model itself stays readable.

Variants
in addition to the model rolewildcard leaves it out, name is refusedthe change is what gets checkedclearing needs the delete rolePUT without a value keeps itfilter and order need the read role

What this is about

Picture a payslip payslip. Many people may read it: name, month, department. But only a few should see the field salary, and even fewer should change it.

For this you set a role on the simple field salary, e.g. payslip-salary-read. A simple field is a field with a value (text, number, date), not a relation to another model.

On a relation this works differently: there a field role opens an additional way to the other model. See Permissions on relations (field roles).

One record, two views

flowchart LR
    A(["Person with payslip-read"]) -->|"response: +"| R1["employeeName: Anna<br/>salary: –"]
    B(["Person with payslip-read<br/>and payslip-salary-read"]) -->|"response: +"| R2["employeeName: Anna<br/>salary: 5000"]

Both read the same record. The first person does not get to see salary, the second one does.

Which role for what

A role only applies to the operation it is set for. If you set no role for an operation, the role of the model is enough there.

What the request doesRole on the field
read the field, filter or order by it…-read
set a value on create…-create
change an existing value (PUT or PATCH)…-update
clear a value (set it to null)…-delete, or …-update if the field has no delete role

The generator builds the names like for every field role: base role, field name, operation. See How role names are built.

Reading

What happens to salary in the response?
Person holds payslip-salary-readHow the response requests the fieldResult
yes–field with its value
nothrough + or *field is missing (null), rest of the response as usual
noby name, e.g. "salary"403 missing-permission|payslip-salary-read

Why two answers? + means “all simple fields I may see”. If you name the field, you want exactly this value. An empty value could then not be told apart from “not set”, so CDMS refuses.

The field is missing everywhere the record comes out:

  • on a read and in every row of a search
  • in the response to create, PUT and PATCH (it is a read)
  • when you read the model through a relation of another model
  • in every version of the change history
  • in every row of a search on an abstract model

Writing

When writing, CDMS checks the change, not the mere mention of the field.

May the request write salary?
Value in the requestRole heldResult
same as the stored value–allowed, nothing changes
different value…-update (on create …-create)is stored
different valueno403 missing-permission|payslip-salary-update, nothing stored
PATCH with null…-deletefield is cleared
PATCH with nullno403 missing-permission|payslip-salary-delete
PUT without a value…-deletefield is cleared
PUT without a valuenostored value stays, no error

Values set by a default value or a hook are not checked. They come from the server, not from the person.

Searching

A filter on a value gives it away, step by step: “Is the salary greater than 4000? Greater than 5000?” An order gives it away through the position. That is why:

  • A filter on a field you may not read results in 403 with the read role of the field. This also applies inside nested groups.
  • An order by it results in 403 as well.
  • A path such as vault.code is followed to its last field. If code has a read role, you need it for the filter.
  • On an abstract model the field of every subtype counts.

See also Broken filters.

Variants

A protected field in the operations

When: POST /payslip/read/{id}, response: ["+"], only payslip-read

  1. 1
    CDMS
    checks the read role of payslip and reads the record
  2. 2
    CDMS
    payslip-salary-read is missing → leaves out salary

Result: 200, salary is null.

When: POST /payslip/create with salary, without payslip-salary-create

  1. 1
    CDMS
    checks the create role of payslip
  2. 2
    CDMS
    salary has a value, payslip-salary-create is missing

Result: 403, nothing is created. Without salary in the body the create goes through.

When: read without payslip-salary-read, changed, sent back with PUT

  1. 1
    CDMS
    salary is missing in the body, payslip-salary-delete is missing
  2. 2
    CDMS
    keeps the stored value and writes the other fields

Result: 200, the salary is unchanged.

When: POST /payslip/query with the filter salary AFTER 4500, without payslip-salary-read

  1. 1
    CDMS
    checks every filter and every order against the read roles of the fields

Result: 403 missing-permission|payslip-salary-read, before anything is searched.

Pitfalls

Where to go next

Sources in the code and the knowledge base
  • CDMS/cdms-system-layer – SimpleFieldRoles, AbstractLayer (guardedValue, maskUnreadable, recursiveQuery, queryHistory)
  • CDMS/cdms-rest-api – Expander (expandStringField), AbstractHubApi (queryData)
  • CDMS/cdms-integrationtest – AbstractSimpleFieldRoleTest, model payslip
  • CDMS/cdms-system-layer/docs/adr – ADR-015
Search