Skip to content

Instantly share code, notes, and snippets.

@cjbell
Last active March 9, 2018 23:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cjbell/acb2b4fa75d9add6f1ab04937d5cf159 to your computer and use it in GitHub Desktop.
Save cjbell/acb2b4fa75d9add6f1ab04937d5cf159 to your computer and use it in GitHub Desktop.
Authorization Plugs
# Usage in a controller
plug :load_and_authorize, [
params: "account_id",
loader: &Accounts.get_account/1,
policy: AccountPolicy,
assign: :account
] when action in [:index]
# The plugs themselves
@doc """
A plug to be used to load and authorize a specific resource.
The authorization policy will be passed the resource fetched (if it exists).
If the resource is not found, a 404 error will be returned.
Accepts a keyword list of options:
* `loader`: A method to be called to load the desired resource
* `params`: A single or list of params to pass to the loader
* `policy`: The policy module to invoke (will always accept `current_user` and `resource`)
* `assign`: An atom of a key to assign the resource onto
* `cmd`: (Optional) a command to pass to the policy
"""
def load_and_authorize(conn, options) do
conn
|> resolve_required_params(options)
|> load_resource(options)
|> authorize_resource(options)
end
defp load_resource(conn, options) do
loader = Keyword.get(options, :loader)
assign = Keyword.get(options, :assign)
func = :erlang.fun_info(loader)
params = conn.assigns[:authorization_params]
# Only apply the relevant number of params based on the arity of the
# specified function call
apply(func[:module], func[:name], Enum.take(params, func[:arity]))
|> case do
nil -> render_error(conn, :not_found)
item -> conn |> assign(assign, item)
end
end
defp authorize_resource(%{halted: true} = conn, _), do: conn
defp authorize_resource(conn, options) do
Keyword.get(options, :assign)
|> case do
nil -> conn
assign -> handle_authorize_assign(conn, assign, options)
end
end
defp handle_authorize_assign(conn, assign, options) do
policy = Keyword.get(options, :policy)
resource = conn.assigns[assign]
user = Guardian.Plug.current_resource(conn)
cmd = Keyword.get(options, :cmd, :access)
policy.allow?(user, resource, cmd)
|> case do
true -> conn
false -> render_error(conn, :forbidden)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment