Winter CMS Build
1.2.12
PHP Version
8.3
Database engine
MySQL/MariaDB
Plugins installed
No response
Issue description
Summary
When editing pivot data on a BelongsToMany relation where the related model is Backend\Models\User, the RelationController saves the related User model even when only pivot fields change. This triggers the new beforeSave() authorization checks added in 1.2.12 and causes an AuthorizationException for users without backend.manage_users permission, even though they are only updating pivot data, not the User record itself.
Description
In 1.2.12, Backend\Models\User gained a beforeSave() hook that enforces authorization rules to prevent privilege escalation. One rule requires backend.manage_users when saving another user’s record.
For BelongsToMany relations where the related model is Backend\Models\User, editing only pivot data (e.g. pivot[is_default]) causes the RelationController to save the related User model. That save triggers User::beforeSave(), which then throws an AuthorizationException for users without backend.manage_users, even though no User attributes are being changed.
Steps to replicate
- Create a BelongsToMany relation where the related model is
Backend\Models\User.
- Configure a pivot form with at least one pivot field (e.g.
pivot[is_default]).
- Log in as a backend user who does not have
backend.manage_users.
- Open a record that uses this relation.
- Add another backend user to the relation.
- Edit the pivot data for that user (e.g. toggle
is_default).
- Submit the pivot form.
Expected behavior
Only the pivot record should be updated. The related User model should not be saved when only pivot fields change, since no User attributes are modified.
Actual behavior
The RelationController calls prepareModelsToSave() with the related model (User). FormModelSaver::setModelAttributes() always adds the main model to modelsToSave (line 55), so User::save() is called even when only pivot data changes. This triggers User::beforeSave(), which throws:
You do not have permission to manage other administrators.
Workaround
Root cause
In RelationController::onRelationManagePivotUpdate() (and onRelationManagePivotCreate()):
$modelsToSave = $this->prepareModelsToSave($hydratedModel, $saveData);
foreach ($modelsToSave as $modelToSave) {
$modelToSave->save(null, $this->pivotWidget->getSessionKey());
}
prepareModelsToSave() uses FormModelSaver::setModelAttributes(), which always pushes the main model into modelsToSave before processing nested attributes. For BelongsToMany relations, the main model is the related model (User). As a result, the User is saved even when the form only contains pivot data.
Impact
- Users without
backend.manage_users cannot edit pivot data for relations that use Backend\Models\User as the related model.
- This affects any plugin or module that uses BelongsToMany relations to
Backend\Models\User with editable pivot data.
- Actually bad workaround: grant
backend.manage_users to these users, which broadens their permissions beyond what is needed for pivot editing.
Suggested fix
For BelongsToMany (and similar) relations when handling pivot datas, the RelationController should only save models whose attributes actually changed. In particular:
- If
$saveData only contains pivot (or equivalent pivot keys), only the pivot record should be saved.
- The related model (e.g. User) should not be saved when no non-pivot attributes are present in
$saveData.
Additional context
- This worked in 1.2.9 because
User did not have the beforeSave() authorization checks.
- The new security rules in
User::beforeSave() are appropriate; the issue is that the RelationController saves the related model unnecessarily when only pivot data changes.
- Related files:
modules/backend/behaviors/RelationController.php (e.g. onRelationManagePivotUpdate, onRelationManagePivotCreate)
modules/backend/traits/FormModelSaver.php (prepareModelsToSave, setModelAttributes)
modules/backend/models/User.php (beforeSave)
Winter CMS Build
1.2.12
PHP Version
8.3
Database engine
MySQL/MariaDB
Plugins installed
No response
Issue description
Summary
When editing pivot data on a BelongsToMany relation where the related model is
Backend\Models\User, the RelationController saves the related User model even when only pivot fields change. This triggers the newbeforeSave()authorization checks added in 1.2.12 and causes anAuthorizationExceptionfor users withoutbackend.manage_userspermission, even though they are only updating pivot data, not the User record itself.Description
In 1.2.12,
Backend\Models\Usergained abeforeSave()hook that enforces authorization rules to prevent privilege escalation. One rule requiresbackend.manage_userswhen saving another user’s record.For BelongsToMany relations where the related model is
Backend\Models\User, editing only pivot data (e.g.pivot[is_default]) causes the RelationController to save the related User model. That save triggersUser::beforeSave(), which then throws anAuthorizationExceptionfor userswithout backend.manage_users, even though no User attributes are being changed.Steps to replicate
Backend\Models\User.pivot[is_default]).backend.manage_users.is_default).Expected behavior
Only the pivot record should be updated. The related User model should not be saved when only pivot fields change, since no User attributes are modified.
Actual behavior
The RelationController calls
prepareModelsToSave()with the related model (User).FormModelSaver::setModelAttributes()always adds the main model tomodelsToSave(line 55), soUser::save()is called even when only pivot data changes. This triggersUser::beforeSave(), which throws:Workaround
Root cause
In
RelationController::onRelationManagePivotUpdate()(andonRelationManagePivotCreate()):prepareModelsToSave()usesFormModelSaver::setModelAttributes(), which always pushes the main model intomodelsToSavebefore processing nested attributes. For BelongsToMany relations, the main model is the related model (User). As a result, the User is saved even when the form only contains pivot data.Impact
backend.manage_userscannot edit pivot data for relations that useBackend\Models\Useras the related model.Backend\Models\Userwith editable pivot data.backend.manage_usersto these users, which broadens their permissions beyond what is needed for pivot editing.Suggested fix
For BelongsToMany (and similar) relations when handling pivot datas, the RelationController should only save models whose attributes actually changed. In particular:
$saveDataonly containspivot(or equivalent pivot keys), only the pivot record should be saved.$saveData.Additional context
Userdid not have thebeforeSave()authorization checks.User::beforeSave()are appropriate; the issue is that the RelationController saves the related model unnecessarily when only pivot data changes.modules/backend/behaviors/RelationController.php(e.g.onRelationManagePivotUpdate,onRelationManagePivotCreate)modules/backend/traits/FormModelSaver.php(prepareModelsToSave,setModelAttributes)modules/backend/models/User.php(beforeSave)