# Permit.Ecto

Ecto integration for Permit - Convert permissions to Ecto queries with automatic authorization scoping.

[![Contact Us](https://img.shields.io/badge/Contact%20Us-%23F36D2E?style=for-the-badge&logo=maildotru&logoColor=white&labelColor=F36D2E)](https://curiosum.com/contact) [![Visit Curiosum](https://img.shields.io/badge/Visit%20Curiosum-%236819E6?style=for-the-badge&logo=elixir&logoColor=white&labelColor=6819E6)](https://curiosum.com/services/elixir-software-development) [![License: MIT](https://img.shields.io/badge/License-MIT-1D0642?style=for-the-badge&logo=open-source-initiative&logoColor=white&labelColor=1D0642)]()

Purpose and usage

Permit.Ecto integrates Permit with Ecto, providing means to convert permissions to Ecto queries and automatically constructing Ecto.Query scopes to preload records that meet authorization criteria.

Key features:

Hex version badgeActions StatusCode coverage badgeLicense badge

Dependencies and related libraries

Permit.Ecto depends on Permit. It can be used to build custom integrations or in conjunction with Permit.Phoenix, which uses the generated accessible_by/4 functions to automatically preload, authorize and inject records loaded via Ecto into controller assigns (see more in Permit.Phoenix documentation).

Usage

defmodule MyApp.Authorization do
  use Permit.Ecto,
    permissions_module: MyApp.Permissions,
    repo: MyApp.Repo
end

defmodule MyApp.Permissions do
  use Permit.Ecto.Permissions, actions_module: Permit.Actions.CrudActions

  def can(%{role: :admin} = user) do
    permit()
    |> all(MyApp.Blog.Article)
  end

  def can(%{id: user_id} = user) do
    # Checks can be performed directly for record columns as well as associated
    # record values in has_one, has_many and belongs_to associations.
    #
    # For has_one and belongs_to relationships, given condition must be satisfied
    # for the associated record. For has_many relationships, at least one associated
    # record must satisfy the condition - as seen in example below.

    permit()
    |> all(MyApp.Blog.Article, author_id: user_id)
    |> read(MyApp.Blog.Article, reviews: [approved: true])
  end

  def can(user), do: permit()
end

iex> MyApp.Repo.all(MyApp.Blog.Article)
[
  %MyApp.Blog.Article{id: 1, author_id: 1},
  %MyApp.Blog.Article{id: 2, author_id: 1},
  %MyApp.Blog.Article{id: 3, author_id: 2}
]

# The `accessible_by!/3` function also has a `accessible_by/3` variant which returns `{:ok, ...}` tuples.

iex> MyApp.Permissions.accessible_by!(%MyApp.Users.User{id: 1}, :update, MyApp.Blog.Article) |> MyApp.Repo.all()
[%MyApp.Blog.Article{id: 1, ...}, %MyApp.Blog.Article{id: 2, ...}]

iex> MyApp.Permissions.accessible_by!(%MyApp.Users.User{id: 1}, :read, MyApp.Blog.Article) |> MyApp.Repo.all()
[%MyApp.Blog.Article{id: 1, ...}, %MyApp.Blog.Article{id: 2, ...}, %MyApp.Blog.Article{id: 3, ...}]

iex> MyApp.Permissions.accessible_by!(%MyApp.Users.User{id: 3, role: :admin}, :update, MyApp.Blog.Article) |> MyApp.Repo.all()
[%MyApp.Blog.Article{id: 1, ...}, %MyApp.Blog.Article{id: 2, ...}, %MyApp.Blog.Article{id: 3, ...}]

Quick authorization checks

Generate Ecto queries based on permissions for direct database querying:

# Generate query scoped to user permissions
query = MyApp.Authorization.accessible_by!(current_user, :read, Article)
articles = MyApp.Repo.all(query)

# Use with pagination and other Ecto features
query
|> where([a], a.published == true)
|> order_by([a], desc: a.inserted_at)
|> limit(10)
|> MyApp.Repo.all()

Ecosystem

Permit.Ecto is part of the modular Permit ecosystem:

Package Version Description
permitHex.pm Core authorization library
permit_ectoHex.pm Ecto integration for database queries
permit_phoenixHex.pm Phoenix Controllers & LiveView integration
permit_absintheHex.pm GraphQL API authorization via Absinthe

Installation

Using Igniter (recommended)

If you use Igniter for project setup, add permit_ecto and igniter to your deps and run:

mix permit_ecto.install

This creates your Authorization and Authorization.Permissions modules pre configured for Ecto.

Options:

Option Description Default
--authorization-module Authorization module name <MyApp>.Authorization
--permissions-module Permissions module name <MyApp>.Authorization.Permissions
--actions-module Actions module to use Permit.Actions.CrudActions
--repo Ecto repo module auto-detected

Example with options:

mix permit_ecto.install \
  --authorization-module MyApp.Auth \
  --permissions-module MyApp.Auth.Permissions \
  --repo MyApp.Repo

Manual installation

The package can be installed by adding permit_ecto to your list of dependencies in mix.exs:

def deps do
  [
    {:permit, "~> 0.3.0"},        # Core authorization library
    {:permit_ecto, "~> 0.2.4"}    # Ecto integration
  ]
end

Then create your authorization module:

defmodule MyApp.Authorization do
  use Permit.Ecto,
    permissions_module: MyApp.Authorization.Permissions,
    repo: MyApp.Repo
end

defmodule MyApp.Authorization.Permissions do
  use Permit.Ecto.Permissions, actions_module: Permit.Actions.CrudActions

  def can(_user), do: permit()
end

For a complete setup with Phoenix or Absinthe integration, add :permit_phoenix or :permit_absinthe.

Documentation

Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development setup

Just clone the repository, install dependencies normally, develop and run tests. When running Credo and Dialyzer, please use MIX_ENV=test to ensure tests and support files are validated, too.

Community

Contact

License

This project is licensed under the MIT License - see the LICENSE file for details.