UserRolePolicy
in package
Gates over user roles: pure decisions without DB (the data comes from {@see \App\UI\Api\System\Models\Managers\UserManager}).
- Role changes (self::assignmentRefusal()):
- only a caller holding the "set roles" privilege (
:Api:System:User:setRoles, granted to the admin role only) may change the role set of a user. Without it any change of the set is refused, adding and removing alike, even with a forged POST; saving with the same set (or withoutmeta.roleIds) passes. The right to edit the user (other fields) was already checked by the Api gate of thesaveaction; - a role with
assignable = 0(owner, receiver, guest: implicit or conditional grants) is assigned by nobody, neither a privileged caller nor internal code; removing a mistakenly created link is allowed (with the privilege); - an unknown role id is not assigned (it would end in a foreign key failure);
- the baseline when creating is the roles a new account gets by itself (
set_after_registration); a caller without the privilege may send only those (or nothing), otherwise[]would silently drop the default roles; $canSetRoles === nullmeans no caller (cron/CLI calls the manager directly, the audience was not evaluated) and the privilege is not checked (same boundary as FieldAudienceContext).
- only a caller holding the "set roles" privilege (
- Protected role (
protect_last_holder = 1, currently admin):- self::lastHolderRefusal(): the role cannot be removed from its last active holder and that account
cannot be deactivated, banned or canceled (self::deactivationRefusal() does the same in bulk via
updateColumns); - self::deleteRefusal(): the last active holder cannot be deleted. The caller's own account is never deleted. Applies to every caller including internal code. "Active" means an activated, non-banned, non-canceled account without a pending cancellation request (counted by UserRelationManager).
- self::lastHolderRefusal(): the role cannot be removed from its last active holder and that account
cannot be deactivated, banned or canceled (self::deactivationRefusal() does the same in bulk via
Table of Contents
Methods
- assignmentRefusal() : Result|null
- deactivationRefusal() : Result|null
- Refuses a bulk deactivation / ban / cancellation request (`updateColumns`) when the affected accounts are all the active holders of some protected role.
- defaultRoleIds() : array<int, int>
- Roles a new account gets by itself ({@see \App\UI\Api\System\Models\Managers\UserRelationManager::grantDefaultRoles()}).
- deleteRefusal() : Result|null
- ids() : array<int, int>
- Returns unique positive integer ids (same normalization as the set-based link sync).
- lastHolderRefusal() : Result|null
- Refuses an account save that would leave a protected role without its last active holder: removing that role (`USER_ROLE_LAST_HOLDER_BLOCKED`), or deactivating / banning / canceling the account that still holds it (`USER_DEACTIVATE_LAST_HOLDER_BLOCKED`).
- deactivateRefusal() : Result
- roleLosingAllHolders() : int|null
- Returns the first protected role whose active holders are all among `$userIds` (and at least one is).
Methods
assignmentRefusal()
public
static assignmentRefusal(array<int, int> $currentIds, array<string|int, mixed> $requestedIds, array<int, array{assignable: bool, default: bool, protectLastHolder: bool}> $roles, bool $isInsert, bool|null $canSetRoles) : Result|null
Parameters
- $currentIds : array<int, int>
-
roles the target account has now (ignored when creating)
- $requestedIds : array<string|int, mixed>
-
requested resulting set (
meta.roleIds) - $roles : array<int, array{assignable: bool, default: bool, protectLastHolder: bool}>
-
role catalog (id to flags)
- $isInsert : bool
- $canSetRoles : bool|null
-
whether the caller holds
:Api:System:User:setRoles; null = internal code without a caller
Return values
Result|nulldeactivationRefusal()
Refuses a bulk deactivation / ban / cancellation request (`updateColumns`) when the affected accounts are all the active holders of some protected role.
public
static deactivationRefusal(array<string|int, mixed> $userIds, array<int, array<int, int>> $activeHoldersByRole) : Result|null
Parameters
- $userIds : array<string|int, mixed>
- $activeHoldersByRole : array<int, array<int, int>>
Return values
Result|nulldefaultRoleIds()
Roles a new account gets by itself ({@see \App\UI\Api\System\Models\Managers\UserRelationManager::grantDefaultRoles()}).
public
static defaultRoleIds(array<int, array{assignable: bool, default: bool, protectLastHolder: bool}> $roles) : array<int, int>
Parameters
- $roles : array<int, array{assignable: bool, default: bool, protectLastHolder: bool}>
Return values
array<int, int>deleteRefusal()
public
static deleteRefusal(array<string|int, mixed> $deleteIds, int|null $callerId, array<int, array<int, int>> $activeHoldersByRole) : Result|null
Parameters
- $deleteIds : array<string|int, mixed>
-
ids of the accounts to delete
- $callerId : int|null
-
signed-in caller (null = internal code / anonymous)
- $activeHoldersByRole : array<int, array<int, int>>
-
protected role to ids of its active holders
Return values
Result|nullids()
Returns unique positive integer ids (same normalization as the set-based link sync).
public
static ids(array<string|int, mixed> $values) : array<int, int>
Parameters
- $values : array<string|int, mixed>
Return values
array<int, int>lastHolderRefusal()
Refuses an account save that would leave a protected role without its last active holder: removing that role (`USER_ROLE_LAST_HOLDER_BLOCKED`), or deactivating / banning / canceling the account that still holds it (`USER_DEACTIVATE_LAST_HOLDER_BLOCKED`).
public
static lastHolderRefusal(int $userId, array<int, int> $currentIds, array<string|int, mixed>|null $requestedIds, bool $staysActive, array<int, array<int, int>> $activeHoldersByRole) : Result|null
Parameters
- $userId : int
- $currentIds : array<int, int>
-
roles the account has now
- $requestedIds : array<string|int, mixed>|null
-
requested resulting set; null = roles unchanged
- $staysActive : bool
-
whether the account stays active after the save (activated, non-banned, non-canceled)
- $activeHoldersByRole : array<int, array<int, int>>
-
protected role to ids of its active holders
Return values
Result|nulldeactivateRefusal()
private
static deactivateRefusal(int $roleId) : Result
Parameters
- $roleId : int
Return values
ResultroleLosingAllHolders()
Returns the first protected role whose active holders are all among `$userIds` (and at least one is).
private
static roleLosingAllHolders(array<int, int> $userIds, array<int, array<int, int>> $activeHoldersByRole) : int|null
Parameters
- $userIds : array<int, int>
- $activeHoldersByRole : array<int, array<int, int>>