Skip to content

Commit

Permalink
feat: Introduce can_see?/2 to add field specific authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
staylorwr committed Apr 7, 2023
1 parent a6026f9 commit 1d52b6b
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 17 deletions.
1 change: 1 addition & 0 deletions lib/ex_teal/field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ defmodule ExTeal.Field do
panel: nil,
embed_field: nil,
getter: nil,
can_see: nil,
show_on_index: true,
show_on_detail: true,
show_on_new: true,
Expand Down
10 changes: 10 additions & 0 deletions lib/ex_teal/field_visibility.ex
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,14 @@ defmodule ExTeal.FieldVisibility do
"""
@spec as_html(Field.t()) :: Field.t()
def as_html(field), do: %{field | as_html: true}

@doc """
Conditionally render a field on a resource as a whole based on the current
request. Helpful for filtering out fields based on the current
users permissions.
"""
@spec can_see?(Field.t(), (Plug.Conn.t() -> boolean())) :: Field.t()
def can_see?(field, func) do
Map.put(field, :can_see, func)
end
end
20 changes: 16 additions & 4 deletions lib/ex_teal/resource/fields.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ defmodule ExTeal.Resource.Fields do
"""
@callback fields() :: list(Field.t())

@doc """
Used to decorate the fields before
"""

defmacro __using__(_) do
quote do
@behaviour ExTeal.Resource.Fields
Expand Down Expand Up @@ -241,10 +237,26 @@ defmodule ExTeal.Resource.Fields do
|> Map.put(:value, value)
|> add_panel_key(panel)
|> field.type.apply_options_for(model, conn, type)
|> apply_can_see(conn)
end)
|> Enum.reject(&is_nil/1)
end

@doc """
Apply the can_see function attached to a field to filter
out the field from serialized responses.
"""
@spec apply_can_see(Field.t(), Plug.Conn.t()) :: Field.t() | nil
def apply_can_see(%Field{can_see: nil} = field, _), do: field

def apply_can_see(%Field{can_see: see} = field, conn) when is_function(see, 1) do
if see.(conn) do
field
else
nil
end
end

def add_panel_key(%Field{panel: panel} = field, _) when not is_nil(panel), do: field
def add_panel_key(%Field{} = field, nil), do: field
def add_panel_key(%Field{} = field, %Panel{key: key}), do: Map.put(field, :panel, key)
Expand Down
45 changes: 32 additions & 13 deletions test/ex_teal/resource/fields_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule ExTeal.Resource.FieldsTest do
use TestExTeal.ConnCase

alias ExTeal.Fields.{Hidden, ManyToManyBelongsTo, Text}
alias ExTeal.Fields.{ManyToManyBelongsTo, Text}
alias ExTeal.Resource.Fields
alias TestExTeal.{PostResource, TagResource, UserResource}

Expand Down Expand Up @@ -43,7 +43,9 @@ defmodule ExTeal.Resource.FieldsTest do
Embedded.new(:location, [
Text.make(:street_line_1),
Text.make(:city)
])
]),
Text.make(:description)
|> can_see?(fn %{assigns: assigns} -> Map.get(assigns, :foo) == :bar end)
]
end

Expand All @@ -65,18 +67,20 @@ defmodule ExTeal.Resource.FieldsTest do
test "returns all fields for an embedded resource" do
fields = Fields.all_fields(EmbeddedPostResource)

as_panel_field = fn field ->
field
|> Map.put(:attribute, :"location.#{field.field}")
|> Map.put(:panel, :location)
|> Map.put(:embed_field, :location)
end
assert Enum.map(fields, & &1.field) == [
:name,
:id,
:street_line_1,
:city,
:description
]

assert fields == [
Text.make(:name),
as_panel_field.(Hidden.make(:id)),
as_panel_field.(Text.make(:street_line_1)),
as_panel_field.(Text.make(:city))
assert Enum.map(fields, & &1.attribute) == [
"name",
:"location.id",
:"location.street_line_1",
:"location.city",
"description"
]
end
end
Expand Down Expand Up @@ -141,6 +145,21 @@ defmodule ExTeal.Resource.FieldsTest do
end
end

test "apply_values_for/5 hides fields based on can_see?/1" do
p = insert(:post)

conn = prep_conn(:get, "/post-embeds/#{p.id}")
fields = Fields.fields_for(:index, EmbeddedPostResource)
normal_fields = Fields.apply_values(fields, p, EmbeddedPostResource, conn, :index, nil)

assert Enum.count(normal_fields) == 3

conn = Plug.Conn.assign(conn, :foo, :bar)

fields = Fields.apply_values(fields, p, EmbeddedPostResource, conn, :index, nil)
assert Enum.count(fields) == 4
end

def prep_conn(method, path, params \\ %{}) do
params = Map.merge(params, %{"_format" => "json"})

Expand Down

0 comments on commit 1d52b6b

Please sign in to comment.