Skip to content
This repository was archived by the owner on Aug 11, 2026. It is now read-only.

Repository files navigation

🛡 Terminator 🛡

Coverage StatusBuild StatusVersion

Terminator is toolkit for granular ability management for performers. It allows you to define granular abilities such as:

  • Performer -> Ability
  • Performer -> [Ability, Ability, ...]
  • Role -> [Ability, Ability, ...]
  • Performer -> Role -> [Ability, Ability, Ability]
  • Performer -> [Role -> [Ability], Role -> [Ability, ...]]
  • Performer -> AnyEntity -> [Ability, ...]

It tries to mimic https://en.wikipedia.org/wiki/Attribute-based_access_control and allow to define any policy which is needed.

Here is a small example:

defmoduleSample.PostuseTerminatordefdelete_post(id)doperformer=Sample.Repo.get(Terminator.Performer,1)load_and_authorize_performer(performer)post=%Post{id: 1}permissionsdohas_role(:admin)# orhas_role(:editor)# orhas_ability(:delete_posts)# orhas_ability(:delete,post)# Entity related abilitiescalculated(fnperformer->performer.email_confirmed?end)endas_authorizeddoSample.Repo.get(Sample.Post,id)|>Sample.repo.delete()end# Notice that you can use both macros or functionscaseis_authorized?do:ok->Sample.Repo.get(Sample.Post,id)|>Sample.repo.delete(){:error,message}->"Raise error"_->"Raise error"endend

Features

  • Performer -> [Ability] permission schema
  • Role -> [Ability] permission schema
  • Performer -> [Role] -> [Ability] permission schema
  • Performer -> Object -> [Ability] permission schema
  • Computed permission in runtime
  • Easily readable DSL
  • ueberauth integration
  • absinthe middleware
  • Session plug to get current_user

Installation

defdepsdo[{:terminator,"~> 0.5.2"}]end
# In your config/config.exs fileconfig:terminator,Terminator.Repo,username: "postgres",password: "postgres",database: "terminator_dev",hostname: "localhost"
iex>mixterminator.setup

Usage with ecto

Terminator is originally designed to be used with Ecto. Usually you will want to have your own table for Accounts/Users living in your application. To do so you can link performer with belongs_to association within your schema.

# In your migrations add performer_id fielddefmoduleSample.Migrations.CreateUsersTabledouseEcto.Migrationdefchangedocreatetable(:users)doadd:username,:stringadd:performer_id,references(Terminator.Performer.table())timestamps()endcreateunique_index(:users,[:username])endend

This will allow you link any internal entity with 1-1 association to performers. Please note that you need to create performer on each user creation (e.g with Terminator.Performer.changeset/2) and call put_assoc inside your changeset

# In schema defintiondefmoduleSample.UserdouseEcto.Schemaschema"users"dofield:username,:Stringbelongs_to:performer,Terminator.Performertimestamps()endend
# In your modeldefmoduleSample.PostuseTerminatordefdelete_post(id)douser=Sample.Repo.get(Sample.User,1)load_and_authorize_performer(user)# Function allows multiple signatues of performer it can# be either:# * %Terminator.Performer{}# * %AnyStruct{performer: %Terminator.Performer{}}# * %AnyStruct{performer_id: id} (this will perform database preload)permissionsdohas_role(:admin)# orhas_role(:editor)# orhas_ability(:delete_posts)# orendas_authorizeddoSample.Repo.get(Sample.Post,id)|>Sample.repo.delete()end# Notice that you can use both macros or functionscaseis_authorized?do:ok->Sample.Repo.get(Sample.Post,id)|>Sample.repo.delete(){:error,message}->"Raise error"_->"Raise error"endend

Terminator tries to infer the performer, so it is easy to pass any struct (could be for example User in your application) which has set up belongs_to association for performer. If the performer was already preloaded from database Terminator will take it as loaded performer. If you didn't do preload and just loaded User -> Repo.get(User, 1) Terminator will fetch the performer on each authorization try.

Calculated permissions

Often you will come to case when static permissions are not enough. For example allow only users who confirmed their email address.

defmoduleSample.Postdodefcreate()douser=Sample.Repo.get(Sample.User,1)load_and_authorize_performer(user)permissionsdocalculated(fnperformer->doperformer.email_confirmed?end)endendend

We can also use DSL form of calculated keyword

defmoduleSample.Postdodefcreate()douser=Sample.Repo.get(Sample.User,1)load_and_authorize_performer(user)permissionsdocalculated(:confirmed_email)endenddefconfirmed_email(performer)doperformer.email_confirmed?endend

Composing calculations

When we need to performer calculation based on external data we can invoke bindings to calculated/2

defmoduleSample.Postdodefcreate()douser=Sample.Repo.get(Sample.User,1)post=%Post{owner_id: 1}load_and_authorize_performer(user)permissionsdocalculated(:confirmed_email)calculated(:is_owner,[post])endenddefconfirmed_email(performer)doperformer.email_confirmed?enddefis_owner(performer,[post])doperformer.id==post.owner_idendend

To perform exclusive abilities such as when User is owner of post AND is in editor role we can do so as in following example

defmoduleSample.Postdodefcreate()douser=Sample.Repo.get(Sample.User,1)post=%Post{owner_id: 1}load_and_authorize_performer(user)permissionsdohas_role(:editor)endas_authorizeddocaseis_owner(performer,post)do:ok->...{:error,message}->...endendenddefis_owner(performer,post)doload_and_authorize_performer(performer)permissionsdocalculated(fnp,[post]->p.id==post.owner_idend)endis_authorized?endend

We can simplify example in this case by excluding DSL for permissions

defmoduleSample.Postdodefcreate()douser=Sample.Repo.get(Sample.User,1)post=%Post{owner_id: 1}# We can also use has_ability?/2ifhas_role?(user,:admin)andis_owner(user,post)do...endenddefis_owner(performer,post)doperformer.id==post.owner_idendend

Entity related abilities

Terminator allows you to grant abilities on any particular struct. Struct needs to have signature of %{__struct__: entity_name, id: entity_id} to infer correct relations. Lets assume that we want to grant :delete ability on particular Post for our performer:

iex>{:ok,performer}=%Terminator.Performer{}|>Terminator.Repo.insert()iex>post=%Post{id: 1}iex>ability=%Ability{identifier: "delete"}iex>Terminator.Performer.grant(performer,:delete,post)iex>Terminator.has_ability?(performer,:delete,post)true
defmoduleSample.Postdodefdelete()douser=Sample.Repo.get(Sample.User,1)post=%Post{id: 1}load_and_authorize_performer(user)permissionsdohas_ability(:delete,post)endas_authorizeddo:okendendend

Granting abilities

Let's assume we want to create new Role - admin which is able to delete accounts inside our system. We want to have special Performer who is given this role but also he is able to have Ability for banning users.

  1. Create performer
iex>{:ok,performer}=%Terminator.Performer{}|>Terminator.Repo.insert()
  1. Create some abilities
iex>{:ok,ability_delete}=Terminator.Ability.build("delete_accounts","Delete accounts of users")|>Terminator.Repo.insert()iex>{:ok,ability_ban}=Terminator.Ability.build("ban_accounts","Ban users")|>Terminator.Repo.insert()
  1. Create role
iex>{:ok,role}=Terminator.Role.build("admin",[],"Site administrator")|>Terminator.Repo.insert()
  1. Grant abilities to a role
iex>Terminator.Role.grant(role,ability_delete)
  1. Grant role to a performer
iex>Terminator.Performer.grant(performer,role)
  1. Grant abilities to a performer
iex>Terminator.Performer.grant(performer,ability_ban)
iex>performer|>Terminator.Repo.preload([:roles,:abilities])%Terminator.Performer{abilities: [%Terminator.Ability{identifier: "ban_accounts"}]roles: [%Terminator.Role{identifier: "admin"abilities: ["delete_accounts"]}]}

Revoking abilities

Same as we can grant any abilities to models we can also revoke them.

iex>Terminator.Performer.revoke(performer,role)iex>performer|>Terminator.Repo.preload([:roles,:abilities])%Terminator.Performer{abilities: [%Terminator.Ability{identifier: "ban_accounts"}]roles: []}iex>Terminator.Performer.revoke(performer,ability_ban)iex>performer|>Terminator.Repo.preload([:roles,:abilities])%Terminator.Performer{abilities: []roles: []}

License

MIT © Milos Mosovsky

About

🛡 Modern elixir ACL/ABAC library for managing granular user abilities and permissions

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages