Skip to content

Commit

Permalink
plug to ensure load_resource actually worked (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericsullivan authored and doomspork committed Dec 22, 2016
1 parent 52b7e1b commit 824e2a4
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 3 deletions.
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ defmodule MySecretKey do
|> JOSE.JWK.from_binary
Redix.stop(conn)
rsa_jwk
end
end
end

config :guardian, Guardian,
Expand All @@ -114,7 +114,7 @@ config :guardian, Guardian,
defmodule MySecretKey do
def fetch do
System.get_env("SECRET_KEY_PASSPHRASE") |> JOSE.JWK.from_file(System.get_env("SECRET_KEY_FILE"))
end
end
end

config :guardian, Guardian,
Expand Down Expand Up @@ -185,7 +185,21 @@ resource from the Serializer and makes it available via

Note that this does _not ensure_ a resource will be loaded.
If there is no available resource (because it could not be found)
`current_resource` will return nil.
`current_resource` will return nil. You can ensure it's loaded with
`Guardian.Plug.EnsureResource`

### Guardian.Plug.EnsureResource

Looks for a previously loaded resource. If not found, the `:no_resource`
function is called on your handler.

```elixir
defmodule MyApp.MyController do
use MyApp.Web, :controller

plug Guardian.Plug.EnsureResource, handler: MyApp.MyAuthErrorHandler
end
```

### Guardian.Plug.EnsurePermissions

Expand Down
63 changes: 63 additions & 0 deletions lib/guardian/plug/ensure_resource.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
defmodule Guardian.Plug.EnsureResource do
@moduledoc """
This plug ensures that the current_resource has been set, usually in
Guardian.Plug.LoadResource.
If one is not found, the `no_resource/2` function is invoked with the
`Plug.Conn.t` object and its params.
## Example
# Will call the no_resource/2 function on your handler
plug Guardian.Plug.EnsureAuthenticated, handler: SomeModule
# look in the :secret location.
plug Guardian.Plug.EnsureAuthenticated, handler: SomeModule, key: :secret
If the handler option is not passed, `Guardian.Plug.ErrorHandler` will provide
the default behavior.
"""
require Logger
import Plug.Conn

@doc false
def init(opts) do
opts = Enum.into(opts, %{})
handler = build_handler_tuple(opts)

%{
handler: handler,
key: Map.get(opts, :key, :default)
}
end

@doc false
def call(conn, opts) do
key = Map.get(opts, :key, :default)

case Guardian.Plug.current_resource(conn, key) do
nil -> handle_error(conn, opts)
_ -> conn
end
end

defp handle_error(%Plug.Conn{params: params} = conn, opts) do
conn = conn |> assign(:guardian_failure, :no_resource) |> halt
params = Map.merge(params, %{reason: :no_resource})

{mod, meth} = Map.get(opts, :handler)

apply(mod, meth, [conn, params])
end

defp build_handler_tuple(%{handler: mod}) do
{mod, :no_resource}
end
defp build_handler_tuple(%{on_failure: {mod, fun}}) do
_ = Logger.warn(":on_failure is deprecated. Use the :handler option instead")
{mod, fun}
end
defp build_handler_tuple(_) do
{Guardian.Plug.ErrorHandler, :no_resource}
end
end
5 changes: 5 additions & 0 deletions lib/guardian/plug/error_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Guardian.Plug.ErrorHandler do

@callback unauthenticated(Plug.Conn.t, map) :: Plug.Conn.t
@callback unauthorized(Plug.Conn.t, map) :: Plug.Conn.t
@callback no_resource(Plug.Conn.t, map) :: Plug.Conn.t

import Plug.Conn

Expand All @@ -16,6 +17,10 @@ defmodule Guardian.Plug.ErrorHandler do
respond(conn, response_type(conn), 403, "Unauthorized")
end

def no_resource(conn, _params) do
respond(conn, response_type(conn), 403, "Unauthorized")
end

def already_authenticated(conn, _params) do
conn |> halt
end
Expand Down
111 changes: 111 additions & 0 deletions test/guardian/plug/ensure_resource_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
defmodule Guardian.Plug.EnsureResourceTest do
@moduledoc false
use ExUnit.Case, async: true
use Plug.Test
import ExUnit.CaptureLog
import Guardian.TestHelper

alias Guardian.Plug.EnsureResource

defmodule TestHandler do
@moduledoc false

def no_resource(conn, _) do
conn
|> Plug.Conn.assign(:guardian_spec, :no_resource)
|> Plug.Conn.send_resp(403, "Unauthorized")
end
end

setup do
conn = conn(:get, "/foo")
{:ok, %{conn: conn}}
end

test "init/1 sets the handler option to the module that's passed in" do
%{handler: handler_opts} = EnsureResource.init(handler: TestHandler)

assert handler_opts == {TestHandler, :no_resource}
end

test "init/1 sets the handler option to the value of on_failure" do
fun = fn ->
%{handler: handler_opts} = EnsureResource.init(
on_failure: {TestHandler, :custom_failure_method}
)

assert handler_opts == {TestHandler, :custom_failure_method}
end

assert capture_log([level: :warn], fun) =~ ":on_failure is deprecated"
end

test "init/1 defaults the handler option to Guardian.Plug.ErrorHandler" do
%{handler: handler_opts} = EnsureResource.init %{}

assert handler_opts == {Guardian.Plug.ErrorHandler, :no_resource}
end

test "init/1 with default options" do
options = EnsureResource.init %{}

assert options == %{
handler: {Guardian.Plug.ErrorHandler, :no_resource},
key: :default
}
end

test "with a resource already set doesn't call no_resource for default key", %{conn: conn} do
ensured_conn =
conn
|> Guardian.Plug.set_current_resource(:the_resource)
|> run_plug(EnsureResource, handler: TestHandler)

refute must_have_resource(ensured_conn)
end

test "with a resource already set doesn't call no_resource for key", %{conn: conn} do
ensured_conn =
conn
|> Guardian.Plug.set_current_resource(:the_resource, :secret)
|> run_plug(EnsureResource, handler: TestHandler, key: :secret)

refute must_have_resource(ensured_conn)
end

test "with no resource set calls no_resource for default key", %{conn: conn} do
ensured_conn = run_plug(
conn,
EnsureResource,
handler: TestHandler
)

assert must_have_resource(ensured_conn)
end

test "with no resource set calls no_resource for key", %{conn: conn} do
ensured_conn = run_plug(
conn,
EnsureResource,
handler: TestHandler,
key: :secret
)

assert must_have_resource(ensured_conn)
end

test "it halts the connection", %{conn: conn} do
ensured_conn = run_plug(
conn,
EnsureResource,
handler: TestHandler,
key: :secret
)

assert ensured_conn.halted
end

defp must_have_resource(conn) do
conn.assigns[:guardian_spec] == :no_resource
end
end

0 comments on commit 824e2a4

Please sign in to comment.