KemalIdentity::Authz::RBAC
Inherits KemalIdentity::Authz::Authorizer < Reference < Object
Role-based authorization over a Repository, with tenancy.
The order the checks run in
- Is the permission declared? An undeclared name is a typo or a half-finished rename, and it is refused before anything is read.
- Is the principal bound to a different tenant? A session issued inside tenant A asking about tenant B is refused without consulting membership at all. This is the horizontal escalation attempt — the identifier in the URL swapped for somebody else's — and it is the one check that must not depend on a database row being correct.
- Does any role the principal actually holds grant it? Global roles always count. Tenant roles count only for a member.
- Have they proved who they are strongly enough? Last, so that a denial for weak assurance is distinguishable in the trail from a denial for having no grant at all.
Tenant roles are inert without a membership
Holding finance_admin in tenant A grants nothing unless there is also a membership row
for A. Two rows to say one thing, and the redundancy is the point: removing somebody from a
tenant is then a single row that revokes everything at once, and it cannot be defeated by a
role assignment that was missed in the cleanup.
A global assignment — tenant_id nil — is not gated this way. It applies everywhere,
including inside every tenant, which is what makes it the dangerous kind of grant and why
Assignment#granted_by exists.
Constructors
Instance methods
context.resource and context.attributes are read by neither this method nor anything
it calls, and that is what an RBAC implementation should do: a role grants a permission
everywhere or nowhere. An application whose rules turn on the object being acted on
implements Authorizer itself — which is the whole reason authorization is a contract
rather than a table (blueprints/0018-authorization-and-tenancy.md).
Grants a role, and drops this process's cached copy of the account's grants.
Administration goes through here rather than straight to the repository so that the cache
cannot be left holding an answer the database no longer agrees with. Behind several
processes the others still wait out the TTL — Cache says why that is the honest bound.
Raises ArgumentError for a role the catalog does not define. A row naming a role that
does not exist grants nothing, forever, and looks like it grants something.
Drops this process's cached grants for an account. Public because an application that writes to the repository through some other path — a migration, an admin tool sharing the process — has to be able to say so.
Every permission this principal currently holds, for a screen that renders a menu.
Not for guarding an action — a route guards with #decide, against the permission it is
about to perform, at the moment it performs it. A list handed to a template is a snapshot,
and a snapshot used as an authorization decision is the stale-grant problem this whole
module exists to avoid.
Assurance is not applied here: the list says what the account has been granted, not what it can do at this instant, so a menu can show an action and let the step-up prompt happen when it is clicked.
Deletes every membership and assignment for an account. What account deletion calls.
Removes somebody from a tenant, taking that tenant's roles with them — see
Repository#remove_member for why those are one operation.