fix: restoring history cannot change an account's privacy - #928
Merged
blaipr merged 1 commit intoSep 17, 2026
Merged
Conversation
AccountUseCases::restoreModified() copies isPrivate and isPrivateGroup straight from the history snapshot, and the repository's restoreModified() excludes userId/userGroupId from the UPDATE but not those two, so a restore wrote them back unconditionally. On the edit screen AccountForm::constrainPrivacyToPermission() allows them only for an application administrator, or the owner holding isAccPrivate() (the group holding isAccPrivateGroup()). A history DTO never passes through AccountForm, so nothing re-applied that rule coming back in — and SaveEditRestoreController checks only ACCOUNT_EDIT_RESTORE, which AccountPermission buckets with plain edit access. So anybody the account was shared with for editing could restore an old version to mark it private, and AccountAcl tests privacy before the administrator branch, so a private account disappears for account administrators too; or strip the privacy from one that had it. This method already refuses to take the owner and the group from the snapshot, for the same reason. Privacy is the same question about a different pair of columns, and the decision now sits beside those two.
blaipr
deleted the
fix/restoring-history-cannot-change-an-accounts-privacy
branch
September 17, 2026 22:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AccountUseCases::restoreModified()copiesisPrivateandisPrivateGroupstraight from thehistory snapshot, and the repository's
restoreModified()excludesuserId/userGroupIdfrom theUPDATE but not those two — so a restore wrote them back unconditionally.
On the edit screen the same two flags are gated:
AccountForm::constrainPrivacyToPermission()allows them only for an application administrator, or for the owner holding
isAccPrivate()(thegroup holding
isAccPrivateGroup()). A history DTO never passes throughAccountForm, so nothingre-applied that rule on the way back in.
SaveEditRestoreControllerchecks onlyACCOUNT_EDIT_RESTORE, whichAccountPermissionbucketswith plain edit access. So anybody the account was shared with for editing could restore an old
version and, with it, set the account private — and
AccountAcltests privacy before theadministrator branch, so a private account disappears for account administrators too. Or strip the
privacy from an account that had it.
The same shape, one field over
This method already refuses to take the owner and the group from the snapshot:
restoreModified()takes explicit
$changeOwner/$changeUserGroupflags, computed fromuserCanChangeOwner()anduserCanChangeGroup(), because restoring used to revert ownership past the ACL. Privacy is the samequestion asked about a different pair of columns, and it was still being answered by the snapshot.
The fix puts the decision in the same place, beside those two:
constrainPrivacyToPermission()mirrors the form's rule and zeroes what the caller may not set, before the model is built.
Tests
testRestoreModifiedCannotMakeAnAccountPrivaterestores a snapshot with both flags set as anordinary user and asserts the model reaching the repository has both at zero. Mutation-verified:
reverting
src/fails it.The two existing restore tests built their expectation straight from the snapshot, so they now say
what the method actually promises — the privacy flags are decided, not copied.