XWiki has no right protection on rollback action
Description
XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. The rollback action is missing a right protection, a user can rollback to a previous version of the page to gain rights they don't have anymore. The problem has been patched in XWiki 14.10.17, 15.5.3 and 15.8-rc-1 by ensuring that the rights are checked before performing the rollback.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.xwiki.platform:xwiki-platform-oldcoreMaven | >= 1.0, < 14.10.17 | 14.10.17 |
org.xwiki.platform:xwiki-platformMaven | >= 15.0-rc-1, < 15.5.3 | 15.5.3 |
org.xwiki.platform:xwiki-platformMaven | >= 15.6-rc-1, < 15.8-rc-1 | 15.8-rc-1 |
Affected products
1- Range: >= 1.0, < 14.10.17
Patches
34de72875ca49XWIKI-21257: Rollback is not triggering a UserUpdatingDocumentEvent
8 files changed · +488 −11
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/main/java/org/xwiki/test/CustomUserUpdatedDocumentEventListener.java+74 −0 added@@ -0,0 +1,74 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.test; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.xwiki.component.annotation.Component; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.observation.AbstractEventListener; +import org.xwiki.observation.event.Event; + +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.internal.event.UserUpdatingDocumentEvent; + +/** + * Listener dedicated to cancel a specific rolling back event triggered in VersionIT test. + * + * @version $Id$ + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ +@Component +@Singleton +@Named(CustomUserUpdatedDocumentEventListener.NAME) +public class CustomUserUpdatedDocumentEventListener extends AbstractEventListener +{ + static final String NAME = "CustomUserUpdatedDocumentEventListener"; + + @Inject + private Logger logger; + + /** + * Default constructor. + */ + public CustomUserUpdatedDocumentEventListener() + { + super(NAME, new UserUpdatingDocumentEvent()); + } + + @Override + public void onEvent(Event event, Object source, Object data) + { + UserUpdatingDocumentEvent userEvent = (UserUpdatingDocumentEvent) event; + XWikiDocument sourceDoc = (XWikiDocument) source; + DocumentReference expectedReference = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + if (StringUtils.equals(userEvent.getUserReference().getName(), "DeleteVersionTestUserCancelEvent") + && sourceDoc.getDocumentReference().equals(expectedReference)) { + logger.info("Cancelling user event on purpose"); + userEvent.cancel(); + } + } +}
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/main/resources/META-INF/components.txt+1 −0 modified@@ -1 +1,2 @@ +org.xwiki.test.CustomUserUpdatedDocumentEventListener org.xwiki.test.TestMacro
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/VersionIT.java+324 −0 modified@@ -19,24 +19,30 @@ */ package org.xwiki.flamingo.test.docker; +import java.util.List; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.xwiki.flamingo.skin.test.po.AttachmentsPane; import org.xwiki.flamingo.skin.test.po.AttachmentsViewPage; import org.xwiki.model.reference.AttachmentReference; +import org.xwiki.model.reference.DocumentReference; import org.xwiki.rest.model.jaxb.Page; import org.xwiki.test.docker.junit5.TestReference; import org.xwiki.test.docker.junit5.UITest; import org.xwiki.test.ui.TestUtils; import org.xwiki.test.ui.po.HistoryPane; import org.xwiki.test.ui.po.ViewPage; +import org.xwiki.test.ui.po.editor.ObjectEditPage; +import org.xwiki.test.ui.po.editor.ObjectEditPane; import org.xwiki.test.ui.po.editor.WikiEditPage; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -321,4 +327,322 @@ void testDeleteAllButFirstVersion(TestUtils setup, TestReference testReference) assertEquals("1.1", page.getVersion()); assertEquals("1.1", page.getContent()); } + + /** + * Scenario: + * * Create a user RollbackTestUser + * * Create a page, allow RollbackTestUser script right on it, and then deny it + * * Login with RollbackTestUser and try to rollback the page to the version where the right was allowed + * * Check that the page still has the right xobject set to deny + */ + @Test + @Order(7) + void testRollbackDontMessUpRights(TestUtils setup, TestReference testReference) throws Exception + { + setup.loginAsSuperAdmin(); + setup.rest().delete(testReference); + String rollbackTestUser = "RollbackTestUser"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", rollbackTestUser)); + setup.createUser(rollbackTestUser, rollbackTestUser, ""); + setup.createPage(testReference, "Test Rollback Page"); + + setup.setRights(testReference, "", "XWiki." + rollbackTestUser, "script", true); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(testReference, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + ObjectEditPane objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("1", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + objectEditPane.setFieldValue(objectEditPane.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + setup.gotoPage(testReference); + + setup.login(rollbackTestUser, rollbackTestUser); + + // check that the right is as expected + setup.gotoPage(testReference, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("0", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + + ViewPage viewPage = objectEditPage.clickCancel(); + HistoryPane historyPane = viewPage.openHistoryDocExtraPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that the history contains what we're expecting + assertEquals(3, historyPane.getNumberOfVersions()); + assertEquals("2.2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion("2.1")); + + viewPage = historyPane.rollbackToVersion("2.1"); + historyPane = viewPage.openHistoryDocExtraPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that the history contains what we're expecting + assertEquals(4, historyPane.getNumberOfVersions()); + + assertEquals("3.1", historyPane.getCurrentVersion()); + assertEquals("Rollback to version 2.1", historyPane.getCurrentVersionComment()); + assertTrue(historyPane.hasVersion("2.2")); + assertTrue(historyPane.hasVersion("2.1")); + + // check that the right is still the same + setup.gotoPage(testReference, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("0", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + + objectEditPage.clickCancel(); + } + + /** + * Scenario: + * * Create a user DeleteVersionTestUser + * * Give DeleteVersionTestUser Admin right with a dedicated xobject in XWiki.XWikiPreferences + * * Give DeleteVersionTestUser PR right with a dedicated xobject in XWiki.XWikiPreferences + * * Edit the xobject to deny PR right to DeleteVersionTestUser + * * Login with DeleteVersionTestUser and delete last version of XWiki.XWikiPreferences + * * Check that the version has been deleted but the PR right is still denied + */ + @Test + @Order(8) + void testDeleteVersionDontMessUpRights(TestUtils setup) throws Exception + { + setup.loginAsSuperAdmin(); + + String deleteVersionTestUser = "DeleteVersionTestUser"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", deleteVersionTestUser)); + setup.createUser(deleteVersionTestUser, deleteVersionTestUser, ""); + + setup.setGlobalRights("", deleteVersionTestUser, "admin", true); + + DocumentReference xwikiPreferences = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + HistoryPane historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + // store the current version as it will be our basis for next steps + String latestVersionBeforeChanges = historyPane.getCurrentVersion(); + int numberOfVersions = historyPane.getNumberOfVersions(); + + // We just create a new major version in the history + setup.gotoPage(xwikiPreferences, "edit", "editor=wiki"); + WikiEditPage wikiEditPage = new WikiEditPage(); + wikiEditPage.clickSaveAndView(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + // Version where we start our changes + String startChangesVersion = historyPane.getCurrentVersion(); + + String currentMajor = startChangesVersion.split("\\.")[0]; + + setup.setGlobalRights("", deleteVersionTestUser, "programming", true); + currentMajor = String.valueOf(Integer.parseInt(currentMajor) + 1); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + ObjectEditPane rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("1", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + rightObject.setFieldValue(rightObject.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + + setup.gotoPage(xwikiPreferences); + + setup.login(deleteVersionTestUser, deleteVersionTestUser); + + // first check that the right is properly denied in the objects + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion(currentMajor + ".1")); + + try { + historyPane = historyPane.deleteVersion(historyPane.getCurrentVersion()); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 2, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".1", historyPane.getCurrentVersion()); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, + rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + } finally { + // Put back the page in the state it was before our changes + setup.loginAsSuperAdmin(); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + historyPane.rollbackToVersion(latestVersionBeforeChanges); + } + } + + /** + * Scenario: + * * Same as above but using DeleteVersionTestUserCancelEvent as user to trigger the + * {@link org.xwiki.test.CustomUserUpdatedDocumentEventListener} which should cancel immediately the change + * * Expectation here is that the reset is not performed at all + */ + @Test + @Order(9) + void testDeleteVersionDontMessUpRightsWithCancellingEvent(TestUtils setup) + throws Exception + { + setup.loginAsSuperAdmin(); + + String deleteVersionTestUser = "DeleteVersionTestUserCancelEvent"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", deleteVersionTestUser)); + setup.createUser(deleteVersionTestUser, deleteVersionTestUser, ""); + + setup.setGlobalRights("", deleteVersionTestUser, "admin", true); + + DocumentReference xwikiPreferences = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + HistoryPane historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + // store the current version as it will be our basis for next steps + String latestVersionBeforeChanges = historyPane.getCurrentVersion(); + int numberOfVersions = historyPane.getNumberOfVersions(); + + // We just create a new major version in the history + setup.gotoPage(xwikiPreferences, "edit", "editor=wiki"); + WikiEditPage wikiEditPage = new WikiEditPage(); + wikiEditPage.clickSaveAndView(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + // Version where we start our changes + String startChangesVersion = historyPane.getCurrentVersion(); + + String currentMajor = startChangesVersion.split("\\.")[0]; + + setup.setGlobalRights("", deleteVersionTestUser, "programming", true); + currentMajor = String.valueOf(Integer.parseInt(currentMajor) + 1); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + ObjectEditPane rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("1", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + rightObject.setFieldValue(rightObject.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + + setup.gotoPage(xwikiPreferences); + + setup.login(deleteVersionTestUser, deleteVersionTestUser); + + // first check that the right is properly denied in the objects + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion(currentMajor + ".1")); + + try { + historyPane.deleteVersion(historyPane.getCurrentVersion()); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + + // here the history shouldn't have changed because of the CustomUserUpdatedDocumentEventListener + // that should have cancel the event + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that deleting another version still works + historyPane = historyPane.deleteVersion(currentMajor + ".1"); + historyPane = historyPane.showMinorEdits(); + + assertEquals(numberOfVersions + 2, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertFalse(historyPane.hasVersion(currentMajor + ".1")); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + } finally { + // Put back the page in the state it was before our changes + setup.loginAsSuperAdmin(); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + historyPane.rollbackToVersion(latestVersionBeforeChanges); + } + } }
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/DeleteVersionsAction.java+1 −1 modified@@ -65,7 +65,7 @@ public boolean action(XWikiContext context) throws XWikiException Version v2 = versions[1]; if (v1 != null && v2 != null) { - context.getWiki().deleteDocumentVersions(tdoc, v1.toString(), v2.toString(), context); + context.getWiki().deleteDocumentVersions(tdoc, v1.toString(), v2.toString(), true, context); } sendRedirect(context);
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/RollbackAction.java+1 −1 modified@@ -79,7 +79,7 @@ public boolean action(XWikiContext context) throws XWikiException } // Perform the rollback. - xwiki.rollback(tdoc, rev, context); + xwiki.rollback(tdoc, rev, true, true, context); // Forward to view. String redirect = Utils.getRedirect("view", context);
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java+53 −7 modified@@ -4708,6 +4708,27 @@ public void checkDeletingDocument(DocumentReference userReference, XWikiDocument */ public void deleteDocumentVersions(XWikiDocument document, String version1, String version2, XWikiContext context) throws XWikiException + { + deleteDocumentVersions(document, version1, version2, false, context); + } + + /** + * Delete a range of versions from a document history. + * + * @param document the document from which to delete versions + * @param version1 one end of the versions range to remove + * @param version2 the other end of the versions range to remove + * @param triggeredByUser {@code true} if the API is called directly by an action from a user and checks need to + * be performed for the rollback (See: {@link #rollback(XWikiDocument, String, boolean, boolean, XWikiContext)}). + * @param context the XWiki context + * @throws XWikiException + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + @Unstable + public void deleteDocumentVersions(XWikiDocument document, String version1, String version2, + boolean triggeredByUser, XWikiContext context) throws XWikiException { Version v1 = new Version(version1); Version v2 = new Version(version2); @@ -4750,20 +4771,22 @@ public void deleteDocumentVersions(XWikiDocument document, String version1, Stri .notify(new DocumentVersionRangeDeletingEvent(document.getDocumentReferenceWithLocale(), lowerBound.toString(), upperBound.toString()), document, context); - // Update the archive - context.getWiki().getVersioningStore().saveXWikiDocArchive(archive, true, context); - // Make sure the cached document archive is updated too - XWikiDocument cachedDocument = - context.getWiki().getDocument(document.getDocumentReferenceWithLocale(), context); - cachedDocument.setDocumentArchive(archive); // There are still some versions left. // If we delete the most recent (current) version, then rollback to latest undeleted version. + // We do that right before updating the archive, in case it would cancel the action. Version previousVersion = archive.getLatestVersion(); if (!document.getRCSVersion().equals(previousVersion)) { - context.getWiki().rollback(document, previousVersion.toString(), false, context); + context.getWiki().rollback(document, previousVersion.toString(), false, triggeredByUser, context); } + // Update the archive + context.getWiki().getVersioningStore().saveXWikiDocArchive(archive, true, context); + // Make sure the cached document archive is updated too + XWikiDocument cachedDocument = + context.getWiki().getDocument(document.getDocumentReferenceWithLocale(), context); + cachedDocument.setDocumentArchive(archive); + // Notify after versions delete getObservationManager() .notify(new DocumentVersionRangeDeletedEvent(document.getDocumentReferenceWithLocale(), @@ -7569,6 +7592,25 @@ private void restoreDeletedAttachment(XWikiAttachment rolledbackAttachment, XWik */ public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addRevision, XWikiContext xcontext) throws XWikiException + { + return rollback(tdoc, rev, addRevision, false, xcontext); + } + + /** + * @param tdoc the document to rollback + * @param rev the revision to rollback to + * @param addRevision true if a new revision should be created + * @param triggeredByUser {@code true} if this has been triggered by a user and a check needs to be performed + * @param xcontext the XWiki context + * @return the new document + * @throws XWikiException when failing to rollback the document + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + @Unstable + public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addRevision, + boolean triggeredByUser, XWikiContext xcontext) throws XWikiException { LOGGER.debug("Rolling back [{}] to version [{}]", tdoc, rev); @@ -7647,6 +7689,10 @@ public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addR message = localizePlainOrKey("core.comment.rollback", rev); } + if (triggeredByUser) { + checkSavingDocument(xcontext.getUserReference(), document, message, false, xcontext); + } + ObservationManager om = getObservationManager(); if (om != null) { // Notify listeners about the document that is going to be rolled back.
xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/XWikiMockitoTest.java+8 −2 modified@@ -73,6 +73,7 @@ import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.internal.ReadOnlyXWikiContextProvider; import com.xpn.xwiki.internal.debug.DebugConfiguration; +import com.xpn.xwiki.internal.event.UserUpdatingDocumentEvent; import com.xpn.xwiki.internal.render.groovy.ParseGroovyFromString; import com.xpn.xwiki.internal.skin.InternalSkinManager; import com.xpn.xwiki.internal.store.StoreConfiguration; @@ -216,7 +217,7 @@ public void copyDocumentPreservesAttachmentsVersion() throws Exception } /** - * Verify that {@link XWiki#rollback(XWikiDocument, String, XWikiContext)} fires the right events. + * Verify that {@link XWiki#rollback(XWikiDocument, String, boolean, boolean, XWikiContext)} fires the right events. */ @Test public void rollbackFiresEvents() throws Exception @@ -236,17 +237,22 @@ public void rollbackFiresEvents() throws Exception XWikiDocument result = mock(XWikiDocument.class); when(result.getDocumentReference()).thenReturn(documentReference); + DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "ContextUser"); + this.context.setUserReference(userReference); + String revision = "3.5"; when(this.documentRevisionProvider.getRevision(document, revision)).thenReturn(result); this.componentManager.registerMockComponent(ContextualLocalizationManager.class); - xwiki.rollback(document, revision, context); + xwiki.rollback(document, revision, true, true, context); verify(observationManager).notify(new DocumentRollingBackEvent(documentReference, revision), document, context); verify(observationManager).notify(new DocumentUpdatingEvent(documentReference), document, context); verify(observationManager).notify(new DocumentUpdatedEvent(documentReference), document, context); verify(observationManager).notify(new DocumentRolledBackEvent(documentReference, revision), document, context); + verify(observationManager).notify(new UserUpdatingDocumentEvent(userReference, documentReference), + document, context); } @Test
xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java+26 −0 modified@@ -183,4 +183,30 @@ public ComparePage compare(String fromVersion, String toVersion) getDriver().findElementWithoutWaiting(pane, By.xpath(".//input[@accesskey = 'c']")).click(); return new ComparePage(); } + + /** + * @return the total number of versions contained in the history as returned by the live table + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + public int getNumberOfVersions() + { + String xpath = ".//div[@class='paginationFilter' and following-sibling::div[@id='historycontent']]"; + WebElement paginationDiv = getDriver().findElementWithoutWaiting(By.xpath(xpath)); + return Integer.parseInt(getDriver().findElementWithoutWaiting(paginationDiv, By.className("totalResultsNo")) + .getText()); + } + + /** + * @return {@code true} if the requested version is currently displayed in the history + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + public boolean hasVersion(String version) + { + String xpath = String.format(".//table//tr[contains(., '%s')]", version); + return getDriver().hasElementWithoutWaiting(pane, By.xpath(xpath)); + } }
1f3220f14bb3XWIKI-21257: Rollback is not triggering a UserUpdatingDocumentEvent
8 files changed · +488 −11
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/main/java/org/xwiki/test/CustomUserUpdatedDocumentEventListener.java+74 −0 added@@ -0,0 +1,74 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.test; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.xwiki.component.annotation.Component; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.observation.AbstractEventListener; +import org.xwiki.observation.event.Event; + +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.internal.event.UserUpdatingDocumentEvent; + +/** + * Listener dedicated to cancel a specific rolling back event triggered in VersionIT test. + * + * @version $Id$ + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ +@Component +@Singleton +@Named(CustomUserUpdatedDocumentEventListener.NAME) +public class CustomUserUpdatedDocumentEventListener extends AbstractEventListener +{ + static final String NAME = "CustomUserUpdatedDocumentEventListener"; + + @Inject + private Logger logger; + + /** + * Default constructor. + */ + public CustomUserUpdatedDocumentEventListener() + { + super(NAME, new UserUpdatingDocumentEvent()); + } + + @Override + public void onEvent(Event event, Object source, Object data) + { + UserUpdatingDocumentEvent userEvent = (UserUpdatingDocumentEvent) event; + XWikiDocument sourceDoc = (XWikiDocument) source; + DocumentReference expectedReference = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + if (StringUtils.equals(userEvent.getUserReference().getName(), "DeleteVersionTestUserCancelEvent") + && sourceDoc.getDocumentReference().equals(expectedReference)) { + logger.info("Cancelling user event on purpose"); + userEvent.cancel(); + } + } +}
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/main/resources/META-INF/components.txt+1 −0 modified@@ -1 +1,2 @@ +org.xwiki.test.CustomUserUpdatedDocumentEventListener org.xwiki.test.TestMacro
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/VersionIT.java+324 −0 modified@@ -19,24 +19,30 @@ */ package org.xwiki.flamingo.test.docker; +import java.util.List; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.xwiki.flamingo.skin.test.po.AttachmentsPane; import org.xwiki.flamingo.skin.test.po.AttachmentsViewPage; import org.xwiki.model.reference.AttachmentReference; +import org.xwiki.model.reference.DocumentReference; import org.xwiki.rest.model.jaxb.Page; import org.xwiki.test.docker.junit5.TestReference; import org.xwiki.test.docker.junit5.UITest; import org.xwiki.test.ui.TestUtils; import org.xwiki.test.ui.po.HistoryPane; import org.xwiki.test.ui.po.ViewPage; +import org.xwiki.test.ui.po.editor.ObjectEditPage; +import org.xwiki.test.ui.po.editor.ObjectEditPane; import org.xwiki.test.ui.po.editor.WikiEditPage; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -321,4 +327,322 @@ void testDeleteAllButFirstVersion(TestUtils setup, TestReference testReference) assertEquals("1.1", page.getVersion()); assertEquals("1.1", page.getContent()); } + + /** + * Scenario: + * * Create a user RollbackTestUser + * * Create a page, allow RollbackTestUser script right on it, and then deny it + * * Login with RollbackTestUser and try to rollback the page to the version where the right was allowed + * * Check that the page still has the right xobject set to deny + */ + @Test + @Order(7) + void testRollbackDontMessUpRights(TestUtils setup, TestReference testReference) throws Exception + { + setup.loginAsSuperAdmin(); + setup.rest().delete(testReference); + String rollbackTestUser = "RollbackTestUser"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", rollbackTestUser)); + setup.createUser(rollbackTestUser, rollbackTestUser, ""); + setup.createPage(testReference, "Test Rollback Page"); + + setup.setRights(testReference, "", "XWiki." + rollbackTestUser, "script", true); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(testReference, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + ObjectEditPane objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("1", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + objectEditPane.setFieldValue(objectEditPane.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + setup.gotoPage(testReference); + + setup.login(rollbackTestUser, rollbackTestUser); + + // check that the right is as expected + setup.gotoPage(testReference, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("0", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + + ViewPage viewPage = objectEditPage.clickCancel(); + HistoryPane historyPane = viewPage.openHistoryDocExtraPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that the history contains what we're expecting + assertEquals(3, historyPane.getNumberOfVersions()); + assertEquals("2.2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion("2.1")); + + viewPage = historyPane.rollbackToVersion("2.1"); + historyPane = viewPage.openHistoryDocExtraPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that the history contains what we're expecting + assertEquals(4, historyPane.getNumberOfVersions()); + + assertEquals("3.1", historyPane.getCurrentVersion()); + assertEquals("Rollback to version 2.1", historyPane.getCurrentVersionComment()); + assertTrue(historyPane.hasVersion("2.2")); + assertTrue(historyPane.hasVersion("2.1")); + + // check that the right is still the same + setup.gotoPage(testReference, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("0", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + + objectEditPage.clickCancel(); + } + + /** + * Scenario: + * * Create a user DeleteVersionTestUser + * * Give DeleteVersionTestUser Admin right with a dedicated xobject in XWiki.XWikiPreferences + * * Give DeleteVersionTestUser PR right with a dedicated xobject in XWiki.XWikiPreferences + * * Edit the xobject to deny PR right to DeleteVersionTestUser + * * Login with DeleteVersionTestUser and delete last version of XWiki.XWikiPreferences + * * Check that the version has been deleted but the PR right is still denied + */ + @Test + @Order(8) + void testDeleteVersionDontMessUpRights(TestUtils setup) throws Exception + { + setup.loginAsSuperAdmin(); + + String deleteVersionTestUser = "DeleteVersionTestUser"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", deleteVersionTestUser)); + setup.createUser(deleteVersionTestUser, deleteVersionTestUser, ""); + + setup.setGlobalRights("", deleteVersionTestUser, "admin", true); + + DocumentReference xwikiPreferences = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + HistoryPane historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + // store the current version as it will be our basis for next steps + String latestVersionBeforeChanges = historyPane.getCurrentVersion(); + int numberOfVersions = historyPane.getNumberOfVersions(); + + // We just create a new major version in the history + setup.gotoPage(xwikiPreferences, "edit", "editor=wiki"); + WikiEditPage wikiEditPage = new WikiEditPage(); + wikiEditPage.clickSaveAndView(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + // Version where we start our changes + String startChangesVersion = historyPane.getCurrentVersion(); + + String currentMajor = startChangesVersion.split("\\.")[0]; + + setup.setGlobalRights("", deleteVersionTestUser, "programming", true); + currentMajor = String.valueOf(Integer.parseInt(currentMajor) + 1); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + ObjectEditPane rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("1", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + rightObject.setFieldValue(rightObject.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + + setup.gotoPage(xwikiPreferences); + + setup.login(deleteVersionTestUser, deleteVersionTestUser); + + // first check that the right is properly denied in the objects + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion(currentMajor + ".1")); + + try { + historyPane = historyPane.deleteVersion(historyPane.getCurrentVersion()); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 2, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".1", historyPane.getCurrentVersion()); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, + rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + } finally { + // Put back the page in the state it was before our changes + setup.loginAsSuperAdmin(); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + historyPane.rollbackToVersion(latestVersionBeforeChanges); + } + } + + /** + * Scenario: + * * Same as above but using DeleteVersionTestUserCancelEvent as user to trigger the + * {@link org.xwiki.test.CustomUserUpdatedDocumentEventListener} which should cancel immediately the change + * * Expectation here is that the reset is not performed at all + */ + @Test + @Order(9) + void testDeleteVersionDontMessUpRightsWithCancellingEvent(TestUtils setup) + throws Exception + { + setup.loginAsSuperAdmin(); + + String deleteVersionTestUser = "DeleteVersionTestUserCancelEvent"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", deleteVersionTestUser)); + setup.createUser(deleteVersionTestUser, deleteVersionTestUser, ""); + + setup.setGlobalRights("", deleteVersionTestUser, "admin", true); + + DocumentReference xwikiPreferences = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + HistoryPane historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + // store the current version as it will be our basis for next steps + String latestVersionBeforeChanges = historyPane.getCurrentVersion(); + int numberOfVersions = historyPane.getNumberOfVersions(); + + // We just create a new major version in the history + setup.gotoPage(xwikiPreferences, "edit", "editor=wiki"); + WikiEditPage wikiEditPage = new WikiEditPage(); + wikiEditPage.clickSaveAndView(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + // Version where we start our changes + String startChangesVersion = historyPane.getCurrentVersion(); + + String currentMajor = startChangesVersion.split("\\.")[0]; + + setup.setGlobalRights("", deleteVersionTestUser, "programming", true); + currentMajor = String.valueOf(Integer.parseInt(currentMajor) + 1); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + ObjectEditPane rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("1", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + rightObject.setFieldValue(rightObject.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + + setup.gotoPage(xwikiPreferences); + + setup.login(deleteVersionTestUser, deleteVersionTestUser); + + // first check that the right is properly denied in the objects + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion(currentMajor + ".1")); + + try { + historyPane.deleteVersion(historyPane.getCurrentVersion()); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + + // here the history shouldn't have changed because of the CustomUserUpdatedDocumentEventListener + // that should have cancel the event + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that deleting another version still works + historyPane = historyPane.deleteVersion(currentMajor + ".1"); + historyPane = historyPane.showMinorEdits(); + + assertEquals(numberOfVersions + 2, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertFalse(historyPane.hasVersion(currentMajor + ".1")); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + } finally { + // Put back the page in the state it was before our changes + setup.loginAsSuperAdmin(); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + historyPane.rollbackToVersion(latestVersionBeforeChanges); + } + } }
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/DeleteVersionsAction.java+1 −1 modified@@ -65,7 +65,7 @@ public boolean action(XWikiContext context) throws XWikiException Version v2 = versions[1]; if (v1 != null && v2 != null) { - context.getWiki().deleteDocumentVersions(tdoc, v1.toString(), v2.toString(), context); + context.getWiki().deleteDocumentVersions(tdoc, v1.toString(), v2.toString(), true, context); } sendRedirect(context);
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/RollbackAction.java+1 −1 modified@@ -79,7 +79,7 @@ public boolean action(XWikiContext context) throws XWikiException } // Perform the rollback. - xwiki.rollback(tdoc, rev, context); + xwiki.rollback(tdoc, rev, true, true, context); // Forward to view. String redirect = Utils.getRedirect("view", context);
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java+53 −7 modified@@ -4694,6 +4694,27 @@ public void checkDeletingDocument(DocumentReference userReference, XWikiDocument */ public void deleteDocumentVersions(XWikiDocument document, String version1, String version2, XWikiContext context) throws XWikiException + { + deleteDocumentVersions(document, version1, version2, false, context); + } + + /** + * Delete a range of versions from a document history. + * + * @param document the document from which to delete versions + * @param version1 one end of the versions range to remove + * @param version2 the other end of the versions range to remove + * @param triggeredByUser {@code true} if the API is called directly by an action from a user and checks need to + * be performed for the rollback (See: {@link #rollback(XWikiDocument, String, boolean, boolean, XWikiContext)}). + * @param context the XWiki context + * @throws XWikiException + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + @Unstable + public void deleteDocumentVersions(XWikiDocument document, String version1, String version2, + boolean triggeredByUser, XWikiContext context) throws XWikiException { Version v1 = new Version(version1); Version v2 = new Version(version2); @@ -4736,20 +4757,22 @@ public void deleteDocumentVersions(XWikiDocument document, String version1, Stri .notify(new DocumentVersionRangeDeletingEvent(document.getDocumentReferenceWithLocale(), lowerBound.toString(), upperBound.toString()), document, context); - // Update the archive - context.getWiki().getVersioningStore().saveXWikiDocArchive(archive, true, context); - // Make sure the cached document archive is updated too - XWikiDocument cachedDocument = - context.getWiki().getDocument(document.getDocumentReferenceWithLocale(), context); - cachedDocument.setDocumentArchive(archive); // There are still some versions left. // If we delete the most recent (current) version, then rollback to latest undeleted version. + // We do that right before updating the archive, in case it would cancel the action. Version previousVersion = archive.getLatestVersion(); if (!document.getRCSVersion().equals(previousVersion)) { - context.getWiki().rollback(document, previousVersion.toString(), false, context); + context.getWiki().rollback(document, previousVersion.toString(), false, triggeredByUser, context); } + // Update the archive + context.getWiki().getVersioningStore().saveXWikiDocArchive(archive, true, context); + // Make sure the cached document archive is updated too + XWikiDocument cachedDocument = + context.getWiki().getDocument(document.getDocumentReferenceWithLocale(), context); + cachedDocument.setDocumentArchive(archive); + // Notify after versions delete getObservationManager() .notify(new DocumentVersionRangeDeletedEvent(document.getDocumentReferenceWithLocale(), @@ -7606,6 +7629,25 @@ private void restoreDeletedAttachment(XWikiAttachment rolledbackAttachment, XWik */ public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addRevision, XWikiContext xcontext) throws XWikiException + { + return rollback(tdoc, rev, addRevision, false, xcontext); + } + + /** + * @param tdoc the document to rollback + * @param rev the revision to rollback to + * @param addRevision true if a new revision should be created + * @param triggeredByUser {@code true} if this has been triggered by a user and a check needs to be performed + * @param xcontext the XWiki context + * @return the new document + * @throws XWikiException when failing to rollback the document + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + @Unstable + public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addRevision, + boolean triggeredByUser, XWikiContext xcontext) throws XWikiException { LOGGER.debug("Rolling back [{}] to version [{}]", tdoc, rev); @@ -7684,6 +7726,10 @@ public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addR message = localizePlainOrKey("core.comment.rollback", rev); } + if (triggeredByUser) { + checkSavingDocument(xcontext.getUserReference(), document, message, false, xcontext); + } + ObservationManager om = getObservationManager(); if (om != null) { // Notify listeners about the document that is going to be rolled back.
xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/XWikiMockitoTest.java+8 −2 modified@@ -73,6 +73,7 @@ import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.internal.ReadOnlyXWikiContextProvider; import com.xpn.xwiki.internal.debug.DebugConfiguration; +import com.xpn.xwiki.internal.event.UserUpdatingDocumentEvent; import com.xpn.xwiki.internal.render.groovy.ParseGroovyFromString; import com.xpn.xwiki.internal.skin.InternalSkinManager; import com.xpn.xwiki.internal.store.StoreConfiguration; @@ -216,7 +217,7 @@ public void copyDocumentPreservesAttachmentsVersion() throws Exception } /** - * Verify that {@link XWiki#rollback(XWikiDocument, String, XWikiContext)} fires the right events. + * Verify that {@link XWiki#rollback(XWikiDocument, String, boolean, boolean, XWikiContext)} fires the right events. */ @Test public void rollbackFiresEvents() throws Exception @@ -236,17 +237,22 @@ public void rollbackFiresEvents() throws Exception XWikiDocument result = mock(XWikiDocument.class); when(result.getDocumentReference()).thenReturn(documentReference); + DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "ContextUser"); + this.context.setUserReference(userReference); + String revision = "3.5"; when(this.documentRevisionProvider.getRevision(document, revision)).thenReturn(result); this.componentManager.registerMockComponent(ContextualLocalizationManager.class); - xwiki.rollback(document, revision, context); + xwiki.rollback(document, revision, true, true, context); verify(observationManager).notify(new DocumentRollingBackEvent(documentReference, revision), document, context); verify(observationManager).notify(new DocumentUpdatingEvent(documentReference), document, context); verify(observationManager).notify(new DocumentUpdatedEvent(documentReference), document, context); verify(observationManager).notify(new DocumentRolledBackEvent(documentReference, revision), document, context); + verify(observationManager).notify(new UserUpdatingDocumentEvent(userReference, documentReference), + document, context); } @Test
xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java+26 −0 modified@@ -183,4 +183,30 @@ public ComparePage compare(String fromVersion, String toVersion) getDriver().findElementWithoutWaiting(pane, By.xpath(".//input[@accesskey = 'c']")).click(); return new ComparePage(); } + + /** + * @return the total number of versions contained in the history as returned by the live table + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + public int getNumberOfVersions() + { + String xpath = ".//div[@class='paginationFilter' and following-sibling::div[@id='historycontent']]"; + WebElement paginationDiv = getDriver().findElementWithoutWaiting(By.xpath(xpath)); + return Integer.parseInt(getDriver().findElementWithoutWaiting(paginationDiv, By.className("totalResultsNo")) + .getText()); + } + + /** + * @return {@code true} if the requested version is currently displayed in the history + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + public boolean hasVersion(String version) + { + String xpath = String.format(".//table//tr[contains(., '%s')]", version); + return getDriver().hasElementWithoutWaiting(pane, By.xpath(xpath)); + } }
4fa7f302b14dXWIKI-21257: Rollback is not triggering a UserUpdatingDocumentEvent
8 files changed · +488 −11
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/main/java/org/xwiki/test/CustomUserUpdatedDocumentEventListener.java+74 −0 added@@ -0,0 +1,74 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.test; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.xwiki.component.annotation.Component; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.observation.AbstractEventListener; +import org.xwiki.observation.event.Event; + +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.internal.event.UserUpdatingDocumentEvent; + +/** + * Listener dedicated to cancel a specific rolling back event triggered in VersionIT test. + * + * @version $Id$ + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ +@Component +@Singleton +@Named(CustomUserUpdatedDocumentEventListener.NAME) +public class CustomUserUpdatedDocumentEventListener extends AbstractEventListener +{ + static final String NAME = "CustomUserUpdatedDocumentEventListener"; + + @Inject + private Logger logger; + + /** + * Default constructor. + */ + public CustomUserUpdatedDocumentEventListener() + { + super(NAME, new UserUpdatingDocumentEvent()); + } + + @Override + public void onEvent(Event event, Object source, Object data) + { + UserUpdatingDocumentEvent userEvent = (UserUpdatingDocumentEvent) event; + XWikiDocument sourceDoc = (XWikiDocument) source; + DocumentReference expectedReference = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + if (StringUtils.equals(userEvent.getUserReference().getName(), "DeleteVersionTestUserCancelEvent") + && sourceDoc.getDocumentReference().equals(expectedReference)) { + logger.info("Cancelling user event on purpose"); + userEvent.cancel(); + } + } +}
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/main/resources/META-INF/components.txt+1 −0 modified@@ -1 +1,2 @@ +org.xwiki.test.CustomUserUpdatedDocumentEventListener org.xwiki.test.TestMacro
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/VersionIT.java+324 −0 modified@@ -19,24 +19,30 @@ */ package org.xwiki.flamingo.test.docker; +import java.util.List; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.xwiki.flamingo.skin.test.po.AttachmentsPane; import org.xwiki.flamingo.skin.test.po.AttachmentsViewPage; import org.xwiki.model.reference.AttachmentReference; +import org.xwiki.model.reference.DocumentReference; import org.xwiki.rest.model.jaxb.Page; import org.xwiki.test.docker.junit5.TestReference; import org.xwiki.test.docker.junit5.UITest; import org.xwiki.test.ui.TestUtils; import org.xwiki.test.ui.po.HistoryPane; import org.xwiki.test.ui.po.ViewPage; +import org.xwiki.test.ui.po.editor.ObjectEditPage; +import org.xwiki.test.ui.po.editor.ObjectEditPane; import org.xwiki.test.ui.po.editor.WikiEditPage; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -321,4 +327,322 @@ void testDeleteAllButFirstVersion(TestUtils setup, TestReference testReference) assertEquals("1.1", page.getVersion()); assertEquals("1.1", page.getContent()); } + + /** + * Scenario: + * * Create a user RollbackTestUser + * * Create a page, allow RollbackTestUser script right on it, and then deny it + * * Login with RollbackTestUser and try to rollback the page to the version where the right was allowed + * * Check that the page still has the right xobject set to deny + */ + @Test + @Order(7) + void testRollbackDontMessUpRights(TestUtils setup, TestReference testReference) throws Exception + { + setup.loginAsSuperAdmin(); + setup.rest().delete(testReference); + String rollbackTestUser = "RollbackTestUser"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", rollbackTestUser)); + setup.createUser(rollbackTestUser, rollbackTestUser, ""); + setup.createPage(testReference, "Test Rollback Page"); + + setup.setRights(testReference, "", "XWiki." + rollbackTestUser, "script", true); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(testReference, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + ObjectEditPane objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("1", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + objectEditPane.setFieldValue(objectEditPane.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + setup.gotoPage(testReference); + + setup.login(rollbackTestUser, rollbackTestUser); + + // check that the right is as expected + setup.gotoPage(testReference, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("0", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + + ViewPage viewPage = objectEditPage.clickCancel(); + HistoryPane historyPane = viewPage.openHistoryDocExtraPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that the history contains what we're expecting + assertEquals(3, historyPane.getNumberOfVersions()); + assertEquals("2.2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion("2.1")); + + viewPage = historyPane.rollbackToVersion("2.1"); + historyPane = viewPage.openHistoryDocExtraPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that the history contains what we're expecting + assertEquals(4, historyPane.getNumberOfVersions()); + + assertEquals("3.1", historyPane.getCurrentVersion()); + assertEquals("Rollback to version 2.1", historyPane.getCurrentVersionComment()); + assertTrue(historyPane.hasVersion("2.2")); + assertTrue(historyPane.hasVersion("2.1")); + + // check that the right is still the same + setup.gotoPage(testReference, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiRights", true); + assertEquals(1, rightObjects.size()); + objectEditPane = rightObjects.get(0); + assertEquals("XWiki." + rollbackTestUser, objectEditPane.getFieldValue(objectEditPane.byPropertyName("users"))); + assertEquals("script", objectEditPane.getFieldValue(objectEditPane.byPropertyName("levels"))); + assertEquals("0", objectEditPane.getFieldValue(objectEditPane.byPropertyName("allow"))); + + objectEditPage.clickCancel(); + } + + /** + * Scenario: + * * Create a user DeleteVersionTestUser + * * Give DeleteVersionTestUser Admin right with a dedicated xobject in XWiki.XWikiPreferences + * * Give DeleteVersionTestUser PR right with a dedicated xobject in XWiki.XWikiPreferences + * * Edit the xobject to deny PR right to DeleteVersionTestUser + * * Login with DeleteVersionTestUser and delete last version of XWiki.XWikiPreferences + * * Check that the version has been deleted but the PR right is still denied + */ + @Test + @Order(8) + void testDeleteVersionDontMessUpRights(TestUtils setup) throws Exception + { + setup.loginAsSuperAdmin(); + + String deleteVersionTestUser = "DeleteVersionTestUser"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", deleteVersionTestUser)); + setup.createUser(deleteVersionTestUser, deleteVersionTestUser, ""); + + setup.setGlobalRights("", deleteVersionTestUser, "admin", true); + + DocumentReference xwikiPreferences = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + HistoryPane historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + // store the current version as it will be our basis for next steps + String latestVersionBeforeChanges = historyPane.getCurrentVersion(); + int numberOfVersions = historyPane.getNumberOfVersions(); + + // We just create a new major version in the history + setup.gotoPage(xwikiPreferences, "edit", "editor=wiki"); + WikiEditPage wikiEditPage = new WikiEditPage(); + wikiEditPage.clickSaveAndView(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + // Version where we start our changes + String startChangesVersion = historyPane.getCurrentVersion(); + + String currentMajor = startChangesVersion.split("\\.")[0]; + + setup.setGlobalRights("", deleteVersionTestUser, "programming", true); + currentMajor = String.valueOf(Integer.parseInt(currentMajor) + 1); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + ObjectEditPane rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("1", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + rightObject.setFieldValue(rightObject.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + + setup.gotoPage(xwikiPreferences); + + setup.login(deleteVersionTestUser, deleteVersionTestUser); + + // first check that the right is properly denied in the objects + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion(currentMajor + ".1")); + + try { + historyPane = historyPane.deleteVersion(historyPane.getCurrentVersion()); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 2, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".1", historyPane.getCurrentVersion()); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, + rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + } finally { + // Put back the page in the state it was before our changes + setup.loginAsSuperAdmin(); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + historyPane.rollbackToVersion(latestVersionBeforeChanges); + } + } + + /** + * Scenario: + * * Same as above but using DeleteVersionTestUserCancelEvent as user to trigger the + * {@link org.xwiki.test.CustomUserUpdatedDocumentEventListener} which should cancel immediately the change + * * Expectation here is that the reset is not performed at all + */ + @Test + @Order(9) + void testDeleteVersionDontMessUpRightsWithCancellingEvent(TestUtils setup) + throws Exception + { + setup.loginAsSuperAdmin(); + + String deleteVersionTestUser = "DeleteVersionTestUserCancelEvent"; + setup.rest().delete(new DocumentReference("xwiki", "XWiki", deleteVersionTestUser)); + setup.createUser(deleteVersionTestUser, deleteVersionTestUser, ""); + + setup.setGlobalRights("", deleteVersionTestUser, "admin", true); + + DocumentReference xwikiPreferences = new DocumentReference("xwiki", "XWiki", "XWikiPreferences"); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + HistoryPane historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + // store the current version as it will be our basis for next steps + String latestVersionBeforeChanges = historyPane.getCurrentVersion(); + int numberOfVersions = historyPane.getNumberOfVersions(); + + // We just create a new major version in the history + setup.gotoPage(xwikiPreferences, "edit", "editor=wiki"); + WikiEditPage wikiEditPage = new WikiEditPage(); + wikiEditPage.clickSaveAndView(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + // Version where we start our changes + String startChangesVersion = historyPane.getCurrentVersion(); + + String currentMajor = startChangesVersion.split("\\.")[0]; + + setup.setGlobalRights("", deleteVersionTestUser, "programming", true); + currentMajor = String.valueOf(Integer.parseInt(currentMajor) + 1); + + // We don't use setRights twice as it would create another object and we want to edit the existing one. + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + ObjectEditPage objectEditPage = new ObjectEditPage(); + List<ObjectEditPane> rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + ObjectEditPane rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("1", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + rightObject.setFieldValue(rightObject.byPropertyName("allow"), "0"); + // We want a minor version + objectEditPage.clickSaveAndContinue(); + + setup.gotoPage(xwikiPreferences); + + setup.login(deleteVersionTestUser, deleteVersionTestUser); + + // first check that the right is properly denied in the objects + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertTrue(historyPane.hasVersion(currentMajor + ".1")); + + try { + historyPane.deleteVersion(historyPane.getCurrentVersion()); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + + // here the history shouldn't have changed because of the CustomUserUpdatedDocumentEventListener + // that should have cancel the event + assertEquals(numberOfVersions + 3, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + + // Check that deleting another version still works + historyPane = historyPane.deleteVersion(currentMajor + ".1"); + historyPane = historyPane.showMinorEdits(); + + assertEquals(numberOfVersions + 2, historyPane.getNumberOfVersions()); + assertEquals(currentMajor + ".2", historyPane.getCurrentVersion()); + assertFalse(historyPane.hasVersion(currentMajor + ".1")); + + // Check that the page remained with rights unchanged + setup.gotoPage(xwikiPreferences, "edit", "editor=object"); + objectEditPage = new ObjectEditPage(); + rightObjects = objectEditPage.getObjectsOfClass("XWiki.XWikiGlobalRights", false); + rightObject = rightObjects.get(rightObjects.size() - 1); + rightObject.displayObject(); + assertEquals(deleteVersionTestUser, rightObject.getFieldValue(rightObject.byPropertyName("users"))); + assertEquals("programming", rightObject.getFieldValue(rightObject.byPropertyName("levels"))); + assertEquals("0", rightObject.getFieldValue(rightObject.byPropertyName("allow"))); + objectEditPage.clickCancel(); + } finally { + // Put back the page in the state it was before our changes + setup.loginAsSuperAdmin(); + setup.gotoPage(xwikiPreferences, "view", "viewer=history"); + historyPane = new HistoryPane(); + historyPane = historyPane.showMinorEdits(); + historyPane.rollbackToVersion(latestVersionBeforeChanges); + } + } }
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/DeleteVersionsAction.java+1 −1 modified@@ -65,7 +65,7 @@ public boolean action(XWikiContext context) throws XWikiException Version v2 = versions[1]; if (v1 != null && v2 != null) { - context.getWiki().deleteDocumentVersions(tdoc, v1.toString(), v2.toString(), context); + context.getWiki().deleteDocumentVersions(tdoc, v1.toString(), v2.toString(), true, context); } sendRedirect(context);
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/RollbackAction.java+1 −1 modified@@ -79,7 +79,7 @@ public boolean action(XWikiContext context) throws XWikiException } // Perform the rollback. - xwiki.rollback(tdoc, rev, context); + xwiki.rollback(tdoc, rev, true, true, context); // Forward to view. String redirect = Utils.getRedirect("view", context);
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java+53 −7 modified@@ -4704,6 +4704,27 @@ public void checkDeletingDocument(DocumentReference userReference, XWikiDocument @Unstable public void deleteDocumentVersions(XWikiDocument document, String version1, String version2, XWikiContext context) throws XWikiException + { + deleteDocumentVersions(document, version1, version2, false, context); + } + + /** + * Delete a range of versions from a document history. + * + * @param document the document from which to delete versions + * @param version1 one end of the versions range to remove + * @param version2 the other end of the versions range to remove + * @param triggeredByUser {@code true} if the API is called directly by an action from a user and checks need to + * be performed for the rollback (See: {@link #rollback(XWikiDocument, String, boolean, boolean, XWikiContext)}). + * @param context the XWiki context + * @throws XWikiException + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + @Unstable + public void deleteDocumentVersions(XWikiDocument document, String version1, String version2, + boolean triggeredByUser, XWikiContext context) throws XWikiException { Version v1 = new Version(version1); Version v2 = new Version(version2); @@ -4746,20 +4767,22 @@ public void deleteDocumentVersions(XWikiDocument document, String version1, Stri .notify(new DocumentVersionRangeDeletingEvent(document.getDocumentReferenceWithLocale(), lowerBound.toString(), upperBound.toString()), document, context); - // Update the archive - context.getWiki().getVersioningStore().saveXWikiDocArchive(archive, true, context); - // Make sure the cached document archive is updated too - XWikiDocument cachedDocument = - context.getWiki().getDocument(document.getDocumentReferenceWithLocale(), context); - cachedDocument.setDocumentArchive(archive); // There are still some versions left. // If we delete the most recent (current) version, then rollback to latest undeleted version. + // We do that right before updating the archive, in case it would cancel the action. Version previousVersion = archive.getLatestVersion(); if (!document.getRCSVersion().equals(previousVersion)) { - context.getWiki().rollback(document, previousVersion.toString(), false, context); + context.getWiki().rollback(document, previousVersion.toString(), false, triggeredByUser, context); } + // Update the archive + context.getWiki().getVersioningStore().saveXWikiDocArchive(archive, true, context); + // Make sure the cached document archive is updated too + XWikiDocument cachedDocument = + context.getWiki().getDocument(document.getDocumentReferenceWithLocale(), context); + cachedDocument.setDocumentArchive(archive); + // Notify after versions delete getObservationManager() .notify(new DocumentVersionRangeDeletedEvent(document.getDocumentReferenceWithLocale(), @@ -7602,6 +7625,25 @@ private void restoreDeletedAttachment(XWikiAttachment rolledbackAttachment, XWik */ public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addRevision, XWikiContext xcontext) throws XWikiException + { + return rollback(tdoc, rev, addRevision, false, xcontext); + } + + /** + * @param tdoc the document to rollback + * @param rev the revision to rollback to + * @param addRevision true if a new revision should be created + * @param triggeredByUser {@code true} if this has been triggered by a user and a check needs to be performed + * @param xcontext the XWiki context + * @return the new document + * @throws XWikiException when failing to rollback the document + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + @Unstable + public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addRevision, + boolean triggeredByUser, XWikiContext xcontext) throws XWikiException { LOGGER.debug("Rolling back [{}] to version [{}]", tdoc, rev); @@ -7680,6 +7722,10 @@ public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addR message = localizePlainOrKey("core.comment.rollback", rev); } + if (triggeredByUser) { + checkSavingDocument(xcontext.getUserReference(), document, message, false, xcontext); + } + ObservationManager om = getObservationManager(); if (om != null) { // Notify listeners about the document that is going to be rolled back.
xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/XWikiMockitoTest.java+8 −2 modified@@ -73,6 +73,7 @@ import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.internal.ReadOnlyXWikiContextProvider; import com.xpn.xwiki.internal.debug.DebugConfiguration; +import com.xpn.xwiki.internal.event.UserUpdatingDocumentEvent; import com.xpn.xwiki.internal.render.groovy.ParseGroovyFromString; import com.xpn.xwiki.internal.skin.InternalSkinManager; import com.xpn.xwiki.internal.store.StoreConfiguration; @@ -216,7 +217,7 @@ public void copyDocumentPreservesAttachmentsVersion() throws Exception } /** - * Verify that {@link XWiki#rollback(XWikiDocument, String, XWikiContext)} fires the right events. + * Verify that {@link XWiki#rollback(XWikiDocument, String, boolean, boolean, XWikiContext)} fires the right events. */ @Test public void rollbackFiresEvents() throws Exception @@ -236,17 +237,22 @@ public void rollbackFiresEvents() throws Exception XWikiDocument result = mock(XWikiDocument.class); when(result.getDocumentReference()).thenReturn(documentReference); + DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "ContextUser"); + this.context.setUserReference(userReference); + String revision = "3.5"; when(this.documentRevisionProvider.getRevision(document, revision)).thenReturn(result); this.componentManager.registerMockComponent(ContextualLocalizationManager.class); - xwiki.rollback(document, revision, context); + xwiki.rollback(document, revision, true, true, context); verify(observationManager).notify(new DocumentRollingBackEvent(documentReference, revision), document, context); verify(observationManager).notify(new DocumentUpdatingEvent(documentReference), document, context); verify(observationManager).notify(new DocumentUpdatedEvent(documentReference), document, context); verify(observationManager).notify(new DocumentRolledBackEvent(documentReference, revision), document, context); + verify(observationManager).notify(new UserUpdatingDocumentEvent(userReference, documentReference), + document, context); } @Test
xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java+26 −0 modified@@ -183,4 +183,30 @@ public ComparePage compare(String fromVersion, String toVersion) getDriver().findElementWithoutWaiting(pane, By.xpath(".//input[@accesskey = 'c']")).click(); return new ComparePage(); } + + /** + * @return the total number of versions contained in the history as returned by the live table + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + public int getNumberOfVersions() + { + String xpath = ".//div[@class='paginationFilter' and following-sibling::div[@id='historycontent']]"; + WebElement paginationDiv = getDriver().findElementWithoutWaiting(By.xpath(xpath)); + return Integer.parseInt(getDriver().findElementWithoutWaiting(paginationDiv, By.className("totalResultsNo")) + .getText()); + } + + /** + * @return {@code true} if the requested version is currently displayed in the history + * @since 14.10.17 + * @since 15.5.3 + * @since 15.8RC1 + */ + public boolean hasVersion(String version) + { + String xpath = String.format(".//table//tr[contains(., '%s')]", version); + return getDriver().hasElementWithoutWaiting(pane, By.xpath(xpath)); + } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-xh35-w7wg-95v3ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-21648ghsaADVISORY
- github.com/xwiki/xwiki-platform/commit/1f3220f14bb3a4dcbd10d31134c39a06037f9a74ghsaWEB
- github.com/xwiki/xwiki-platform/commit/4de72875ca49602796165412741033bfdbf1e680ghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/commit/4fa7f302b14da6f05a6904a14e3741c4c06c40a1ghsaWEB
- github.com/xwiki/xwiki-platform/security/advisories/GHSA-xh35-w7wg-95v3ghsax_refsource_CONFIRMWEB
- jira.xwiki.org/browse/XWIKI-21257ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.