CVE-2024-21648 (Java, published 2024-01-09T00:15Z)
Description: 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. 
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-274 \u2014 Improper Handling of Insufficient Privileges
The product does not handle or incorrectly handles when it has insufficient privileges to perform an operation, leading to resultant weaknesses.
Repository: https://github.com/xwiki/xwiki-platform
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 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


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));
    }