Last active
April 21, 2016 02:49
-
-
Save ashrafhasson/56f6b72fcb75504278f5aee834456f49 to your computer and use it in GitHub Desktop.
Guardian modification to allow OR behaviour when checking permissions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Guardian.Plug.EnsurePermissions do | |
@moduledoc """ | |
Use this plug to ensure that there are the | |
correct permissions set in the claims found on the connection. | |
### Example | |
alias Guardian.Plug.EnsurePermissions | |
# read and write permissions for the admin set | |
plug EnsurePermissions, admin: [:read, :write], handler: SomeMod, | |
# read AND write permissions for the admin set | |
# AND :profile for the default set | |
plug EnsurePermissions, admin: [:read, :write], | |
default: [:profile], | |
handler: SomeMod | |
# read AND write permissions for the admin set | |
# OR :profile for the default set | |
plug EnsurePermissions, one_of: %{ admin: [:read, :write], | |
default: [:profile] }, | |
handler: SomeMod | |
# admin :read AND :write for the claims located in the :secret location | |
plug EnsurePermissions, key: :secret, | |
admin: [:read, :write], | |
handler:SomeMod | |
On failure will be handed the connection with the conn, | |
and params where reason: `:forbidden` | |
The handler will be called on failure. | |
The `:unauthorized` function will be called when a failure is detected. | |
""" | |
require Logger | |
import Plug.Conn | |
def init(opts) do | |
opts = Enum.into(opts, %{}) | |
on_failure = Map.get(opts, :on_failure) | |
key = Map.get(opts, :key, :default) | |
handler = Map.get(opts, :handler) | |
one_of = Map.get(opts, :one_of) | |
perms = Map.drop(opts, [:handler, :on_failure, :key, :one_of]) | |
if handler do | |
handler = {handler, :unauthorized} | |
else | |
handler = case on_failure do | |
{mod, f} -> | |
Logger.log(:warn, "on_failure is deprecated. Use handler") | |
{mod, f} | |
_ -> raise "Requires a handler module to be passed" | |
end | |
end | |
%{ | |
handler: handler, | |
key: key, | |
perm_keys: Map.keys(perms), | |
one_of: one_of, | |
perms: perms, | |
} | |
end | |
@doc false | |
def call(conn, opts) do | |
key = Map.get(opts, :key) | |
case Guardian.Plug.claims(conn, key) do | |
{:ok, claims} -> | |
perms = Map.get(opts, :perms, %{}) | |
result = | |
case Map.get(opts, :one_of) do | |
nil -> check_all_perm_sets(Map.get(opts, :perm_keys), claims, perms) | |
one_of -> check_any_perm_set(Map.keys(one_of), claims, one_of) | |
end | |
if result, do: conn, else: handle_error(conn, opts) | |
{:error, _} -> handle_error(conn, opts) | |
end | |
end | |
defp check_any_perm_set(perm_keys, claims, perms) do | |
Enum.any?(perm_keys, fn(perm_key) -> | |
found_perms = Guardian.Permissions.from_claims(claims, perm_key) | |
check_permissions(found_perms, Map.get(perms, perm_key), perm_key) | |
end) | |
end | |
defp check_all_perm_sets(perm_keys, claims, perms) do | |
Enum.all?(perm_keys, fn(perm_key) -> | |
found_perms = Guardian.Permissions.from_claims(claims, perm_key) | |
check_permissions(found_perms, Map.get(perms, perm_key), perm_key) | |
end) | |
end | |
defp check_permissions(found_perms, perms, perm_key) do | |
Guardian.Permissions.all?( | |
found_perms, | |
perms, | |
perm_key | |
) | |
end | |
defp handle_error(conn, opts) do | |
the_connection = conn |> assign(:guardian_failure, :forbidden) |> halt | |
{mod, meth} = Map.get(opts, :handler) | |
apply( | |
mod, | |
meth, | |
[ | |
the_connection, | |
Map.merge(the_connection.params, %{reason: :forbidden}) | |
] | |
) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Guardian.Plug.EnsurePermissionTest do | |
@moduledoc false | |
use ExUnit.Case, async: true | |
use Plug.Test | |
import Guardian.TestHelper | |
alias Guardian.Plug.EnsurePermissions | |
defmodule TestHandler do | |
@moduledoc false | |
def unauthorized(conn, _) do | |
conn | |
|> Plug.Conn.assign(:guardian_spec, :forbidden) | |
|> Plug.Conn.send_resp(401, "Unauthorized") | |
end | |
end | |
setup do | |
conn = conn(:get, "/foo") | |
{:ok, %{conn: conn}} | |
end | |
test "doesnt call unauthorized when permissions are present", %{conn: conn} do | |
pems = Guardian.Permissions.to_value([:read, :write]) | |
claims = %{"pem" => %{"default" => pems}} | |
expected_conn = | |
conn | |
|> Guardian.Plug.set_claims({:ok, claims}) | |
|> Plug.Conn.fetch_query_params | |
|> run_plug(EnsurePermissions, handler: TestHandler, | |
default: [:read, :write]) | |
refute unauthorized?(expected_conn) | |
end | |
test "is invalid when missing a requested permission", %{conn: conn} do | |
pems = Guardian.Permissions.to_value([:read]) | |
claims = %{"pem" => %{"default" => pems}} | |
expected_conn = | |
conn | |
|> Guardian.Plug.set_claims({:ok, claims}) | |
|> Plug.Conn.fetch_query_params | |
|> run_plug(EnsurePermissions, handler: TestHandler, | |
default: [:read, :write]) | |
assert unauthorized?(expected_conn) | |
end | |
test "is invalid when claims don't include the pem key", %{conn: conn} do | |
pems = Guardian.Permissions.to_value([:other_read], :other) | |
claims = %{"pem" => %{"default" => pems}} | |
expected_conn = | |
conn | |
|> Guardian.Plug.set_claims({:ok, claims}) | |
|> Plug.Conn.fetch_query_params | |
|> run_plug(EnsurePermissions, handler: TestHandler, | |
default: [:read, :write]) | |
assert unauthorized?(expected_conn) | |
end | |
test "is invalid when all permissions are not present", %{conn: conn} do | |
pems = Guardian.Permissions.to_value( | |
[:read, :write, :update, :delete], | |
:default | |
) | |
other_pems = Guardian.Permissions.to_value([:other_write], :other) | |
claims = %{"pem" => %{"default" => pems, "other" => other_pems}} | |
expected_conn = | |
conn | |
|> Guardian.Plug.set_claims({:ok, claims}) | |
|> Plug.Conn.fetch_query_params | |
|> run_plug(EnsurePermissions, handler: TestHandler, | |
default: [:read, :write], other: [:other_read]) | |
assert unauthorized?(expected_conn) | |
end | |
test "is valid when all permissions are present", %{conn: conn} do | |
pems = Guardian.Permissions.to_value( | |
[:read, :write, :update, :delete], | |
:default | |
) | |
other_pems = Guardian.Permissions.to_value( | |
[:other_read, :other_write], | |
:other | |
) | |
claims = %{"pem" => %{"default" => pems, "other" => other_pems}} | |
expected_conn = | |
conn | |
|> Guardian.Plug.set_claims({:ok, claims}) | |
|> Plug.Conn.fetch_query_params | |
|> run_plug(EnsurePermissions, handler: TestHandler, | |
default: [:read, :write], other: [:other_read]) | |
refute unauthorized?(expected_conn) | |
end | |
test "is invalid when non of the one_of permissions set is present", %{conn: conn} do | |
pems = Guardian.Permissions.to_value( | |
[:read, :write, :update, :delete], | |
:default | |
) | |
other_pems = Guardian.Permissions.to_value( | |
[:other_read, :other_write], | |
:other | |
) | |
claims = %{"pem" => %{"admin" => pems, "special" => other_pems}} | |
expected_conn = | |
conn | |
|> Guardian.Plug.set_claims({:ok, claims}) | |
|> Plug.Conn.fetch_query_params | |
|> run_plug(EnsurePermissions, handler: TestHandler, | |
one_of: %{other: [:other_read], default: [:read, :write]}) | |
assert unauthorized?(expected_conn) | |
end | |
test "is valid when at least one_of the permissions set is present", %{conn: conn} do | |
pems = Guardian.Permissions.to_value( | |
[:read, :write, :update, :delete], | |
:default | |
) | |
other_pems = Guardian.Permissions.to_value( | |
[:other_read, :other_write], | |
:other | |
) | |
claims = %{"pem" => %{"special" => other_pems, "default" => pems}} | |
expected_conn = | |
conn | |
|> Guardian.Plug.set_claims({:ok, claims}) | |
|> Plug.Conn.fetch_query_params | |
|> run_plug(EnsurePermissions, handler: TestHandler, | |
one_of: %{other: [:other_read], default: [:read, :write]}) | |
refute unauthorized?(expected_conn) | |
end | |
test "halts the connection", %{conn: conn} do | |
pems = Guardian.Permissions.to_value( | |
[:read, :write, :update, :delete], | |
:default | |
) | |
other_pems = Guardian.Permissions.to_value([:other_write], :other) | |
claims = %{"pem" => %{"default" => pems, "other" => other_pems}} | |
expected_conn = | |
conn | |
|> Guardian.Plug.set_claims({:ok, claims}) | |
|> Plug.Conn.fetch_query_params | |
|> run_plug(EnsurePermissions, handler: TestHandler, | |
default: [:read, :write], other: [:other_read]) | |
assert expected_conn.halted | |
end | |
def unauthorized?(conn) do | |
conn.assigns[:guardian_spec] == :forbidden | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment