Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Allow specifying resource module separately for each action #13

Open
wants to merge 4 commits into
base: 7-allow-using-loader-with-permitectoresolver
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions lib/live_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ defmodule Permit.Phoenix.LiveView do
]
|> Enum.filter(& &1)

defmacro __before_compile__(_env) do
quote do
def get_events, do: @events
end
end

defmacro __using__(opts) do
quote generated: true do
import unquote(__MODULE__)
Expand All @@ -88,6 +94,9 @@ defmodule Permit.Phoenix.LiveView do
end

@behaviour unquote(__MODULE__)
@on_definition {unquote(__MODULE__), :__on_definition__}
@before_compile unquote(__MODULE__)
@events []

@impl true
def handle_unauthorized(action, socket) do
Expand Down Expand Up @@ -177,6 +186,21 @@ defmodule Permit.Phoenix.LiveView do
end
end

def __on_definition__(env, _kind, :handle_event, args, _guards, _body) do
resource_module = Module.get_attribute(env.module, :resource_module)
events = Module.get_attribute(env.module, :events)
action = extract_action(args)

Module.put_attribute(env.module, :events, [{action, resource_module} | events])

Module.delete_attribute(env.module, :resource_module)
end

def __on_definition__(_env, _kind, _name, _args, _guards, _body), do: nil

defp extract_action([action, _, _]), do: action
defp extract_action(_), do: nil

@doc """
Returns true if inside mount/1, false otherwise. Useful for distinguishing between
rendering directly via router or being in a handle_params lifecycle.
Expand Down
71 changes: 45 additions & 26 deletions lib/permit_phoenix/controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,10 @@ defmodule Permit.Phoenix.Controller do
quote generated: true do
require Logger

# with {:module, Permit.Ecto} <- Code.ensure_compiled(Permit.Ecto) do
# require Ecto.Query
# end

@behaviour unquote(__MODULE__)
@on_definition {unquote(__MODULE__), :__on_definition__}
@before_compile unquote(__MODULE__)
@controller_actions []

@impl true
def handle_unauthorized(action, conn) do
Expand Down Expand Up @@ -331,28 +330,48 @@ defmodule Permit.Phoenix.Controller do
|> Enum.filter(& &1)
)

plug(
Permit.Phoenix.Plug,
[
if({:module, Permit.Ecto} == Code.ensure_compiled(Permit.Ecto),
do: {:base_query, &__MODULE__.base_query/1}
),
if({:module, Permit.Ecto} == Code.ensure_compiled(Permit.Ecto),
do: {:finalize_query, &__MODULE__.finalize_query/2}
),
authorization_module: &__MODULE__.authorization_module/0,
resource_module: &__MODULE__.resource_module/0,
preload_actions: &__MODULE__.preload_actions/0,
fallback_path: &__MODULE__.fallback_path/2,
except: &__MODULE__.except/0,
fetch_subject: &__MODULE__.fetch_subject/1,
handle_unauthorized: &__MODULE__.handle_unauthorized/2,
loader: &__MODULE__.loader/1,
id_param_name: &__MODULE__.id_param_name/2,
id_struct_field_name: &__MODULE__.id_struct_field_name/2
]
|> Enum.filter(& &1)
)
plug(:permit_phoenix_plug)
end
end

def __on_definition__(env, _kind, name, _args, _guards, _body) do
resource_module = Module.get_attribute(env.module, :resource_module)
controller_actions = Module.get_attribute(env.module, :controller_actions)

Module.put_attribute(env.module, :controller_actions, [
{name, resource_module} | controller_actions
])

Module.delete_attribute(env.module, :resource_module)
end

defmacro __before_compile__(_env) do
quote do
def permit_phoenix_plug(conn, _opts) do
Permit.Phoenix.Plug.call(
conn,
[
if({:module, Permit.Ecto} == Code.ensure_compiled(Permit.Ecto),
do: {:base_query, &__MODULE__.base_query/1}
),
if({:module, Permit.Ecto} == Code.ensure_compiled(Permit.Ecto),
do: {:finalize_query, &__MODULE__.finalize_query/2}
),
authorization_module: &__MODULE__.authorization_module/0,
resource_module: &__MODULE__.resource_module/0,
preload_actions: &__MODULE__.preload_actions/0,
fallback_path: &__MODULE__.fallback_path/2,
except: &__MODULE__.except/0,
fetch_subject: &__MODULE__.fetch_subject/1,
handle_unauthorized: &__MODULE__.handle_unauthorized/2,
loader: &__MODULE__.loader/1,
id_param_name: &__MODULE__.id_param_name/2,
id_struct_field_name: &__MODULE__.id_struct_field_name/2,
controller_actions: @controller_actions
]
|> Enum.filter(& &1)
)
end
end
end

Expand Down
26 changes: 24 additions & 2 deletions lib/permit_phoenix/live_view/authorize_hook.ex
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,13 @@ defmodule Permit.Phoenix.LiveView.AuthorizeHook do
@spec just_authorize(PhoenixTypes.socket()) :: PhoenixTypes.live_authorization_result()
defp just_authorize(socket) do
authorization_module = socket.view.authorization_module()
resource_module = socket.view.resource_module()
events = socket.view.get_events()

# TODO: change socket.assigns.live_action to action from function params
# when event authorization is merged in
resource_module =
extract_resource_module(events, socket.assigns.live_action, socket.view.resource_module())

resolver_module = authorization_module.resolver_module()
subject = socket.assigns.current_user
action = socket.assigns.live_action
Expand All @@ -168,7 +174,13 @@ defmodule Permit.Phoenix.LiveView.AuthorizeHook do
authorization_module = socket.view.authorization_module()
actions_module = authorization_module.actions_module()
resolver_module = authorization_module.resolver_module()
resource_module = socket.view.resource_module()
events = socket.view.get_events()

# TODO: change socket.assigns.live_action to action from function params
# when event authorization is merged in

resource_module =
extract_resource_module(events, socket.assigns.live_action, socket.view.resource_module())

base_query = &socket.view.base_query/1
loader = &socket.view.loader/1
Expand Down Expand Up @@ -230,4 +242,14 @@ defmodule Permit.Phoenix.LiveView.AuthorizeHook do
authenticate_and_authorize!(socket, session, params)
end)
end

defp extract_resource_module(events, action, default) do
action = Atom.to_string(action)

events
|> Enum.find({nil, default}, fn {event, _resource_module} ->
event == action
end)
|> then(fn {_, resource_module} -> resource_module end)
end
end
3 changes: 2 additions & 1 deletion lib/permit_phoenix/plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ defmodule Permit.Phoenix.Plug do
if action_group in opts[:except] do
conn
else
resource_module = opts[:resource_module]
resource_module =
Keyword.get(opts[:controller_actions], action_group) || opts[:resource_module]

subject = opts[:fetch_subject].(conn)
authorize(conn, opts, action_group, subject, resource_module)
Expand Down
17 changes: 12 additions & 5 deletions test/permit/ecto_plug_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defmodule Permit.EctoPlugTest do

test "authorizes :index action", %{conn: conn} do
conn = call(conn, :get, "/items")
assert conn.resp_body == "listing all items"
assert conn.resp_body == "listing all users"
end

test "authorizes :show action", %{conn: conn} do
Expand All @@ -31,6 +31,13 @@ defmodule Permit.EctoPlugTest do
assert %Item{id: 1} = conn.assigns[:loaded_resource]
end

test "authorizes :details action and preloads resource via :action_crud_mapping and :preload_actions options",
%{conn: conn} do
conn = call(conn, :get, "/details/1")
assert conn.resp_body =~ ~r[Item]
assert %Item{id: 1} = conn.assigns[:loaded_resource]
end

test "raises when record does not exist", %{conn: conn} do
assert_raise Plug.Conn.WrapperError, ~r/Ecto\.NoResultsError/, fn ->
call(conn, :get, "/items/0")
Expand Down Expand Up @@ -71,7 +78,7 @@ defmodule Permit.EctoPlugTest do

test "authorizes :index action", %{conn: conn} do
conn = call(conn, :get, "/items")
assert conn.resp_body == "listing all items"
assert conn.resp_body == "listing all users"
end

test "authorizes :show action for object with matching :owner_id", %{conn: conn} do
Expand Down Expand Up @@ -105,7 +112,7 @@ defmodule Permit.EctoPlugTest do

test "authorizes :index action", %{conn: conn} do
conn = call(conn, :get, "/items")
assert conn.resp_body == "listing all items"
assert conn.resp_body == "listing all users"
end

test "authorizes :show action", %{conn: conn} do
Expand Down Expand Up @@ -137,7 +144,7 @@ defmodule Permit.EctoPlugTest do

test "authorizes :index action", %{conn: conn} do
conn = call(conn, :get, "/items")
assert conn.resp_body == "listing all items"
assert conn.resp_body == "listing all users"
end

test "authorizes :show action on item 1", %{conn: conn} do
Expand Down Expand Up @@ -188,7 +195,7 @@ defmodule Permit.EctoPlugTest do

test "authorizes :index action", %{conn: conn} do
conn = call(conn, :get, "/items")
assert conn.resp_body == "listing all items"
assert conn.resp_body == "listing all users"
end

test "authorizes :show action on item 2", %{conn: conn} do
Expand Down
2 changes: 1 addition & 1 deletion test/permit/non_ecto_plug_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule Permit.NonEctoPlugTest do

test "authorizes :index action", %{conn: conn} do
conn = call(conn, :get, "/items")
assert conn.resp_body == "listing all items"
assert conn.resp_body == "listing all users"
end

test "authorizes :show action", %{conn: conn} do
Expand Down
17 changes: 14 additions & 3 deletions test/support/ecto_fake_app/item_controller_using_repo.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
defmodule Permit.EctoFakeApp.ItemControllerUsingRepo do
@moduledoc false
require Logger

use Phoenix.Controller

alias Permit.EctoFakeApp.{Authorization, Item}
alias Permit.EctoFakeApp.{Authorization, Item, User}

use Permit.Phoenix.Controller,
authorization_module: Authorization,
Expand All @@ -11,9 +13,18 @@ defmodule Permit.EctoFakeApp.ItemControllerUsingRepo do
except: [:action_without_authorizing],
fallback_path: "/?foo"

def index(conn, _params), do: text(conn, "listing all items")
def show(conn, _params), do: text(conn, inspect(conn.assigns[:loaded_resource]))
@spec index(Plug.Conn.t(), map) :: Plug.Conn.t()
@resource_module User
def index(conn, _params), do: text(conn, "listing all users")

@spec show(Plug.Conn.t(), map) :: Plug.Conn.t()
@resource_module Item
def show(conn, _params) do
text(conn, inspect(conn.assigns[:loaded_resource]))
end

def edit(conn, _params), do: text(conn, inspect(conn.assigns[:loaded_resource]))

def details(conn, _params), do: text(conn, inspect(conn.assigns[:loaded_resource]))
def action_without_authorizing(conn, _params), do: text(conn, "okay")
end
6 changes: 6 additions & 0 deletions test/support/ecto_fake_app/permissions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ defmodule Permit.EctoFakeApp.Permissions do
def can(:admin = _role) do
permit()
|> all(Item)
|> all(User)
end

def can(:owner = _role) do
permit()
|> all(Item, [user, item], owner_id: user.id)
|> all(User)
end

def can(:function_owner = _role) do
Expand All @@ -23,21 +25,25 @@ defmodule Permit.EctoFakeApp.Permissions do
def can(:inspector = _role) do
permit()
|> read(Item)
|> read(User)
end

def can(%{role: :moderator, level: 1} = _role) do
permit()
|> all(Item, permission_level: {:<=, 1})
|> all(User)
end

def can(%{role: :moderator, level: 2} = _role) do
permit()
|> all(Item, permission_level: {{:not, :>}, 2})
|> all(User)
end

def can(%{role: :thread_moderator, thread_name: thread} = _role) do
permit()
|> all(Item, permission_level: {:<=, 3}, thread_name: {:ilike, thread})
|> all(User)
end

def can(%User{id: id} = _role) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ defmodule Permit.NonEctoFakeApp.ItemControllerUsingLoader do
@moduledoc false
use Phoenix.Controller

alias Permit.NonEctoFakeApp.{Authorization, Item, NoResultsError}
alias Permit.NonEctoFakeApp.{Authorization, Item, NoResultsError, User}

use Permit.Phoenix.Controller,
authorization_module: Authorization,
resource_module: Item

def index(conn, _params), do: text(conn, "listing all items")
@resource_module User
def index(conn, _params), do: text(conn, "listing all users")
def show(conn, _params), do: text(conn, inspect(conn.assigns[:loaded_resource]))

@item1 %Item{id: 1, owner_id: 1, permission_level: 1}
Expand Down
1 change: 1 addition & 0 deletions test/support/non_ecto_fake_app/permissions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Permit.NonEctoFakeApp.Permissions do
def can(:admin = _role) do
permit()
|> all(Item)
|> all(User)
end

def can(:owner = _role) do
Expand Down