From 26482ee5d29fc21f31134d1ee13db48716e89e0f Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Thu, 4 Apr 2024 17:00:08 +0200 Subject: [PATCH] XWIKI-22052: Improve right handling in page history resources * Add unit tests for the page history resources. (cherry picked from commit 9cbca9808300797c67779bb9a665d85cf9e3d4b8) --- .../pages/PageHistoryResourceImpl.java | 18 ++ .../PageTranslationHistoryResourceImpl.java | 18 ++ .../AbstractPageHistoryResourceImplTest.java | 187 ++++++++++++++++++ .../pages/PageHistoryResourceImplTest.java | 58 ++++++ ...ageTranslationHistoryResourceImplTest.java | 59 ++++++ 5 files changed, 340 insertions(+) create mode 100644 xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/AbstractPageHistoryResourceImplTest.java create mode 100644 xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImplTest.java create mode 100644 xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImplTest.java diff --git a/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImpl.java b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImpl.java index 39b4bc777e19..eaf8dd38ca35 100644 --- a/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImpl.java +++ b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImpl.java @@ -23,9 +23,13 @@ import java.util.Date; import java.util.List; +import javax.inject.Inject; import javax.inject.Named; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; import org.xwiki.component.annotation.Component; +import org.xwiki.model.reference.DocumentReference; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.rest.XWikiResource; @@ -35,6 +39,9 @@ import org.xwiki.rest.model.jaxb.History; import org.xwiki.rest.model.jaxb.HistorySummary; import org.xwiki.rest.resources.pages.PageHistoryResource; +import org.xwiki.security.authorization.AccessDeniedException; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; import com.xpn.xwiki.doc.rcs.XWikiRCSNodeId; @@ -45,11 +52,22 @@ @Named("org.xwiki.rest.internal.resources.pages.PageHistoryResourceImpl") public class PageHistoryResourceImpl extends XWikiResource implements PageHistoryResource { + @Inject + private ContextualAuthorizationManager contextualAuthorizationManager; + @Override public History getPageHistory(String wikiName, String spaceName, String pageName, Integer start, Integer number, String order, Boolean withPrettyNames) throws XWikiRestException { List spaces = parseSpaceSegments(spaceName); + + DocumentReference documentReference = new DocumentReference(wikiName, spaces, pageName); + try { + this.contextualAuthorizationManager.checkAccess(Right.VIEW, documentReference); + } catch (AccessDeniedException e) { + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + String spaceId = Utils.getLocalSpaceId(spaces); History history = new History(); diff --git a/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImpl.java b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImpl.java index 6a99a687eab3..4b145c78a194 100644 --- a/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImpl.java +++ b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImpl.java @@ -23,9 +23,13 @@ import java.util.Date; import java.util.List; +import javax.inject.Inject; import javax.inject.Named; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; import org.xwiki.component.annotation.Component; +import org.xwiki.model.reference.DocumentReference; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.rest.XWikiResource; @@ -35,6 +39,9 @@ import org.xwiki.rest.model.jaxb.History; import org.xwiki.rest.model.jaxb.HistorySummary; import org.xwiki.rest.resources.pages.PageTranslationHistoryResource; +import org.xwiki.security.authorization.AccessDeniedException; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; import com.xpn.xwiki.doc.rcs.XWikiRCSNodeId; @@ -45,11 +52,22 @@ @Named("org.xwiki.rest.internal.resources.pages.PageTranslationHistoryResourceImpl") public class PageTranslationHistoryResourceImpl extends XWikiResource implements PageTranslationHistoryResource { + @Inject + private ContextualAuthorizationManager contextualAuthorizationManager; + @Override public History getPageTranslationHistory(String wikiName, String spaceName, String pageName, String language, Integer start, Integer number, String order, Boolean withPrettyNames) throws XWikiRestException { List spaces = parseSpaceSegments(spaceName); + + DocumentReference documentReference = new DocumentReference(wikiName, spaces, pageName); + try { + this.contextualAuthorizationManager.checkAccess(Right.VIEW, documentReference); + } catch (AccessDeniedException e) { + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + String spaceId = Utils.getLocalSpaceId(spaces); History history = new History(); diff --git a/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/AbstractPageHistoryResourceImplTest.java b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/AbstractPageHistoryResourceImplTest.java new file mode 100644 index 000000000000..9de27b7e8cc0 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/AbstractPageHistoryResourceImplTest.java @@ -0,0 +1,187 @@ +/* + * 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.rest.internal.resources.pages; + +import java.net.URI; +import java.net.URISyntaxException; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.Collections; +import java.util.List; + +import javax.inject.Named; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.suigeneris.jrcs.rcs.Version; +import org.xwiki.component.manager.ComponentLookupException; +import org.xwiki.component.manager.ComponentManager; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.query.Query; +import org.xwiki.query.QueryException; +import org.xwiki.query.QueryManager; +import org.xwiki.rest.XWikiResource; +import org.xwiki.rest.XWikiRestException; +import org.xwiki.rest.model.jaxb.History; +import org.xwiki.rest.model.jaxb.HistorySummary; +import org.xwiki.security.authorization.AccessDeniedException; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; +import org.xwiki.test.junit5.mockito.MockComponent; +import org.xwiki.test.mockito.MockitoComponentManager; + +import com.xpn.xwiki.doc.rcs.XWikiRCSNodeId; +import com.xpn.xwiki.web.Utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Abstract base class for tests for page history with and without translation. + * + * @version $Id$ + */ +public abstract class AbstractPageHistoryResourceImplTest +{ + protected static final String WIKI_NAME = "wiki"; + + protected static final String PAGE_NAME = "page"; + + protected static final int START = 8; + + protected static final int LIMIT = 11; + + private static final List SPACE_NAMES = List.of("parent", "space"); + + protected static final String SPACE_URL = String.join("/spaces/", SPACE_NAMES); + + private static final DocumentReference DOCUMENT_REFERENCE = + new DocumentReference(WIKI_NAME, SPACE_NAMES, PAGE_NAME); + + private static final DocumentReference USER_REFERENCE = new DocumentReference("xwiki", "XWiki", "user"); + + private static final String AUTHOR = "author"; + + private static final String COMMENT = "comment"; + + @MockComponent + private QueryManager queryManager; + + @MockComponent + private ContextualAuthorizationManager contextualAuthorizationManager; + + @MockComponent + @Named("context") + private ComponentManager contextComponentManager; + + @Mock + private UriInfo uriInfo; + + protected abstract History getTranslationHistory() throws XWikiRestException; + + @BeforeEach + void setUp(MockitoComponentManager componentManager) + throws ComponentLookupException, URISyntaxException, IllegalAccessException + { + Utils.setComponentManager(componentManager); + + // Because XWikiResource injects the context component manager, it exists as a mock, and we thus need to mock + // its behavior - otherwise it would just be ignored. + when(this.contextComponentManager.getInstance(any())) + .thenAnswer(invocation -> componentManager.getInstance(invocation.getArgument(0))); + when(this.contextComponentManager.getInstance(any(), any())) + .thenAnswer( + invocation -> componentManager.getInstance(invocation.getArgument(0), invocation.getArgument(1))); + + when(this.uriInfo.getBaseUri()).thenReturn(new URI("https://test/")); + + injectURIInfo(); + } + + abstract void injectURIInfo() throws IllegalAccessException; + + protected void injectURIInfo(XWikiResource resource) throws IllegalAccessException + { + FieldUtils.writeField(resource, "uriInfo", this.uriInfo, true); + } + + @Test + void getPageTranslationHistoryAccessDenied() throws AccessDeniedException, XWikiRestException + { + doThrow(new AccessDeniedException(Right.VIEW, DOCUMENT_REFERENCE, USER_REFERENCE)) + .when(this.contextualAuthorizationManager).checkAccess(Right.VIEW, DOCUMENT_REFERENCE); + + WebApplicationException exception = assertThrows(WebApplicationException.class, this::getTranslationHistory); + + assertEquals(401, exception.getResponse().getStatus()); + } + + @Test + void getPageTranslationHistory() throws XWikiRestException, QueryException + { + Query mockQuery = mock(); + when(this.queryManager.createQuery(any(), eq(Query.XWQL))).thenReturn(mockQuery); + when(mockQuery.bindValue(any(), any())).thenReturn(mockQuery); + when(mockQuery.setOffset(anyInt())).thenReturn(mockQuery); + when(mockQuery.setLimit(anyInt())).thenReturn(mockQuery); + when(mockQuery.setWiki(any())).thenReturn(mockQuery); + XWikiRCSNodeId nodeId = new XWikiRCSNodeId(DOCUMENT_REFERENCE.getWikiReference(), 42, new Version(2, 3)); + Instant now = Instant.now(); + String spaceName = String.join(".", SPACE_NAMES); + when(mockQuery.execute()).thenReturn(Collections.singletonList( + new Object[] { spaceName, PAGE_NAME, nodeId, Timestamp.from(now), AUTHOR, COMMENT })); + History history = getTranslationHistory(); + // Verify that the query was correctly constructed. + verify(mockQuery).bindValue("space", spaceName); + verify(mockQuery).bindValue("name", PAGE_NAME); + String language = getLanguage(); + if (language != null) { + verify(mockQuery).bindValue("language", language); + } + verify(mockQuery).setOffset(START); + verify(mockQuery).setLimit(LIMIT); + verify(mockQuery).setWiki(WIKI_NAME); + // Verify that the history summary was correctly constructed. + assertEquals(1, history.getHistorySummaries().size()); + HistorySummary historySummary = history.getHistorySummaries().get(0); + assertEquals("parent.space", historySummary.getSpace()); + assertEquals(PAGE_NAME, historySummary.getName()); + assertEquals(2, historySummary.getMajorVersion()); + assertEquals(3, historySummary.getMinorVersion()); + // Compare the modified date to the current time. + assertEquals(now.getEpochSecond(), historySummary.getModified().getTime().getTime() / 1000); + assertEquals(AUTHOR, historySummary.getModifier()); + assertEquals(COMMENT, historySummary.getComment()); + assertEquals(language, historySummary.getLanguage()); + } + + protected abstract String getLanguage(); +} diff --git a/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImplTest.java b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImplTest.java new file mode 100644 index 000000000000..739136d92ec6 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImplTest.java @@ -0,0 +1,58 @@ +/* + * 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.rest.internal.resources.pages; + +import org.xwiki.rest.XWikiRestException; +import org.xwiki.rest.model.jaxb.History; +import org.xwiki.test.junit5.mockito.ComponentTest; +import org.xwiki.test.junit5.mockito.InjectMockComponents; + +import com.xpn.xwiki.test.reference.ReferenceComponentList; + +/** + * Unit test for {@link PageHistoryResourceImpl}. + * + * @version $Id$ + */ +@ComponentTest +@ReferenceComponentList +public class PageHistoryResourceImplTest extends AbstractPageHistoryResourceImplTest +{ + @InjectMockComponents + private PageHistoryResourceImpl pageHistoryResource; + + @Override + protected History getTranslationHistory() throws XWikiRestException + { + return this.pageHistoryResource.getPageHistory(WIKI_NAME, SPACE_URL, PAGE_NAME, START, LIMIT, "order", false); + } + + @Override + protected String getLanguage() + { + return null; + } + + @Override + void injectURIInfo() throws IllegalAccessException + { + injectURIInfo(this.pageHistoryResource); + } +} diff --git a/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImplTest.java b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImplTest.java new file mode 100644 index 000000000000..712bec32d6d6 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImplTest.java @@ -0,0 +1,59 @@ +/* + * 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.rest.internal.resources.pages; + +import org.xwiki.rest.XWikiRestException; +import org.xwiki.rest.model.jaxb.History; +import org.xwiki.test.junit5.mockito.ComponentTest; +import org.xwiki.test.junit5.mockito.InjectMockComponents; + +import com.xpn.xwiki.test.reference.ReferenceComponentList; + +/** + * Unit tests for {@link PageTranslationHistoryResourceImpl}. + * + * @version $Id$ + */ +@ComponentTest +@ReferenceComponentList +class PageTranslationHistoryResourceImplTest extends AbstractPageHistoryResourceImplTest +{ + @InjectMockComponents + private PageTranslationHistoryResourceImpl pageTranslationHistoryResource; + + @Override + protected History getTranslationHistory() throws XWikiRestException + { + return this.pageTranslationHistoryResource.getPageTranslationHistory(WIKI_NAME, SPACE_URL, PAGE_NAME, + getLanguage(), START, LIMIT, "order", false); + } + + @Override + protected String getLanguage() + { + return "language"; + } + + @Override + void injectURIInfo() throws IllegalAccessException + { + injectURIInfo(this.pageTranslationHistoryResource); + } +}