{"record":{"id":"09e57b410d883b6a","repo":"passbolt/passbolt_api","slug":"only-administrators-are-allowed-to-create-update-mfa-09e57b","errorCode":null,"errorMessage":"Only administrators are allowed to create/update MFA policies settings.","messagePattern":"Only administrators are allowed to create/update MFA policies settings\\.","errorType":"http","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"plugins/PassboltEe/MfaPolicies/src/Service/MfaPoliciesSetSettingsService.php","lineNumber":51,"sourceCode":"     * Event name. Fired after MFA policies settings has been saved.\n     *\n     * @var string\n     */\n    public const EVENT_SETTINGS_UPDATED = 'Service.MfaPoliciesSetSettings.updated';\n\n    /**\n     * Create MFA policies settings if not present already in DB or updates the settings value if already exists.\n     *\n     * @param \\App\\Utility\\ExtendedUserAccessControl $uac Extended user access control.\n     * @param \\Passbolt\\MfaPolicies\\Model\\Dto\\MfaPolicySettings $mfaPolicySettingsDto DTO object.\n     * @return \\Passbolt\\MfaPolicies\\Model\\Dto\\MfaPolicySettings\n     */\n    public function createOrUpdate(\n        ExtendedUserAccessControl $uac,\n        MfaPolicySettings $mfaPolicySettingsDto\n    ): MfaPolicySettings {\n        if (!$uac->isAdmin()) {\n            throw new ForbiddenException(\n                __('Only administrators are allowed to create/update MFA policies settings.')\n            );\n        }\n\n        $originalMfaPoliciesSettingDto = (new MfaPoliciesGetSettingsService())->get();\n\n        /** @var \\Passbolt\\MfaPolicies\\Model\\Table\\MfaPoliciesSettingsTable $mfaPoliciesSettingsTable */\n        $mfaPoliciesSettingsTable = $this->fetchTable('Passbolt/MfaPolicies.MfaPoliciesSettings');\n\n        /** @var \\Passbolt\\MfaPolicies\\Model\\Entity\\MfaPoliciesSetting $mfaPoliciesSetting */\n        $mfaPoliciesSetting = $mfaPoliciesSettingsTable->createOrUpdateSetting(\n            $mfaPoliciesSettingsTable->getProperty(),\n            [\n                'policy' => $mfaPolicySettingsDto->policy,\n                'remember_me_for_a_month' => $mfaPolicySettingsDto->remember_me_for_a_month,\n            ],\n            $uac\n        );","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/MfaPolicies/src/Service/MfaPoliciesSetSettingsService.php#L33-L69","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Construct the ExtendedUserAccessControl from an authenticated admin user","Ensure the user's role in the database is 'admin' and roles table associations are intact","If calling from non-interactive code, impersonate/administer via a properly privileged UAC only"],"exampleFix":"// before\n$uac = new ExtendedUserAccessControl($regularUser['id'], $regularUser['role_id']);\n// after\n$uac = ExtendedUserAccessControlFactory::makeFromUser($adminUser);","handlingStrategy":"type-guard","validationCode":"if (!$uac->isAdmin()) {\n    throw new ForbiddenException('Admin UAC required');\n}\n$service->createOrUpdate($uac, $dto);","typeGuard":"$isAdminUac = $uac instanceof ExtendedUserAccessControl && $uac->isAdmin();","tryCatchPattern":"try {\n    $dto = $service->createOrUpdate($uac, $policySettings);\n} catch (ForbiddenException $e) {\n    // caller is not admin\n}","preventionTips":["Build UAC only from authenticated admin users","Re-verify role when tokens/sessions are long-lived","Keep service-layer authorization checks in custom callers"],"tags":["authorization","mfa-policies","service-layer","permissions"],"backgroundTag":"permission-denied","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}