Skip to content

fix(test): MockAuthService discards constraint arguments — third divergence, and the justifying comment is now false #1762

Description

@cristim

Summary

MockAuthService.grantPermissionsScoped (internal/api/mocks_test.go:403-417) wires both HasPermissionAPI and HasPermissionForConstraintsAPI to the same closure:

decide := func(action, resource string) bool {
    return authCtx.HasPermission(action, resource)
}
m.On("HasPermissionAPI", ...).Return(decide, nil).Maybe()
// The SEC-01 execution-time constraint check. A principal holding only
// admin:* satisfies any constraint set for the verbs it holds, and holds
// none of the carved-out verbs, so the same decision function applies.
m.On("HasPermissionForConstraintsAPI", ...).Return(decide, nil).Maybe()

decide takes (action, resource) and never looks at the constraint arguments. So the constraint dimension — providers, regions, MaxPurchaseAmount — is not modelled at all: every handler test that appears to exercise it is measuring the mock.

The comment justifying it is now false

The quoted justification holds only while no test principal holds a carved-out verb. PR #1758 introduces exactly such a principal: execute:ri-exchange joins adminCarvedOuts, and membership in the seeded RI Exchanger group (migration 000096) grants it explicitly.

A stale justification is worse than none — the next reader stops at the comment and concludes the shortcut is sound.

Evidence (execution-verified)

Measured against real permissionsAllow in internal/auth, compared to what the mock's decide closure answers for the same principal:

held:    execute:ri-exchange constrained to Providers: ["aws"]
request: Providers: ["azure"]

production (permissionsAllow):  false
mock      (decide closure):     true
divergent:                      true

Production refuses the cross-provider request; the mock allows it. Any handler test asserting a constraint refusal for such a principal is green regardless of whether the handler enforces constraints at all.

This is the third divergence in this mock today — the pattern is the design, not three bugs

# divergence status
1 grantAdmin stubbed the authorization decision, answering true for carved-out verbs production denies fixed in #1744
2 empty constraintSets: production fails closed, the mock answered true fixed in #1744
3 this one: HasPermissionForConstraintsAPI discards the constraint arguments entirely open

Three in one day in one mock is a signal about its shape: after #1744 it models the permission set faithfully (via the real AuthContext.HasPermission) and the constraint answer not at all. The two halves of the authorization decision are held to different standards, and the constraint half has no backing implementation.

That changes what a fix should look like. Patching the third case in isolation invites a fourth. The direction that removes the class is to make HasPermissionForConstraintsAPI delegate to the same real code path production uses — Service.permissionsAllow(perms, action, resource, constraints) — so the mock models the principal rather than the answer, exactly as #1744 did for the permission half.

Suggested work

  1. Wire HasPermissionForConstraintsAPI to real constraint evaluation rather than to decide(action, resource).
  2. Delete or correct the justification comment at mocks_test.go:410-412.
  3. Add a regression test that a constrained holder of a carved-out verb is refused a request outside its constraints, and confirm it fails against the current stub.

Expect fallout: handler tests that currently pass because constraints are ignored may start failing, and each such failure is a genuine gap worth triaging rather than papering over.

References

Found during independent adversarial review of PR #1758.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions