CasbinPolicySync uses CasbinPolicySyncEdgeTrait, CasbinPolicySyncPolicyTrait, CasbinPolicySyncRoleTrait, CasbinPolicySyncServiceTrait, CasbinPolicySyncUserTrait, EnforcerTrait, LoggerTrait, PermissionsModelTrait, PoliciesModelTrait, PolicyHasPermissionsTrait, RoleHasPermissionsTrait, RoleHasPoliciesTrait, RolesModelTrait, ServiceHasPermissionsTrait, ServiceHasPoliciesTrait, ServicesModelTrait, UserHasPermissionsTrait, UsersModelTrait
Synchronizes Casbin policies in real-time when edge relations are modified.
Coordinator class : every actual sync logic lives in one of the per-domain traits (edge dispatcher, role, user, service, policy). This class carries the dependencies (Enforcer + Arango models + edge collections + logger) by composing the canonical XxxModelTrait, XxxEdgesTrait, EnforcerTrait and LoggerTrait families, then wires them through the standard $init / $container init pattern shared by every other auth-side service.
Tags
Table of Contents
Constants
- DOMAIN : string = 'domain'
- Initialization key for the Casbin domain (e.g. the active API identifier such as `my-api`). Forwarded to every implicit-permission lookup performed by the per-domain sync traits.
- PERMISSIONS_MODEL : string = 'permissionsModel'
- Initialization key for the permissions Documents model.
- POLICIES_MODEL : string = 'policiesModel'
- Initialization key for the policies Documents model.
- POLICY_HAS_PERMISSIONS : string = 'policyHasPermissions'
- Initialization key for the policy_has_permissions Edges model.
- ROLE_HAS_PERMISSIONS : string = 'roleHasPermissions'
- Initialization key for the role_has_permissions Edges model.
- ROLE_HAS_POLICIES : string = 'roleHasPolicies'
- Initialization key for the role_has_policies Edges model.
- ROLES_MODEL : string = 'rolesModel'
- Initialization key for the roles Documents model.
- SERVICE_HAS_PERMISSIONS : string = 'serviceHasPermissions'
- Initialization key for the service_has_permissions Edges model.
- SERVICE_HAS_POLICIES : string = 'serviceHasPolicies'
- Initialization key for the service_has_policies Edges model.
- SERVICES_MODEL : string = 'servicesModel'
- Initialization key for the services Documents model.
- USER_HAS_PERMISSIONS : string = 'userHasPermissions'
- Initialization key for the user_has_permissions Edges model.
- USERS_MODEL : string = 'usersModel'
- Initialization key for the users Documents model.
Properties
- $domain : string
- The Casbin domain (= the active API identifier) the policies live in.
- $permissionsModel : Documents|null
- The permissions Documents model.
- $policiesModel : Documents|null
- The policies Documents model.
- $policyHasPermissions : Edges|null
- The policy_has_permissions Edges model.
- $roleHasPermissions : Edges|null
- The role_has_permissions Edges model.
- $roleHasPolicies : Edges|null
- The role_has_policies Edges model.
- $rolesModel : Documents|null
- The roles Documents model.
- $serviceHasPermissions : Edges|null
- The service_has_permissions Edges model.
- $serviceHasPolicies : Edges|null
- The service_has_policies Edges model.
- $servicesModel : Documents|null
- The services Documents model.
- $userHasPermissions : Edges|null
- The user_has_permissions Edges model.
- $usersModel : Documents|null
- The users Documents model.
Methods
- __construct() : mixed
- Creates a new CasbinPolicySync instance.
- cleanupPermissionDerivedPolicies() : void
- Removes every Casbin policy derived from a permission about to be deleted.
- cleanupPolicyDerivedPolicies() : void
- Removes every Casbin policy derived from attaching the given policy to a subject (service OR role), for every subject currently holding the policy.
- register() : void
- Registers this sync on an edge model's insert/delete signals.
- registerPermissionDelete() : void
- Registers cleanup of Casbin tuples when a permission vertex is about to be deleted.
- registerPolicyDelete() : void
- Registers cleanup of Casbin tuples when a policy vertex is about to be deleted.
- registerRoleDelete() : void
- Registers cleanup of Casbin policies / groupings when a role vertex is deleted.
- registerServiceDelete() : void
- Registers cleanup of Casbin policies / groupings when a service vertex is deleted.
- registerUserDelete() : void
- Registers cleanup of Casbin policies / groupings when a user vertex is deleted.
- addPolicyPermissionPolicy() : void
- Propagates a `policy_has_permissions` insertion to every subject (service or role) currently attached to the policy.
- addRolePermissionPolicy() : void
- Adds: p, roleIdentifier, domain, object, action, effect
- addRolePolicyPolicies() : void
- Adds policies for every permission of a policy attached to a role.
- addServicePermissionPolicy() : void
- Adds a direct permission policy for a service.
- addServicePolicyPolicies() : void
- Adds Casbin policies for every permission of a policy attached to a service.
- addUserPermissionPolicy() : void
- Adds: p, userId (identifier), domain, object, action, effect
- addUserRoleGrouping() : void
- Adds: g, userIdentifier, roleIdentifier, domain
- initializePermissionsModel() : static
- Initializes the permissions model dependency from the $init array.
- initializePoliciesModel() : static
- Initializes the policies model dependency from the $init array.
- initializePolicyHasPermissions() : static
- Initializes the policy_has_permissions edges dependency from the $init array.
- initializeRoleHasPermissions() : static
- Initializes the role_has_permissions edges dependency from the $init array.
- initializeRoleHasPolicies() : static
- Initializes the role_has_policies edges dependency from the $init array.
- initializeRolesModel() : static
- Initializes the roles model dependency from the $init array.
- initializeServiceHasPermissions() : static
- Initializes the service_has_permissions edges dependency from the $init array.
- initializeServiceHasPolicies() : static
- Initializes the service_has_policies edges dependency from the $init array.
- initializeServicesModel() : static
- Initializes the services model dependency from the $init array.
- initializeUserHasPermissions() : static
- Initializes the user_has_permissions edges dependency from the $init array.
- initializeUsersModel() : static
- Initializes the users model dependency from the $init array.
- onEdgeDelete() : void
- Called when an edge is deleted.
- onEdgeInsert() : void
- Called when a new edge is inserted.
- onPermissionDelete() : void
- Called before one or more permission vertices are deleted.
- onPolicyDelete() : void
- Called before one or more policy vertices are deleted.
- onRoleDelete() : void
- Called when a role vertex is deleted — wipes every Casbin trace of that role.
- onServiceDelete() : void
- Called when a service vertex is deleted — wipes every Casbin trace.
- onUserDelete() : void
- Called when a user vertex is deleted — wipes every Casbin trace of that user.
- removePolicyPermissionPolicy() : void
- Propagates a `policy_has_permissions` deletion to every service AND every role currently attached to the policy.
- removeRolePermissionPolicy() : void
- Removes: p, roleIdentifier, domain, object, action, effect
- removeRolePolicyPolicies() : void
- Removes policies for every permission of a policy detached from a role.
- removeServicePermissionPolicy() : void
- Removes a direct permission policy from a service.
- removeServicePolicyPolicies() : void
- Removes Casbin policies for every permission of a policy detached from a service.
- removeUserPermissionPolicy() : void
- Removes: p, userId (identifier), domain, object, action, effect
- removeUserRoleGrouping() : void
- Removes: g, userIdentifier, roleIdentifier, domain
- resolveRoleSubject() : string|null
- Resolves the Casbin subject for a role.
- resolveServicesForPolicy() : array<string|int, string>
- Returns the list of service `_key`s currently linked to the given policy via the `service_has_policies` edge collection.
- resolveServiceSubject() : string
- Resolves the namespaced Casbin subject for a service.
- resolveUserIdentifier() : string|null
- Resolves a user's identifier (Zitadel ID) from their ArangoDB _key.
- loadPolicyPermissionContext() : array{permission: object, serviceKeys: string[], roleKeys: string[], domain: string, object: string, action: string, effect: string}|null
- Loads the shared context used by `addPolicyPermissionPolicy` and `removePolicyPermissionPolicy` : looks up the permission, resolves every service AND role currently attached to the policy, and returns a precomputed bundle. Returns null when nothing can be done (no enforcer, missing permission, no attached subjects).
- purgePoliciesByEdges() : int
- Lists every edge of the given relation pointing to `$permissionId`, resolves each `_from` vertex into its Casbin subject through `$resolver`, and removes the matching `(subject, domain, object, action, effect)` policy via the Enforcer.
- purgePoliciesViaPolicy() : int
- Removes every Casbin policy derived from the permission via the indirect "policy materialised on subject" path: lists policies that hold the permission, then for each policy lists the services and roles that hold the policy, and removes the `(subject, domain, object, action, effect)` policy keyed on each of them.
- resolveRolesForPolicy() : array<string|int, string>
- Lists every role currently attached to the given policy via the `role_has_policies` edge.
Constants
DOMAIN
Initialization key for the Casbin domain (e.g. the active API identifier such as `my-api`). Forwarded to every implicit-permission lookup performed by the per-domain sync traits.
public
string
DOMAIN
= 'domain'
PERMISSIONS_MODEL
Initialization key for the permissions Documents model.
public
string
PERMISSIONS_MODEL
= 'permissionsModel'
POLICIES_MODEL
Initialization key for the policies Documents model.
public
string
POLICIES_MODEL
= 'policiesModel'
POLICY_HAS_PERMISSIONS
Initialization key for the policy_has_permissions Edges model.
public
string
POLICY_HAS_PERMISSIONS
= 'policyHasPermissions'
ROLE_HAS_PERMISSIONS
Initialization key for the role_has_permissions Edges model.
public
string
ROLE_HAS_PERMISSIONS
= 'roleHasPermissions'
ROLE_HAS_POLICIES
Initialization key for the role_has_policies Edges model.
public
string
ROLE_HAS_POLICIES
= 'roleHasPolicies'
ROLES_MODEL
Initialization key for the roles Documents model.
public
string
ROLES_MODEL
= 'rolesModel'
SERVICE_HAS_PERMISSIONS
Initialization key for the service_has_permissions Edges model.
public
string
SERVICE_HAS_PERMISSIONS
= 'serviceHasPermissions'
SERVICE_HAS_POLICIES
Initialization key for the service_has_policies Edges model.
public
string
SERVICE_HAS_POLICIES
= 'serviceHasPolicies'
SERVICES_MODEL
Initialization key for the services Documents model.
public
string
SERVICES_MODEL
= 'servicesModel'
USER_HAS_PERMISSIONS
Initialization key for the user_has_permissions Edges model.
public
string
USER_HAS_PERMISSIONS
= 'userHasPermissions'
USERS_MODEL
Initialization key for the users Documents model.
public
string
USERS_MODEL
= 'usersModel'
Properties
$domain
The Casbin domain (= the active API identifier) the policies live in.
protected
string
$domain
= ''
$permissionsModel
The permissions Documents model.
protected
Documents|null
$permissionsModel
= null
$policiesModel
The policies Documents model.
protected
Documents|null
$policiesModel
= null
$policyHasPermissions
The policy_has_permissions Edges model.
protected
Edges|null
$policyHasPermissions
= null
$roleHasPermissions
The role_has_permissions Edges model.
protected
Edges|null
$roleHasPermissions
= null
$roleHasPolicies
The role_has_policies Edges model.
protected
Edges|null
$roleHasPolicies
= null
$rolesModel
The roles Documents model.
protected
Documents|null
$rolesModel
= null
$serviceHasPermissions
The service_has_permissions Edges model.
protected
Edges|null
$serviceHasPermissions
= null
$serviceHasPolicies
The service_has_policies Edges model.
protected
Edges|null
$serviceHasPolicies
= null
$servicesModel
The services Documents model.
protected
Documents|null
$servicesModel
= null
$userHasPermissions
The user_has_permissions Edges model.
protected
Edges|null
$userHasPermissions
= null
$usersModel
The users Documents model.
protected
Documents|null
$usersModel
= null
Methods
__construct()
Creates a new CasbinPolicySync instance.
public
__construct([array<string|int, mixed> $init = [] ][, Container|null $container = null ]) : mixed
Parameters
- $init : array<string|int, mixed> = []
-
Init array.
- $container : Container|null = null
-
Optional DI container — strings in $init are resolved via $container->get().
Tags
cleanupPermissionDerivedPolicies()
Removes every Casbin policy derived from a permission about to be deleted.
public
cleanupPermissionDerivedPolicies(string $permissionKey) : void
Motivation — a permission is not itself a Casbin subject. It surfaces in
the (object, action, effect) tuple of every policy that references it,
keyed on a subject (role identifier, user identifier or service
identifier). When the permission vertex is deleted, the native cascade
purges the inbound edges (role_has_permissions,
user_has_permissions, service_has_permissions and
policy_has_permissions) via raw AQL REMOVE, which bypasses per-edge
afterDelete signals — so the corresponding removePolicy calls never
fire and policies survive in the rbac collection as orphan rows. Any
subject keyed on those rows (role / user / M2M service) keeps passing
Casbin checks against an object/action that no longer exists.
Strategy (approach B — targeted by edges) :
- Read the permission to capture
(domain, object, action, effect). - Walk each direct inbound edge type, resolve every subject's stable
identifier and call
removePolicy(subject, ...)once per match. - Walk
policy_has_permissionsto find policies that hold this permission, then for each policy walkservice_has_policies/role_has_policiesto find every subject that holds that policy, andremovePolicykeyed on the subject's identifier (this covers the indirect "policy materialised on subject" path that no direct edge expresses).
This method must be called before the permission vertex is removed from Arango — once the cascade fires, the inbound edges are gone and we can no longer enumerate the subjects to purge.
Parameters
- $permissionKey : string
-
The
_keyof the permission about to be deleted.
Tags
cleanupPolicyDerivedPolicies()
Removes every Casbin policy derived from attaching the given policy to a subject (service OR role), for every subject currently holding the policy.
public
cleanupPolicyDerivedPolicies(string $policyKey) : void
Motivation — policies are not Casbin subjects: when a policy is attached
to a service via service_has_policies (or to a role via
role_has_policies), the corresponding edge listener materialises one
Casbin policy per permission in the policy, keyed by the subject :
p, service:<service._key>, <domain>, <object>, <action>, <effect>
p, <roleIdentifier>, <domain>, <object>, <action>, <effect>
Deleting a policy triggers a cascade purge of the *_has_policies
edges via raw AQL REMOVE, which bypasses per-edge afterDelete
signals — so the derived policies would survive in the rbac
collection and M2M subjects would keep passing Casbin checks for
capabilities that no longer exist. Silent security gap.
This method must be called before the policy vertex is removed from
Arango (it reads policy.permissions and the current *_has_policies
edges, which both disappear once the cascade runs).
Parameters
- $policyKey : string
-
The
_keyof the policy about to be deleted.
Tags
register()
Registers this sync on an edge model's insert/delete signals.
public
register(Edges $edges, string $type) : void
Parameters
- $edges : Edges
-
The edge model to listen to.
- $type : string
-
The edge type — one of EdgeSyncType's constants.
registerPermissionDelete()
Registers cleanup of Casbin tuples when a permission vertex is about to be deleted.
public
registerPermissionDelete(Documents $permissionsModel) : void
Subscribes to the permissions model's beforeDelete signal (not
afterDelete like role / user / service): a permission is not itself
a Casbin subject, so the tuples to purge are keyed on the role / user /
service vertices that point to it. We must enumerate those subjects
via the inbound *_has_permissions and policy_has_permissions edges
before the cascade edge purge fires — once afterDelete runs,
the edges are gone and the subject set is unrecoverable.
Wired alongside the existing registerRoleDelete / registerUserDelete
/ registerServiceDelete so every delete path (HTTP controller, CLI
command, raw model call, future transitive cascade) automatically
cleans the rbac collection.
Parameters
- $permissionsModel : Documents
-
The permissions vertex model.
registerPolicyDelete()
Registers cleanup of Casbin tuples when a policy vertex is about to be deleted.
public
registerPolicyDelete(Documents $policiesModel) : void
Symmetric to registerPermissionDelete but for policies. Connects
to beforeDelete because the cleanup walks *_has_policies and
policy_has_permissions edges that the cascade purge wipes via raw
AQL (bypassing per-edge afterDelete signals).
Parameters
- $policiesModel : Documents
-
The policies vertex model.
registerRoleDelete()
Registers cleanup of Casbin policies / groupings when a role vertex is deleted.
public
registerRoleDelete(Documents $rolesModel) : void
The cascade edge purge wired on the Roles model (role_has_permissions,
user_has_roles via RolesController::CASCADE_EDGES) removes the edges
with a raw AQL query, which bypasses per-edge afterDelete signals.
As a consequence the normal edge-level Casbin sync is never triggered
for those cascaded deletes and policies/groupings would leak.
This listener subscribes to the role model's afterDelete signal and
calls Enforcer::deleteRole($name) for every deleted role — which wipes
both the p, <roleName>, ... policies and the g, <userId>, <roleName>, ...
groupings in one shot.
Parameters
- $rolesModel : Documents
-
The roles vertex model.
registerServiceDelete()
Registers cleanup of Casbin policies / groupings when a service vertex is deleted.
public
registerServiceDelete(Documents $servicesModel) : void
The cascade edge purge wired on the Services model
(service_has_policies, service_has_permissions) removes the
edges with a raw AQL REMOVE, which bypasses per-edge
afterDelete signals — so the normal edge-level Casbin sync
is never triggered for those cascaded deletes and policies
would leak. This handler subscribes to the service model's
afterDelete signal and calls Enforcer::deleteUser($subject)
keyed on the service's namespaced subject (service:{_key}).
Parameters
- $servicesModel : Documents
-
The services vertex model.
registerUserDelete()
Registers cleanup of Casbin policies / groupings when a user vertex is deleted.
public
registerUserDelete(Documents $usersModel) : void
Symmetric to CasbinPolicySyncRoleTrait::registerRoleDelete but for
users. The cascade edge purge wired on the Users model
(user_has_roles, user_has_permissions via
UsersController::CASCADE_EDGES) removes the edges with a raw AQL
query, which bypasses per-edge afterDelete signals — so the
edge-level Casbin sync never fires and any
g, <userIdentifier>, <roleIdentifier>, ... grouping or
p, <userIdentifier>, ... direct policy would survive the deletion as
orphaned data (silent security gap on M2M flows that rely on the
user's identifier).
This listener subscribes to the users model's afterDelete signal and
calls Enforcer::deleteUser($identifier) which purges both the user's
groupings AND any direct user→permission policy in one shot.
Parameters
- $usersModel : Documents
-
The users vertex model.
addPolicyPermissionPolicy()
Propagates a `policy_has_permissions` insertion to every subject (service or role) currently attached to the policy.
protected
addPolicyPermissionPolicy(string $policyKey, string $permissionKey) : void
Walks both service_has_policies and role_has_policies for the
policy, then for each attached subject adds one Casbin row :
p, service:<service._key>, <domain>, <object>, <action>, <effect>
p, <roleIdentifier>, <domain>, <object>, <action>, <effect>
Without this propagation, an admin attaching a permission to a policy
would only update the rbac collection at the next full materialization
(php bin/console.php auth:materialize) — subjects already attached to the policy
would keep operating on a stale permission set until then.
Casbin's addPolicy is idempotent (returns false on duplicates) so a
subject already granted the same permission via another source (a
sibling policy or a direct *_has_permissions edge) keeps a single
row in rbac.
Parameters
- $policyKey : string
-
The policy ArangoDB
_key. - $permissionKey : string
-
The permission ArangoDB
_key.
Tags
addRolePermissionPolicy()
Adds: p, roleIdentifier, domain, object, action, effect
protected
addRolePermissionPolicy(string $roleKey, string $permissionKey) : void
The Casbin subject for role-level policies is the role's stable
identifier (Zitadel role key), never the mutable name. This way
a PATCH rename never has to rewrite policies — name drifts but
identifier is set once at POST time and pinned forever.
Legacy fallback: for roles created before the identifier pin
convention landed (typically the 3 seeded ones: admin/guest/superadmin
when not yet backfilled) we fall back to $role->name, which is
their current Zitadel key. Backfill command:
php bin/console.php auth:roles:backfill-identifiers.
Parameters
- $roleKey : string
- $permissionKey : string
Tags
addRolePolicyPolicies()
Adds policies for every permission of a policy attached to a role.
protected
addRolePolicyPolicies(string $roleKey, string $policyKey) : void
When a policy vertex is attached to a role via the
role_has_policies edge, every permission contained in the policy
must be materialised in Casbin keyed on the role's stable
identifier — otherwise getImplicitPermissionsForUser will not
walk through policy_has_permissions and the role's effective
permission set silently misses the policy's permissions.
Without this method, the M2M branch (service → policy) is the
only path Casbin sees, and self-service callers whose effective
permissions depend on a role-attached policy fail
validatePolicyAttachmentPermissions even though they have the
permission "on paper" (in the role's policies tab).
Parameters
- $roleKey : string
-
The role ArangoDB
_key. - $policyKey : string
-
The policy ArangoDB
_key.
Tags
addServicePermissionPolicy()
Adds a direct permission policy for a service.
protected
addServicePermissionPolicy(string $serviceKey, string $permissionKey) : void
Parameters
- $serviceKey : string
-
The service ArangoDB
_key. - $permissionKey : string
-
The permission ArangoDB
_key.
Tags
addServicePolicyPolicies()
Adds Casbin policies for every permission of a policy attached to a service.
protected
addServicePolicyPolicies(string $serviceKey, string $policyKey) : void
Subject : namespaced service:{_key} — see the trait header for
why the prefix is load-bearing.
Parameters
- $serviceKey : string
-
The service ArangoDB
_key. - $policyKey : string
-
The policy ArangoDB
_key.
Tags
addUserPermissionPolicy()
Adds: p, userId (identifier), domain, object, action, effect
protected
addUserPermissionPolicy(string $userKey, string $permissionKey) : void
Parameters
- $userKey : string
- $permissionKey : string
Tags
addUserRoleGrouping()
Adds: g, userIdentifier, roleIdentifier, domain
protected
addUserRoleGrouping(string $userKey, string $roleKey) : void
Parameters
- $userKey : string
- $roleKey : string
Tags
initializePermissionsModel()
Initializes the permissions model dependency from the $init array.
protected
initializePermissionsModel(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializePoliciesModel()
Initializes the policies model dependency from the $init array.
protected
initializePoliciesModel(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializePolicyHasPermissions()
Initializes the policy_has_permissions edges dependency from the $init array.
protected
initializePolicyHasPermissions(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializeRoleHasPermissions()
Initializes the role_has_permissions edges dependency from the $init array.
protected
initializeRoleHasPermissions(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializeRoleHasPolicies()
Initializes the role_has_policies edges dependency from the $init array.
protected
initializeRoleHasPolicies(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializeRolesModel()
Initializes the roles model dependency from the $init array.
protected
initializeRolesModel(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializeServiceHasPermissions()
Initializes the service_has_permissions edges dependency from the $init array.
protected
initializeServiceHasPermissions(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializeServiceHasPolicies()
Initializes the service_has_policies edges dependency from the $init array.
protected
initializeServiceHasPolicies(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializeServicesModel()
Initializes the services model dependency from the $init array.
protected
initializeServicesModel(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializeUserHasPermissions()
Initializes the user_has_permissions edges dependency from the $init array.
protected
initializeUserHasPermissions(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticinitializeUsersModel()
Initializes the users model dependency from the $init array.
protected
initializeUsersModel(array<string|int, mixed> $init, Container|null $container) : static
Parameters
- $init : array<string|int, mixed>
-
The initialization array.
- $container : Container|null
-
The DI container.
Tags
Return values
staticonEdgeDelete()
Called when an edge is deleted.
protected
onEdgeDelete(Payload $payload, string $type) : void
Parameters
- $payload : Payload
- $type : string
Tags
onEdgeInsert()
Called when a new edge is inserted.
protected
onEdgeInsert(Payload $payload, string $type) : void
Parameters
- $payload : Payload
- $type : string
Tags
onPermissionDelete()
Called before one or more permission vertices are deleted.
protected
onPermissionDelete(Payload $payload) : void
The payload context mirrors the $init array passed to
Documents::delete() — the keys to purge live in Arango::VALUE
(always normalised to an array by DocumentsControllerDeleteTrait).
Each key triggers the existing edge-walk + Enforcer purge.
Parameters
- $payload : Payload
onPolicyDelete()
Called before one or more policy vertices are deleted.
protected
onPolicyDelete(Payload $payload) : void
Same payload contract as onPermissionDelete: keys come from
context[Arango::VALUE]. Each key triggers the existing
service / role attached-subjects purge.
Parameters
- $payload : Payload
onRoleDelete()
Called when a role vertex is deleted — wipes every Casbin trace of that role.
protected
onRoleDelete(Payload $payload) : void
The payload data is the OLD document (or list of documents) returned
by the ArangoDB REMOVE query, so role names are still accessible even
though the vertex is gone.
Parameters
- $payload : Payload
onServiceDelete()
Called when a service vertex is deleted — wipes every Casbin trace.
protected
onServiceDelete(Payload $payload) : void
The payload data is the OLD document (or list of documents)
returned by the ArangoDB REMOVE query, so the service's _key
is still accessible even though the vertex is gone.
Enforcer::deleteUser($subject) purges every
p, <subject>, <domain>, <object>, <action>, <effect> policy keyed
on the service's namespaced subject — covering both
service_policy derived policies and service_permission direct
policies in a single call.
Parameters
- $payload : Payload
onUserDelete()
Called when a user vertex is deleted — wipes every Casbin trace of that user.
protected
onUserDelete(Payload $payload) : void
The payload data is the OLD document (or list of documents) returned
by the ArangoDB REMOVE query, so the Zitadel identifier is still
accessible even though the vertex is gone.
Enforcer::deleteUser($subject) purges in one shot:
- every
g, <subject>, *, *grouping (role assignments removed when the cascade-purgeduser_has_rolesedges silently bypass per-edgeafterDeletesignals) ; - every
p, <subject>, *, *, *, *direct user→permission policy (created viauser_has_permissions, equally silent under cascade).
Parameters
- $payload : Payload
removePolicyPermissionPolicy()
Propagates a `policy_has_permissions` deletion to every service AND every role currently attached to the policy.
protected
removePolicyPermissionPolicy(string $policyKey, string $permissionKey) : void
Symmetric to addPolicyPermissionPolicy: removes the
(subject, domain, object, action, effect) row keyed on each
attached subject (the service's namespaced service:{_key} or
the role identifier). Subjects that grant the same permission
via a sibling policy or a direct *_has_permissions edge will
lose the row here — this is a known limitation shared with the
existing removeService* / removeRole* handlers (no overlap
audit). A full reseed (php bin/console.php auth:materialize) restores the
rows from any remaining source.
Parameters
- $policyKey : string
-
The policy ArangoDB
_key. - $permissionKey : string
-
The permission ArangoDB
_key.
Tags
removeRolePermissionPolicy()
Removes: p, roleIdentifier, domain, object, action, effect
protected
removeRolePermissionPolicy(string $roleKey, string $permissionKey) : void
Same identifier-vs-name convention as addRolePermissionPolicy.
Parameters
- $roleKey : string
- $permissionKey : string
Tags
removeRolePolicyPolicies()
Removes policies for every permission of a policy detached from a role.
protected
removeRolePolicyPolicies(string $roleKey, string $policyKey) : void
Mirror of addRolePolicyPolicies.
Parameters
- $roleKey : string
-
The role ArangoDB
_key. - $policyKey : string
-
The policy ArangoDB
_key.
Tags
removeServicePermissionPolicy()
Removes a direct permission policy from a service.
protected
removeServicePermissionPolicy(string $serviceKey, string $permissionKey) : void
Parameters
- $serviceKey : string
-
The service ArangoDB
_key. - $permissionKey : string
-
The permission ArangoDB
_key.
Tags
removeServicePolicyPolicies()
Removes Casbin policies for every permission of a policy detached from a service.
protected
removeServicePolicyPolicies(string $serviceKey, string $policyKey) : void
Parameters
- $serviceKey : string
-
The service ArangoDB
_key. - $policyKey : string
-
The policy ArangoDB
_key.
Tags
removeUserPermissionPolicy()
Removes: p, userId (identifier), domain, object, action, effect
protected
removeUserPermissionPolicy(string $userKey, string $permissionKey) : void
Parameters
- $userKey : string
- $permissionKey : string
Tags
removeUserRoleGrouping()
Removes: g, userIdentifier, roleIdentifier, domain
protected
removeUserRoleGrouping(string $userKey, string $roleKey) : void
Parameters
- $userKey : string
- $roleKey : string
Tags
resolveRoleSubject()
Resolves the Casbin subject for a role.
protected
resolveRoleSubject(string $roleKey) : string|null
Roles created since the Zitadel pin convention carry their stable
identifier (Zitadel role key = Arango _key of the role vertex).
Legacy roles (admin / guest / superadmin seeded before the pin
landed, not yet backfilled) fall back to name, which is their
current Zitadel key — safe for Casbin. Once the backfill command
php bin/console.php auth:roles:backfill-identifiers has run, every role has an
identifier and the fallback becomes dead code.
Parameters
- $roleKey : string
-
The ArangoDB
_keyof the role vertex.
Tags
Return values
string|null —null if the role vertex is gone.
resolveServicesForPolicy()
Returns the list of service `_key`s currently linked to the given policy via the `service_has_policies` edge collection.
protected
resolveServicesForPolicy(string $policyKey) : array<string|int, string>
Used by the policy-permission propagation handlers when a perm is added to / removed from a policy : every service holding that policy needs its Casbin tuples kept in sync.
Parameters
- $policyKey : string
-
The policy ArangoDB
_key.
Return values
array<string|int, string> —Deduplicated service _keys.
resolveServiceSubject()
Resolves the namespaced Casbin subject for a service.
protected
resolveServiceSubject(string $serviceKey) : string
The service: prefix partitions Service Account RBAC tuples
from raw user identifiers — a security load-bearing invariant
since a leaked human token must never match a Service Account's
RBAC bundle. The middleware writes the same prefix into
ATTR_USER_ID for incoming JWTs (see
CheckJwtAuthentication::process()).
Parameters
- $serviceKey : string
-
The service ArangoDB
_key.
Return values
string —Casbin-safe subject : service:{_key} (passed
through safeSubject for the digit-only quirk).
resolveUserIdentifier()
Resolves a user's identifier (Zitadel ID) from their ArangoDB _key.
protected
resolveUserIdentifier(string $userKey) : string|null
Parameters
- $userKey : string
Tags
Return values
string|null —null if the user has no identifier set.
loadPolicyPermissionContext()
Loads the shared context used by `addPolicyPermissionPolicy` and `removePolicyPermissionPolicy` : looks up the permission, resolves every service AND role currently attached to the policy, and returns a precomputed bundle. Returns null when nothing can be done (no enforcer, missing permission, no attached subjects).
private
loadPolicyPermissionContext(string $policyKey, string $permissionKey) : array{permission: object, serviceKeys: string[], roleKeys: string[], domain: string, object: string, action: string, effect: string}|null
Lifted out of the two propagation handlers to avoid duplicating the early-return + lookup + (domain, object, action, effect) extraction. Keeps both handlers down to a clean two-loop body.
Parameters
- $policyKey : string
-
The policy ArangoDB
_key. - $permissionKey : string
-
The permission ArangoDB
_key.
Tags
Return values
array{permission: object, serviceKeys: string[], roleKeys: string[], domain: string, object: string, action: string, effect: string}|nullpurgePoliciesByEdges()
Lists every edge of the given relation pointing to `$permissionId`, resolves each `_from` vertex into its Casbin subject through `$resolver`, and removes the matching `(subject, domain, object, action, effect)` policy via the Enforcer.
private
purgePoliciesByEdges(Edges $edges, string $permissionId, callable $resolver, string $domain, string $object, string $action, string $effect) : int
Used by cleanupPermissionDerivedPolicies for the three direct
inbound relations: role_has_permissions, user_has_permissions and
service_has_permissions.
Parameters
- $edges : Edges
-
The inbound edge relation.
- $permissionId : string
-
The permission's
_id(e.g.permissions/123). - $resolver : callable
-
Maps an Arango
_keyto its Casbin subject string, or null if the subject cannot be resolved. - $domain : string
- $object : string
- $action : string
- $effect : string
Tags
Return values
int —The number of policies actually removed.
purgePoliciesViaPolicy()
Removes every Casbin policy derived from the permission via the indirect "policy materialised on subject" path: lists policies that hold the permission, then for each policy lists the services and roles that hold the policy, and removes the `(subject, domain, object, action, effect)` policy keyed on each of them.
private
purgePoliciesViaPolicy(string $permissionId, string $domain, string $object, string $action, string $effect) : int
Used by cleanupPermissionDerivedPolicies. Direct edges
are already covered by purgePoliciesByEdges; this method
covers the case where the policy was materialised by the
service_policy / role_policy listener and no direct
service_has_permissions / role_has_permissions edge ever
existed.
Parameters
- $permissionId : string
- $domain : string
- $object : string
- $action : string
- $effect : string
Tags
Return values
int —The number of policies actually removed.
resolveRolesForPolicy()
Lists every role currently attached to the given policy via the `role_has_policies` edge.
private
resolveRolesForPolicy(string $policyKey) : array<string|int, string>
Parameters
- $policyKey : string
-
The policy ArangoDB
_key.
Return values
array<string|int, string> —Distinct role _keys — empty array on failure or
when no role is attached.