CVE-2024-21648: Improper Handling of Insufficient Privileges (CWE-274)
value: 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. 
CWE description: The product does not handle or incorrectly handles when it has insufficient privileges to perform an operation, leading to resultant weaknesses.
CVSS v3 base score: 8.8
Published: 2024-01-09T00:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: Java
Files changed: 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, 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, 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, xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java, xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/DeleteVersionsAction.java, xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/RollbackAction.java, xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/XWikiMockitoTest.java, xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java
Commit date: 2023-09-05 16:45:45 +0200
Commit message: XWIKI-21257: Rollback is not triggering a UserUpdatingDocumentEvent

  * Ensure that the rollback API properly calls the check method
  * Back the changes in a unit test that check events triggered in
    rollback

Diff: diff --git a/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 b/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
new file mode 100644
index 0000000000000..81d3707380c02
--- /dev/null
+++ b/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
@@ -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();
+        }
+    }
+}
diff --git a/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 b/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
index 53f7c53081843..ec070ec8b41be 100644
--- a/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
+++ b/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 +1,2 @@
+org.xwiki.test.CustomUserUpdatedDocumentEventListener
 org.xwiki.test.TestMacro
diff --git a/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 b/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
index 8f16662769c62..015208e7458a6 100644
--- a/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
+++ b/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
@@ -17,28 +17,34 @@
  * 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.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;
 
 /**
  * Verify versioning features of documents and attachments.
  * 
@@ -319,6 +325,324 @@ void testDeleteAllButFirstVersion(TestUtils setup, TestReference testReference)
         assertEquals("1.1", historyTab.getCurrentVersion());
         Page page = (Page) setup.rest().get(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);
+        }
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java
index efe208a496f8f..d3e859f70dda6 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java
@@ -4706,10 +4706,31 @@ public void checkDeletingDocument(DocumentReference userReference, XWikiDocument
      * @throws XWikiException
      * @since 13.6RC1
      */
     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);
 
         // Find the lower and upper bounds
@@ -4748,24 +4769,26 @@ public void deleteDocumentVersions(XWikiDocument document, String version1, Stri
             // Notify before versions delete
             getObservationManager()
                 .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(),
                     lowerBound.toString(), upperBound.toString()), document, context);
         }
@@ -7567,10 +7590,29 @@ private void restoreDeletedAttachment(XWikiAttachment rolledbackAttachment, XWik
      * @since 10.7RC1
      * @since 9.11.8
      */
     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);
 
         // Clone the document before modifying to avoid concurrency issues
         XWikiDocument document = tdoc.clone();
@@ -7645,10 +7687,14 @@ public XWikiDocument rollback(final XWikiDocument tdoc, String rev, boolean addR
             // Make sure to save a new version even if nothing changed
             document.setMetaDataDirty(true);
             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.
             // Note that for the moment the event being send is a bridge event, as we are still passing around
             // an XWikiDocument as source and an XWikiContext as data.
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/DeleteVersionsAction.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/DeleteVersionsAction.java
index 0173f418f9017..0608f030c6701 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/DeleteVersionsAction.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/DeleteVersionsAction.java
@@ -63,11 +63,11 @@ public boolean action(XWikiContext context) throws XWikiException
         Version[] versions = getVersionsFromForm(form, archive);
         Version v1 = versions[0];
         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);
         return false;
     }
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/RollbackAction.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/RollbackAction.java
index 94c7a67790767..f40cf683377b3 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/RollbackAction.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/RollbackAction.java
@@ -77,11 +77,11 @@ public boolean action(XWikiContext context) throws XWikiException
                 rev = "-1";
             }
         }
 
         // Perform the rollback.
-        xwiki.rollback(tdoc, rev, context);
+        xwiki.rollback(tdoc, rev, true, true, context);
 
         // Forward to view.
         String redirect = Utils.getRedirect("view", context);
         sendRedirect(response, redirect);
         return false;
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/XWikiMockitoTest.java b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/XWikiMockitoTest.java
index 1541a0b06e3f6..225ac6dc6c8fb 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/XWikiMockitoTest.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/XWikiMockitoTest.java
@@ -71,10 +71,11 @@
 
 import com.xpn.xwiki.doc.DocumentRevisionProvider;
 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;
 import com.xpn.xwiki.objects.BaseObject;
 import com.xpn.xwiki.store.XWikiRecycleBinStoreInterface;
@@ -214,11 +215,11 @@ public void copyDocumentPreservesAttachmentsVersion() throws Exception
 
         verify(xwiki.getStore()).saveXWikiDoc(target, context);
     }
 
     /**
-     * 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
     {
         ObservationManager observationManager = this.componentManager.getInstance(ObservationManager.class);
@@ -234,21 +235,26 @@ public void rollbackFiresEvents() throws Exception
         when(document.getOriginalDocument()).thenReturn(originalDocument);
 
         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
     public void deleteAllDocumentsAndWithoutSendingToTrash() throws Exception
     {
diff --git a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java
index 62e438527f715..a4907409f3a83 100644
--- a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java
+++ b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/HistoryPane.java
@@ -181,6 +181,32 @@ public ComparePage compare(String fromVersion, String toVersion)
     {
         this.selectVersions(fromVersion, 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));
+    }
 }

Vulnerable code:             // 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);
                context.getWiki().rollback(document, previousVersion.toString(), false, context);
            context.getWiki().deleteDocumentVersions(tdoc, v1.toString(), v2.toString(), context);
        xwiki.rollback(tdoc, rev, context);
     * Verify that {@link XWiki#rollback(XWikiDocument, String, XWikiContext)} fires the right events.
        xwiki.rollback(document, revision, context);
Fixed code: /*
 * 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();
        }
    }
}
org.xwiki.test.CustomUserUpdatedDocumentEventListener
import java.util.List;

import org.xwiki.model.reference.DocumentReference;
import org.xwiki.test.ui.po.editor.ObjectEditPage;
import org.xwiki.test.ui.po.editor.ObjectEditPane;
import static org.junit.jupiter.api.Assertions.assertFalse;

    /**
     * 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);
        }
    }
    {
        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
            // We do that right before updating the archive, in case it would cancel the action.
                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);

    {
        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
        if (triggeredByUser) {
            checkSavingDocument(xcontext.getUserReference(), document, message, false, xcontext);
        }

            context.getWiki().deleteDocumentVersions(tdoc, v1.toString(), v2.toString(), true, context);
        xwiki.rollback(tdoc, rev, true, true, context);
import com.xpn.xwiki.internal.event.UserUpdatingDocumentEvent;
     * Verify that {@link XWiki#rollback(XWikiDocument, String, boolean, boolean, XWikiContext)} fires the right events.
        DocumentReference userReference = new DocumentReference("xwiki", "XWiki", "ContextUser");
        this.context.setUserReference(userReference);

        xwiki.rollback(document, revision, true, true, context);
        verify(observationManager).notify(new UserUpdatingDocumentEvent(userReference, documentReference),
            document, context);

    /**
     * @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));
    }