Adding .agent support in addition to .claude
This commit is contained in:
180
.agents/skills/ash-framework/references/authorization.md
Normal file
180
.agents/skills/ash-framework/references/authorization.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user