MishkaCmsCore.Accounts.Validations.GrantWithinActorScope (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Refuses a permission grant that would hand out more than the granting actor holds.

What is handed out is what the account does not hold yet: a permission it already holds, sent back as the account form does, is no grant, whoever holds it.

This is what lets a site run itself. A tenant admin holding admin:dashboard:permission and admin:dashboard:blog:* can make a colleague a blog editor without a master being involved — which is the point, because a CMS with many sites and many managers cannot route every routine grant through one person. What it cannot do is reach past its own ceiling: it does not hold * or admin:dashboard:document:*, so it cannot grant them.

The ceiling is the same wildcard rule every gate uses (MishkaCmsCore.Accounts.Checks.AccessPermissions.covers?/2), so admin:dashboard:blog:* covers admin:dashboard:blog:post, and nothing but * covers *. A tenant holding * — a site owner — may grant anything, and that is still contained: the row policy keeps them to their own site's users, and require_master gates master actions on site_id, never on permissions. Their * is site-wide, not system-wide.

A MASTER (a user whose site_id is nil) has a ceiling too: what it holds. A master holding * grants anything; one holding only admin:dashboard:permission grants nothing it does not hold, itself included. A call made with authorize?: false (seeds, the installer) is a system call and has none. Anything else with no actor is refused: a missing actor is nobody, not a master.

Declare it with before_action?: true, on an update, a create or a generic action taking permission_ids. The ceiling is then asked right before the write, of the actor the action RUNS with — the actor the policies ask — and not of the one the changeset was built with. A grant built by a master, by a system call or with no actor, and run by a site's admin, is held to that admin's ceiling.

update :assign_permissions do
  validate {MishkaCmsCore.Accounts.Validations.GrantWithinActorScope, []},
    before_action?: true
end

Escalation is closed from both directions only in combination with the policy that forbids editing your own permissions — otherwise two colleagues could raise each other. See MishkaCmsCore.Accounts.User's assign_permissions policy.