# Authorization - When performing administrative actions, you can bypass authorization with `authorize?: false` - To run actions as a particular user, look that user up and pass it as the `actor` option - Always set the actor on the query/changeset/input, not when calling the action - Use policies to define authorization rules ```elixir # Good Post |> Ash.Query.for_read(:read, %{}, actor: current_user) |> Ash.read!() # BAD, DO NOT DO THIS Post |> Ash.Query.for_read(:read, %{}) |> Ash.read!(actor: current_user) ``` ## Policies To use policies, add the `Ash.Policy.Authorizer` to your resource: ```elixir defmodule MyApp.Post do use Ash.Resource, domain: MyApp.Blog, authorizers: [Ash.Policy.Authorizer] # Rest of resource definition... end ``` ## Policy Basics Policies determine what actions on a resource are permitted for a given actor. Define policies in the `policies` block: ```elixir policies do # A simple policy that applies to all read actions policy action_type(:read) do # Authorize if record is public authorize_if expr(public == true) # Authorize if actor is the owner authorize_if relates_to_actor_via(:owner) end # A policy for create actions policy action_type(:create) do # Only allow active users to create records forbid_unless actor_attribute_equals(:active, true) # Ensure the record being created relates to the actor authorize_if relating_to_actor(:owner) end end ``` ## Policy Evaluation Flow Policies evaluate from top to bottom with the following logic: 1. All policies that apply to an action must pass for the action to be allowed 2. Within each policy, checks evaluate from top to bottom 3. The first check that produces a decision determines the policy result 4. If no check produces a decision, the policy defaults to forbidden ## IMPORTANT: Policy Check Logic **the first check that yields a result determines the policy outcome** ```elixir # WRONG - This is OR logic, not AND logic! policy action_type(:update) do authorize_if actor_attribute_equals(:admin?, true) # If this passes, policy passes authorize_if relates_to_actor_via(:owner) # Only checked if first fails end ``` To require BOTH conditions in that example, you would use `forbid_unless` for the first condition: ```elixir # CORRECT - This requires BOTH conditions policy action_type(:update) do forbid_unless actor_attribute_equals(:admin?, true) # Must be admin authorize_if relates_to_actor_via(:owner) # AND must be owner end ``` Alternative patterns for AND logic: - Use multiple separate policies (each must pass independently) - Use a single complex expression with `expr(condition1 and condition2)` - Use `forbid_unless` for required conditions, then `authorize_if` for the final check ## Bypass Policies Use bypass policies to allow certain actors to bypass other policy restrictions. This should be used almost exclusively for admin bypasses. ```elixir policies do # Bypass policy for admins - if this passes, other policies don't need to pass bypass actor_attribute_equals(:admin, true) do authorize_if always() end # Regular policies follow... policy action_type(:read) do # ... end end ``` ## Field Policies Field policies control access to specific fields (attributes, calculations, aggregates): ```elixir field_policies do # Only supervisors can see the salary field field_policy :salary do authorize_if actor_attribute_equals(:role, :supervisor) end # Allow access to all other fields field_policy :* do authorize_if always() end end ``` ## Policy Checks There are two main types of checks used in policies: 1. **Simple checks** - Return true/false answers (e.g., "is the actor an admin?") 2. **Filter checks** - Return filters to apply to data (e.g., "only show records owned by the actor") You can use built-in checks or create custom ones: ```elixir # Built-in checks authorize_if actor_attribute_equals(:role, :admin) authorize_if relates_to_actor_via(:owner) authorize_if expr(public == true) # Custom check module authorize_if MyApp.Checks.ActorHasPermission ``` ### Custom Policy Checks Create custom checks by implementing `Ash.Policy.SimpleCheck` or `Ash.Policy.FilterCheck`: ```elixir # Simple check - returns true/false defmodule MyApp.Checks.ActorHasRole do use Ash.Policy.SimpleCheck def match?(%{role: actor_role}, _context, opts) do actor_role == (opts[:role] || :admin) end def match?(_, _, _), do: false end # Filter check - returns query filter defmodule MyApp.Checks.VisibleToUserLevel do use Ash.Policy.FilterCheck def filter(actor, _authorizer, _opts) do expr(visibility_level <= ^actor.user_level) end end # Usage policy action_type(:read) do authorize_if {MyApp.Checks.ActorHasRole, role: :manager} authorize_if MyApp.Checks.VisibleToUserLevel end ```