Apica-Pawan-2026 opened a new pull request, #1763:
URL: https://github.com/apache/casbin/pull/1763

   [OPL-14504](https://apicasystems.atlassian.net/browse/OPL-14504)
   
   ## Problem
   
   `GetImplicitAscentPermissionsForUser` and 
`GetNamedImplicitAscentPermissionsForUser`
   are Apica-only additions to the base `Enforcer` (`rbac_api.go:339` and 
`:347`).
   Neither has a `SyncedEnforcer` override, so Go promotes them straight 
through to
   the unlocked base.
   
   The effect is a `SyncedEnforcer` that is only partly synced. A caller doing 
this:
   
   ```go
   roles, _ := enforcer.GetImplicitRolesForUser(user)              // 
overridden, locked
   perms, _ := enforcer.GetImplicitAscentPermissionsForUser(user)  // promoted, 
NOT locked
   
   gets protection on the first call and none on the second, with nothing in 
the type
   system or the compiler to indicate it. That is the shape of the consumer in
   ApicaSystem/flash (utils/cluster/auth.go:253 and :275), which runs on every 
API
   request against an enforcer that background goroutines reload.
   
   Change
   
   Two overrides in rbac_api_synced.go, delegating to the base implementation 
under
   the enforcer's lock, in the same style as the rest of the file.
   
   Why the write lock
   
   Lock rather than RLock, matching the sibling GetImplicitPermissionsForUser
   at rbac_api_synced.go:170.
   
   The base path can reach model.GetFieldIndex, which memoises into
   assertion.FieldIndexMap (model/model.go:436) — a plain map write. For a model
   with no dom token that write is unreachable, because GetFieldIndex returns 
the
   error at model.go:433 first. But these are general-purpose exported methods, 
and
   their thread safety should not depend on the caller's model text. Verified 
both
   ways: a dom-bearing model under -race shows Write at model/model.go:436 from
   concurrent readers; the no-dom case leaves FieldIndexMap empty.
   
   The cost is real — roughly 6x on an isolated auth-path microbenchmark versus
   RLock — and was accepted deliberately in favour of safety that holds for any
   model.
   
   No self-deadlock
   
   Both overrides delegate via the explicit e.Enforcer.X(...) selector, which
   resolves to the base type. The inner GetAllImplicitRoles and
   GetImplicitRolesForUser calls therefore never re-enter the synced wrapper, 
so the
   non-reentrant RWMutex is acquired exactly once per call. Confirmed with a 
probe
   that times out if the call fails to return.
   
   Verification
   
   - go build ./... and go vet ./... clean
   - gofmt clean
   - Race-detector probe with concurrent readers and LoadPolicy writers: clean 
with
     the overrides, 17 data races without them
   - Against the flash consumer: the deadlock probe returns; removing the 
overrides
     reproduces the unlocked base path


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to