Read and understand the [Security](/documentation/topics/security.md) guide before proceeding, which explains actors, how to set them, and other relevant configurations.
- a condition, such as `action_type(:read)` or `actor_attribute_equals(:admin, true)` or `always()`. If this condition is true for a given action request, then the policy will be applied to the request.
- a set of policy checks, each of which will be evaluated individually if a policy applies to a request.
If more than one policy applies to any given request (eg. an admin actor calls a read action) then **all applicable policies must pass** for the action to be performed.
The first argument to `policy` is the condition. In this case, the condition is `always()` - a built-in helper always returning true, meaning that the policy applies to every request.
Within this policy we have a single policy check, declared with `authorize_if`. Checks logically apply from top to bottom, based on their check type. In this case, we'd read the policy as "this policy always applies, and authorizes always".
If a single check does not explicitly authorize or forbid the whole policy, then the flow moves to the next check. For example, if an `authorize_if` check does NOT return true, this _does not mean the whole policy is forbidden_ - it means that further checking is required.
**Not every check in a policy must pass!** This is described above, but is very important so another example is provided here. Checks go from top to bottom, are evaluated independently of each other, and _the first one that reaches a decision_ determines the overall _policy result_. For example:
authorize_if IsAdminUser # If this is true, then the policy is still authorized.
# And none of these checks matter
forbid_if RegularUserCanCreate
authorize_if RegularUserAuthorized
```
### Not all policy checks have yes/no answers
This will be covered in greater detail in [Checks](#checks), but will be briefly mentioned here.
Ash provides two basic types of policy checks - _simple_ checks and _filter_ checks. Simple checks are what we commonly think of with authorization, and what the above example would suggest - is an actor allowed to perform a given operation, yes or no? But we can also use filter checks - given a list of resources, which ones is an actor allowed to perform the operation on?
Filter checks are frequently used with read actions, as they can refer to multiple instances (eg. "list all products"), but may also be applied to actions like bulk-deleting records (which is not currently supported, but will be eventually).
### Bypass policies
A bypass policy is just like a regular policy, except if a bypass passes, then other policies after it _do not need to pass_. This can be useful for writing complex access rules, or for a simple rule like "an admin can do anything" without needing to specify it as part of every other policy.
Checks evaluate from top to bottom within a policy. A check can produce one of three results, the same that a policy can produce. While checks are not necessarily evaluated in order, they _logically apply_ in that order, so you may as well think of it in that way. It can be thought of as a step-through algorithm.
- If this check succeeds, it returns `:authorized`, the whole policy is `:authorized`, and checks stop running
- If this check fails, it returns `:unknown` and the next check is checked
-`forbid_if Deactivated`
- We only care about this result if the previous check failed, ie. the actor is not a super user.
- If this check succeeds, it returns `:forbidden`, the whole policy is `:forbidden`, and checks stop running
- If this check fails, it returns `:unknown` and the next check is checked
-`authorize_if IsAdminUser`
- We only care about this result if the previous checks failed, ie. the actor is not a super user and is not deactivated.
- If this check succeeds, it returns `:authorized`, the whole policy is `:authorized` and checks stop running.
- If this check fails, it returns `:unknown` and the next check is checked
-`authorize_if RegularUserAuthorized`
- We only care about this result if the previous checks failed, ie. the actor is not a super user, not deactivated and not an admin user.
- If this check succeeds, it returns `:authorized`, the whole policy is `:authorized` and checks stop running.
- If this check fails, it returns `:unknown`. As there are no more checks to run, the whole policy returns `:unknown`, which is treated as forbidden and the actor is not allowed to perform the action.
As mentioned earlier, there are two distinct types of checks - _simple_ checks and _filter_ checks. So far we've seen examples of both - let's look in a bit more detail.
(Both simple and filter checks are a subset of a third type of check - a _manual_ check - but you will almost always want to write simple or filter checks.)
Simple checks are determined at the outset of a request, and can only cause a request to be authorized or forbidden. These are typically yes/no questions - is the actor an admin? Did the actor create the post they want to call the `update` action on? Is the actor old enough to drink alcohol?
You can then use this module as the check name, as part of a policy:
```elixir
defmodule MyApp.Beer do
# ...
policies do
policy action(:drink) do
authorize_if MyApp.Checks.ActorIsOldEnough
end
end
# ...
end
```
Ash will internally convert the true/false return value from `match?/3` to a `:authorized`/`:forbidden`/`:unknown` response, depending on how the check is being run (ie. whether it's part of an `authorize_if`/`forbid_if`/etc.)
#### Filter checks
Many checks won't return a status yes/no, but instead return a "filter" to apply to a collection of data. They are most commonly used for read actions, but can be used for all types of actions.
For update and destroy actions, they apply to the data _before_ the action is run.
For read actions, they will automatically restrict the returned data to be compliant with the filter. Using the drinking example from earlier, we could write a filter check to list only users that are old enough to drink alcohol.
There are two ways to write a filter check - by creating a module and using the `Ash.Policy.FilterCheck` module, or by using inline expression syntax.
Keep in mind that, for create actions, many `expr/1` checks won't make sense, and may return `false` when you wouldn't expect. Expression (and other filter) policies apply to "a synthesized result" of applying the action, so related values won't be available. For this reason, you may end up wanting to use other checks that are built for working against changesets, or only simple attribute-based filter checks. Custom checks may also be warranted here.
In expression checks, the `actor` template can be used (other templates that may work in filter expressions, for example, are not available). For example:
The resulting filter is `friends.first_name == "ted" and friends.last_name == "dansen"`- this means that you'll get users that have a friend with the full name "ted dansen". That _might_ be what you meant, but more likely you would want "users that have a friend with the first name "ted", that also have a friend with the last name 'dansen'".
To accomplish that, we can use the `exists` helper and rework the example like so:
In policies (and often any time you mean "a related thing exists where some condition is true"), it is advised to use `exists/2` when referring to relationships because of the way that the policy authorizer may mix & match your policies when building filters. This is also true when adding filters to actions. If you use `exists`, then your policies can be used in filters without excluding unnecessary data.
Policy breakdowns can be fetched on demand for a given forbidden error (either an `Ash.Error.Forbidden` that contains one ore more `Ash.Error.Forbidden.Policy` errors, or an `Ash.Error.Forbidden.Policy` error itself), via `Ash.Error.Forbidden.Policy.report/2`.
For security reasons, authorization errors don't include any extra information, aside from `forbidden`. To have authorization errors include a policy breakdown (without help text) use the following config.