ueberauth / guardian

Elixir Authentication
MIT License
3.44k stars 381 forks source link

Bitwise Plug one_of and ensure with when clause #442

Closed pedromvieira closed 6 years ago

pedromvieira commented 6 years ago

I'm trying to use Bitwise plug without success. My token works and I can use any_permissions? and all_permissions? manually. Any_permissions could check both conditions on a map? Our user has a "profile" and "permissions", like "account_user" = account (default) + read (user_actions).

TOKEN_MODEL

...
  @key Application.get_env(:phishx, :guardian_secret_key)

  use Guardian, otp_app: :phishx,
                secret_key: @key,
                permissions: %{
                  default: [:account, :group, :mgmt],
                  user_actions: [:read, :export, :write, :delete, :dashboard]
                }

  use Guardian.Permissions.Bitwise
...

CLAIM EXAMPLE

%{"aud" => "phishx", "exp" => 1517408852,
  "extra" => %{"id" => 1, "subdomain" => "aaa", "type" => "user"},
  "iat" => 1514989652, "iss" => "phishx",
  "jti" => "1585d8d7-d32b-41d1-8e23-1f6b004c0a17", "nbf" => 1514989651,
  "perms" => %{"default" => 4, "user_actions" => 31}, "sub" => "user|aaa|1",
  "typ" => "user"}

PERMISSIONS DECODE

all = Phishx.Guardian.decode_permissions(claims["perms"])
%{default: [:mgmt], user_actions: [:dashboard, :delete, :export, :read, :write]}

MANUAL VALIDATION

ensure = %{default: [:mgmt], user_actions: [:read]}
Phishx.Guardian.all_permissions?(all, ensure)
true

ONE_OF PLUG

  plug Guardian.Permissions.Bitwise,
    one_of: [
      %{default: [:account], user_actions: [:read]},
      %{default: [:group], user_actions: [:read]},
      %{default: [:mgmt], user_actions: [:read]},
    ]# when action in ~w(index)a

This plugs always halts.

pedromvieira commented 6 years ago

With a simpler permission model, same thing occurs, manually works but plug dont. Do we need to write our plug?

TOKEN_MODULE

  use Guardian, otp_app: :phishx,
                secret_key: @key,
                permissions: %{
                  default: [:account, :group, :mgmt],
                  account: [:read, :export, :write, :delete, :dashboard],
                  group: [:read, :export, :write, :delete, :dashboard],
                  mgmt: [:read, :export, :write, :delete, :dashboard],
                }

CLAIM

%{"aud" => "phishx", "exp" => 1517416805,
  "extra" => %{"id" => 1, "subdomain" => "aaa", "type" => "user"},
  "iat" => 1514997605, "iss" => "phishx",
  "jti" => "729af38d-8251-4523-af24-8e5611b990f6", "nbf" => 1514997604,
  "perms" => %{"mgmt" => 31}, "sub" => "user|aaa|1", "typ" => "user"}

DECODE

all = Phishx.Guardian.decode_permissions(claims)
%{mgmt: [:dashboard, :delete, :export, :read, :write]}
test = %{mgmt: [:write]}
%{mgmt: [:write]}
Phishx.Guardian.any_permissions?(all, test)
true
pedromvieira commented 6 years ago

Just to make it clear, with this simple custom plug works. Is there a way to do it with vanilla guardian?

PLUG EXAMPLE

defmodule Phishx.Guardian.Plug.Auth do
  @moduledoc """
  Guardian Auth Plug.
  """

  import Plug.Conn

  alias Phishx.Guardian
  alias Phishx.Guardian.ErrorHandler

  def init(options), do: options

  def call(conn, {cmd, test} = options) do
    permissions =
      conn.private.guardian_default_claims["perms"]
      |> Guardian.decode_permissions()
    result =
      case cmd do
        :one_of ->
          Guardian.any_permissions?(permissions, test)
        :ensure ->
          Guardian.all_permissions?(permissions, test)
      end
    auth(conn, result)
  end

  defp auth(conn, true = result), do: conn
  defp auth(conn, _), do: ErrorHandler.auth_error(conn, {:unauthorized, __MODULE__}, [])

end

CONTROLLER CALL

  plug Phishx.Guardian.Plug.Auth,
    {:one_of, %{ account: [:super], group: [:super], mgmt: [:read]} }
    when action in ~w(index)a

  def index(conn, _params) do
    render(conn, "index.html")
  end
doomspork commented 6 years ago

@pedromvieira this sounds like you may have discovered a bug (or a needed feature). Have you had a chance to peek at the Guardian plug? I'll try to set aside some time this week to look into this more for you.

pedromvieira commented 6 years ago

@doomspork Yes. No luck with Guardian.Plug.

hassox commented 6 years ago

@pedromvieira where does the "perms" key in your claims come from? It should be "pem". How are you creating your token?

hassox commented 6 years ago

From the docs, you should use the build_claims callback in your implementation module

    def build_claims(claims, _resource, opts) do
      claims =
        claims
        |> encode_permissions_into_claims!(Keyword.get(opts, :permissions))
      {:ok, claims}
    end

Where the :permissions key for the options is chosen by you. To use it you'd do something like:

Phishx.Guardian.encode_and_sign(user, %{}, permissions: %{default: [:mgmt], user_actions: [:dashboard]})

The same is true for sign_in