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]
