passbolt/passbolt_api · error · Cake\Http\Exception\UnauthorizedException
Only admin can create or update organization settings.
Error message
Only admin can create or update organization settings.
What it means
OrganizationSettingsTable::createOrUpdateSetting() throws UnauthorizedException when the UserAccessControl object does not represent an administrator. Only admins may create or update organization settings (e.g. SMTP, MFA policy). The check happens before any database work, so the request is rejected with 403/401 before any write.
Solutions
- Log in with, or impersonate, an administrator account (role id/name 'admin') when calling the settings API.
- For CLI tasks, ensure the UserAccessControl is built with admin privileges (e.g. root/admin user context).
- Fix the role assignment if the user should be an admin (update users.role_id to the admin role).
- If RBAC plugin customizes role permissions, ensure it still allows admins to write OrgSettings.
Example fix
// before
$control = new UserAccessControl($user);
$this->OrganizationSettings->createOrUpdateSetting('smtp', $value, $control);
// after
if ($control->isAdmin()) {
$this->OrganizationSettings->createOrUpdateSetting('smtp', $value, $control);
} Defensive patterns
Strategy: validation
Validate before calling
if (!$control->isAdmin()) { throw new UnauthorizedException('Admin role required to modify organization settings.'); } Type guard
function isAdminContext(App\Utility\UserAccessControl $c): bool { return $c->isAdmin(); } Try / catch
try { $this->OrganizationSettings->createOrUpdateSetting($property, $value, $control); } catch (\Cake\Http\Exception\UnauthorizedException $e) { // return 403 to the client } Prevention
- Check UserAccessControl::isAdmin() before calling settings APIs.
- Use admin/root context for CLI and automation tasks.
- Keep role fixtures correct in tests.
- Gate settings UI/routes on admin role.
When it happens
Trigger: POST/PUT to organization settings endpoints with a logged-in user whose role is not admin — e.g. a 'user' role account calling PUT /org-settings.json, or a service calling createOrUpdateSetting() with a UserAccessControl built from a non-admin user or a missing/invalid role.
Common situations: Running CLI/automation tasks without impersonating an admin; provisioning scripts using a normal user's credentials; forgetting to pass role context so $control->isAdmin() is false; testing with a non-admin account.
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
- Only admin can create or update subscription information.
- Only administrators are allowed to create/update MFA…
- Only administrators are allowed to create/update MFA…
- Only administrators are allowed to create/update password…
- Only administrators can create SSO settings.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/9c866d2bdb89168f.
Report an issue: GitHub.
Appendix: source
Thrown at src/Model/Table/OrganizationSettingsTable.php:169
return null;
}
}
/**
* Create (or update) an organization setting
*
* @param string $property The property name
* @param array|string $value The property value
* @param \App\Utility\UserAccessControl $control user access control object
* @return \App\Model\Entity\OrganizationSetting
* @throws \Cake\Http\Exception\UnauthorizedException When user role is not admin.
* @throws \App\Error\Exception\CustomValidationException When there are validation errors.
* @throws \Cake\Http\Exception\InternalErrorException|\Exception When unable to save the entity.
*/
public function createOrUpdateSetting(string $property, string|array $value, UserAccessControl $control): OrganizationSetting // phpcs:ignore
{
if (!$control->isAdmin()) {
throw new UnauthorizedException(__('Only admin can create or update organization settings.'));
}
$settingId = $this->_getSettingPropertyId($property);
$settingFinder = ['property_id' => $settingId];
$settingValues = ['value' => $value, 'property' => $property];
$settingItem = $this->find()
->where($settingFinder)
->first();
if ($settingItem) {
$settingValues['modified_by'] = $control->getId();
/** @var \App\Model\Entity\OrganizationSetting $settingItem */
$settingItem = $this->patchEntity($settingItem, $settingValues);
} else {
$settingValues['created_by'] = $settingValues['modified_by'] = $control->getId();
$settingItem = $this->newEntity(array_merge($settingFinder, $settingValues));
}
if ($settingItem->getErrors()) {
throw new CustomValidationException(__('This is not a valid setting.'), $settingItem->getErrors(), $this);View on GitHub (pinned to 31c1bbc10f)