woylie

woylie

LetMe - authorization DSL

I just released LetMe 1.0, marking the first stable release.

LetMe (hex)
LetMe (Github)

LetMe is an authorization DSL with introspection capabilities.

Why?

There are plenty of authorization libraries, but when an application grows and with it the number of authorization rules, it becomes harder to see at a glance what is allowed under which circumstances. I wanted a solution that a) provides me an easily readable format to define authorization rules, and b) allows me to list and filter those rules, so that I can dynamically generate documentation pages and help texts for permission forms.

Example

A simple policy module would look like this:

defmodule MyApp.Policy do
  use LetMe.Policy

  object :article do
    action :create do
      desc "allows a user to create a new article"
      allow role: :editor
      allow role: :writer
    end

    action :read do
      desc "allows a user to read an article and to see a list of articles"
      allow true
      deny :banned
    end

    action :update do
      allow role: :editor
      allow [:own_resource, role: :writer]
    end

    action :delete do
      allow role: :editor
    end
  end
end

The allow and deny conditions only reference check functions which you have to define on your own. This means the DSL is much simpler than a full-fledged policy language. In the end it’s just a means of combining custom checks.

defmodule MyApp.Policy.Checks do
  alias MyApp.Accounts.User

  @doc """
  Returns `true` if the `banned` flag is set on the user.
  """
  def banned(%User{banned: banned}, _, _), do: banned

  @doc """
  Checks whether the user ID of the object matches the ID of the current user.

  Assumes that the object has a `:user_id` field.
  """
  def own_resource(%User{id: id}, %{user_id: id}, _opts) when is_binary(id), do: true
  def own_resource(_, _, _), do: false

  @doc """
  Checks whether the user role matches the role passed as an option.

  ## Usage

      allow role: :editor

  or

      allow {:role, :editor}
  """
  def role(%User{role: role}, _object, role), do: true
  def role(_, _, _), do: false
end

LetMe compiles the defined rules into authorization and introspection functions.

iex> MyApp.Policy.authorize?(:article_read, current_user)
true

iex> MyApp.Policy.get_rule(:article_create)
%LetMe.Rule{
  action: :create,
  allow: [
    [role: :admin],
    [role: :writer]
  ],
  name: :article_create,
  object: :article,
  # ...
}

You can also get a list of rules and apply filters on them. There are some more features including a Schema behaviour for query scoping and field redactions.

Most Liked

woylie

woylie

I just released LetMe 2.0.0!

What’s New?

Internal Policy Rule Representation

The internal representation of the policy rules has been changed to a tree-like expression format using AllOf, AnyOf, Check, Literal, and Not structs. An expression might look like this:

%AllOf{
  children: [
    %Check{name: :role, arg: :admin},
    %Check{name: :two_fa}
  ]
}

Or this:

%AllOf{
  children: [
    %Not{expression: %Check{name: :suspended}},
    %Check{name: :two_fa},
    %AnyOf{
      children: [
        %Check{name: :user_type, arg: :admin},
        %Check{name: :user_type, arg: :client}
      ]
    }
  ]
}

Or just:

%Literal{passed?: true}

These expressions are generated from the policy rules you define with the macro DSL. At compile time, a few basic normalization steps are applied, for example to remove unnecessary nesting or to factorize common checks in AnyOf expressions.

Lazy Evaluation

All expressions are now evaluated lazily when an authorization check is performed.

Checks With Custom Return Values

Previously, all check functions had to return a boolean. Now, the return type is boolean | :ok | :error | {:ok, term} | {:error, term}.

Detailed Authorization Errors

By default, c:LetMe.Policy.authorize/4 still returns {:error, :unauthorized} if an authorization check fails. But there is a new error option you can pass to use LetMe to set a default value and to c:LetMe.Policy.authorize/4 and c:LetMe.Policy.authorize!/4 to override the default. The available values are:

:detailed

If you set the value to :detailed, authorize/4 returns an UnauthorizedError struct with the parts of the expression that were evaluated until a decision was made. You can find the exact return value of the check function in the result field of the Check struct. The UnauthorizedErrorexception raised by authorize!/4 also contains the expression with this option value.

defmodule MyApp.Policy
  use LetMe.Policy, error: :detailed
  
  # ...
end

iex> MyApp.Policy.authorize(:article_update, user_2, article)
{
  :error,
  %LetMe.UnauthorizedError{
    expression: %LetMe.AllOf{
      children: [
        %LetMe.Check{
          name: :role,
          arg: :admin,
          result: true,
          passed?: true
        },
        %LetMe.Check{
          name: :aal2,
          result: {:error, :aal1},
          passed?: false
        }
      ],
      passed?: false
    },
    message: "unauthorized"
  }
}

:simple

If you set the value to :simple, authorize/4 returns an UnauthorizedError struct without the expression. Likewise, authorize!/4 raises an UnauthorizedError exception without it.

defmodule MyApp.Policy
  use LetMe.Policy, error: :simple
  
  # ...
end

iex> MyApp.Policy.authorize(:article_update, user_2, article)
{:error, %LetMe.UnauthorizedError{
  message: "unauthorized",
  expression: nil
}}

any

Any other value will be used directly in the error tuple:

defmodule MyApp.Policy
  use LetMe.Policy, error: :forbidden
  
  # ...
end

iex> MyApp.Policy.authorize(:article_update, user_2, article)
{:error, :forbidden}

Upgrade from v1

The policy DSL and authorization API remain unchanged. The only breaking changes are:

  • The removal of the allow and deny fields of the LetMe.Rule struct in favor of the new expression field.
  • The removal of the error_reason and error_message options from use LetMe.Policy in favor of the new error option.
  • The removal of the allow and deny filter options on LetMe.filter_rules/2 and
    c:LetMe.Policy.list_rules/1 in favor of a single check option.

For more details, refer to the changelog.

woylie

woylie

LetMe 1.1.0 was released, which adds a metadata macro and a metadata field to the LetMe.Rule struct, which allows you to extend the functionality of the library. Thanks to Stephen for the contribution.

derpycoder

derpycoder

Awesome Library. Looks succinct. I loved the introspection aspect and redaction of information!

I was curious about how the library will handle Role explosion and some of the scenarios mentioned below?


Say a customer comes to bar, instead of having a role depicting that they can consume alcohol, they claim that they can_drink.

We can take the claim and restrict it with policy saying, age must be drinking age, based on attribute country.

And maybe add a scope, saying that the customer can only access the drinks available on counter.

Later, bouncer might grant or revoke access on the fly!

Maybe the customer gets recognised as a VIP, so now he should have access to drinks from cellar! Perhaps they fancy a private lounge. (Instead of creating another role for people who can enter the lounge or drink special wine, they can be assigned claims like: can_enter_private_lounge)


I have a verbose way achieving the above scenarios:


P.S. I am excited about this. I won’t have to write much code. I am just trying to piece together how I can make use of the library. :sweat_smile:

Where Next?

Popular in Announcing Top

wmnnd
Hi there, for my project DBLSQD, I needed a file storage solution that is a bit more flexible than Arc. Because I thought others might f...
New
mplatts
With HEEX released we decided to start a components library using Tailwind CSS - check it out here: Petal Components. We also have a boi...
New
gabrielpoca
Hello everyone! I want to share with you something that I’m really proud of: https://stillstatic.io/ Still is a static site builder for...
New
Crowdhailer
Experimenting with this code. OK.try do user <- fetch_user(1) cart <- fetch_cart(1) order = checkout(cart, user) save_or...
New
tmbb
I’ve been working on two packages (not on hex.pm yet) to build admin interfaces for phoenix apps: bureaucrat - which contains a bunch ...
New
woutdp
Hi! I wanted to introduce my latest project LiveSvelte. It allows you to render Svelte inside LiveView with end-to-end reactivity. It’s ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35953 110
New
kelvinst
Hey everyone! Well, we made this lib a while ago and now we decided to finally go out and public with it! It’s a tool for creating and m...
New
achempion
Hi, I would like to tell about my initiative to further maintain and develop Waffle project which is the fork of Arc library. The progre...
New
pkrawat1
Hey guyz We at @aviabird are working on a payment library in elixir/phoenix. We are targeting March 2018 to add 56 Gateways to it. Have...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I fore...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35953 110
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31107 143
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47849 226
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

We're in Beta

About us Mission Statement