passbolt/passbolt_api · error · ForbiddenException

Only administrators are allowed to create/update MFA…

Error message

Only administrators are allowed to create/update MFA policies settings.

What it means

MfaPoliciesSetSettingsService::createOrUpdate() enforces authorization independently of the controller: the ExtendedUserAccessControl passed in must belong to an admin, otherwise ForbiddenException is thrown. This is the service-layer defense-in-depth duplicate of the controller's admin check.

Solutions

  1. Construct the ExtendedUserAccessControl from an authenticated admin user
  2. Ensure the user's role in the database is 'admin' and roles table associations are intact
  3. If calling from non-interactive code, impersonate/administer via a properly privileged UAC only

Example fix

// before
$uac = new ExtendedUserAccessControl($regularUser['id'], $regularUser['role_id']);
// after
$uac = ExtendedUserAccessControlFactory::makeFromUser($adminUser);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!$uac->isAdmin()) {
    throw new ForbiddenException('Admin UAC required');
}
$service->createOrUpdate($uac, $dto);

Type guard

$isAdminUac = $uac instanceof ExtendedUserAccessControl && $uac->isAdmin();

Try / catch

try {
    $dto = $service->createOrUpdate($uac, $policySettings);
} catch (ForbiddenException $e) {
    // caller is not admin
}

Prevention

When it happens

Trigger: Calling createOrUpdate() with a UAC built for a non-admin user — e.g. internal code, CLI command, or API path that skips the controller check.

Common situations: Custom plugins or scripts calling the service directly with the wrong UAC; endpoints invoked by a user whose role changed to non-admin mid-session.

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/09e57b410d883b6a. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/MfaPolicies/src/Service/MfaPoliciesSetSettingsService.php:51

     * Event name. Fired after MFA policies settings has been saved.
     *
     * @var string
     */
    public const EVENT_SETTINGS_UPDATED = 'Service.MfaPoliciesSetSettings.updated';

    /**
     * Create MFA policies settings if not present already in DB or updates the settings value if already exists.
     *
     * @param \App\Utility\ExtendedUserAccessControl $uac Extended user access control.
     * @param \Passbolt\MfaPolicies\Model\Dto\MfaPolicySettings $mfaPolicySettingsDto DTO object.
     * @return \Passbolt\MfaPolicies\Model\Dto\MfaPolicySettings
     */
    public function createOrUpdate(
        ExtendedUserAccessControl $uac,
        MfaPolicySettings $mfaPolicySettingsDto
    ): MfaPolicySettings {
        if (!$uac->isAdmin()) {
            throw new ForbiddenException(
                __('Only administrators are allowed to create/update MFA policies settings.')
            );
        }

        $originalMfaPoliciesSettingDto = (new MfaPoliciesGetSettingsService())->get();

        /** @var \Passbolt\MfaPolicies\Model\Table\MfaPoliciesSettingsTable $mfaPoliciesSettingsTable */
        $mfaPoliciesSettingsTable = $this->fetchTable('Passbolt/MfaPolicies.MfaPoliciesSettings');

        /** @var \Passbolt\MfaPolicies\Model\Entity\MfaPoliciesSetting $mfaPoliciesSetting */
        $mfaPoliciesSetting = $mfaPoliciesSettingsTable->createOrUpdateSetting(
            $mfaPoliciesSettingsTable->getProperty(),
            [
                'policy' => $mfaPolicySettingsDto->policy,
                'remember_me_for_a_month' => $mfaPolicySettingsDto->remember_me_for_a_month,
            ],
            $uac
        );

View on GitHub (pinned to 31c1bbc10f)