A simple authorization package that uses protocols.
If available in Hex, the package can be installed
by adding bosun
to your list of dependencies in mix.exs
:
def deps do
[
{:bosun, "~> 1.0.1"}
]
end
To use Bosun all you need to do is define your policy by implementing the Bosun.Policy
protocol for a struct. Example below:
defmodule User do
defstruct role: :guest, username: "", blocked: false
end
defmodule Post do
defstruct title: "", body: ""
end
defimpl Bosun.Policy, for: Post do
alias Bosun.Context
# Could also be defined on the fallback Any implementation
def resource_id(resource, _action, _subject, _context, _options) do
resource.id
end
# Could also be defined on the fallback Any implementation
def subject_id(_resource, _action, subject, _context, _options) do
subject.id
end
def permitted?(_resource, _action, %User{role: :admin}, context, _options) do
Context.permit(context, "Admins are allowed to do anything")
end
def permitted?(%Post{title: "A Guest Post"}, _action, %User{role: :guest}, context, _options) do
Context.permit(context, "Guests are allowed to do stuff to guest posts")
end
def permitted?(_resource, :read, %User{role: :guest}, context, _options) do
Context.permit(context, "Guests are allowed to read posts")
end
def permitted?(_resource, :comment, %User{role: :guest} = user, context, options) do
if options[:super_fan] do
Context.permit(context, "Super fans are permitted")
else
Context.deny(context, "Guests that are not super fans are not permitted")
end
|> blocked_commenter?(user)
end
def blocked_commenter?(%Context{permitted: true} = context, %User{blocked: true}) do
Context.deny(context, "User blocked from commenting")
end
def blocked_commenter?(context, _user) do
context
end
def permitted?(_resource, :update, %User{role: :guest}, context, _options) do
Context.deny(context, "User is a guest")
end
def permitted?(_resource, _action, _user, context, _options) do
context
end
end
After defining your policy as seen above anywhere in your codebase you can call the Bosun.permit?/3
or Bosun.permit?/4
functions.
if Bosun.permit?(%User{role: :guest}, :comment, %Post{title: "Another Guest Post"}, super_fan: true) do
do_something()
else
Logger.error("Boom!!!?!")
end
case Bosun.permit(%User{role: :guest}, :comment, %Post{title: "Another Guest Post"}, super_fan: true) do
{:ok, _} -> do_something()
{:error, context} -> Logger.error(context.reason)
end
try do
Bosun.permit!(%User{role: :guest}, :comment, %Post{title: "Another Guest Post"}, super_fan: true)
do_something()
rescue
e in Impermissible -> Logger.error(e.message)
end
You can define an Any
implementation as a fallback policy
defimpl Bosun.Policy, for: Any do
alias Bosun.Context
def permitted?(_resource, _action, _subject, context, _options) do
Context.deny(context, "Impermissible")
end
end
Here is the default library config.
config :bosun,
debug: true
Integration with EventRelay
One of the unique features of Bosun is it keeps a log of why something is permitted or denied. Bosun will also send this log to EventRelay for storage and searching. Using this integration gives you an audit log for free that can be used for compliance and debugging purposes.
The event_relay_token
can be obtained from EventRelay's API Key detail page for a producer API key.
config :bosun, :send_to_event_relay, true
config :bosun, :event_relay_host, "localhost"
config :bosun, :event_relay_port, "50051"
config :bosun, :event_relay_token, "..."
- integrate with EventRelay
- improve documentation
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/bosun.