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 RecordRulefrom 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:
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.nameFour 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.