Skip to content

Commit 96d22a4

Browse files
committed
Revert "improvement: Simplify forbidden_due_to_strict_policy check (#2400)"
This reverts commit 4d58a06.
1 parent a3ce79d commit 96d22a4

File tree

2 files changed

+125
-41
lines changed

2 files changed

+125
-41
lines changed

lib/ash/policy/authorizer/authorizer.ex

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,32 +1810,122 @@ defmodule Ash.Policy.Authorizer do
18101810
authorizer.action.type != :read do
18111811
true
18121812
else
1813-
check_context = %{resource: authorizer.resource}
1814-
1815-
{any_strict_error?, _authorizer} =
1813+
if Enum.any?(authorizer.policies, fn policy ->
1814+
{all_conditions_true?, _authorizer} =
1815+
Enum.reduce_while(policy.condition || [], {true, authorizer}, fn
1816+
{check_module, check_opts}, {_acc, auth} ->
1817+
case Policy.fetch_or_strict_check_fact(auth, {check_module, check_opts}) do
1818+
{:ok, true, updated_auth} ->
1819+
{:cont, {true, updated_auth}}
1820+
1821+
_ ->
1822+
{:halt, {false, auth}}
1823+
end
1824+
end)
1825+
1826+
all_conditions_true?
1827+
end) do
18161828
authorizer.policies
1817-
|> Enum.map(&{&1, Policy.policy_expression(&1, check_context)})
1818-
|> Enum.reduce_while({true, authorizer}, fn
1819-
{policy, {cond_expr, complete_expr}}, {acc, authorizer} ->
1820-
strict? = policy.access_type == :strict
1821-
1822-
{cond_expr, authorizer} =
1823-
Policy.expand_constants(cond_expr, authorizer, check_context)
1824-
1825-
{complete_expr, authorizer} =
1826-
Policy.expand_constants(complete_expr, authorizer, check_context)
1829+
|> Enum.reduce_while(false, fn policy, _acc ->
1830+
if policy.access_type == :strict do
1831+
{all_conditions_true?, updated_authorizer} =
1832+
Enum.reduce_while(
1833+
policy.condition || [],
1834+
{true, authorizer},
1835+
fn {check_module, check_opts}, {_acc, auth} ->
1836+
case Policy.fetch_or_strict_check_fact(auth, {check_module, check_opts}) do
1837+
{:ok, true, updated_auth} ->
1838+
{:cont, {true, updated_auth}}
1839+
1840+
_ ->
1841+
{:halt, {false, auth}}
1842+
end
1843+
end
1844+
)
18271845

1828-
case {cond_expr, complete_expr} do
1829-
{true, false} when strict? -> {:halt, {true, authorizer}}
1830-
{true, _} -> {:cont, {false, authorizer}}
1831-
_ -> {:cont, {acc, authorizer}}
1846+
if all_conditions_true? and policy_fails_statically?(updated_authorizer, policy) do
1847+
{:halt, true}
1848+
else
1849+
{:cont, false}
18321850
end
1851+
else
1852+
{:cont, false}
1853+
end
18331854
end)
1834-
1835-
any_strict_error?
1855+
else
1856+
true
1857+
end
18361858
end
18371859
end
18381860

1861+
defp policy_fails_statically?(authorizer, policy) do
1862+
Enum.reduce_while(policy.policies, {:forbidden, authorizer}, fn check, {status, auth} ->
1863+
case check.type do
1864+
:authorize_if ->
1865+
case Policy.fetch_or_strict_check_fact(
1866+
auth,
1867+
{check.check_module, check.check_opts}
1868+
) do
1869+
{:ok, true, updated_auth} ->
1870+
{:halt, {:authorized, updated_auth}}
1871+
1872+
{:ok, _, updated_auth} ->
1873+
{:cont, {status, updated_auth}}
1874+
1875+
{:error, updated_auth} ->
1876+
{:halt, {:forbidden, updated_auth}}
1877+
end
1878+
1879+
:forbid_if ->
1880+
case Policy.fetch_or_strict_check_fact(
1881+
auth,
1882+
{check.check_module, check.check_opts}
1883+
) do
1884+
{:ok, true, updated_auth} ->
1885+
{:halt, {:forbidden, updated_auth}}
1886+
1887+
{:ok, _, updated_auth} ->
1888+
{:cont, {status, updated_auth}}
1889+
1890+
{:error, updated_auth} ->
1891+
{:halt, {:forbidden, updated_auth}}
1892+
end
1893+
1894+
:authorize_unless ->
1895+
case Policy.fetch_or_strict_check_fact(
1896+
auth,
1897+
{check.check_module, check.check_opts}
1898+
) do
1899+
{:ok, false, updated_auth} ->
1900+
{:halt, {:authorized, updated_auth}}
1901+
1902+
{:ok, _, updated_auth} ->
1903+
{:cont, {status, updated_auth}}
1904+
1905+
{:error, updated_auth} ->
1906+
{:halt, {:forbidden, updated_auth}}
1907+
end
1908+
1909+
:forbid_unless ->
1910+
case Policy.fetch_or_strict_check_fact(
1911+
auth,
1912+
{check.check_module, check.check_opts}
1913+
) do
1914+
{:ok, false, updated_auth} ->
1915+
{:cont, {:forbidden, updated_auth}}
1916+
1917+
{:ok, _, updated_auth} ->
1918+
{:cont, {status, updated_auth}}
1919+
1920+
{:error, updated_auth} ->
1921+
{:halt, {:forbidden, updated_auth}}
1922+
end
1923+
end
1924+
end)
1925+
|> elem(0)
1926+
|> Kernel.==(:forbidden)
1927+
end
1928+
18391929
defp get_policies(authorizer) do
18401930
%{
18411931
authorizer

lib/ash/policy/policy.ex

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,28 @@ defmodule Ash.Policy.Policy do
4141
def expression(policies, check_context) do
4242
policies
4343
|> List.wrap()
44-
|> Enum.map(&{&1, policy_expression(&1, check_context)})
44+
|> Enum.map(fn policy ->
45+
# Simplification is important here to detect empty field policies
46+
cond_expr = policy |> condition_expression() |> simplify_policy_expression(check_context)
47+
pol_expr = policies_expression(policy)
48+
complete_expr = b(cond_expr and pol_expr)
49+
{policy, cond_expr, complete_expr}
50+
end)
4551
|> List.foldr({false, true}, fn
46-
{%{bypass?: true}, {_cond_expr, complete_expr}}, {one_condition_matches, true} ->
52+
{%{bypass?: true}, _cond_expr, complete_expr}, {one_condition_matches, true} ->
4753
{
4854
b(complete_expr or one_condition_matches),
4955
complete_expr
5056
}
5157

52-
{%FieldPolicy{bypass?: true}, {true, complete_expr}},
58+
{%FieldPolicy{bypass?: true}, true, complete_expr},
5359
{one_condition_matches, all_policies_match} ->
5460
{
5561
one_condition_matches,
5662
b(complete_expr or all_policies_match)
5763
}
5864

59-
{%{bypass?: true}, {_cond_expr, complete_expr}},
65+
{%{bypass?: true}, _cond_expr, complete_expr},
6066
{one_condition_matches, all_policies_match} ->
6167
{
6268
# Bypass should only contribute to "at least one policy applies" if it actually authorizes.
@@ -65,7 +71,7 @@ defmodule Ash.Policy.Policy do
6571
b(complete_expr or all_policies_match)
6672
}
6773

68-
{%{}, {cond_expr, complete_expr}}, {one_condition_matches, all_policies_match} ->
74+
{%{}, cond_expr, complete_expr}, {one_condition_matches, all_policies_match} ->
6975
{
7076
b(cond_expr or one_condition_matches),
7177
b(implied_by(complete_expr, cond_expr) and all_policies_match)
@@ -173,9 +179,10 @@ defmodule Ash.Policy.Policy do
173179
def fetch_or_strict_check_fact(authorizer, {check_module, opts}) do
174180
authorizer.facts
175181
|> Enum.find_value(fn
176-
{{^check_module, fact_opts}, result} when result != :unknown ->
177-
if Keyword.drop(fact_opts, [:access_type, :ash_field_policy?]) ==
178-
Keyword.drop(opts, [:access_type, :ash_field_policy?]) do
182+
{{fact_mod, fact_opts}, result} when result != :unknown ->
183+
if check_module == fact_mod &&
184+
Keyword.drop(fact_opts, [:access_type, :ash_field_policy?]) ==
185+
Keyword.drop(opts, [:access_type, :ash_field_policy?]) do
179186
{:ok, result}
180187
end
181188

@@ -261,18 +268,6 @@ defmodule Ash.Policy.Policy do
261268
end
262269
end
263270

264-
@doc false
265-
@spec policy_expression(policy :: t() | FieldPolicy.t(), check_context :: Check.context()) ::
266-
{Expression.t(Check.ref()), Expression.t(Check.ref())}
267-
def policy_expression(policy, check_context) do
268-
# Simplification is important here to detect empty field policies
269-
cond_expr = policy |> condition_expression() |> simplify_policy_expression(check_context)
270-
pol_expr = policies_expression(policy)
271-
complete_expr = b(cond_expr and pol_expr)
272-
273-
{cond_expr, complete_expr}
274-
end
275-
276271
@spec condition_expression(policy :: t() | FieldPolicy.t()) :: Expression.t(Check.ref())
277272
defp condition_expression(%{condition: condition}) do
278273
condition
@@ -299,13 +294,12 @@ defmodule Ash.Policy.Policy do
299294
end)
300295
end
301296

302-
@doc false
303297
@spec expand_constants(
304298
expression :: Expression.t(Check.ref()),
305299
authorizer :: Authorizer.t(),
306300
check_context :: Check.context()
307301
) :: {Expression.t(Check.ref()), Authorizer.t()}
308-
def expand_constants(expression, authorizer, check_context) do
302+
defp expand_constants(expression, authorizer, check_context) do
309303
{expression, authorizer} =
310304
Expression.expand(expression, authorizer, fn
311305
expr, authorizer when is_variable(expr) ->

0 commit comments

Comments
 (0)