AshGrant.FilterCheck (AshGrant v0.14.1)

Copy Markdown View Source

FilterCheck for read actions.

This check integrates with Ash's policy system to provide permission-based authorization for read operations. Unlike AshGrant.Check which returns true/false, this check returns a filter expression that limits query results to records the actor has permission to access.

For write actions, use AshGrant.Check instead.

Auto-generated Policies

When using default_policies: true in your resource's ash_grant block, this check is automatically configured for read actions. You don't need to manually add it to your policies.

When to Use

Use AshGrant.filter_check/1 for:

  • :read actions
  • List/index queries
  • Any action where you want to filter results based on permissions

Usage in Policies

policies do
  # For all read actions
  policy action_type(:read) do
    authorize_if AshGrant.filter_check()
  end

  # For specific read actions with action override
  policy action(:list_published) do
    authorize_if AshGrant.filter_check(action: "read")
  end
end

Options

OptionTypeDescription
:actionstringOverride action name for permission matching
:resourcestringOverride resource name for permission matching

How It Works

  1. Resolve permissions: Calls the configured PermissionResolver to get the actor's permissions
  2. Get all scopes: Uses AshGrant.Evaluator.get_all_scopes/3 to find all matching scopes (respecting deny-wins semantics)
  3. Check for global access: If scopes include "always", "all", or "global", returns true (no filter needed)
  4. Resolve scopes to filters: Uses inline scope DSL or ScopeResolver to get filter expressions
  5. Combine filters: Combines all filters with OR logic

Multi-Scope Support

When an actor has permissions with multiple scopes, all scopes are combined:

# Actor has both permissions:
# - "post:*:read:own"       → filters to author_id == actor.id
# - "post:*:read:published" → filters to status == :published

# Result: author_id == actor.id OR status == :published

This allows users to see both their own posts AND all published posts.

Examples

Basic Usage

# Permission: "post:*:read:always"
# Returns: true (no filter)

# Permission: "post:*:read:own"
# Returns: expr(author_id == ^actor(:id))

# Permission: "post:*:read:published"
# Returns: expr(status == :published)

With Custom Action Name

# Ash action is :get_by_slug, but we check "read" permission
policy action(:get_by_slug) do
  authorize_if AshGrant.filter_check(action: "read")
end

Filter Return Values

The check returns one of:

  • true - No filtering (actor has "always", "all", or "global" scope)
  • false - Block all (no matching permissions or denied)
  • Ash.Expr.t() - Filter expression to apply to the query

Context Injection

Scopes can use ^context(:key) for injectable values. Pass context via Ash.Query.set_context/2:

# Scope definition:
scope :today, expr(fragment("DATE(inserted_at) = ?", ^context(:reference_date)))

# Query with injected context:
Post
|> Ash.Query.for_read(:read)
|> Ash.Query.set_context(%{reference_date: ~D[2025-01-15]})
|> Ash.read!(actor: actor)

This enables deterministic testing of temporal and parameterized scopes.

See Also

Summary

Functions

Determines if two check references conflict (are mutually exclusive).

Creates a filter check tuple for use in policies.

Determines if one check reference implies another.

Callback implementation for Ash.Policy.Check.init/1.

Simplifies a check reference into a SAT expression of simpler check references.

Callback implementation for Ash.Policy.Check.type/0.

Functions

auto_filter(actor, authorizer, opts)

Callback implementation for Ash.Policy.Check.auto_filter/3.

auto_filter_not(actor, authorizer, opts)

check(actor, data, authorizer, opts)

Callback implementation for Ash.Policy.Check.check/4.

conflicts?(ref1, ref2, context)

Determines if two check references conflict (are mutually exclusive).

AshGrant filter checks don't inherently conflict with each other. Returns false for all cases.

eager_evaluate?()

Callback implementation for Ash.Policy.Check.eager_evaluate?/0.

expand_description(actor, authorizer, opts)

Callback implementation for Ash.Policy.Check.expand_description/3.

filter_check(opts \\ [])

Creates a filter check tuple for use in policies.

implies?(ref1, ref2, context)

Determines if one check reference implies another.

Two AshGrant filter checks imply each other if they have identical options (same action and resource overrides). This helps the SAT solver avoid redundant evaluations.

init(opts)

Callback implementation for Ash.Policy.Check.init/1.

prefer_expanded_description?()

Callback implementation for Ash.Policy.Check.prefer_expanded_description?/0.

reject(actor, authorizer, opts)

Callback implementation for Ash.Policy.FilterCheck.reject/3.

requires_original_data?(_, _)

Callback implementation for Ash.Policy.Check.requires_original_data?/2.

simplify(ref, context)

Simplifies a check reference into a SAT expression of simpler check references.

For AshGrant filter checks, we return the ref unchanged since permissions are resolved dynamically at runtime and cannot be further decomposed statically.

This callback is used by Ash's SAT solver to optimize policy evaluation.

strict_check(actor, authorizer, opts)

Callback implementation for Ash.Policy.Check.strict_check/3.

strict_check_context(opts)

type()

Callback implementation for Ash.Policy.Check.type/0.