Skip to content

6. The rights

Three levels, widest first. All three apply in the data-access layer, never in the screen nor in the API: a forgotten endpoint cannot open a hole, and a user calling the API directly gets exactly what the interface would have shown them.

Level 1: who may touch the model

acl.json, at the module’s root.

{
"library_title": {
"read": ["library_reader", "library_librarian", "kws_admin"],
"create": ["library_librarian", "kws_admin"],
"write": ["library_librarian", "kws_admin"],
"unlink": ["library_librarian", "kws_admin"]
},
"library_member": {
"read": ["library_librarian", "kws_admin"],
"create": ["library_librarian", "kws_admin"],
"write": ["library_librarian", "kws_admin"],
"unlink": ["kws_admin"]
}
}

Note the second entry: a reader does not see the member list. That is not a hidden screen, it is an absent right, and the API refuses it too.

Four verbs: read, create, write, delete. A group absent from a list does not have the right.

Level 2: which rows

A record rule filters the visible rows, per group.

from kewos.security import RecordRule
from kewos.auth.context import current_user_id
def _own_loans(cls):
"""A reader sees the loans they took out, and no one else's."""
return cls.created_by_id == current_user_id()
_LOAN_RULES = (
RecordRule(
name="library_loan_own",
domain=_own_loans,
groups=("library_reader",),
),
)

Then attach it to the model:

registry.register_model(
"library_loan",
module="library",
fields=[...],
record_rules=_LOAN_RULES,
)

Two properties are worth knowing, and they surprise people:

  • A rule only applies to holders of its groups. A user without the targeted group is not filtered by that rule.
  • Several applicable rules combine with AND. A user holding two targeted groups sees the intersection. That is deliberately restrictive: the result of a mistake is seeing less, never more.

If you want a condition to widen visibility, put it in an OR inside a single rule, never in a second rule.

Level 3: which fields

A field rule hides or locks a column.

from kewos.security import FieldRule
_MEMBER_FIELD_RULES = (
FieldRule(field="email", read=("library_librarian", "kws_admin"),
write=("library_librarian", "kws_admin")),
)

Attached the same way, through field_rules=.

A user without read access does not receive the column, neither in the API nor on the screen. They do not see it greyed out: it does not exist for them.

Declaring the groups

Your module’s groups must exist. Declare them with their labels, and give each a group.<name> translation key.

A user receives their groups in Settings → Users. While developing:

Fenêtre de terminal
kewos access grant --user <login> --group library_librarian --database-url "$KEWOS_DATABASE_URL"

The three levels, in KM

The same three levels, inside the module’s file:

group library_reader "group.library_reader"
group library_librarian "group.library_librarian"
# Level 1: who may touch the model.
access library_title {
library_reader read
library_librarian read, create, write, unlink
kws_admin read, create, write, unlink
}
# Level 3: which fields. The field stays, its value is hidden.
restrict library_loan.note read library_librarian write library_librarian
# Level 2: which rows. The condition is the expression language's.
rule library_loan for library_reader where member_id.name == user.name

Four keywords for four things the kernel deliberately keeps apart: an identity, a right on a model, a hidden field, a filtered row. Collapsing any two of them is the moment a security model starts lying.

Checking

The only test that counts is this one: sign in with an account holding only library_reader and look. An administrator always sees everything, so testing as one proves nothing.

Next

The states.