Authorization
All authorization decisions go through Anvil\Security\Authorization\Gate
(service('gate')). The gate denies by default.
Permissions
Section titled “Permissions”A permission is a string like orders.view. Access is granted only when
the current actor holds the permission.
$gate = service('gate');
if ($gate->allows('orders.view')) { // ...}
$gate->authorize('orders.update'); // throws AuthorizationException (403)Permissions Anvil checks by itself:
| Permission | Checked for |
|---|---|
<resource slug>.view, .create, .update, .delete, .history |
the pages and operations of a resource |
<resource slug>.export |
exports, when the resource is exportable |
<resource slug>.update |
actions and bulk actions without own permission (->permission('orders.cancel')) |
Panel::permission() |
entering the panel at all (null: every logged in user) |
Page::permission() |
a custom page (default <panel id>.page.<page slug>, null for every logged in user) |
Gate::AUTHENTICATED (@authenticated) |
pseudo permission of every logged in user, only for data of the users themselves (profile, notifications) |
All of them are listed in the role editor and by
php spark anvil:permissions.
Permission checkers
Section titled “Permission checkers”Where permissions come from is decided by a PermissionChecker
(service('permissionChecker')):
| Checker | Use |
|---|---|
Anvil\Access\RbacPermissionChecker |
default (Config\Anvil::$rbac = true): database roles of the user in the current tenant, optionally with Shield groups as fallback |
ShieldPermissionChecker |
$rbac = false: asks Shield for the logged in user |
ArrayPermissionChecker |
fixed permissions, for tests or system jobs |
Roles are stored in the database and maintained in the panel, see Users and roles.
Shield groups
Section titled “Shield groups”Config\Anvil::$shieldGroupFallback (default true) also grants the
permissions of the Shield groups in Config\AuthGroups. These apply in
every tenant. Turn the fallback off once roles are maintained in Anvil, so
that all permissions are tenant bound and visible in the role editor.
Policies
Section titled “Policies”A policy restricts access to single records, e.g. “agents may only edit orders of their own region”. It is asked after the permission check and can only restrict, never grant.
use Anvil\Security\Authorization\Policy;
/** * @implements Policy<Order> */final class OrderPolicy implements Policy{ public function allows(string $permission, object $order): bool { return $order->region === current_user_region(); }}
service('gate')->allows('orders.update', $order);Register policies in app/Config/Anvil.php, so that they apply everywhere:
panels, API clients and commands.
public array $policies = [\App\Entities\Order::class => \App\Policies\OrderPolicy::class];service('gate')->policy(Order::class, new OrderPolicy()) registers one at
runtime, e.g. in tests. API requests keep the policies of the gate
(Gate::withPermissions() swaps only the permissions).
A policy registered for a class also applies to its subclasses.
Where policies apply: detail and edit pages, single and bulk actions, deleting, lists, related lists and exports (records the policy refuses are left out), selects of related records, relations (attach, detach, create), file downloads and the API.
- Entities required: a policy is found by the class of the record. Models
returning arrays (
$returnType = 'array') give plain objects no policy can be registered for; such records are allowed, and a warning is logged when policies exist. Use entities (the generators create them). - Lists: rows that should never be listed belong into
Resource::scopeQuery()as well; the totals and pages count the rows of the query.
Running code as a user
Section titled “Running code as a user”Outside of a request, e.g. in a queue job, Anvil\Security\RunAs runs code
with the permissions, tenant and audit actor of a user. The user must exist
and be active; they are logged in in memory only.
use Anvil\Security\RunAs;
RunAs::user($userId, $tenantId, function ($user): void { service('gate')->authorize('orders.update'); // ...});