# `MishkaCmsCore.Accounts.Checks.AccessPermissions`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/accounts/__checks/access_permissions.ex#L5)

Whether an actor holds what a gate requires — the one matcher every policy, plug, page guard,
sidebar entry, widget, setting and notification audience asks.

## Names

A permission name is a plain string of colon-separated, lower snake_case segments, written where
it is used:

  * `admin:dashboard:<area>[:<sub>]` — the admin screens and the data they read and write, such as
    `admin:dashboard:runtime:page`, `admin:dashboard:blog:post` or `admin:dashboard:code`.
  * `admin:api:<area>[:<sub>]` — the admin JSON:API and GraphQL, such as `admin:api:runtime:page`.
  * `*` only as the whole last segment: `admin:dashboard:*` is the whole dashboard and nothing on
    the API, `admin:api:*` the whole API and nothing on the dashboard, `admin:dashboard:runtime:*`
    every runtime section. `admin:*` and `*` are both surfaces.

A held name ending in `*` covers every name below it, and a leaf covers only itself:
`admin:dashboard:code` does not cover `admin:dashboard:code:x`. A narrow name never meets a
required wildcard: a required `admin:dashboard:*` is met only by `admin:dashboard:*`, `admin:*`
or `*`.

## What may be required

A list where holding ANY entry is enough. An entry is:

  * a name — held when a held name covers it. A required `admin:dashboard:…` name counts only
    when the request came from the dashboard, and a required `admin:api:…` name only when it came
    from the API (`surface/1`). Any other name counts on both.
  * `{:under, prefix}` — held when the actor holds anything at or under `prefix`: a name starting
    with `prefix <> ":"`, or a wildcard covering `prefix <> ":*"`. `*`, `admin:*`,
    `admin:dashboard:*` and `admin:dashboard:blog:post` are all under `"admin:dashboard"`. It
    counts wherever the request came from.
  * `{:under, :surface}` — `{:under, "admin:dashboard"}` or `{:under, "admin:api"}`, for the
    surface the request came from.

Anything else grants nothing.

As a policy check it takes `:permissions` and `:under`, and the entries they name are added
together. A policy lists the name of each surface it answers on:

    authorize_if {AccessPermissions, permissions: ["admin:dashboard:runtime:page", "admin:api:runtime:page"]}
    authorize_if {AccessPermissions, permissions: [{:under, "admin:dashboard"}]}
    authorize_if {AccessPermissions, under: "admin:dashboard"}

The admin JSON:API, GraphQL and the GraphQL socket put `admin_surface: :api` in the shared context
(`MishkaCmsCore.Runtime.Plugs.AdminSurfacePlug`); every other request is the dashboard's. Called
directly, pass a context carrying that surface to ask about the API:

    AccessPermissions.match?(user, %{shared: %{admin_surface: :api}}, permissions: ["admin:api:*"])

## Sites

`*` is not master status. A master is a user whose `site_id` is nil
(`MishkaCmsCore.Accounts.Helper.master_user?/1`); a site user holding `*` holds every permission
within its own site and nothing beyond it.

As a policy check it asks about the action the policy is authorizing, with the actor and tenant it
RUNS with. A site user holds none of its permissions under another site's tenant, nor for a
master's action (one that declares `TenantContext` with `require_master: true`). A master holds
its permissions under any tenant and for any action. Called directly, with no action to ask about,
it answers for the permissions alone.

## Loading

An actor whose `:permissions` are not loaded has them read with no tenant and no authorization:
which names a user holds is not a question of the site a request runs under.

## Attribution
Originally developed based on the core implementation by Mr. Toomaj Boloorian
(https://github.com/toomaj). Original source available at:
https://github.com/mishka-group/mishka_developer_tools/blob/master/lib/modules/permission_access.ex

# `requirement`

```elixir
@type requirement() :: String.t() | {:under, String.t() | :surface}
```

One entry of a required list: a name, or anything under a prefix.

# `surface`

```elixir
@type surface() :: :dashboard | :api
```

Where an admin works: the screens, or the admin JSON:API and GraphQL.

# `beyond`

```elixir
@spec beyond(map() | nil, [String.t()]) :: [String.t()]
```

The names among `names` that nothing `actor` holds covers (`covers?/2`): what the actor would
gain by granting them, or by taking over an account that holds them. Every name for no actor.

    AccessPermissions.beyond(actor, ["admin:dashboard:runtime:page", "*"])
    #=> ["*"]

# `covers?`

```elixir
@spec covers?(String.t(), String.t()) :: boolean()
```

Whether holding `permission` grants `action`, with the same wildcard semantics every gate uses:
`admin:dashboard:*` covers `admin:dashboard:blog:post`, `*` covers everything, and a narrower
`admin:dashboard:runtime:page` does NOT cover `admin:dashboard:*`. The surface plays no part.

Public because granting permissions asks the same question in the other direction — "may this
actor hand out that permission" is "does what they hold cover it".

# `describe`

What the check asks, in words, for a policy breakdown.

# `eager_evaluate?`

# `holds_under?`

```elixir
@spec holds_under?(map() | nil, String.t()) :: boolean()
```

Whether `actor` holds anything at or under `prefix`, such as `"admin:dashboard"`. See
`{:under, prefix}` above.

# `init`

# `match?`

Whether `actor` holds any of what `opts` requires (`:permissions`, `:under`) on the request's
surface, within its own site when `context` carries the policy's subject. `false` for no actor or
nothing required.

# `prefer_expanded_description?`

# `requires_original_data?`

# `strict_check`

# `surface`

```elixir
@spec surface(map() | nil) :: surface()
```

The surface a request came from: `:api` when the shared context says `admin_surface: :api`,
otherwise `:dashboard`. Takes a policy context (with its `:subject`), a query, changeset or action
input, or a context map.

# `type`

# `with_permissions`

```elixir
@spec with_permissions(map() | nil) :: map() | nil
```

`actor` with its `:permissions` loaded — read with no tenant and no authorization when they are
not. A map that is not a resource is returned as it is.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
