Skip to content

authorize A B requires both policies, but renders as Roles(A, B) which admits either #20

Description

@woksin

Summary

A command declaring authorize A B requires both policies. Stage renders it as [Roles("A", "B")], which Arc evaluates as either. A rendered application therefore admits callers the document does not.

This is present today and does not depend on any unreleased change.

Evidence

Adjacency means AND. PolicyReferenceSyntax.IsAlternative documents itself as "Whether the reference was introduced with or, making it an alternative to the preceding policies" — so a reference written without or is an additional demand, not an alternative.

Stage unions every reference regardlessSource/Rendering.Cratis/Authorization/AuthorizationRenderer.cs:67-78:

var required = declared
    .SelectMany(authorize => authorize!.Policies)
    .Select(reference => RolesRequiredBy(reference, applicationSet, subject, diagnostics))
    .ToArray();var union = required.SelectMany(roles => roles).Distinct(StringComparer.Ordinal).Select(CSharpCodeBuilder.StringLiteral);
return $"Roles({string.Join(", ", union)})";

IsAlternative is never read.

Arc evaluates [Roles] as a disjunctionArc.Core/Authorization/AuthorizationEvaluator.cs:123-124:

var requiredRoles = roles.Split(',').Select(r => r.Trim());
var userHasRequiredRole = requiredRoles.Any(user.IsInRole);

So authorize Administrator Auditor — meaning both — renders as a rule admitting anyone holding either.

Why no spec caught it

Both authorize fixtures build the reference list the same way, and can only produce disjunctions — for_AuthorizationRenderer/given/an_application_with_policies.cs:51:

new PolicyReferenceSyntax(policy, index > 0, SourceLocation.Start)

Every policy after the first is hard-coded as an alternative, so the conjunction case is unreachable from the specs.

The fix is constrained by what Arc can express

A faithful conjunction cannot be rendered with Arc as it stands:

So the honest rendering for a conjunction is [Authorize] plus a diagnostic — weaker than declared, never anonymous — rather than a Roles union that is wrong in the permissive direction.

Suggested direction

Cratis/Screenplay#68 replaces the flat list with a requirement tree, so the grouping becomes expressible. When Stage absorbs that:

  • PolicyReferenceSyntax → existing RolesRequiredBy
  • LogicalPolicyRequirementSyntax { Operator: Or } → the existing union, which is correct for a disjunction
  • LogicalPolicyRequirementSyntax { Operator: And } → report it and render Authorize

Use the tree rather than References() — the flattened view discards exactly the information this bug is about.

Worth fixing the fixtures in the same change, so the conjunction case is reachable from specs at all.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions