Skip to content

feat(security): Tier 5c — ACL / domain-object security [26.6.35]#43

Merged
ancongui merged 3 commits into
mainfrom
feat/spring-security-tier5c-acl
Jun 19, 2026
Merged

feat(security): Tier 5c — ACL / domain-object security [26.6.35]#43
ancongui merged 3 commits into
mainfrom
feat/spring-security-tier5c-acl

Conversation

@ancongui

Copy link
Copy Markdown
Contributor

Tier 5c — ACL / domain-object security

The Rust analog of spring-security-acl: answering hasPermission(object, permission) from per-object access-control lists. Pure Rust — no new dependencies. Fully additive.

What's new

  • Permission — the BasePermission bitmask (READ=1, WRITE=2, CREATE=4, DELETE=8, ADMINISTRATION=16), with cumulative union, bit-contains, and case-insensitive name parsing.
  • Sid (Principal / Authority — Spring's PrincipalSid / GrantedAuthoritySid), ObjectIdentity (type + identifier), AccessControlEntry, and Acl (owner + ordered ACEs + optional parent for inheritance).
  • AclService + InMemoryAclService (Spring's MutableAclService) and the free is_granted resolver.
  • AclPermissionEvaluator — bridges an AclService to the Tier 3 PermissionEvaluator, resolving hasPermission(...) against per-object ACLs by object reference or (type, id). The principal + roles/authorities map to PrincipalSid / GrantedAuthoritySid (each role matched both bare and ROLE_-prefixed).
  • PermissionEvaluator::has_permission_for_id + the free has_permission_for_id — Spring's id-based hasPermission overload (default-deny, backward compatible).

Security / correctness

  • Default-deny: a permission is granted only when an applicable granting entry is found (locally or up the inheritance chain); the first entry matching (sid, permission) wins, so a deny placed before a grant takes precedence (Spring's DefaultPermissionGrantingStrategy).
  • The inheritance walk is bounded, so a cyclic / pathologically deep parent chain terminates and denies rather than looping.
  • ACE matching is bit-containment (a documented, deliberate divergence from Spring's exact-mask default — Spring's documented bitwise strategy).

Verification

  • 11 unit tests (incl. deny-precedence, inheritance on/off, the cycle guard, grant-by-principal / grant-by-authority). fmt + clippy -D warnings clean. Full-workspace cargo check clean at 26.6.35.
  • Adversarially reviewed before release (20-agent, 4 dimensions): 4 findings confirmed (all low). All fixed here:
    • zero-mask required permission no longer satisfied by any ACE (default-deny);
    • the principal SID is only mapped for an authenticated caller (a blank/anonymous principal can't match a misconfigured Sid::Principal("")/("anonymous") ACE);
    • documented the bit-containment-vs-exact-match divergence + pinned it with a regression test;
    • read_acl returns Arc<Acl> (refcount bump instead of deep clone; shorter lock hold).

Docs

  • Parity appendix (EN + ES) updated with the ACL row + section; SAML2 split into its own (planned) row. CHANGELOG v26.6.35; MODULES.md; rebuilt book PDF + EPUB (EN + ES).

Note on Tier 5b (SAML2)

SAML2 is parked on feat/spring-security-tier5b-saml2 (not merged): the SP-side registration + signed-response verification is implemented and works, but samael 0.0.21's in-process signing segfaults against libxmlsec1 1.3.x, so release waits for a stable Rust XML-Security stack.

Andres Contreras added 3 commits June 20, 2026 00:29
Spring Security's spring-security-acl, pure-Rust (no new dependencies):

- Permission bitmask (BasePermission: READ/WRITE/CREATE/DELETE/ADMINISTRATION),
  cumulative union, bit-containment check, and case-insensitive name parsing
  (the bridge from a method-security `hasPermission(obj, "read")` string).
- Sid (Principal / Authority — Spring's PrincipalSid / GrantedAuthoritySid),
  ObjectIdentity (type + identifier), AccessControlEntry (sid + permission +
  granting), and Acl (owner + ordered ACEs + optional parent for inheritance).
- AclService + InMemoryAclService (Spring's MutableAclService). is_granted()
  resolves the inheritance chain default-deny, first-matching-ACE-wins (a deny
  ACE placed before a grant takes precedence), and is bounded against cyclic /
  pathologically deep parent chains (MAX_INHERITANCE_DEPTH).
- AclPermissionEvaluator bridges an AclService to the Tier 3 PermissionEvaluator
  (both the object-based and id-based forms); it maps the principal plus its
  roles/authorities to PrincipalSid / GrantedAuthoritySid (each role matched both
  bare and ROLE_-prefixed).
- Extends PermissionEvaluator with Spring's id-based hasPermission overload
  (has_permission_for_id, defaulted to deny — backward compatible) plus the
  matching free function.

8 tests (6 acl + 2 permission), incl. deny-precedence, inheritance on/off, the
cycle guard, and grant-by-principal / grant-by-authority. fmt + clippy clean.
Applies the four confirmed findings from the multi-agent adversarial review
of the ACL module (all low severity — the design is fail-closed and internally
consistent; no bypasses):

- Permission::contains() now rejects a zero (no-bits) required mask, which the
  bitwise check `(self & req) == req` previously treated as satisfied by any
  matching ACE. Only reachable via Permission::from_mask(0), but it defeated
  default-deny — now never granted.
- AclPermissionEvaluator maps the principal to a Sid::Principal only when the
  authentication is actually authenticated, so an unauthenticated / anonymous
  caller (principal "" or "anonymous") can no longer match a misconfigured
  Sid::Principal("") / Sid::Principal("anonymous") ACE. Anonymous access is
  still grantable deliberately via a Sid::Authority entry.
- Documented the deliberate divergence from Spring's exact-mask-equality default:
  Firefly matches by bit-containment (Spring's documented bitwise strategy),
  and pinned it with a cumulative-grant regression test.
- AclService::read_acl now returns Option<Arc<Acl>> (InMemoryAclService stores
  Arc<Acl>), so a lookup and each inheritance hop is a refcount bump instead of
  a deep clone, and the store's lock is held only briefly.

Adds 3 regression tests (zero-mask deny, unauthenticated/anonymous deny,
cumulative-grant containment). 11 acl+permission tests pass; fmt + clippy clean.
Bumps the workspace 26.6.34 -> 26.6.35 and ships the Tier 5c docs:

- Parity appendix (EN + ES): ACL row marked supported + a dedicated
  "Domain-object security (ACL)" section; SAML2 split out as a separate
  (planned) row noting the SP implementation pending a stable XML-Security stack.
- CHANGELOG v26.6.35 entry.
- MODULES.md firefly-security ACL note.
- Rebuilt book PDF + EPUB (EN + ES).
@ancongui ancongui merged commit 729f618 into main Jun 19, 2026
4 checks passed
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.

1 participant