Current section

Files

Jump to
cassette_plug lib cassette controller.ex
Raw

lib/cassette/controller.ex

defmodule Cassette.Controller do
@moduledoc """
A helper module to quickly validate roles and get the current user
To use in your controller, add as a plug restricting the actions:
```elixir
defmodule MyApp.MyController do
use MyApp.Web, :controller
use Cassette.Controller
plug :require_role!, "ADMIN" when action in [:edit, :update, :new, :create]
def update(conn, %{"id" => id}) do
something = Repo.get!(Something, id)
changeset = Something.changeset(something)
render(conn, "edit.html", something: something, changeset: changeset)
end
end
```
You can also customize how a forbidden situation is handled:
```elixir
defmodule MyApp.MyController do
use MyApp.Web, :controller
use Cassette.Controller, on_forbidden: fn(conn) ->
redirect(conn, to: "/403.html")
end
plug :require_role!("VIEWER")
def index(conn, _params) do
render(conn, "index.html")
end
end
```
You can use one of your controller functions as well:
```elixir
defmodule MyApp.MyController do
use MyApp.Web, :controller
use Cassette.Controller, on_forbidden: &MyApp.MyController.forbidden/1
plug :require_role!("VIEWER")
def index(conn, _params) do
render(conn, "index.html")
end
end
```
Or since `require_role!/2` halts the connection you may do the following for simple actions.
```elixir
defmodule MyApp.MyController do
use MyApp.Web, :controller
use Cassette.Controller
def index(conn, _params) do
conn
|> require_role!("VIEWER")
|> render("index.html")
end
end
```
You can also write your own plugs using the "softer" `has_role?/2` or `has_raw_role?/2`:
```elixir
defmodule MyApp.MyController do
use MyApp.web, :controller
use Cassette.Controller
plug :check_authorization
def index(conn, _params) do
render(conn, "index.html")
end
def check_authorization(conn, _params) do
if has_role?(conn, "viewer") do
conn
else
conn |> render("forbidden.html") |> halt
end
end
end
```
"""
alias Cassette.User
alias Plug.Conn
defmacro __using__(opts \\ []) do
quote do
import Conn
defp __config__, do: (unquote(opts[:cassette]) || Cassette).config
defp __forbidden_callback__, do: unquote(opts[:on_forbidden]) || fn(conn) ->
conn |> resp(403, "Forbidden") |> halt
end
@spec current_user(Conn.t) :: User.t | nil
@doc """
Fetches the current user from the session.
Returns `nil` if has no user
"""
def current_user(conn) do
conn |> fetch_session |> get_session("cas_user")
end
@doc """
Tests if the user has roles.
When roles is a list, tests if `current_user` has *any* of the roles.
When roles ia function, it will receive the `Plug.Conn.t` and must return role to validate.
Returns `false` if there is no user in the session.
"""
@spec has_role?(Conn.t, [String.t]) :: boolean
def has_role?(conn, roles) when is_list(roles) do
user = current_user(conn)
Enum.any?(roles, &User.has_role?(user, __config__(), &1))
end
@spec has_role?(Conn.t, ((Conn.t) -> String.t)) :: boolean
def has_role?(conn, role_fn) when is_function(role_fn, 1) do
has_role?(conn, role_fn.(conn))
end
@spec has_role?(Conn.t, String.t) :: boolean
def has_role?(conn, role) do
has_role?(conn, [role])
end
@doc """
Tests if the user has (raw) roles.
Arguments follow the same logic as has_role?/2 but they are forwarded to `Cassette.User.has_raw_role?/2`
"""
@spec has_raw_role?(Conn.t, [String.t]) :: boolean
def has_raw_role?(conn, roles) when is_list(roles) do
user = current_user(conn)
Enum.any?(roles, &User.has_raw_role?(user, &1))
end
@spec has_raw_role?(Conn.t, ((Conn.t) -> String.t)) :: boolean
def has_raw_role?(conn, role_fn) when is_function(role_fn, 1) do
has_raw_role?(conn, role_fn.(conn))
end
@spec has_raw_role?(Conn.t, String.t) :: boolean
def has_raw_role?(conn, role) do
has_raw_role?(conn, [role])
end
@spec require_role!(Conn.t,
String.t | [String.t] | ((Conn.t) -> String.t)) :: Conn.t
@doc """
Tests if the user has the role. Where role can be any of the terms accepted by any implementation of `has_role?/2`.
This will halt the connection and set the status to forbidden if authorization fails.
"""
def require_role!(conn, roles) do
if has_role?(conn, roles) do
conn
else
__forbidden_callback__().(conn)
end
end
@doc """
Tests if the user has the (raw) role. Where role can be any of the terms accepted by any implementation of `has_raw_role?/2`.
This will halt the connection and set the status to forbidden if authorization fails.
"""
@spec require_raw_role!(Conn.t,
String.t | [String.t] | ((Conn.t) -> String.t)) :: Conn.t
def require_raw_role!(conn, roles) do
if has_raw_role?(conn, roles) do
conn
else
__forbidden_callback__().(conn)
end
end
end
end
end