Skip to content

Policy Refactoring#2365

Merged
zachdaniel merged 19 commits into
ash-project:mainfrom
maennchen:jm/policy_refactoring
Oct 10, 2025
Merged

Policy Refactoring#2365
zachdaniel merged 19 commits into
ash-project:mainfrom
maennchen:jm/policy_refactoring

Conversation

@maennchen

Copy link
Copy Markdown
Contributor

Contributor checklist

Leave anything that you believe does not apply unchecked.

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

Changing the order of statements can have a performance impact.

eg. (var and expensive()) will check expensive() only if var is true.
If it were reversed, the expensive() check would always be run.
Allows to execute a callback on each node in the given boolean
expression to replace the current node. Has a variant with and
without an accumulator.
Takes a boolean expression and performs logical constant folding and
simplification by removing redundant negations, constants, and tautologies.
Will walk the expression and apply the callback to expand nodes.
It will constantly optimize the expression itself to avoid expansion
where logical constant folding does no longer require a value.
Allows direct access to the internally used expression
Remove conditions to check which policies might apply and track the
decision in the expression instead.
All done in expand_constant instead
All besides true / false are done in simplify_policy_expression
@maennchen maennchen changed the title Jm/policy refactoring Policy Refactoring Oct 10, 2025
Comment thread lib/ash/policy/policy.ex Outdated
@zachdaniel zachdaniel merged commit 79749c2 into ash-project:main Oct 10, 2025
45 checks passed
@zachdaniel

Copy link
Copy Markdown
Contributor

🚀 Thank you for your contribution! 🚀

@maennchen maennchen deleted the jm/policy_refactoring branch October 10, 2025 17:56
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
This was referenced Oct 17, 2025
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
Add failing test that shows bypass policies with always-true conditions
incorrectly authorize when bypass policies fail.

This test currently FAILS with:
  left:  true (incorrectly authorized)
  right: false (expected)

The bug is fixed in PR #3.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies with always-true conditions (like always())
to incorrectly satisfy the "at least one policy applies" requirement
even when the bypass policies themselves failed.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition (always) was true → one_condition_matches = true
2. policy condition (action_type(:read)) was false
3. Final: true AND (tautology) = true (incorrect!)

The fix restores the old behavior where bypass policies contribute
their complete expression to one_condition_matches, ensuring that
a bypass only satisfies the requirement if it actually authorizes.

Fixes authorization bypass when bypass conditions are always true but
bypass policies fail.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies to incorrectly satisfy the "at least one
policy applies" requirement when their condition evaluates to true
but their authorization checks fail.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition evaluates to true -> one_condition_matches = true
2. bypass policies fail (user is not admin)
3. policy condition doesn't match (create is not read)
4. Final: true AND true = true (incorrect!)

The fix ensures bypass policies contribute their complete expression
to one_condition_matches, so a bypass only satisfies the requirement
if it actually authorizes.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies to incorrectly satisfy the "at least one
policy applies" requirement when their condition evaluates to true
but their authorization checks fail.

For example, with these policies:
- bypass always() do authorize_if actor_attribute_equals(:is_admin, true)
- policy action_type(:read) do authorize_if always()

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition evaluates to true -> one_condition_matches = true
2. bypass policies fail (user is not admin)
3. policy condition doesn't match (create is not read)
4. Final: true AND true = true (incorrect!)

The fix ensures bypass policies contribute their complete expression
to one_condition_matches, so a bypass only satisfies the requirement
if it actually authorizes.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies to incorrectly satisfy the "at least one
policy applies" requirement when their condition evaluates to true
but their authorization checks fail.

For example, with these policies:

  bypass always(), do: authorize_if(actor_attribute_equals(:is_admin, true))
  policy action_type(:read), do: authorize_if(always())

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition evaluates to true -> one_condition_matches = true
2. bypass policies fail (user is not admin)
3. policy condition doesn't match (create is not read)
4. Final: true AND true = true (incorrect!)

The fix ensures bypass policies contribute their complete expression
to one_condition_matches, so a bypass only satisfies the requirement
if it actually authorizes.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies to incorrectly satisfy the "at least one
policy applies" requirement when their condition evaluates to true
but their authorization checks fail.

For example, with these policies:

  bypass always(), do: authorize_if(actor_attribute_equals(:is_admin, true))
  policy action_type(:read), do: authorize_if(always())

A non-admin user performing a create action would be incorrectly
authorized because:
1. bypass condition evaluates to true -> one_condition_matches = true
2. bypass policies fail (user is not admin)
3. policy condition doesn't match (create is not read)
4. all_policies_match remains true (non-matching policies don't apply)
5. Final: one_condition_matches AND all_policies_match = true AND true = true

The fix ensures bypass policies contribute their complete expression
to one_condition_matches, so a bypass only satisfies the requirement
if it actually authorizes.
jechol added a commit to jechol/ash that referenced this pull request Oct 17, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies to incorrectly satisfy the "at least one
policy applies" requirement when their condition evaluates to true
but their authorization checks fail.

For example, with these policies:

  bypass always(), do: authorize_if(actor_attribute_equals(:is_admin, true))
  policy action_type(:read), do: authorize_if(always())

The final authorization decision is: one_condition_matches AND all_policies_match

When a bypass condition is true but bypass policies fail, and subsequent
policies have non-matching conditions:

1. one_condition_matches = cond_expr (bypass condition) = true
   (bug - should check if bypass actually authorizes)
2. all_policies_match = (complete_expr OR NOT cond_expr) for each policy
   - For non-matching policies: (false OR NOT false) = true (policies don't apply)
3. Final: true AND true = true (incorrectly authorized)

The bypass condition alone satisfies "at least one policy applies" even
though the bypass fails to authorize.

The fix ensures bypass policies contribute their complete expression
(condition AND policies) to one_condition_matches.
zachdaniel pushed a commit that referenced this pull request Oct 17, 2025
The Policy Refactoring (#2365) introduced a bug where bypass policies
were contributing only their condition to one_condition_matches instead
of their complete expression (condition AND policies).

This caused bypass policies to incorrectly satisfy the "at least one
policy applies" requirement when their condition evaluates to true
but their authorization checks fail.

For example, with these policies:

  bypass always(), do: authorize_if(actor_attribute_equals(:is_admin, true))
  policy action_type(:read), do: authorize_if(always())

The final authorization decision is: one_condition_matches AND all_policies_match

When a bypass condition is true but bypass policies fail, and subsequent
policies have non-matching conditions:

1. one_condition_matches = cond_expr (bypass condition) = true
   (bug - should check if bypass actually authorizes)
2. all_policies_match = (complete_expr OR NOT cond_expr) for each policy
   - For non-matching policies: (false OR NOT false) = true (policies don't apply)
3. Final: true AND true = true (incorrectly authorized)

The bypass condition alone satisfies "at least one policy applies" even
though the bypass fails to authorize.

The fix ensures bypass policies contribute their complete expression
(condition AND policies) to one_condition_matches.
jechol added a commit to jechol/ash that referenced this pull request Oct 18, 2025
The Policy Refactoring (ash-project#2365) introduced a bug in Checker.strict_check_all_facts
where policy checks were evaluated even when the policy condition was false.

For example, with this policy:

    policy action_type([:create, :update]) do
      authorize_if AshStateMachine.Checks.ValidNextState
    end

When a read action was performed:
1. The action_type condition was evaluated and set to false in facts
2. But strict_check_all_policies was called unconditionally
3. ValidNextState.filter/3 was called with a query context (no changeset)
4. This caused FunctionClauseError since filter/3 only handles changeset contexts

The fix checks if any condition is false before evaluating policy checks.
Since policy conditions are ANDed together, one false condition means
the policy doesn't apply regardless of its checks.

This improves both correctness and performance by avoiding unnecessary
check evaluations when policies don't apply.
jechol added a commit to jechol/ash that referenced this pull request Oct 18, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where ActionType checks
were not evaluated during the simplification phase, even when the action
was known. This caused unnecessary evaluation of policy checks when the
action type didn't match.

For example, with this policy:

    policy action_type([:create, :update]) do
      authorize_if AshStateMachine.Checks.ValidNextState
    end

When a read action was performed:
1. ActionType.simplify didn't evaluate to false (action was not in context)
2. Expression remained: action_type([:create, :update]) and ValidNextState
3. expand_constants tried to evaluate ValidNextState
4. ValidNextState.filter/3 was called with query context (no changeset)
5. FunctionClauseError - filter/3 only handles changeset contexts

Solutions:
1. Modified check_context to include action in the context
2. Modified ActionType.simplify to evaluate immediately when action is
   available, returning true/false instead of check reference

This enables proper short-circuit evaluation:
- Read action: action_type([:create, :update]) → false
- Expression: false and ValidNextState → false (ValidNextState not evaluated)
- Avoids crashes and improves performance
jechol added a commit to jechol/ash that referenced this pull request Oct 18, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where ActionType checks
were not evaluated during the simplification phase, even when the action
was known. This caused unnecessary evaluation of policy checks when the
action type didn't match.

For example, with this policy:

    policy action_type([:create, :update]) do
      authorize_if AshStateMachine.Checks.ValidNextState
    end

When a read action was performed:
1. ActionType.simplify didn't evaluate to false (action was not in context)
2. Expression remained: action_type([:create, :update]) and ValidNextState
3. expand_constants tried to evaluate ValidNextState
4. ValidNextState.filter/3 was called with query context (no changeset)
5. FunctionClauseError - filter/3 only handles changeset contexts

Solutions:
1. Modified check_context to include action in the context
2. Modified ActionType.simplify to evaluate immediately when action is
   available, returning true/false instead of check reference

This enables proper short-circuit evaluation:
- Read action: action_type([:create, :update]) → false
- Expression: false and ValidNextState → false (ValidNextState not evaluated)
- Avoids crashes and improves performance
jechol added a commit to jechol/ash that referenced this pull request Oct 18, 2025
The Policy Refactoring (ash-project#2365) introduced a bug where ActionType checks
were not evaluated during the simplification phase, even when the action
was known. This caused unnecessary evaluation of policy checks when the
action type didn't match.

For example, with this policy:

    policy action_type([:create, :update]) do
      authorize_if AshStateMachine.Checks.ValidNextState
    end

When a read action was performed:
1. ActionType.simplify didn't evaluate to false (action was not in context)
2. Expression remained: action_type([:create, :update]) and ValidNextState
3. expand_constants tried to evaluate ValidNextState
4. ValidNextState.filter/3 was called with query context (no changeset)
5. FunctionClauseError - filter/3 only handles changeset contexts

Solutions:
1. Modified check_context to include action in the context
2. Modified ActionType.simplify to evaluate immediately when action is
   available, returning true/false instead of check reference

This enables proper short-circuit evaluation:
- Read action: action_type([:create, :update]) → false
- Expression: false and ValidNextState → false (ValidNextState not evaluated)
- Avoids crashes and improves performance
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants