feat(security): Tier 5c — ACL / domain-object security [26.6.35]#43
Merged
Conversation
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tier 5c — ACL / domain-object security
The Rust analog of
spring-security-acl: answeringhasPermission(object, permission)from per-object access-control lists. Pure Rust — no new dependencies. Fully additive.What's new
Permission— theBasePermissionbitmask (READ=1,WRITE=2,CREATE=4,DELETE=8,ADMINISTRATION=16), with cumulativeunion, bit-contains, and case-insensitive name parsing.Sid(Principal/Authority— Spring'sPrincipalSid/GrantedAuthoritySid),ObjectIdentity(type+identifier),AccessControlEntry, andAcl(owner + ordered ACEs + optional parent for inheritance).AclService+InMemoryAclService(Spring'sMutableAclService) and the freeis_grantedresolver.AclPermissionEvaluator— bridges anAclServiceto the Tier 3PermissionEvaluator, resolvinghasPermission(...)against per-object ACLs by object reference or(type, id). The principal + roles/authorities map toPrincipalSid/GrantedAuthoritySid(each role matched both bare andROLE_-prefixed).PermissionEvaluator::has_permission_for_id+ the freehas_permission_for_id— Spring's id-basedhasPermissionoverload (default-deny, backward compatible).Security / correctness
(sid, permission)wins, so a deny placed before a grant takes precedence (Spring'sDefaultPermissionGrantingStrategy).Verification
fmt+clippy -D warningsclean. Full-workspacecargo checkclean at 26.6.35.Sid::Principal("")/("anonymous")ACE);read_aclreturnsArc<Acl>(refcount bump instead of deep clone; shorter lock hold).Docs
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.