mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
Revert "improvement: make authorization failures behave consistently across reads"
This reverts commit ffa37d0c95
.
This commit is contained in:
parent
ffa37d0c95
commit
4adddcdd69
12 changed files with 66 additions and 125 deletions
|
@ -299,12 +299,6 @@ defmodule Ash.Policy.Authorizer do
|
||||||
Ash.Expr
|
Ash.Expr
|
||||||
],
|
],
|
||||||
schema: [
|
schema: [
|
||||||
statically_deniable_reads: [
|
|
||||||
type: {:one_of, [:error, :filter]},
|
|
||||||
default: :filter,
|
|
||||||
doc:
|
|
||||||
"When a read request is denied immediately, i.e we know its forbidden without ever running a query. `:filter` will filter all records out, and `:error` will result in a forbidden error."
|
|
||||||
],
|
|
||||||
default_access_type: [
|
default_access_type: [
|
||||||
type: {:one_of, [:strict, :filter, :runtime]},
|
type: {:one_of, [:strict, :filter, :runtime]},
|
||||||
default: :filter,
|
default: :filter,
|
||||||
|
@ -1175,7 +1169,7 @@ defmodule Ash.Policy.Authorizer do
|
||||||
{_filters, _require_check} ->
|
{_filters, _require_check} ->
|
||||||
case global_filters(authorizer) do
|
case global_filters(authorizer) do
|
||||||
nil ->
|
nil ->
|
||||||
maybe_forbid_strict(authorizer) |> IO.inspect()
|
maybe_forbid_strict(authorizer)
|
||||||
|
|
||||||
{filters, scenarios_without_global} ->
|
{filters, scenarios_without_global} ->
|
||||||
with {:ok, %Ash.Filter{expression: filter}} <-
|
with {:ok, %Ash.Filter{expression: filter}} <-
|
||||||
|
@ -1504,30 +1498,12 @@ defmodule Ash.Policy.Authorizer do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp strict_check_result(authorizer, opts \\ []) do
|
defp strict_check_result(authorizer, opts \\ []) do
|
||||||
case authorizer.policies do
|
|
||||||
[] ->
|
|
||||||
error =
|
|
||||||
Ash.Error.Forbidden.Policy.exception(
|
|
||||||
facts: authorizer.facts,
|
|
||||||
policies: authorizer.policies,
|
|
||||||
context_description: opts[:context_description],
|
|
||||||
for_fields: opts[:for_fields],
|
|
||||||
resource: Map.get(authorizer, :resource),
|
|
||||||
actor: Map.get(authorizer, :action),
|
|
||||||
action: Map.get(authorizer, :action),
|
|
||||||
scenarios: []
|
|
||||||
)
|
|
||||||
|
|
||||||
{:error, error}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
case Checker.strict_check_scenarios(authorizer) do
|
case Checker.strict_check_scenarios(authorizer) do
|
||||||
{:ok, true, authorizer} ->
|
{:ok, true, authorizer} ->
|
||||||
{:authorized, authorizer}
|
{:authorized, authorizer}
|
||||||
|
|
||||||
{:ok, none, authorizer} when none in [false, []] ->
|
{:ok, none, authorizer} when none in [false, []] ->
|
||||||
# we construct the error to log policy breakdown
|
{:error,
|
||||||
error =
|
|
||||||
Ash.Error.Forbidden.Policy.exception(
|
Ash.Error.Forbidden.Policy.exception(
|
||||||
facts: authorizer.facts,
|
facts: authorizer.facts,
|
||||||
policies: authorizer.policies,
|
policies: authorizer.policies,
|
||||||
|
@ -1537,15 +1513,7 @@ defmodule Ash.Policy.Authorizer do
|
||||||
actor: Map.get(authorizer, :action),
|
actor: Map.get(authorizer, :action),
|
||||||
action: Map.get(authorizer, :action),
|
action: Map.get(authorizer, :action),
|
||||||
scenarios: []
|
scenarios: []
|
||||||
)
|
)}
|
||||||
|
|
||||||
if authorizer.action.type == :read and
|
|
||||||
Ash.Policy.Info.statically_deniable_reads(authorizer.resource) ==
|
|
||||||
:filter do
|
|
||||||
{:filter, authorizer, false}
|
|
||||||
else
|
|
||||||
{:error, error}
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, scenarios, authorizer} ->
|
{:ok, scenarios, authorizer} ->
|
||||||
case Checker.find_real_scenarios(scenarios, authorizer.facts) do
|
case Checker.find_real_scenarios(scenarios, authorizer.facts) do
|
||||||
|
@ -1557,32 +1525,22 @@ defmodule Ash.Policy.Authorizer do
|
||||||
end
|
end
|
||||||
|
|
||||||
{:error, authorizer, :unsatisfiable} ->
|
{:error, authorizer, :unsatisfiable} ->
|
||||||
# we construct the error to log policy breakdown
|
{:error,
|
||||||
error =
|
|
||||||
Ash.Error.Forbidden.Policy.exception(
|
Ash.Error.Forbidden.Policy.exception(
|
||||||
facts: authorizer.facts,
|
facts: authorizer.facts,
|
||||||
policies: authorizer.policies,
|
policies: authorizer.policies,
|
||||||
context_description: opts[:context_description],
|
context_description: opts[:context_description],
|
||||||
for_fields: opts[:for_fields],
|
for_fields: opts[:for_fields],
|
||||||
resource: Map.get(authorizer, :resource),
|
resource: Map.get(authorizer, :resource),
|
||||||
actor: Map.get(authorizer, :action),
|
|
||||||
action: Map.get(authorizer, :action),
|
action: Map.get(authorizer, :action),
|
||||||
|
actor: Map.get(authorizer, :action),
|
||||||
scenarios: []
|
scenarios: []
|
||||||
)
|
)}
|
||||||
|
|
||||||
if authorizer.action.type == :read and
|
|
||||||
Ash.Policy.Info.statically_deniable_reads(authorizer.resource) ==
|
|
||||||
:filter do
|
|
||||||
{:filter, authorizer, false}
|
|
||||||
else
|
|
||||||
{:error, error}
|
|
||||||
end
|
|
||||||
|
|
||||||
{:error, _authorizer, exception} ->
|
{:error, _authorizer, exception} ->
|
||||||
{:error, Ash.Error.to_ash_error(exception)}
|
{:error, Ash.Error.to_ash_error(exception)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_strict_filter(authorizer, scenarios) do
|
defp maybe_strict_filter(authorizer, scenarios) do
|
||||||
strict_filter(%{authorizer | scenarios: scenarios})
|
strict_filter(%{authorizer | scenarios: scenarios})
|
||||||
|
|
|
@ -188,10 +188,6 @@ defmodule Ash.Policy.Info do
|
||||||
Extension.get_opt(resource, [:policies], :default_access_type, :filter, false)
|
Extension.get_opt(resource, [:policies], :default_access_type, :filter, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
def statically_deniable_reads(resource) do
|
|
||||||
Extension.get_opt(resource, [:policies], :statically_deniable_reads, :filter, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
# This should be done at compile time
|
# This should be done at compile time
|
||||||
defp set_access_type(policies, default) when is_list(policies) do
|
defp set_access_type(policies, default) when is_list(policies) do
|
||||||
Enum.map(policies, &set_access_type(&1, default))
|
Enum.map(policies, &set_access_type(&1, default))
|
||||||
|
|
|
@ -568,7 +568,7 @@ defmodule Ash.Query do
|
||||||
def timeout(query, timeout) do
|
def timeout(query, timeout) do
|
||||||
query = new(query)
|
query = new(query)
|
||||||
|
|
||||||
if is_nil(timeout) || Ash.DataLayer.data_layer_can?(query.resource, :timeout) do
|
if Ash.DataLayer.data_layer_can?(query.resource, :timeout) || is_nil(timeout) do
|
||||||
%{query | timeout: timeout}
|
%{query | timeout: timeout}
|
||||||
else
|
else
|
||||||
add_error(query, TimeoutNotSupported.exception(resource: query.resource))
|
add_error(query, TimeoutNotSupported.exception(resource: query.resource))
|
||||||
|
|
3
mix.exs
3
mix.exs
|
@ -336,8 +336,7 @@ defmodule Ash.MixProject do
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
# DSLs
|
# DSLs
|
||||||
# {:spark, "~> 2.1 and >= 2.2.22"},
|
{:spark, "~> 2.1 and >= 2.2.22"},
|
||||||
{:spark, path: "../spark", override: true},
|
|
||||||
# Ash resources are backed by ecto scheams
|
# Ash resources are backed by ecto scheams
|
||||||
{:ecto, "~> 3.7"},
|
{:ecto, "~> 3.7"},
|
||||||
# Used by the ETS data layer
|
# Used by the ETS data layer
|
||||||
|
|
|
@ -792,8 +792,6 @@ defmodule Ash.Test.Actions.LoadTest do
|
||||||
|
|
||||||
assert %{campaign_upcase: "HELLO WORLD"} = author
|
assert %{campaign_upcase: "HELLO WORLD"} = author
|
||||||
|
|
||||||
Application.put_env(:foo, :bar, true)
|
|
||||||
|
|
||||||
assert %{campaign_upcase: "HELLO WORLD"} =
|
assert %{campaign_upcase: "HELLO WORLD"} =
|
||||||
Ash.load!(author, [:campaign_upcase], lazy?: true)
|
Ash.load!(author, [:campaign_upcase], lazy?: true)
|
||||||
end
|
end
|
||||||
|
|
|
@ -76,7 +76,7 @@ defmodule Ash.Test.Policy.Actions.BelongsToTest do
|
||||||
})
|
})
|
||||||
|> Ash.create!()
|
|> Ash.create!()
|
||||||
|
|
||||||
assert_raise Ash.Error.NotFound, fn ->
|
assert_raise Ash.Error.Forbidden, fn ->
|
||||||
post
|
post
|
||||||
|> Ash.Changeset.for_update(:update_with_reviewer, %{
|
|> Ash.Changeset.for_update(:update_with_reviewer, %{
|
||||||
reviewer_id: reviewer.id
|
reviewer_id: reviewer.id
|
||||||
|
|
|
@ -90,9 +90,12 @@ defmodule Ash.Test.Policy.ComplexTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it applies policies from the domain", %{me: me} do
|
test "it applies policies from the domain", %{me: me} do
|
||||||
assert [] ==
|
assert_raise Ash.Error.Forbidden,
|
||||||
|
~r/authorize unless: actor.forbidden_by_domain == true | ✓ |/,
|
||||||
|
fn ->
|
||||||
Ash.read!(Post, actor: %{me | forbidden_by_domain: true})
|
Ash.read!(Post, actor: %{me | forbidden_by_domain: true})
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "it properly limits on reads of comments", %{
|
test "it properly limits on reads of comments", %{
|
||||||
me: me,
|
me: me,
|
||||||
|
@ -184,7 +187,8 @@ defmodule Ash.Test.Policy.ComplexTest do
|
||||||
|
|
||||||
me |> Ash.load!([:bio_text], authorize?: true, actor: me)
|
me |> Ash.load!([:bio_text], authorize?: true, actor: me)
|
||||||
|
|
||||||
assert [] ==
|
assert_raise Ash.Error.Forbidden, fn ->
|
||||||
Ash.read!(Bio, actor: me, authorize?: true)
|
Ash.read!(Bio, actor: me, authorize?: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -53,7 +53,9 @@ defmodule Ash.Test.Policy.RbacTest do
|
||||||
|> Ash.Query.filter(id == ^org.id)
|
|> Ash.Query.filter(id == ^org.id)
|
||||||
|> Ash.Query.load(files: File |> Ash.Query.select([:forbidden]))
|
|> Ash.Query.load(files: File |> Ash.Query.select([:forbidden]))
|
||||||
|
|
||||||
assert [%{files: []}] = Ash.read!(query, actor: user)
|
assert_raise Ash.Error.Forbidden, fn ->
|
||||||
|
Ash.read!(query, actor: user) == []
|
||||||
|
end
|
||||||
|
|
||||||
# specify no select (everything is selected)
|
# specify no select (everything is selected)
|
||||||
query =
|
query =
|
||||||
|
@ -61,7 +63,9 @@ defmodule Ash.Test.Policy.RbacTest do
|
||||||
|> Ash.Query.filter(id == ^org.id)
|
|> Ash.Query.filter(id == ^org.id)
|
||||||
|> Ash.Query.load([:files])
|
|> Ash.Query.load([:files])
|
||||||
|
|
||||||
assert [%{files: []}] = Ash.read!(query, actor: user)
|
assert_raise Ash.Error.Forbidden, fn ->
|
||||||
|
Ash.read!(query, actor: user) == []
|
||||||
|
end
|
||||||
|
|
||||||
# select only an allowed field
|
# select only an allowed field
|
||||||
query =
|
query =
|
||||||
|
|
|
@ -132,7 +132,7 @@ defmodule Ash.Test.Policy.SelectingTest do
|
||||||
refute is_nil(parent.owner_only_resource)
|
refute is_nil(parent.owner_only_resource)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "guest is forbidden from seeing a forbidden field on the rel" do
|
test "guest is forbidden from querying if selecting a forbidden field on the rel" do
|
||||||
parent =
|
parent =
|
||||||
Parent
|
Parent
|
||||||
|> Ash.Changeset.for_create(:create, %{owner_id: "owner", guest_id: "guest"})
|
|> Ash.Changeset.for_create(:create, %{owner_id: "owner", guest_id: "guest"})
|
||||||
|
@ -144,13 +144,11 @@ defmodule Ash.Test.Policy.SelectingTest do
|
||||||
|> Ash.Changeset.for_create(:create)
|
|> Ash.Changeset.for_create(:create)
|
||||||
|> Ash.create!(authorize?: false)
|
|> Ash.create!(authorize?: false)
|
||||||
|
|
||||||
assert {:ok, parent} =
|
assert {:error, %Ash.Error.Forbidden{}} =
|
||||||
Parent
|
Parent
|
||||||
|> Ash.Query.for_read(:read)
|
|> Ash.Query.for_read(:read)
|
||||||
|> Ash.Query.load(:owner_only_resource)
|
|> Ash.Query.load(:owner_only_resource)
|
||||||
|> Ash.Query.limit(1)
|
|> Ash.Query.limit(1)
|
||||||
|> Ash.read_one(actor: %{id: "guest"})
|
|> Ash.read_one(actor: %{id: "guest"})
|
||||||
|
|
||||||
assert %Ash.ForbiddenField{} = parent.owner_only_resource
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,18 +23,6 @@ defmodule Ash.Test.Policy.SimpleTest do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "statically known policies will filter all results", %{user: user} do
|
|
||||||
Post
|
|
||||||
|> Ash.Changeset.for_create(:create, %{author: user.id, text: "aaa"})
|
|
||||||
|> Ash.create!(authorize?: false)
|
|
||||||
|
|
||||||
Post
|
|
||||||
|> Ash.Changeset.for_create(:create, %{author: user.id, text: "aaa"})
|
|
||||||
|> Ash.create!(authorize?: false)
|
|
||||||
|
|
||||||
Ash.read!(Post, action: :never_allowed, actor: user)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "bypass with condition does not apply subsequent filters", %{admin: admin, user: user} do
|
test "bypass with condition does not apply subsequent filters", %{admin: admin, user: user} do
|
||||||
Ash.create!(Ash.Changeset.for_create(Tweet, :create), authorize?: false)
|
Ash.create!(Ash.Changeset.for_create(Tweet, :create), authorize?: false)
|
||||||
|
|
||||||
|
@ -332,10 +320,11 @@ defmodule Ash.Test.Policy.SimpleTest do
|
||||||
|> Ash.Changeset.for_create(:create, %{user_id: user.id})
|
|> Ash.Changeset.for_create(:create, %{user_id: user.id})
|
||||||
|> Ash.create!(authorize?: false)
|
|> Ash.create!(authorize?: false)
|
||||||
|
|
||||||
assert [] =
|
assert_raise Ash.Error.Forbidden, fn ->
|
||||||
Ash.Test.Support.PolicySimple.Always
|
Ash.Test.Support.PolicySimple.Always
|
||||||
|> Ash.read!(authorize?: true, actor: user)
|
|> Ash.read!(authorize?: true, actor: user)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "two filter condition checks combine properly" do
|
test "two filter condition checks combine properly" do
|
||||||
user1 = Ash.create!(Ash.Changeset.for_create(User, :create), authorize?: false)
|
user1 = Ash.create!(Ash.Changeset.for_create(User, :create), authorize?: false)
|
||||||
|
|
|
@ -42,9 +42,10 @@ defmodule Ash.Test.Policy.StrictConditionTest do
|
||||||
|> Ash.Changeset.for_create(:create, %{visible: false}, authorize?: false)
|
|> Ash.Changeset.for_create(:create, %{visible: false}, authorize?: false)
|
||||||
|> Ash.create!()
|
|> Ash.create!()
|
||||||
|
|
||||||
assert [] ==
|
assert_raise Ash.Error.Forbidden, fn ->
|
||||||
Resource
|
Resource
|
||||||
|> Ash.Query.for_read(:read, %{}, actor: %{id: "foo"})
|
|> Ash.Query.for_read(:read, %{}, actor: %{id: "foo"})
|
||||||
|> Ash.read!()
|
|> Ash.read!()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -25,10 +25,6 @@ defmodule Ash.Test.Support.PolicySimple.Post do
|
||||||
forbid_if expr(^arg(:from_an_admin?))
|
forbid_if expr(^arg(:from_an_admin?))
|
||||||
authorize_if always()
|
authorize_if always()
|
||||||
end
|
end
|
||||||
|
|
||||||
policy action(:never_allowed) do
|
|
||||||
authorize_if false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ets do
|
ets do
|
||||||
|
@ -69,8 +65,6 @@ defmodule Ash.Test.Support.PolicySimple.Post do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
read :never_allowed
|
|
||||||
end
|
end
|
||||||
|
|
||||||
relationships do
|
relationships do
|
||||||
|
|
Loading…
Reference in a new issue