Authorization

Gates

Tell clients what the user may do with each row of a search, with the answers and messages of your policies, and your own gates.

A client often needs to know whether to show an edit or a delete button on a row. Ask for gates in a search, and each row answers the abilities of your policies for the current user.

Asking for gates

{
  "search": {
    "gates": ["view", "update", "create"]
  }
}

An excerpt of the page, when the policy denies the update with Response::deny('Closed tickets are read-only.'):

{
  "current_page": 1,
  "data": [
    {"id": 2, "title": "Password reset", "gates": {"authorized_to_view": {"allowed": true, "message": null}, "authorized_to_update": {"allowed": false, "message": "Closed tickets are read-only."}}}
  ],
  "meta": {"gates": {"authorized_to_create": {"allowed": true, "message": null}}}
}
  • Each answer holds allowed and message: the message of the policy's Response::deny(), or null.
  • gates takes view, create, update, delete, restore and forceDelete, and the gates that the resource declares, each once. restore and forceDelete are gates only when the model of the resource uses SoftDeletes. viewAny, another name, or restore and forceDelete on another model, is a 422: The selected search.gates.0 is invalid. An ability listed twice gives The search.gates.0 field has a duplicate value. on each copy.
  • create does not depend on a row: its answer goes to the meta of the page. It is accepted at the root of a search only.
  • An include takes its own gates, answered on each included row. See Includes and aggregates.
  • A row whose gates hold no answer returns {}. A search without gates returns no gates key on its rows.

Gates and the perimeter

update, delete, restore and forceDelete answer for the rows that their endpoint reaches. A row that the endpoint would refuse with a 422 answers {"allowed": false, "message": null} without calling the policy:

  • a trashed row, for update and delete, unless perimeterQuery() takes trashed rows in;
  • a row of a search engine page that perimeterQuery() leaves out. See Search engines.

Declaring your own gates

A resource declares more gates in gates(): the names of policy methods that receive the user and the model.

public function gates(): array
{
    return ['download'];
}
// PostPolicy
public function download(User $user, Post $post): bool
{
    return $post->published;
}

A search of that resource then accepts download in gates, at the root and in each include of that resource, and each row answers it under authorized_to_download.

  • A name is the camelCase name of a policy method, listed once, other than viewAny and the six abilities above. Another value throws Lomkit\Rest\Exceptions\InvalidResourceException when the gates are first read.
  • A declared gate is answered on each row, never in meta.
  • A policy without the method denies it, with a null message, as Laravel does.
  • The details list every gate that a search accepts, with ?with=gates.

Naming the answers

config/rest.php names the key of the answers and each answer:

'gates' => [
    'key' => 'gates',

    'names' => [
        'authorized_to_view' => 'authorized_to_view',
        'authorized_to_create' => 'authorized_to_create',
        'authorized_to_update' => 'authorized_to_update',
        'authorized_to_delete' => 'authorized_to_delete',
        'authorized_to_restore' => 'authorized_to_restore',
        'authorized_to_force_delete' => 'authorized_to_force_delete',
    ],
],
  • gates.key renames the gates key of the rows and of meta. A field named like it throws InvalidResourceException.
  • gates.names renames each answer. It also renames the gates that a resource declares, by their default name: 'authorized_to_download' => 'can_download'.

Turning the gates off

Override isGatingEnabled() on a resource, or on the base resource for every resource, and return false:

public function isGatingEnabled(): bool
{
    return false;
}

At the root of a search, gates stays accepted and returns no answer. In an include of such a resource, gates is a 422: The search.includes.0.gates field is prohibited: the included resource turns its gates off.

Cost

Each answer goes through the resource's authorizedTo(), so the policy runs once per row and gate in a request. To keep the answers between requests, declare the abilities in authorizationCache(), see Decision cache.