passbolt/passbolt_api · error · ForbiddenException

An administrator user cannot be suspended via SCIM.

Error message

An administrator user cannot be suspended via SCIM.

What it means

UserScimResource::assertAdminSuspendAllowed() forbids deactivating (suspending) a user holding the administrator role via SCIM, unless the config flag `passbolt.plugins.scim.security.allowSuspendAdministrators` is enabled. This protects the last-resort admin accounts from being disabled by an IdP synchronization. Throws 403 ForbiddenException.

Solutions

  1. Remove the admin role from the user first (passbolt UI/CLI) so they are a normal user, then suspend via SCIM.
  2. If suspension of admins via SCIM is intended, set passbolt.plugins.scim.security.allowSuspendAdministrators to true in config.
  3. Exclude administrator accounts from the IdP's SCIM sync scope.
  4. Verify with `passbolt users` which users are admins before running bulk deactivations.

Example fix

// before (config/passbolt.php)
//'scim' => ['security' => ['allowSuspendAdministrators' => false]]
// after (only if policy allows admins to be suspended by the IdP)
'scim' => ['security' => ['allowSuspendAdministrators' => true]]
Defensive patterns

Strategy: try-catch

Validate before calling

// before deactivating via SCIM
const user = await scim.getUser(id);
const roles = user.roles || (await passbolt.getUserRoles(id));
if (roles.includes('admin')) throw new Error('target is an admin; SCIM suspend forbidden');

Try / catch

try { await scim.patchUser(adminId, [{op:'replace',path:'active',value:false}]); } catch (e) { if (e.status === 403 && /administrator/.test(e.message)) { /* demote admin first or skip */ } else throw e; }

Prevention

When it happens

Trigger: PATCH /scim/v2/Users/<id-of-admin> with {"op":"replace","path":"active","value":false} (or add/remove with active false) when the target user is an admin and the allowSuspendAdministrators config is false/unset.

Common situations: An IdP deactivates a leaving employee who also happened to be a passbolt admin; a bulk SCIM sync disables all users not present in the IdP, including admins; operators are unaware the opt-in config flag exists.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/d6a9e91e16d24d3a. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/Scim/src/Utility/Resource/UserScimResource.php:711

    protected function assertAdminSuspendAllowed(array $userPatchData): void
    {
        // Only check when user is being disabled (disabled field is being set to a non-null value)
        if (empty($userPatchData['disabled'])) {
            return;
        }

        // If already disabled, no change — skip guard
        if ($this->userEntity->disabled) {
            return;
        }

        // Check if the config allows suspending administrators
        if (Configure::read('passbolt.plugins.scim.security.allowSuspendAdministrators')) {
            return;
        }

        if ($this->isUserAdmin()) {
            throw new ForbiddenException(__('An administrator user cannot be suspended via SCIM.'));
        }
    }

    /**
     * Assert that the user being deleted is not an administrator.
     *
     * @return void
     * @throws \Cake\Http\Exception\ForbiddenException If trying to delete an admin and the config flag is not set.
     */
    protected function assertAdminDeleteAllowed(): void
    {
        $allowed = Configure::read('passbolt.plugins.scim.security.allowDeleteAdministrators');
        // Fallback on allowSuspendAdministrators configuration
        if (!is_bool($allowed)) {
            $allowed = Configure::read('passbolt.plugins.scim.security.allowSuspendAdministrators');
        }

        if ($allowed) {

View on GitHub (pinned to 31c1bbc10f)