VYPR
High severityNVD Advisory· Published Jan 8, 2024· Updated Jun 17, 2025

XWiki has no right protection on rollback action

CVE-2024-21648

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.

PackageAffected versionsPatched versions
org.xwiki.platform:xwiki-platform-oldcoreMaven
>= 1.0, < 14.10.1714.10.17
org.xwiki.platform:xwiki-platformMaven
>= 15.0-rc-1, < 15.5.315.5.3
org.xwiki.platform:xwiki-platformMaven
>= 15.6-rc-1, < 15.8-rc-115.8-rc-1

Affected products

1

Patches

3
4de72875ca49

XWIKI-21257: Rollback is not triggering a UserUpdatingDocumentEvent

https://github.com/xwiki/xwiki-platformSimon UrliAug 30, 2023via ghsa
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));
    +    }
     }
    
1f3220f14bb3

XWIKI-21257: Rollback is not triggering a UserUpdatingDocumentEvent

https://github.com/xwiki/xwiki-platformSimon UrliAug 30, 2023via ghsa
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));
    +    }
     }
    
4fa7f302b14d

XWIKI-21257: Rollback is not triggering a UserUpdatingDocumentEvent

https://github.com/xwiki/xwiki-platformSimon UrliAug 30, 2023via ghsa
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

News mentions

0

No linked articles in our index yet.