XWiki's REST APIs don't enforce any limits, leading to unavailability and OOM in large wikis
Description
XWiki is an open-source wiki software platform. Versions 16.10.10 and below, 17.0.0-rc-1 through 17.4.3 and 17.5.0-rc-1 through 17.6.0 contain a REST API which doesn't enforce any limits for the number of items that can be requested in a single request at the moment. Depending on the number of pages in the wiki and the memory configuration, this can lead to slowness and unavailability of the wiki. As an example, the /rest/wikis/xwiki/spaces resource returns all spaces on the wiki by default, which are basically all pages. This issue is fixed in versions 17.4.4 and 16.10.11.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.xwiki.platform:xwiki-platform-rest-serverMaven | < 16.10.11 | 16.10.11 |
org.xwiki.platform:xwiki-platform-rest-serverMaven | >= 17.0.0-rc-1, < 17.4.4 | 17.4.4 |
org.xwiki.platform:xwiki-platform-rest-serverMaven | >= 17.5.0-rc-1, < 17.7.0-rc-1 | 17.7.0-rc-1 |
Affected products
1- Range: < 16.10.11
Patches
1e3c47745195fXWIKI-23355: Use query limits
83 files changed · +1715 −217
xwiki-platform-core/pom.xml+112 −1 modified@@ -150,7 +150,118 @@ </revapi.differences> --> - + <revapi.differences> + <justification>Removed default values as they're now handled by the implementation to allow + dynamically setting the default values based on the configuration.</justification> + <criticality>allowed</criticality> + <differences> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Attachments org.xwiki.rest.resources.attachments.AttachmentHistoryResource::getAttachmentHistory(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Attachments org.xwiki.rest.resources.attachments.AttachmentHistoryResource::getAttachmentHistory(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Attachments org.xwiki.rest.resources.attachments.AttachmentsResource::getAttachments(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.Boolean, java.lang.String, java.lang.String, java.lang.String) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Attachments org.xwiki.rest.resources.attachments.AttachmentsResource::getAttachments(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.Boolean, java.lang.String, java.lang.String, java.lang.String) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Classes org.xwiki.rest.resources.classes.ClassesResource::getClasses(java.lang.String, java.lang.Integer, ===java.lang.Integer===) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Classes org.xwiki.rest.resources.classes.ClassesResource::getClasses(java.lang.String, java.lang.Integer, ===java.lang.Integer===) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Objects org.xwiki.rest.resources.objects.AllObjectsForClassNameResource::getObjects(java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Objects org.xwiki.rest.resources.objects.AllObjectsForClassNameResource::getObjects(java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Pages org.xwiki.rest.resources.pages.PageChildrenResource::getPageChildren(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.Boolean, java.lang.String, java.lang.String) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Pages org.xwiki.rest.resources.pages.PageChildrenResource::getPageChildren(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.Boolean, java.lang.String, java.lang.String) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.History org.xwiki.rest.resources.pages.PageHistoryResource::getPageHistory(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.History org.xwiki.rest.resources.pages.PageHistoryResource::getPageHistory(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.History org.xwiki.rest.resources.pages.PageTranslationHistoryResource::getPageTranslationHistory(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.History org.xwiki.rest.resources.pages.PageTranslationHistoryResource::getPageTranslationHistory(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Pages org.xwiki.rest.resources.pages.PagesResource::getPages(java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String, java.lang.String, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Pages org.xwiki.rest.resources.pages.PagesResource::getPages(java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String, java.lang.String, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Spaces org.xwiki.rest.resources.spaces.SpacesResource::getSpaces(java.lang.String, java.lang.Integer, ===java.lang.Integer===) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Spaces org.xwiki.rest.resources.spaces.SpacesResource::getSpaces(java.lang.String, java.lang.Integer, ===java.lang.Integer===) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Pages org.xwiki.rest.resources.tags.PagesForTagsResource::getTags(java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Pages org.xwiki.rest.resources.tags.PagesForTagsResource::getTags(java.lang.String, java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.Pages org.xwiki.rest.resources.wikis.WikiChildrenResource::getChildren(java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.Pages org.xwiki.rest.resources.wikis.WikiChildrenResource::getChildren(java.lang.String, java.lang.Integer, ===java.lang.Integer===, java.lang.String) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.SearchResults org.xwiki.rest.resources.wikis.WikiSearchQueryResource::search(java.lang.String, java.lang.String, java.lang.String, ===java.lang.Integer===, java.lang.Integer, java.lang.Boolean, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.String) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.SearchResults org.xwiki.rest.resources.wikis.WikiSearchQueryResource::search(java.lang.String, java.lang.String, java.lang.String, ===java.lang.Integer===, java.lang.Integer, java.lang.Boolean, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.String) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.SearchResults org.xwiki.rest.resources.wikis.WikiSearchResource::search(java.lang.String, java.lang.String, java.util.List<java.lang.String>, ===java.lang.Integer===, java.lang.Integer, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.SearchResults org.xwiki.rest.resources.wikis.WikiSearchResource::search(java.lang.String, java.lang.String, java.util.List<java.lang.String>, ===java.lang.Integer===, java.lang.Integer, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.SearchResults org.xwiki.rest.resources.wikis.WikisSearchQueryResource::search(java.lang.String, ===java.lang.Integer===, java.lang.Integer, java.lang.Boolean, java.lang.String, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.String) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.SearchResults org.xwiki.rest.resources.wikis.WikisSearchQueryResource::search(java.lang.String, ===java.lang.Integer===, java.lang.Integer, java.lang.Boolean, java.lang.String, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.String) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + <item> + <ignore>true</ignore> + <code>java.annotation.removed</code> + <old>parameter org.xwiki.rest.model.jaxb.SearchResults org.xwiki.rest.resources.spaces.SpaceSearchResource::search(java.lang.String, java.lang.String, java.lang.String, java.util.List<java.lang.String>, ===java.lang.Integer===, java.lang.Integer, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</old> + <new>parameter org.xwiki.rest.model.jaxb.SearchResults org.xwiki.rest.resources.spaces.SpaceSearchResource::search(java.lang.String, java.lang.String, java.lang.String, java.util.List<java.lang.String>, ===java.lang.Integer===, java.lang.Integer, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.Boolean) throws org.xwiki.rest.XWikiRestException</new> + <annotation>@javax.ws.rs.DefaultValue("-1")</annotation> + </item> + </differences> + </revapi.differences> </analysisConfiguration> </configuration> </plugin>
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/attachmentsjson.vm+1 −4 modified@@ -26,10 +26,7 @@ #if (!$offset || $offset < 0) #set ($offset = 0) #end - #set ($limit = $numbertool.toNumber($request.limit).intValue()) - #if (!$limit) - #set ($limit = 15) - #end + #getAndValidateQueryLimitFromRequest('limit', 15, $limit) ## ## Apply live table filters. ##
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-test/xwiki-platform-index-test-docker/src/test/it/org/xwiki/index/test/ui/docker/DocumentTreeMacroIT.java+31 −0 modified@@ -371,6 +371,37 @@ void sortWithCollation(TestUtils setup, TestReference testReference) } } + @Test + @Order(5) + void testLimit(TestUtils setup, TestReference testReference) + { + DocumentReference alice = + new DocumentReference("WebHome", new SpaceReference("Alice", testReference.getLastSpaceReference())); + DocumentReference bob = + new DocumentReference("WebHome", new SpaceReference("Bob", testReference.getLastSpaceReference())); + DocumentReference carol = + new DocumentReference("WebHome", new SpaceReference("Carol", testReference.getLastSpaceReference())); + DocumentReference denis = + new DocumentReference("WebHome", new SpaceReference("Denis", testReference.getLastSpaceReference())); + + setup.loginAsSuperAdmin(); + setup.deletePage(testReference, true); + + createPage(setup, alice, "Alice", ""); + createPage(setup, bob, "Bob", ""); + createPage(setup, carol, "Carol", ""); + createPage(setup, denis, "Denis", ""); + + // Limit to 2 nodes. We need at least 4 pages for this, as we show up to one more page than the limit. + TreeElement tree = getDocumentTree(setup, testReference, Map.of("limit", "2")); + assertNodeLabels(tree.getTopLevelNodes(), "Alice", "Bob", "2 more ..."); + + // Limit to 2000 nodes + tree = getDocumentTree(setup, testReference, Map.of("limit", "2000")); + // FIXME: the tree is simply empty when the limit is too high, the error that is returned is simply ignored. + assertNodeLabels(tree.getTopLevelNodes()); + } + private ViewPage createPage(TestUtils setup, DocumentReference documentReference, String title, String content) { // We don't care what parent page is used, we just want to avoid creating orphan pages in order to not interfere
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-tree/xwiki-platform-index-tree-macro/src/main/resources/XWiki/DocumentTreeMacros.xml+2 −0 modified@@ -190,6 +190,8 @@ #set ($limit = $mathtool.max($numbertool.toNumber($request.limit).intValue(), 1)) #if ("$!limit" == '') #set ($limit = 15) + #else + #validateQueryLimit($limit) #end #if ($nodeId == '#' && $docTreeConfig.showRoot) #maybeAddNode($actualNodeId $children)
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-ui/src/main/resources/XWiki/AllAttachmentsResults.xml+1 −4 modified@@ -51,10 +51,7 @@ #if (!$offset || $offset < 0) #set ($offset = 0) #end - #set ($limit = $numbertool.toNumber($request.limit).intValue()) - #if (!$limit) - #set ($limit = 15) - #end + #getAndValidateQueryLimitFromRequest('limit', 15, $limit) ## ## Apply live table filters. ##
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-ui/src/main/resources/XWiki/DeletedAttachments.xml+1 −4 modified@@ -146,10 +146,7 @@ $xwiki.jsx.use('XWiki.DeletedAttachments', {'minify' : 'false'})## #if (!$offset || $offset < 0) #set ($offset = 0) #end - #set ($limit = $numbertool.toNumber($request.get('limit')).intValue()) - #if (!$limit) - #set ($limit = 15) - #end + #getAndValidateQueryLimitFromRequest('limit', 15, $limit) #set ($filenameFilter = $request.get('datt.filename')) #set ($docNameFilter = $request.get('datt.docName')) #set ($dateFilter = $request.get('datt.date'))
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-ui/src/main/resources/XWiki/DeletedDocumentsJSON.xml+1 −4 modified@@ -52,10 +52,7 @@ $response.setContentType('application/json') #if (!$offset || $offset < 0) #set ($offset = 0) #end -#set($limit = $numbertool.toNumber($request.get('limit')).intValue()) -#if (!$limit) - #set ($limit = 15) -#end +#getAndValidateQueryLimitFromRequest('limit', 15, $limit) #set($docNameFilter = $request.get('ddoc.fullName')) #set($dateFilter = $request.get('ddoc.date')) #set($deleterFilter = $request.get('ddoc.deleter'))
xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-rest/src/main/java/org/xwiki/livedata/internal/rest/DefaultLiveDataEntriesResource.java+5 −2 modified@@ -73,8 +73,11 @@ public Entries getEntries(String sourceId, String namespace, List<String> proper { this.contextInitializer.initialize(namespace); - LiveDataConfiguration config = initConfig(sourceId, properties, matchAll, sort, descending, offset, limit); - return getEntries(namespace, offset, limit, config); + int validatedLimit = validateAndGetLimit(limit); + + LiveDataConfiguration config = + initConfig(sourceId, properties, matchAll, sort, descending, offset, validatedLimit); + return getEntries(namespace, offset, validatedLimit, config); } @Override
xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-rest/src/test/java/org/xwiki/livedata/internal/rest/DefaultLiveDataEntriesResourceTest.java+24 −1 modified@@ -34,6 +34,8 @@ import org.apache.commons.lang3.reflect.FieldUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.xwiki.component.manager.ComponentManager; @@ -51,6 +53,7 @@ import org.xwiki.livedata.rest.model.jaxb.Entries; import org.xwiki.livedata.rest.model.jaxb.Entry; import org.xwiki.livedata.rest.model.jaxb.StringMap; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.test.annotation.BeforeComponent; import org.xwiki.test.junit5.mockito.ComponentTest; import org.xwiki.test.junit5.mockito.InjectComponentManager; @@ -102,7 +105,10 @@ class DefaultLiveDataEntriesResourceTest @MockComponent private Provider<XWikiContext> xcontextProvider; - + + @MockComponent + private SecurityConfiguration securityConfiguration; + /* * Cannot be mocked by annotation because it is needed in the @BeforeComponent phase. */ @@ -132,6 +138,8 @@ void configure() throws Exception executionContext.setProperty("xwikicontext", this.xcontext); when(execution.getContext()).thenReturn(executionContext); when(this.xcontextProvider.get()).thenReturn(this.xcontext); + + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); } @BeforeEach @@ -241,6 +249,21 @@ void getEntriesDataSourceNotFound() throws Exception } + @ParameterizedTest + @ValueSource(ints = { -1, 1001 }) + void getEntriesInvalidLimit(int limit) + { + List<String> properties = emptyList(); + List<String> matchAll = emptyList(); + List<String> sort = emptyList(); + List<Boolean> descending = emptyList(); + + WebApplicationException exception = + assertThrows(WebApplicationException.class, () -> this.resource.getEntries("sourceId", null, properties, + matchAll, sort, descending, 0, limit)); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), exception.getResponse().getStatus()); + } + @Test void addEntryMissingSource() {
xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml+1 −4 modified@@ -343,10 +343,7 @@ #if(!$offset || $offset < 0) #set($offset = 0) #end - #set($limit = $numbertool.toNumber($request.get('limit')).intValue()) - #if(!$limit) - #set ($limit = 15) - #end + #getAndValidateQueryLimitFromRequest('limit', 15, $limit) #set($query = $services.query.hql($sql)) ## Apply query filters if defined. Otherwise use default. #foreach ($queryFilter in $stringtool.split($!request.queryFilters, ', '))
xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java+14 −0 modified@@ -81,6 +81,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.xwiki.rendering.syntax.Syntax.XWIKI_2_1; @@ -565,6 +566,19 @@ void cleanupAccessToPasswordFields() throws Exception + "and doc.fullName not in (:classTemplate1, :classTemplate2) "); } + @Test + void highLimitIsRejected() throws Exception + { + setLimit(2000); + + renderPage(new DocumentReference("xwiki", "XWiki", "LiveTableResults")); + + verifyNoInteractions(this.queryService); + + // Unfortunately, we can't verify that sendError was called because the response is neither a mock nor does + // it store the error message. + } + private static Stream<Arguments> provideObfuscateEmails() { return Stream.of(
xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-rest/src/main/java/org/xwiki/notifications/rest/internal/DefaultNotificationsResource.java+1 −1 modified@@ -132,7 +132,7 @@ spaces, wikis, users, toMaxCount(maxCount, 21), displayOwnEvents, displayMinorEv private int toMaxCount(String maxCount, int defaultMaxCount) { - return NumberUtils.toInt(maxCount, defaultMaxCount); + return validateAndGetLimit(NumberUtils.toInt(maxCount, defaultMaxCount)); } private Object getCompositeEvents(String useUserPreferences, String userId, String untilDate,
xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-rest/src/test/java/org/xwiki/notifications/rest/internal/DefaultNotificationsResourceTest.java+66 −1 modified@@ -26,15 +26,19 @@ import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.WebApplicationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.notifications.CompositeEvent; import org.xwiki.notifications.notifiers.internal.DefaultNotificationCacheManager; import org.xwiki.notifications.sources.NotificationParameters; import org.xwiki.notifications.sources.internal.DefaultNotificationParametersFactory; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.test.annotation.BeforeComponent; import org.xwiki.test.junit5.mockito.ComponentTest; import org.xwiki.test.junit5.mockito.InjectMockComponents; @@ -45,8 +49,11 @@ import com.xpn.xwiki.user.api.XWikiUser; import com.xpn.xwiki.web.XWikiResponse; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -82,6 +89,9 @@ class DefaultNotificationsResourceTest @MockComponent private RSSFeedRenderer rssFeedRenderer; + @MockComponent + private SecurityConfiguration securityConfiguration; + private XWikiContext context; private XWiki wiki; private XWikiResponse response; @@ -101,6 +111,7 @@ void setup() when(this.context.getWiki()).thenReturn(this.wiki); this.response = mock(XWikiResponse.class); when(this.context.getResponse()).thenReturn(this.response); + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); } @Test @@ -114,6 +125,60 @@ void getNotificationsRSSGuestUserId() throws Exception verify(this.response).sendError(HttpServletResponse.SC_UNAUTHORIZED); } + @ParameterizedTest + @ValueSource(ints = { -1, 1001 }) + void getgetNotificationsRSSLimitErrors(int limit) throws Exception + { + String userId = "XWiki.Admin"; + XWikiUser wikiUser = mock(); + when(this.wiki.checkAuth(this.context)).thenReturn(wikiUser); + DocumentReference userIdDocReference = mock(); + when(this.documentReferenceResolver.resolve(userId)).thenReturn(userIdDocReference); + when(wikiUser.getUserReference()).thenReturn(userIdDocReference); + String maxCountString = String.valueOf(limit); + WebApplicationException exception = assertThrows(WebApplicationException.class, + () -> this.notificationsResource.getNotificationsRSS(null, userId, + null, null, null, null, null, null, maxCountString, null, null, null, null, null, null, null)); + assertEquals(HttpServletResponse.SC_BAD_REQUEST, exception.getResponse().getStatus()); + assertThat(exception.getResponse().getEntity().toString(), containsString("Invalid limit value: " + limit)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 1001 }) + void getNotificationsLimitErrors(int limit) throws Exception + { + String userId = "XWiki.Admin"; + XWikiUser wikiUser = mock(); + when(this.wiki.checkAuth(this.context)).thenReturn(wikiUser); + + String maxCountString = String.valueOf(limit); + WebApplicationException exception = assertThrows(WebApplicationException.class, + () -> this.notificationsResource.getNotifications( + null, userId, null, true, null, null, null, null, null, maxCountString, null, + null, null, null, null, null, null, null, null, null + )); + assertEquals(HttpServletResponse.SC_BAD_REQUEST, exception.getResponse().getStatus()); + assertThat(exception.getResponse().getEntity().toString(), containsString("Invalid limit value: " + limit)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 1001 }) + void getNotificationsCountLimitErrors(int limit) throws Exception + { + String userId = "XWiki.Admin"; + XWikiUser wikiUser = mock(); + when(this.wiki.checkAuth(this.context)).thenReturn(wikiUser); + + String maxCountString = String.valueOf(limit); + WebApplicationException exception = assertThrows(WebApplicationException.class, + () -> this.notificationsResource.getNotificationsCount( + null, userId, null, null, null, null, maxCountString, null, + null, null, null, null, null, null, null, null + )); + assertEquals(HttpServletResponse.SC_BAD_REQUEST, exception.getResponse().getStatus()); + assertThat(exception.getResponse().getEntity().toString(), containsString("Invalid limit value: " + limit)); + } + @Test void getNotificationsRSSUserId() throws Exception { @@ -185,4 +250,4 @@ void getNotificationsRSSUserId() throws Exception .getNotificationsRSS(useUserPreferences, userId, untilDate, blackList, pages, spaces, wikis, users, "", displayOwnEvents, displayMinorEvents, displaySystemEvents, displayReadEvents, "", tags, currentWiki)); } -} \ No newline at end of file +}
xwiki-platform-core/xwiki-platform-query/xwiki-platform-query-manager/src/test/java/org/xwiki/query/script/QueryManagerScriptServiceTest.java+1 −1 modified@@ -34,7 +34,7 @@ import org.xwiki.test.junit5.mockito.InjectMockComponents; import org.xwiki.test.junit5.mockito.MockComponent; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when;
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/attachments/AttachmentHistoryResource.java+1 −1 modified@@ -41,6 +41,6 @@ public interface AttachmentHistoryResource @PathParam("pageName") String pageName, @PathParam("attachmentName") String attachmentName, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number + @QueryParam("number") Integer number ) throws XWikiRestException; }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/attachments/AttachmentsResource.java+1 −1 modified@@ -45,7 +45,7 @@ public interface AttachmentsResource @PathParam("spaceName") @Encoded String spaceName, @PathParam("pageName") String pageName, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("prettyNames") @DefaultValue("false") Boolean withPrettyNames, @QueryParam("name") @DefaultValue("") String name, @QueryParam("author") @DefaultValue("") String author,
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/classes/ClassesResource.java+1 −1 modified@@ -37,6 +37,6 @@ public interface ClassesResource @GET Classes getClasses( @PathParam("wikiName") String wikiName, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number + @QueryParam("number") Integer number ) throws XWikiRestException; }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/objects/AllObjectsForClassNameResource.java+1 −1 modified@@ -38,7 +38,7 @@ public interface AllObjectsForClassNameResource @PathParam("wikiName") String wikiName, @PathParam("className") String className, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("order") String order, @QueryParam("prettyNames") @DefaultValue("false") Boolean withPrettyNames ) throws XWikiRestException;
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/pages/PageChildrenResource.java+1 −1 modified@@ -57,7 +57,7 @@ public interface PageChildrenResource @PathParam("spaceName") @Encoded String spaceName, @PathParam("pageName") String pageName, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("prettyNames") @DefaultValue("false") Boolean withPrettyNames, @QueryParam("hierarchy") @DefaultValue("parentchild") String hierarchy, @QueryParam("search") @DefaultValue("") String search
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/pages/PageHistoryResource.java+1 −1 modified@@ -40,7 +40,7 @@ public interface PageHistoryResource @PathParam("spaceName") @Encoded String spaceName, @PathParam("pageName") String pageName, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("order") @DefaultValue("desc") String order, @QueryParam("prettyNames") @DefaultValue("false") Boolean withPrettyNames ) throws XWikiRestException;
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/pages/PagesResource.java+1 −1 modified@@ -39,7 +39,7 @@ public interface PagesResource @PathParam("wikiName") String wikiName, @PathParam("spaceName") @Encoded String spaceName, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("parentId") String parentFilterExpression, @QueryParam("order") String order, @QueryParam("prettyNames") @DefaultValue("false") Boolean withPrettyNames
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/pages/PageTranslationHistoryResource.java+1 −1 modified@@ -41,7 +41,7 @@ public interface PageTranslationHistoryResource @PathParam("pageName") String pageName, @PathParam("language") String language, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("order") @DefaultValue("desc") String order, @QueryParam("prettyNames") @DefaultValue("false") Boolean withPrettyNames ) throws XWikiRestException;
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/spaces/SpaceSearchResource.java+1 −1 modified@@ -39,7 +39,7 @@ public interface SpaceSearchResource @PathParam("spaceName") @Encoded String spaceName, @QueryParam("q") String keywords, @QueryParam("scope") List<String> searchScopeStrings, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("start") @DefaultValue("0") Integer start, @QueryParam("orderField") String orderField, @QueryParam("order") @DefaultValue("asc") String order,
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/spaces/SpacesResource.java+1 −1 modified@@ -37,6 +37,6 @@ public interface SpacesResource @GET Spaces getSpaces( @PathParam("wikiName") String wikiName, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number + @QueryParam("number") Integer number ) throws XWikiRestException; }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/tags/PagesForTagsResource.java+1 −1 modified@@ -35,7 +35,7 @@ public interface PagesForTagsResource @PathParam("wikiName") String wikiName, @PathParam("tagNames") String tagNames, @QueryParam("start") @DefaultValue("0") Integer start, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("prettyNames") @DefaultValue("false") Boolean withPrettyNames ) throws XWikiRestException; }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/wikis/WikiChildrenResource.java+1 −1 modified@@ -52,7 +52,7 @@ public interface WikiChildrenResource @GET Pages getChildren( @PathParam("wikiName") String wikiName, @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("limit") @DefaultValue("-1") Integer limit, + @QueryParam("limit") Integer limit, @QueryParam("search") @DefaultValue("") String search ) throws XWikiRestException; }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/wikis/WikiSearchQueryResource.java+1 −1 modified@@ -35,7 +35,7 @@ public interface WikiSearchQueryResource @PathParam("wikiName") String wikiName, @QueryParam("q") String query, @QueryParam("type") String queryTypeString, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("start") @DefaultValue("0") Integer start, @QueryParam("distinct") @DefaultValue("true") Boolean distinct, @QueryParam("orderField") @DefaultValue("") String orderField,
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/wikis/WikiSearchResource.java+1 −1 modified@@ -37,7 +37,7 @@ public interface WikiSearchResource @PathParam("wikiName") String wikiName, @QueryParam("q") String keywords, @QueryParam("scope") List<String> searchScopeStrings, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("start") @DefaultValue("0") Integer start, @QueryParam("orderField") @DefaultValue("") String orderField, @QueryParam("order") @DefaultValue("asc") String order,
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-api/src/main/java/org/xwiki/rest/resources/wikis/WikisSearchQueryResource.java+1 −1 modified@@ -32,7 +32,7 @@ public interface WikisSearchQueryResource { @GET SearchResults search( @QueryParam("q") String query, - @QueryParam("number") @DefaultValue("-1") Integer number, + @QueryParam("number") Integer number, @QueryParam("start") @DefaultValue("0") Integer start, @QueryParam("distinct") @DefaultValue("true") Boolean distinct, @QueryParam("wikis") String searchWikis,
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/attachments/AttachmentHistoryResourceImpl.java+3 −1 modified@@ -51,6 +51,8 @@ public class AttachmentHistoryResourceImpl extends XWikiResource implements Atta public Attachments getAttachmentHistory(String wikiName, String spaceName, String pageName, String attachmentName, Integer start, Integer number) throws XWikiRestException { + int limit = validateAndGetLimit(number); + try { DocumentInfo documentInfo = getDocumentInfo(wikiName, spaceName, pageName, null, null, true, false); Document doc = documentInfo.getDocument(); @@ -68,7 +70,7 @@ public Attachments getAttachmentHistory(String wikiName, String spaceName, Strin versionList.add(version); } - RangeIterable<Version> ri = new RangeIterable<Version>(versionList, start, number); + RangeIterable<Version> ri = new RangeIterable<Version>(versionList, start, limit); for (Version version : ri) { com.xpn.xwiki.api.Attachment xwikiAttachmentAtVersion =
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/attachments/AttachmentsResourceImpl.java+1 −1 modified@@ -76,7 +76,7 @@ public Attachments getAttachments(String wiki, String spaces, String page, Integ filters.put("fileTypes", fileTypes); return super.getAttachments(new DocumentReference(wiki, parseSpaceSegments(spaces), page), filters, offset, - limit, withPrettyNames); + validateAndGetLimit(limit), withPrettyNames); } @Override
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/classes/ClassesResourceImpl.java+3 −1 modified@@ -61,6 +61,8 @@ public class ClassesResourceImpl extends XWikiResource implements ClassesResourc @Override public Classes getClasses(String wikiName, Integer start, Integer number) throws XWikiRestException { + int limit = validateAndGetLimit(number); + String database = Utils.getXWikiContext(componentManager).getWikiId(); try { @@ -69,7 +71,7 @@ public Classes getClasses(String wikiName, Integer start, Integer number) throws List<String> classNames = Utils.getXWikiApi(componentManager).getClassList(); Collections.sort(classNames); - RangeIterable<String> ri = new RangeIterable<String>(classNames, start, number); + RangeIterable<String> ri = new RangeIterable<String>(classNames, start, limit); Classes classes = objectFactory.createClasses();
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/classes/ClassPropertyValuesResourceImpl.java+1 −1 modified@@ -99,7 +99,7 @@ public PropertyValues getClassPropertyValues(String wikiName, String className, } } else { propertyValues = this.propertyValuesProvider - .getValues(classPropertyReference, limit, filterParameters.toArray()); + .getValues(classPropertyReference, validateAndGetLimit(limit), filterParameters.toArray()); } propertyValues.getLinks().add(propertyLink);
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/ModificationsResourceImpl.java+3 −1 modified@@ -62,6 +62,8 @@ public class ModificationsResourceImpl extends XWikiResource implements Modifica public History getModifications(String wikiName, Integer start, Integer number, String order, Long ts, Boolean withPrettyNames) throws XWikiRestException { + int limit = validateAndGetLimit(number); + try { String validOrder = HqlQueryUtils.getValidQueryOrder(order, "desc"); @@ -74,7 +76,7 @@ public History getModifications(String wikiName, Integer start, Integer number, List<Object> queryResult = null; queryResult = this.queryManager.createQuery(query, Query.XWQL).bindValue("date", new Date(ts)) - .setLimit(number).setOffset(start).setWiki(wikiName).execute(); + .setLimit(limit).setOffset(start).setWiki(wikiName).execute(); for (Object object : queryResult) { Object[] fields = (Object[]) object;
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/objects/AllObjectsForClassNameResourceImpl.java+4 −2 modified@@ -55,6 +55,7 @@ public Objects getObjects(String wikiName, String className, Integer start, Inte Boolean withPrettyNames) throws XWikiRestException { String database = Utils.getXWikiContext(componentManager).getWikiId(); + int limit = validateAndGetLimit(number); try { Objects objects = new Objects(); @@ -69,8 +70,9 @@ public Objects getObjects(String wikiName, String className, Integer start, Inte List<Object> queryResult = null; queryResult = - queryManager.createQuery(query, Query.XWQL).bindValue("className", className).setLimit(number) - .setOffset(start).execute(); + this.queryManager.createQuery(query, Query.XWQL).bindValue("className", className) + .setLimit(limit) + .setOffset(start).execute(); for (Object object : queryResult) { Object[] fields = (Object[]) object;
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageChildrenResourceImpl.java+3 −2 modified@@ -57,15 +57,16 @@ public class PageChildrenResourceImpl extends AbstractPagesResource implements P public Pages getPageChildren(String wikiName, String spaceName, String pageName, Integer start, Integer number, Boolean withPrettyNames, String hierarchy, String search) throws XWikiRestException { + int limit = validateAndGetLimit(number); try { DocumentInfo documentInfo = getDocumentInfo(wikiName, spaceName, pageName, null, null, true, false); if ("nestedpages".equals(hierarchy)) { return getPages(this.nestedPageHierarchy.getChildren(documentInfo.getDocument().getDocumentReference()) - .withOffset(start).withLimit(number).matching(search).getDocumentReferences(), withPrettyNames); + .withOffset(start).withLimit(limit).matching(search).getDocumentReferences(), withPrettyNames); } else { // We fall-back (default) to the parent-child hierarchy for backwards compatibility. - return getPages(getPageChildrenForParentChildHierarchy(documentInfo, start, number), withPrettyNames); + return getPages(getPageChildrenForParentChildHierarchy(documentInfo, start, limit), withPrettyNames); } } catch (XWikiException | QueryException e) { throw new XWikiRestException(e);
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageHistoryResourceImpl.java+5 −1 modified@@ -84,7 +84,11 @@ public History getPageHistory(String wikiName, String spaceName, String pageName List<Object> queryResult = null; queryResult = this.queryManager.createQuery(query, Query.XWQL).bindValue("space", spaceId) - .bindValue("name", pageName).setLimit(number).setOffset(start).setWiki(wikiName).execute(); + .bindValue("name", pageName) + .setLimit(validateAndGetLimit(number)) + .setOffset(start) + .setWiki(wikiName) + .execute(); for (Object object : queryResult) { Object[] fields = (Object[]) object;
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PagesResourceImpl.java+6 −2 modified@@ -53,6 +53,7 @@ public Pages getPages(String wikiName, String spaceName, Integer start, Integer String spaceId = Utils.getLocalSpaceId(spaces); Pages pages = objectFactory.createPages(); + int limit = validateAndGetLimit(number); try { Utils.getXWikiContext(componentManager).setWikiId(wikiName); @@ -63,8 +64,11 @@ public Pages getPages(String wikiName, String spaceName, Integer start, Integer /* Use an explicit query to improve performance */ List<String> pageNames = - query.addFilter(componentManager.<QueryFilter>getInstance(QueryFilter.class, "hidden")) - .bindValue("space", spaceId).setOffset(start).setLimit(number).execute(); + query.addFilter(componentManager.<QueryFilter>getInstance(QueryFilter.class, "hidden")) + .bindValue("space", spaceId) + .setOffset(start) + .setLimit(limit) + .execute(); Pattern parentFilter = null; if (parentFilterExpression != null) {
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/pages/PageTranslationHistoryResourceImpl.java+5 −1 modified@@ -72,6 +72,7 @@ public History getPageTranslationHistory(String wikiName, String spaceName, Stri String spaceId = Utils.getLocalSpaceId(spaces); History history = new History(); + int limit = validateAndGetLimit(number); try { String validOrder = HqlQueryUtils.getValidQueryOrder(order, "desc"); @@ -83,7 +84,10 @@ public History getPageTranslationHistory(String wikiName, String spaceName, Stri List<Object> queryResult = null; queryResult = this.queryManager.createQuery(query, Query.XWQL).bindValue("space", spaceId) - .bindValue("name", pageName).setLimit(number).bindValue("language", language).setOffset(start) + .bindValue("name", pageName) + .bindValue("language", language) + .setLimit(limit) + .setOffset(start) .setWiki(wikiName).execute(); for (Object object : queryResult) {
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/spaces/SpaceAttachmentsResourceImpl.java+2 −2 modified@@ -48,7 +48,7 @@ public Attachments getAttachments(String wiki, String spaces, String name, Strin filters.put("author", author); filters.put("fileTypes", fileTypes); - return super.getAttachments(new SpaceReference(wiki, parseSpaceSegments(spaces)), filters, offset, limit, - withPrettyNames); + return super.getAttachments(new SpaceReference(wiki, parseSpaceSegments(spaces)), filters, offset, + validateAndGetLimit(limit), withPrettyNames); } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/spaces/SpaceSearchResourceImpl.java+3 −1 modified@@ -58,11 +58,13 @@ public SearchResults search(String wikiName, String spaceName, String keywords, List<KeywordSearchScope> searchScopes = parseSearchScopeStrings(searchScopeStrings); + int limit = validateAndGetLimit(number); + KeywordSearchOptions searchOptions = KeywordSearchOptions.builder() .searchScopes(searchScopes) .wikiName(wikiName) .space(Utils.getLocalSpaceId(spaces)) - .number(number) + .number(limit) .start(start) .orderField(orderField) .order(order)
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/spaces/SpacesResourceImpl.java+2 −1 modified@@ -46,12 +46,13 @@ public class SpacesResourceImpl extends XWikiResource implements SpacesResource public Spaces getSpaces(String wikiName, Integer start, Integer number) throws XWikiRestException { + int limit = validateAndGetLimit(number); Spaces spaces = objectFactory.createSpaces(); try { List<String> spaceNames = queryManager.getNamedQuery("getSpaces").addFilter( componentManager.<QueryFilter>getInstance(QueryFilter.class, "hidden")).setOffset(start) - .setLimit(number).setWiki(wikiName).execute(); + .setLimit(limit).setWiki(wikiName).execute(); for (String spaceName : spaceNames) { List<String> spaceList = Utils.getSpacesFromSpaceId(spaceName);
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/tags/PagesForTagsResourceImpl.java+27 −24 modified@@ -19,7 +19,6 @@ */ package org.xwiki.rest.internal.resources.tags; -import java.util.ArrayList; import java.util.List; import javax.inject.Named; @@ -30,7 +29,6 @@ import org.xwiki.rest.XWikiResource; import org.xwiki.rest.XWikiRestException; import org.xwiki.rest.internal.DomainObjectFactory; -import org.xwiki.rest.internal.RangeIterable; import org.xwiki.rest.internal.Utils; import org.xwiki.rest.model.jaxb.Pages; import org.xwiki.rest.resources.tags.PagesForTagsResource; @@ -50,28 +48,17 @@ public Pages getTags(String wikiName, String tagNames, Integer start, Integer nu { String database = Utils.getXWikiContext(componentManager).getWikiId(); + int limit = validateAndGetLimit(number); + try { Pages pages = objectFactory.createPages(); Utils.getXWikiContext(componentManager).setWikiId(wikiName); String[] tagNamesArray = tagNames.split(","); + List<String> documentNames = getDocumentsWithTags(tagNamesArray, start, limit); - List<String> documentNames = new ArrayList<String>(); - for (String tagName : tagNamesArray) { - List<String> documentNamesForTag = getDocumentsWithTag(tagName); - - /* Avoid duplicates */ - for (String documentName : documentNamesForTag) { - if (!documentNames.contains(documentName)) { - documentNames.add(documentName); - } - } - } - - RangeIterable<String> ri = new RangeIterable<String>(documentNames, start, number); - - for (String documentName : ri) { + for (String documentName : documentNames) { Document doc = Utils.getXWikiApi(componentManager).getDocument(documentName); if (doc != null) { pages.getPageSummaries().add(DomainObjectFactory @@ -88,15 +75,31 @@ public Pages getTags(String wikiName, String tagNames, Integer start, Integer nu } } - private List<String> getDocumentsWithTag(String tag) throws QueryException + private List<String> getDocumentsWithTags(String[] tags, Integer start, Integer limit) throws QueryException { - String query = - "select doc.fullName from XWikiDocument as doc, BaseObject as obj, DBStringListProperty as prop " - + "where obj.name=doc.fullName and obj.className='XWiki.TagClass' and obj.id=prop.id.id " - + "and prop.id.name='tags' and :tag in elements(prop.list) order by doc.name asc"; + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append( + "select distinct doc.fullName from XWikiDocument as doc, BaseObject as obj, DBStringListProperty as prop "); + queryBuilder.append("where obj.name=doc.fullName and obj.className='XWiki.TagClass' and obj.id=prop.id.id "); + queryBuilder.append("and prop.id.name='tags' and ("); + + for (int i = 0; i < tags.length; i++) { + if (i > 0) { + queryBuilder.append(" or "); + } + queryBuilder.append(":tag").append(i).append(" in elements(prop.list)"); + } + + queryBuilder.append(") order by doc.fullName asc"); - List<String> documentsWithTag = queryManager.createQuery(query, Query.HQL).bindValue("tag", tag).execute(); + Query query = this.queryManager.createQuery(queryBuilder.toString(), Query.HQL) + .setLimit(limit) + .setOffset(start); + + for (int i = 0; i < tags.length; i++) { + query.bindValue("tag" + i, tags[i].trim()); + } - return documentsWithTag; + return query.execute(); } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/wikis/WikiAttachmentsResourceImpl.java+2 −1 modified@@ -49,6 +49,7 @@ public Attachments getAttachments(String wiki, String name, String page, String filters.put("author", author); filters.put("fileTypes", fileTypes); - return super.getAttachments(new WikiReference(wiki), filters, offset, limit, withPrettyNames); + return super.getAttachments(new WikiReference(wiki), filters, offset, validateAndGetLimit(limit), + withPrettyNames); } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/wikis/WikiChildrenResourceImpl.java+3 −1 modified@@ -56,8 +56,10 @@ public Pages getChildren(String wikiName, Integer offset, Integer limit, String throw new WebApplicationException(Response.Status.FORBIDDEN); } + int queryLimit = validateAndGetLimit(limit); + try { - return getPages(this.nestedPageHierarchy.getChildren(wikiReference).withOffset(offset).withLimit(limit) + return getPages(this.nestedPageHierarchy.getChildren(wikiReference).withOffset(offset).withLimit(queryLimit) .matching(search).getDocumentReferences(), true); } catch (QueryException e) { throw new XWikiRestException("Failed to retrieve the top level pages.", e);
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/wikis/WikiPagesResourceImpl.java+1 −1 modified@@ -113,7 +113,7 @@ public Pages getPages(String wikiName, Integer start, String name, String space, List<Object> queryResult = null; Query query = queryManager.createQuery(queryString, Query.XWQL) .setWiki(wikiName) - .setLimit(number) + .setLimit(validateAndGetLimit(number)) .setOffset(start); for (Map.Entry<String, String> filterEntry : filters.entrySet()) {
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/wikis/WikiSearchQueryResourceImpl.java+3 −1 modified@@ -40,6 +40,8 @@ public SearchResults search(String wikiName, String query, String queryTypeString, Integer number, Integer start, Boolean distinct, String orderField, String order, Boolean withPrettyNames, String className) throws XWikiRestException { + int limit = validateAndGetLimit(number); + try { SearchResults searchResults = objectFactory.createSearchResults(); searchResults.setTemplate(String.format("%s?%s", @@ -48,7 +50,7 @@ public SearchResults search(String wikiName, String query, searchResults.getSearchResults().addAll(searchQuery(query, queryTypeString, wikiName, null, Utils.getXWiki(componentManager).getRightService().hasProgrammingRights( - Utils.getXWikiContext(componentManager)), orderField, order, distinct, number, start, + Utils.getXWikiContext(componentManager)), orderField, order, distinct, limit, start, withPrettyNames, className)); return searchResults;
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/wikis/WikiSearchResourceImpl.java+3 −1 modified@@ -49,6 +49,8 @@ public SearchResults search(String wikiName, String keywords, List<String> searc Integer start, String orderField, String order, Boolean withPrettyNames, Boolean isLocaleAware) throws XWikiRestException { + int limit = validateAndGetLimit(number); + SearchResults searchResults = objectFactory.createSearchResults(); searchResults.setTemplate(String.format("%s?%s", Utils.createURI(uriInfo.getBaseUri(), WikiSearchResource.class, wikiName).toString(), @@ -64,7 +66,7 @@ public SearchResults search(String wikiName, String keywords, List<String> searc .searchScopes(searchScopes) .wikiName(getXWikiContext().getWikiId()) .space(null) - .number(number) + .number(limit) .start(start) .orderField(orderField) .order(order)
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/XWikiResource.java+37 −0 modified@@ -30,6 +30,7 @@ import javax.inject.Provider; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; @@ -45,6 +46,8 @@ import org.xwiki.query.QueryManager; import org.xwiki.rest.internal.Utils; import org.xwiki.rest.model.jaxb.ObjectFactory; +import org.xwiki.security.SecurityConfiguration; +import org.xwiki.stability.Unstable; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; @@ -96,6 +99,40 @@ public class XWikiResource implements XWikiRestComponent, Initializable @Inject protected QueryManager queryManager; + @Inject + private SecurityConfiguration securityConfiguration; + + /** + * Validates and returns the query limit based on the provided limit and the system's configuration. + * If the provided limit is null, the method returns the configured default limit. + * If the provided limit exceeds the configured limit or is negative, an exception is thrown. + * + * @param limit the desired query limit, it can be null + * @return the validated query limit value, either the provided limit or the configured default limit + * @throws WebApplicationException if the provided limit is invalid (negative or exceeds the allowed maximum) + * @since 17.7.0RC1 + * @since 17.4.4 + * @since 16.10.11 + */ + @Unstable + protected int validateAndGetLimit(Integer limit) + { + int configuredLimit = this.securityConfiguration.getQueryItemsLimit(); + if (limit == null) { + return configuredLimit; + } else if (configuredLimit >= 0 && (limit < 0 || limit > configuredLimit)) { + throw new WebApplicationException( + Response.status(Status.BAD_REQUEST) + .entity( + "Invalid limit value: %s. The limit must be a positive integer and less than or equal to %s." + .formatted(limit, configuredLimit)) + .type("text/plain") + .build()); + } else { + return limit; + } + } + /** * A wrapper class for returning an XWiki document enriched with information about its status. */
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/AbstractAttachmentsResourceTest.java+6 −0 modified@@ -34,6 +34,7 @@ import org.xwiki.query.QueryParameter; import org.xwiki.rest.XWikiResource; import org.xwiki.rest.internal.ModelFactory; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.test.junit5.mockito.MockComponent; import com.xpn.xwiki.XWiki; @@ -62,6 +63,9 @@ public abstract class AbstractAttachmentsResourceTest @Named("local") protected EntityReferenceSerializer<String> localEntityReferenceSerializer; + @MockComponent + protected SecurityConfiguration securityConfiguration; + @InjectMockitoOldcore protected MockitoOldcore oldCore; @@ -79,6 +83,8 @@ public void setUp() throws Exception this.xcontext = this.oldCore.getXWikiContext(); this.xwiki = this.oldCore.getSpyXWiki(); + + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); } protected void setUriInfo(XWikiResource resource) throws Exception
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/classes/ClassesResourceImplTest.java+8 −2 modified@@ -40,6 +40,7 @@ import org.xwiki.rest.internal.ModelFactory; import org.xwiki.rest.model.jaxb.Class; import org.xwiki.rest.model.jaxb.Classes; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.security.authorization.Right; import org.xwiki.test.annotation.BeforeComponent; @@ -93,6 +94,9 @@ class ClassesResourceImplTest @MockComponent private ModelFactory modelFactory; + @MockComponent + private SecurityConfiguration securityConfiguration; + @Mock private XWiki xWiki; @@ -142,12 +146,14 @@ void configure() throws Exception when(zeclass.getId()).thenReturn(availableClasses.get(i)); restClasses.add(zeclass); } + + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); } @Test void classesNoConstraint() throws XWikiRestException { - Classes xwikiClasses = resource.getClasses("xwiki", 0, -1); + Classes xwikiClasses = resource.getClasses("xwiki", 0, 100); assertEquals(4, xwikiClasses.getClazzs().size()); } @@ -157,7 +163,7 @@ void authorizedClassesOnly() throws XWikiRestException when(authorization.hasAccess(eq(Right.VIEW), eq(new DocumentReference("xwiki", "XWiki", "Protected")))) .thenReturn(false); - Classes xwikiClasses = resource.getClasses("xwiki", 0, -1); + Classes xwikiClasses = resource.getClasses("xwiki", 0, 100); assertEquals(3, xwikiClasses.getClazzs().size()); for (Class clazz : xwikiClasses.getClazzs()) {
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/classes/ClassPropertyValuesResourceImplTest.java+6 −0 modified@@ -41,6 +41,7 @@ import org.xwiki.rest.model.jaxb.Link; import org.xwiki.rest.model.jaxb.PropertyValues; import org.xwiki.rest.resources.classes.ClassPropertyValuesProvider; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.security.authorization.AccessDeniedException; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.security.authorization.Right; @@ -90,6 +91,9 @@ class ClassPropertyValuesResourceImplTest @MockComponent private Provider<XWikiContext> xcontextProvider; + @MockComponent + private SecurityConfiguration securityConfiguration; + private ClassPropertyReference propertyReference = new ClassPropertyReference("status", new DocumentReference("wiki", Arrays.asList("Path", "To"), "Class")); @@ -121,6 +125,8 @@ void configure() throws Exception UriInfo uriInfo = mock(UriInfo.class); when(uriInfo.getBaseUri()).thenReturn(new URI("/xwiki/rest")); ReflectionUtils.setFieldValue(resource, "uriInfo", uriInfo); + + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); } @Test
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/ModificationsResourceImplTest.java+11 −0 modified@@ -28,6 +28,7 @@ import javax.inject.Provider; import javax.ws.rs.core.UriInfo; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.suigeneris.jrcs.rcs.Version; @@ -44,6 +45,7 @@ import org.xwiki.rest.model.jaxb.History; import org.xwiki.rest.model.jaxb.HistorySummary; import org.xwiki.rest.model.jaxb.Link; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.test.annotation.BeforeComponent; import org.xwiki.test.junit5.mockito.ComponentTest; @@ -98,6 +100,9 @@ class ModificationsResourceImplTest @MockComponent private Provider<XWikiContext> xcontextProvider; + @MockComponent + private SecurityConfiguration securityConfiguration; + @MockComponent @Named("local") private EntityReferenceSerializer<String> localEntityReferenceSerializer; @@ -119,6 +124,12 @@ void beforeComponent() throws Exception this.componentManager.registerComponent(ComponentManager.class, "context", this.componentManager); } + @BeforeEach + void setUp() + { + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); + } + @Test void getModifications() throws Exception {
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/pages/AbstractPageHistoryResourceImplTest.java+7 −3 modified@@ -20,7 +20,6 @@ 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; @@ -35,7 +34,6 @@ 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; @@ -45,6 +43,7 @@ import org.xwiki.rest.XWikiRestException; import org.xwiki.rest.model.jaxb.History; import org.xwiki.rest.model.jaxb.HistorySummary; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.security.authorization.AccessDeniedException; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.security.authorization.Right; @@ -95,6 +94,9 @@ public abstract class AbstractPageHistoryResourceImplTest @MockComponent private QueryManager queryManager; + @MockComponent + private SecurityConfiguration securityConfiguration; + @MockComponent private ContextualAuthorizationManager contextualAuthorizationManager; @@ -109,7 +111,7 @@ public abstract class AbstractPageHistoryResourceImplTest @BeforeEach void setUp(MockitoComponentManager componentManager) - throws ComponentLookupException, URISyntaxException, IllegalAccessException + throws Exception { Utils.setComponentManager(componentManager); @@ -121,6 +123,8 @@ void setUp(MockitoComponentManager componentManager) .thenAnswer( invocation -> componentManager.getInstance(invocation.getArgument(0), invocation.getArgument(1))); + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); + when(this.uriInfo.getBaseUri()).thenReturn(new URI("https://test/")); injectURIInfo();
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/wikis/WikiChildrenResourceImplTest.java+11 −5 modified@@ -19,11 +19,6 @@ */ package org.xwiki.rest.internal.resources.wikis; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.net.URI; import java.util.List; @@ -44,6 +39,7 @@ import org.xwiki.rest.internal.ModelFactory; import org.xwiki.rest.model.jaxb.PageSummary; import org.xwiki.rest.model.jaxb.Pages; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.security.authorization.Right; import org.xwiki.test.annotation.BeforeComponent; @@ -57,6 +53,11 @@ import com.xpn.xwiki.api.Document; import com.xpn.xwiki.doc.XWikiDocument; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + /** * Unit tests for {@link WikiChildrenResourceImpl}. * @@ -81,6 +82,9 @@ class WikiChildrenResourceImplTest @MockComponent protected ModelFactory modelFactory; + @MockComponent + private SecurityConfiguration securityConfiguration; + @Mock private ChildrenQuery childrenQuery; @@ -109,6 +113,8 @@ void beforeEach() throws Exception when(this.uriInfo.getBaseUri()).thenReturn(this.baseURI); when(this.xcontext.getWiki()).thenReturn(this.xwiki); + + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); } @Test
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/internal/resources/wikis/WikiPagesResourceImplTest.java+8 −4 modified@@ -21,7 +21,6 @@ import java.net.MalformedURLException; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.util.List; @@ -33,7 +32,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.xwiki.component.manager.ComponentLookupException; import org.xwiki.component.manager.ComponentManager; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.EntityReferenceSerializer; @@ -43,6 +41,7 @@ import org.xwiki.rest.XWikiRestException; import org.xwiki.rest.model.jaxb.PageSummary; import org.xwiki.rest.model.jaxb.Pages; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.security.authorization.Right; import org.xwiki.test.junit5.mockito.ComponentTest; @@ -78,6 +77,9 @@ class WikiPagesResourceImplTest @MockComponent private QueryManager queryManager; + @MockComponent + private SecurityConfiguration securityConfiguration; + @MockComponent private ContextualAuthorizationManager contextualAuthorizationManager; @@ -102,7 +104,7 @@ class WikiPagesResourceImplTest @BeforeEach void setUp(MockitoComponentManager componentManager) - throws ComponentLookupException, URISyntaxException, IllegalAccessException + throws Exception { Utils.setComponentManager(componentManager); @@ -119,6 +121,8 @@ void setUp(MockitoComponentManager componentManager) Provider<XWikiContext> contextProvider = componentManager.getInstance(XWikiContext.TYPE_PROVIDER); this.context = contextProvider.get(); when(this.context.getURLFactory()).thenReturn(urlFactory); + + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); } @Test @@ -223,4 +227,4 @@ void getPages() throws QueryException, MalformedURLException, XWikiRestException verify(mockQuery).bindValue("name", "%BAR%"); verify(mockQuery).bindValue("author", "%BUZ%"); } -} \ No newline at end of file +}
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/test/java/org/xwiki/rest/XWikiResourceTest.java+60 −1 modified@@ -21,16 +21,26 @@ import java.util.Arrays; +import javax.ws.rs.WebApplicationException; + +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.xwiki.security.SecurityConfiguration; import org.xwiki.test.junit5.mockito.ComponentTest; import org.xwiki.test.junit5.mockito.InjectMockComponents; +import org.xwiki.test.junit5.mockito.MockComponent; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.when; /** * Unit tests for {@link XWikiResource}. - * + * * @version $Id$ */ @ComponentTest @@ -39,6 +49,15 @@ class XWikiResourceTest @InjectMockComponents private XWikiResource xwikiResource; + @MockComponent + private SecurityConfiguration securityConfiguration; + + @BeforeEach + void setUp() + { + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(1000); + } + @Test void parseSpaceSegments() throws Exception { @@ -83,4 +102,44 @@ void parseSpaceSegments() throws Exception assertEquals("URLDecoder: Incomplete trailing escape (%) pattern", e.getMessage()); } } + + @Test + void validateAndGetLimitWithNull() + { + assertEquals(1000, this.xwikiResource.validateAndGetLimit(null)); + } + + @Test + void validateAndGetLimitWithValidValue() + { + assertEquals(100, this.xwikiResource.validateAndGetLimit(100)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 1001 }) + void validateAndGetLimitWithInvalidValue(int limit) + { + WebApplicationException exception = + assertThrows(WebApplicationException.class, () -> this.xwikiResource.validateAndGetLimit(limit)); + assertEquals(400, exception.getResponse().getStatus()); + assertEquals( + "Invalid limit value: " + limit + ". The limit must be a positive integer and less than or equal to 1000.", + exception.getResponse().getEntity()); + } + + @ParameterizedTest + @CsvSource({ + ", -1", + "-1, -1", + "1000, 1000", + "100, 100", + "2000, 2000" + }) + void validateAndGetLimitWithNoLimitSet(Integer input, int expected) + { + when(this.securityConfiguration.getQueryItemsLimit()).thenReturn(-1); + + assertEquals(expected, this.xwikiResource.validateAndGetLimit(input)); + } } +
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/AttachmentsResourceIT.java+95 −0 modified@@ -332,6 +332,101 @@ public void testPOSTAttachment() throws Exception Assert.assertEquals(content, getMethod.getResponseBodyAsString()); } + @Test + public void testAttachmentsResourcePaginationAndErrors() throws Exception + { + // Setup: Add two attachments + String attachmentName1 = "att1.txt"; + String attachmentName2 = "att2.txt"; + try { + putAttachmentFilename(attachmentName1); + putAttachmentFilename(attachmentName2); + + String attachmentsUri = buildURIForThisPage(AttachmentsResource.class); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet(attachmentsUri + "?number=-1"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet(attachmentsUri + "?number=1001"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet(attachmentsUri + "?number=1"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + Attachments attachments = (Attachments) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, attachments.getAttachments().size()); + + String firstName = attachments.getAttachments().get(0).getName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet(attachmentsUri + "?number=1&start=1"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + attachments = (Attachments) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, attachments.getAttachments().size()); + Assert.assertNotEquals(firstName, attachments.getAttachments().get(0).getName()); + } finally { + // Clean up + this.testUtils.deletePage(this.reference); + } + } + + @Test + public void testAttachmentHistoryResourcePaginationAndErrors() throws Exception + { + try { + // Setup: Create an attachment with multiple versions + String attachmentName = "history.txt"; + int versionCount = 3; + for (int i = 0; i < versionCount; i++) { + String content = "Content version " + i; + PutMethod putMethod = + executePut(buildURIForThisPage(AttachmentResource.class, attachmentName), content, + MediaType.TEXT_PLAIN, + TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), + TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + if (i == 0) { + Assert.assertEquals(HttpStatus.SC_CREATED, putMethod.getStatusCode()); + } else { + Assert.assertEquals(HttpStatus.SC_ACCEPTED, putMethod.getStatusCode()); + } + } + + String historyUri = buildURIForThisPage(AttachmentHistoryResource.class, attachmentName); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet(historyUri + "?number=-1"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet(historyUri + "?number=1001"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet(historyUri + "?number=1"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + Attachments attachments = (Attachments) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, attachments.getAttachments().size()); + + String firstVersion = attachments.getAttachments().get(0).getVersion(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet(historyUri + "?number=1&start=1"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + attachments = (Attachments) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, attachments.getAttachments().size()); + Assert.assertNotEquals(firstVersion, attachments.getAttachments().get(0).getVersion()); + } finally { + // Clean up + this.testUtils.deletePage(this.reference); + } + } + /** * Creates a URI to access the specified resource with the given path elements. The wiki, space and page path * elements are added by this method so you can skip them.
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/ClassesResourceIT.java+125 −2 modified@@ -19,26 +19,38 @@ */ package org.xwiki.rest.test; +import java.util.List; +import java.util.Map; + import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; import org.junit.Assert; import org.junit.Test; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.SpaceReference; import org.xwiki.rest.model.jaxb.Class; import org.xwiki.rest.model.jaxb.Classes; +import org.xwiki.rest.model.jaxb.Object; import org.xwiki.rest.model.jaxb.Property; +import org.xwiki.rest.model.jaxb.PropertyValue; +import org.xwiki.rest.model.jaxb.PropertyValues; +import org.xwiki.rest.resources.classes.ClassPropertyValuesResource; import org.xwiki.rest.resources.classes.ClassesResource; +import org.xwiki.rest.resources.objects.ObjectsResource; import org.xwiki.rest.test.framework.AbstractHttpIT; +import org.xwiki.test.ui.TestUtils; public class ClassesResourceIT extends AbstractHttpIT { @Override @Test public void testRepresentation() throws Exception { - GetMethod getMethod = executeGet(buildURI(ClassesResource.class, getWiki()).toString()); + GetMethod getMethod = executeGet(buildURI(ClassesResource.class, getWiki())); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); - Classes classes = (Classes) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Classes classes = (Classes) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); for (Class clazz : classes.getClazzs()) { checkLinks(clazz); @@ -48,4 +60,115 @@ public void testRepresentation() throws Exception } } } + + @Test + public void testClassesResourcePaginationAndErrors() throws Exception + { + // Test: number=-1 should return error + GetMethod getMethod = executeGet(buildURI(ClassesResource.class, getWiki()) + "?number=-1"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet(buildURI(ClassesResource.class, getWiki()) + "?number=1001"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet(buildURI(ClassesResource.class, getWiki()) + "?number=1"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + Classes classes = (Classes) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, classes.getClazzs().size()); + + String firstName = classes.getClazzs().get(0).getName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet(buildURI(ClassesResource.class, getWiki()) + "?number=1&start=1"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + classes = (Classes) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, classes.getClazzs().size()); + Assert.assertNotEquals(firstName, classes.getClazzs().get(0).getName()); + } + + @Test + public void testClassPropertyValuesResourceBasicAndPagination() throws Exception + { + // Setup: create two pages with TagClass objects and different tag values + String className = "XWiki.TagClass"; + String propertyName = "tags"; + List<String> spaces1 = List.of(getTestClassName() + "A"); + List<String> spaces2 = List.of(getTestClassName() + "B"); + String pageName1 = getTestMethodName() + "1"; + String pageName2 = getTestMethodName() + "2"; + + DocumentReference reference1 = new DocumentReference(getWiki(), spaces1, pageName1); + DocumentReference reference2 = new DocumentReference(getWiki(), spaces2, pageName2); + + Map<String, DocumentReference> tagReferences = Map.of("tag1", reference1, "tag2", reference2); + + try { + for (Map.Entry<String, DocumentReference> value : tagReferences.entrySet()) { + String tagName = value.getKey(); + DocumentReference reference = value.getValue(); + + this.testUtils.rest().delete(reference); + this.testUtils.rest().savePage(reference, "content", "Title " + reference.getName()); + + // Add the tag to the page + Object tagObject = this.objectFactory.createObject().withClassName(className); + Property tagProperty = this.objectFactory.createProperty() + .withName(propertyName) + .withValue(tagName); + tagObject.getProperties().add(tagProperty); + + List<String> spaces = reference.getSpaceReferences().stream() + .map(SpaceReference::getName) + .toList(); + + PostMethod postMethod = executePostXml( + buildURI(ObjectsResource.class, getWiki(), spaces, reference.getName()), tagObject, + TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + Assert.assertEquals(HttpStatus.SC_CREATED, postMethod.getStatusCode()); + } + + // Test: basic retrieval + GetMethod getMethod = executeGet( + buildURI(ClassPropertyValuesResource.class, getWiki(), className, propertyName) + "?fp=tag"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + PropertyValues values = (PropertyValues) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + + List<String> foundValues = values.getPropertyValues().stream() + .map(PropertyValue::getValue) + .map(java.lang.Object::toString) + .toList(); + for (String tagValue : tagReferences.keySet()) { + Assert.assertTrue( + "Property values [%s] should contain tag value: [%s]".formatted(String.join(", ", foundValues), + tagValue), + foundValues.contains(tagValue)); + } + + // Test: pagination with limit=1 + getMethod = executeGet( + buildURI(ClassPropertyValuesResource.class, getWiki(), className, propertyName) + "?fp=tag&limit=1"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + values = (PropertyValues) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, values.getPropertyValues().size()); + + // Test: error for limit=-1 + getMethod = executeGet( + buildURI(ClassPropertyValuesResource.class, getWiki(), className, propertyName) + "?limit=-1"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: error for limit=1001 + getMethod = executeGet( + buildURI(ClassPropertyValuesResource.class, getWiki(), className, propertyName) + "?limit=1001"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + } finally { + this.testUtils.rest().delete(reference1); + this.testUtils.rest().delete(reference2); + } + } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/framework/AbstractHttpIT.java+6 −0 modified@@ -86,6 +86,12 @@ public abstract class AbstractHttpIT @ClassRule public static final ValidateConsoleRule validateConsole = new ValidateConsoleRule(); + protected static final String INVALID_LIMIT_MINUS_1 = + "Invalid limit value: -1. The limit must be a positive integer and less than or equal to 1000."; + + protected static final String INVALID_LIMIT_1001 = + "Invalid limit value: 1001. The limit must be a positive integer and less than or equal to 1000."; + /** * The object used to access the name of the current test. */
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/ObjectsResourceIT.java+64 −0 modified@@ -44,6 +44,7 @@ import org.xwiki.rest.model.jaxb.Objects; import org.xwiki.rest.model.jaxb.Page; import org.xwiki.rest.model.jaxb.Property; +import org.xwiki.rest.resources.objects.AllObjectsForClassNameResource; import org.xwiki.rest.resources.objects.ObjectAtPageVersionResource; import org.xwiki.rest.resources.objects.ObjectResource; import org.xwiki.rest.resources.objects.ObjectsResource; @@ -662,4 +663,67 @@ public void testGETObjectAtPageVersion() throws Exception } } } + + @Test + public void testAllObjectsForClassNameResourcePaginationAndErrors() throws Exception + { + // Setup: Create two pages with TagClass objects + String className = "XWiki.TagClass"; + List<String> spaces1 = List.of(getTestClassName() + "A"); + List<String> spaces2 = List.of(getTestClassName() + "B"); + String pageName1 = getTestMethodName() + "1"; + String pageName2 = getTestMethodName() + "2"; + DocumentReference ref1 = new DocumentReference(getWiki(), spaces1, pageName1); + DocumentReference ref2 = new DocumentReference(getWiki(), spaces2, pageName2); + + try { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + this.testUtils.rest().savePage(ref1); + this.testUtils.rest().savePage(ref2); + + // Add TagClass objects to both pages + createObjectIfDoesNotExists(className, spaces1, pageName1); + createObjectIfDoesNotExists(className, spaces2, pageName2); + + // Test: basic retrieval + GetMethod getMethod = executeGet( + buildURI(AllObjectsForClassNameResource.class, getWiki(), className)); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + Objects objects = (Objects) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertTrue(objects.getObjectSummaries().size() >= 2); + + // Test: pagination with number=2 + getMethod = executeGet( + buildURI(AllObjectsForClassNameResource.class, getWiki(), className) + "?number=2"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + objects = (Objects) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(2, objects.getObjectSummaries().size()); + + String secondPage = objects.getObjectSummaries().get(1).getPageName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet( + buildURI(AllObjectsForClassNameResource.class, getWiki(), className) + "?number=1&start=1"); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + objects = (Objects) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, objects.getObjectSummaries().size()); + Assert.assertEquals(secondPage, objects.getObjectSummaries().get(0).getPageName()); + + // Test: error for number=-1 + getMethod = executeGet( + buildURI(AllObjectsForClassNameResource.class, getWiki(), className) + "?number=-1"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: error for number=1001 + getMethod = executeGet( + buildURI(AllObjectsForClassNameResource.class, getWiki(), className) + "?number=1001"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + } finally { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + } + } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/PageResourceIT.java+134 −30 modified@@ -91,7 +91,7 @@ public void setUp() throws Exception this.wikiName = getWiki(); this.space = getTestClassName(); - this.spaces = Arrays.asList(this.space); + this.spaces = List.of(this.space); this.pageName = getTestMethodName(); this.reference = new DocumentReference(this.wikiName, this.spaces, this.pageName); @@ -117,7 +117,7 @@ private Page getFirstPage() throws Exception GetMethod getMethod = executeGet(getFullUri(WikisResource.class)); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); - Wikis wikis = (Wikis) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Wikis wikis = (Wikis) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); Assert.assertTrue(wikis.getWikis().size() > 0); Wiki wiki = wikis.getWikis().get(0); @@ -203,14 +203,14 @@ public void whiteSpaceEncoding() throws Exception public void testGETNotExistingPage() throws Exception { GetMethod getMethod = - executeGet(buildURI(PageResource.class, getWiki(), Arrays.asList("NOTEXISTING"), "NOTEXISTING").toString()); + executeGet(buildURI(PageResource.class, getWiki(), List.of("NOTEXISTING"), "NOTEXISTING")); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_NOT_FOUND, getMethod.getStatusCode()); } @Test public void testPUTGETPage() throws Exception { - final String title = String.format("Title (%s)", UUID.randomUUID().toString()); + final String title = String.format("Title (%s)", UUID.randomUUID()); final String content = String.format("This is a content (%d)", System.currentTimeMillis()); final String comment = String.format("Updated title and content (%d)", System.currentTimeMillis()); @@ -247,9 +247,9 @@ public void testPUTGETPage() throws Exception @Test public void testPUTGETWithObject() throws Exception { - String pageURI = buildURI(PageResource.class, getWiki(), Arrays.asList("RESTTest"), "PageWithObject"); + String pageURI = buildURI(PageResource.class, getWiki(), List.of("RESTTest"), "PageWithObject"); - final String title = String.format("Title (%s)", UUID.randomUUID().toString()); + final String title = String.format("Title (%s)", UUID.randomUUID()); final String content = String.format("This is a content (%d)", System.currentTimeMillis()); final String comment = String.format("Updated title and content (%d)", System.currentTimeMillis()); @@ -263,10 +263,10 @@ public void testPUTGETWithObject() throws Exception Property property = new Property(); property.setName("tags"); property.setValue(TAG_VALUE); - Object object = objectFactory.createObject(); + Object object = this.objectFactory.createObject(); object.setClassName("XWiki.TagClass"); object.getProperties().add(property); - newPage.setObjects(objectFactory.createObjects()); + newPage.setObjects(this.objectFactory.createObjects()); newPage.getObjects().getObjectSummaries().add(object); // PUT @@ -301,7 +301,7 @@ public void testPUTGETWithObject() throws Exception putMethod = executePutXml(pageURI, modifiedPage, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); assertThat(getHttpMethodInfo(putMethod), putMethod.getStatusCode(), - isIn(Arrays.asList(HttpStatus.SC_ACCEPTED))); + isIn(List.of(HttpStatus.SC_ACCEPTED))); modifiedPage = (Page) this.unmarshaller.unmarshal(putMethod.getResponseBodyAsStream()); @@ -367,18 +367,18 @@ public void testPUTPageUnauthorized() throws Exception @Test public void testPUTNonExistingPage() throws Exception { - final List<String> SPACE_NAME = Arrays.asList("Test"); + final List<String> SPACE_NAME = List.of("Test"); final String PAGE_NAME = String.format("Test-%d", System.currentTimeMillis()); final String CONTENT = String.format("Content %d", System.currentTimeMillis()); final String TITLE = String.format("Title %d", System.currentTimeMillis()); final String PARENT = "Main.WebHome"; - Page page = objectFactory.createPage(); + Page page = this.objectFactory.createPage(); page.setContent(CONTENT); page.setTitle(TITLE); page.setParent(PARENT); - PutMethod putMethod = executePutXml(buildURI(PageResource.class, getWiki(), SPACE_NAME, PAGE_NAME).toString(), + PutMethod putMethod = executePutXml(buildURI(PageResource.class, getWiki(), SPACE_NAME, PAGE_NAME), page, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); Assert.assertEquals(getHttpMethodInfo(putMethod), HttpStatus.SC_CREATED, putMethod.getStatusCode()); @@ -406,7 +406,7 @@ public void testPUTWithInvalidRepresentation() throws Exception "<?xml version=\"1.0\" encoding=\"UTF-8\"?><invalidPage><content/></invalidPage>", MediaType.TEXT_XML); Assert.assertEquals(getHttpMethodInfo(putMethod), HttpStatus.SC_BAD_REQUEST, putMethod.getStatusCode()); - this.validateConsole.getLogCaptureConfiguration().registerExpected( + validateConsole.getLogCaptureConfiguration().registerExpected( "unexpected element (uri:\"\", local:\"invalidPage\"). Expected elements are" ); } @@ -418,20 +418,20 @@ public void testPUTGETTranslation() throws Exception // PUT String[] languages = Locale.getISOLanguages(); - final String languageId = languages[random.nextInt(languages.length)]; + final String languageId = languages[this.random.nextInt(languages.length)]; - Page page = objectFactory.createPage(); + Page page = this.objectFactory.createPage(); page.setContent(languageId); PutMethod putMethod = executePutXml( buildURI(PageTranslationResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, - TestConstants.TRANSLATIONS_PAGE_NAME, languageId).toString(), + TestConstants.TRANSLATIONS_PAGE_NAME, languageId), page, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); Assert.assertEquals(getHttpMethodInfo(putMethod), HttpStatus.SC_CREATED, putMethod.getStatusCode()); // GET GetMethod getMethod = executeGet(buildURI(PageTranslationResource.class, getWiki(), - TestConstants.TEST_SPACE_NAME, TestConstants.TRANSLATIONS_PAGE_NAME, languageId).toString()); + TestConstants.TEST_SPACE_NAME, TestConstants.TRANSLATIONS_PAGE_NAME, languageId)); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); Page modifiedPage = (Page) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); @@ -462,12 +462,11 @@ public void testGETNotExistingTranslation() throws Exception createPageIfDoesntExist(TestConstants.TEST_SPACE_NAME, TestConstants.TRANSLATIONS_PAGE_NAME, "Translations"); GetMethod getMethod = executeGet( - buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, TestConstants.TRANSLATIONS_PAGE_NAME) - .toString()); + buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, TestConstants.TRANSLATIONS_PAGE_NAME)); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); getMethod = executeGet(buildURI(PageTranslationResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, - TestConstants.TRANSLATIONS_PAGE_NAME, "NOT_EXISTING").toString()); + TestConstants.TRANSLATIONS_PAGE_NAME, "NOT_EXISTING")); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_NOT_FOUND, getMethod.getStatusCode()); } @@ -477,12 +476,12 @@ public void testDELETEPage() throws Exception createPageIfDoesntExist(TestConstants.TEST_SPACE_NAME, this.pageName, "Test page"); DeleteMethod deleteMethod = executeDelete( - buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, this.pageName).toString(), + buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, this.pageName), TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); Assert.assertEquals(getHttpMethodInfo(deleteMethod), HttpStatus.SC_NO_CONTENT, deleteMethod.getStatusCode()); GetMethod getMethod = executeGet( - buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, this.pageName).toString()); + buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, this.pageName)); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_NOT_FOUND, getMethod.getStatusCode()); } @@ -492,27 +491,27 @@ public void testDELETEPageNoRights() throws Exception createPageIfDoesntExist(TestConstants.TEST_SPACE_NAME, this.pageName, "Test page"); DeleteMethod deleteMethod = executeDelete( - buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, this.pageName).toString()); + buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, this.pageName)); Assert.assertEquals(getHttpMethodInfo(deleteMethod), HttpStatus.SC_UNAUTHORIZED, deleteMethod.getStatusCode()); GetMethod getMethod = executeGet( - buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, this.pageName).toString()); + buildURI(PageResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, this.pageName)); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); } @Test public void testPageHistory() throws Exception { GetMethod getMethod = - executeGet(buildURI(PageResource.class, getWiki(), this.spaces, this.pageName).toString()); + executeGet(buildURI(PageResource.class, getWiki(), this.spaces, this.pageName)); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); Page originalPage = (Page) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); Assert.assertEquals(this.spaces.get(0), originalPage.getSpace()); String pageHistoryUri = - buildURI(PageHistoryResource.class, getWiki(), this.spaces, originalPage.getName()).toString(); + buildURI(PageHistoryResource.class, getWiki(), this.spaces, originalPage.getName()); getMethod = executeGet(pageHistoryUri); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); @@ -545,7 +544,7 @@ public void testPageHistory() throws Exception public void testPageTranslationHistory() throws Exception { String pageHistoryUri = buildURI(PageHistoryResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, - TestConstants.TRANSLATIONS_PAGE_NAME).toString(); + TestConstants.TRANSLATIONS_PAGE_NAME); GetMethod getMethod = executeGet(pageHistoryUri); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); @@ -567,7 +566,7 @@ public void testPageTranslationHistory() throws Exception public void testGETPageChildren() throws Exception { GetMethod getMethod = - executeGet(buildURI(PageChildrenResource.class, getWiki(), this.spaces, this.pageName).toString()); + executeGet(buildURI(PageChildrenResource.class, getWiki(), this.spaces, this.pageName)); Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); Pages pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); @@ -582,7 +581,7 @@ public void testGETPageChildren() throws Exception public void testPOSTPageFormUrlEncoded() throws Exception { final String CONTENT = String.format("This is a content (%d)", System.currentTimeMillis()); - final String TITLE = String.format("Title (%s)", UUID.randomUUID().toString()); + final String TITLE = String.format("Title (%s)", UUID.randomUUID()); Page originalPage = getFirstPage(); @@ -607,7 +606,7 @@ public void testPOSTPageFormUrlEncoded() throws Exception public void testPOSTPageFormUrlEncodedNoCSRF() throws Exception { final String CONTENT = String.format("This is a content (%d)", System.currentTimeMillis()); - final String TITLE = String.format("Title (%s)", UUID.randomUUID().toString()); + final String TITLE = String.format("Title (%s)", UUID.randomUUID()); Page originalPage = getFirstPage(); @@ -654,4 +653,109 @@ public void testPUTPageSyntax() throws Exception Assert.assertEquals(newSyntax, modifiedPage.getSyntax()); } + + @Test + public void testPageChildrenResourcePaginationAndErrors() throws Exception + { + // Setup: Create a parent page and two children + String spaceName = getTestClassName(); + String parentPage = getTestMethodName() + "Parent"; + String child1 = getTestMethodName() + "ChildA"; + String child2 = getTestMethodName() + "ChildB"; + DocumentReference parentRef = new DocumentReference(getWiki(), spaceName, parentPage); + DocumentReference childRef1 = new DocumentReference(getWiki(), spaceName, child1); + DocumentReference childRef2 = new DocumentReference(getWiki(), spaceName, child2); + try { + this.testUtils.rest().delete(parentRef); + this.testUtils.rest().delete(childRef1); + this.testUtils.rest().delete(childRef2); + this.testUtils.rest().savePage(parentRef, "parent content", "parent title"); + Page childPageObj1 = this.testUtils.rest().page(childRef1); + childPageObj1.setParent(spaceName + "." + parentPage); + childPageObj1.setContent("child1 content"); + childPageObj1.setTitle("child1 title"); + this.testUtils.rest().save(childPageObj1); + Page childPageObj2 = this.testUtils.rest().page(childRef2); + childPageObj2.setParent(spaceName + "." + parentPage); + childPageObj2.setContent("child2 content"); + childPageObj2.setTitle("child2 title"); + this.testUtils.rest().save(childPageObj2); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + "%s?number=-1".formatted(buildURI(PageChildrenResource.class, getWiki(), spaceName, parentPage))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + "%s?number=1001".formatted(buildURI(PageChildrenResource.class, getWiki(), spaceName, parentPage))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + "%s?number=1".formatted(buildURI(PageChildrenResource.class, getWiki(), spaceName, parentPage))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + Pages pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, pages.getPageSummaries().size()); + + String firstName = pages.getPageSummaries().get(0).getName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet("%s?number=1&start=1".formatted( + buildURI(PageChildrenResource.class, getWiki(), spaceName, parentPage))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, pages.getPageSummaries().size()); + Assert.assertNotEquals(firstName, pages.getPageSummaries().get(0).getName()); + } finally { + this.testUtils.rest().delete(parentRef); + this.testUtils.rest().delete(childRef1); + this.testUtils.rest().delete(childRef2); + } + } + + @Test + public void testPageHistoryResourcePaginationAndErrors() throws Exception + { + // Setup: Create a page and several versions + try { + this.testUtils.rest().delete(this.reference); + this.testUtils.rest().savePage(this.reference, "v1", "title1"); + this.testUtils.rest().savePage(this.reference, "v2", "title2"); + this.testUtils.rest().savePage(this.reference, "v3", "title3"); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + "%s?number=-1".formatted(buildURI(PageHistoryResource.class, getWiki(), this.space, this.pageName))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + "%s?number=1001".formatted(buildURI(PageHistoryResource.class, getWiki(), this.space, this.pageName))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + "%s?number=1".formatted(buildURI(PageHistoryResource.class, getWiki(), this.space, this.pageName))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + History history = (History) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, history.getHistorySummaries().size()); + + String firstVersion = history.getHistorySummaries().get(0).getVersion(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet("%s?number=1&start=1".formatted( + buildURI(PageHistoryResource.class, getWiki(), this.space, this.pageName))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + history = (History) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, history.getHistorySummaries().size()); + Assert.assertNotEquals(firstVersion, history.getHistorySummaries().get(0).getVersion()); + } finally { + this.testUtils.rest().delete(this.reference); + } + } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/PagesResourceIT.java+50 −0 modified@@ -23,6 +23,7 @@ import org.apache.commons.httpclient.methods.GetMethod; import org.junit.Assert; import org.junit.Test; +import org.xwiki.model.reference.DocumentReference; import org.xwiki.rest.Relations; import org.xwiki.rest.model.jaxb.Link; import org.xwiki.rest.model.jaxb.Pages; @@ -67,4 +68,53 @@ public void testRepresentation() throws Exception checkLinks(pages); } + + @Test + public void testPagesResourcePaginationAndErrors() throws Exception + { + // Setup: Ensure at least 2 pages exist in a space + String spaceName = getTestClassName(); + String page1 = getTestMethodName() + "A"; + String page2 = getTestMethodName() + "B"; + DocumentReference ref1 = new DocumentReference(getWiki(), spaceName, page1); + DocumentReference ref2 = new DocumentReference(getWiki(), spaceName, page2); + try { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + this.testUtils.rest().savePage(ref1, "content1", "title1"); + this.testUtils.rest().savePage(ref2, "content2", "title2"); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + "%s?number=-1".formatted(buildURI(org.xwiki.rest.resources.pages.PagesResource.class, getWiki(), spaceName))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + "%s?number=1001".formatted(buildURI(org.xwiki.rest.resources.pages.PagesResource.class, getWiki(), spaceName))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + "%s?number=1".formatted(buildURI(org.xwiki.rest.resources.pages.PagesResource.class, getWiki(), spaceName))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + Pages pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, pages.getPageSummaries().size()); + + String firstName = pages.getPageSummaries().get(0).getName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet( + "%s?number=1&start=1".formatted(buildURI(org.xwiki.rest.resources.pages.PagesResource.class, getWiki(), spaceName))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, pages.getPageSummaries().size()); + Assert.assertNotEquals(firstName, pages.getPageSummaries().get(0).getName()); + } finally { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + } + } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/PageTranslationResourceIT.java+64 −0 modified@@ -27,10 +27,13 @@ import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PutMethod; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.xwiki.model.reference.DocumentReference; +import org.xwiki.rest.model.jaxb.History; import org.xwiki.rest.model.jaxb.Page; +import org.xwiki.rest.resources.pages.PageTranslationHistoryResource; import org.xwiki.rest.resources.pages.PageTranslationResource; import org.xwiki.rest.test.framework.AbstractHttpIT; import org.xwiki.test.ui.TestUtils; @@ -128,4 +131,65 @@ public void testPUTGETDELETETranslation() throws Exception assertFalse(this.testUtils.rest().exists(this.referenceDefault)); assertFalse(this.testUtils.rest().exists(this.referenceFR)); } + + @Test + public void testPageTranslationHistoryResourcePaginationAndErrors() throws Exception + { + // Setup: Create a page and a translation with several versions + Locale language = Locale.FRENCH; + assertFalse(this.testUtils.rest().exists(this.referenceDefault)); + String uri = buildURI(PageTranslationResource.class, getWiki(), this.space, this.pageName, language); + + try { + // Save translation versions using PUT to the translation resource + Page translationPage = this.objectFactory.createPage(); + translationPage.setContent("v1fr"); + translationPage.setTitle("title1fr"); + executePutXml(uri, translationPage, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), + TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + + translationPage.setContent("v2fr"); + translationPage.setTitle("title2fr"); + executePutXml(uri, translationPage, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), + TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + + translationPage.setContent("v3fr"); + translationPage.setTitle("title3fr"); + executePutXml(uri, translationPage, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), + TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet("%s?number=-1".formatted( + buildURI(PageTranslationHistoryResource.class, getWiki(), this.space, this.pageName, language))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet("%s?number=1001".formatted( + buildURI(PageTranslationHistoryResource.class, getWiki(), this.space, this.pageName, language))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet("%s?number=1".formatted( + buildURI(PageTranslationHistoryResource.class, getWiki(), this.space, this.pageName, language))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + History history = (History) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, history.getHistorySummaries().size()); + + String firstVersion = history.getHistorySummaries().get(0).getVersion(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet("%s?number=1&start=1".formatted( + buildURI(PageTranslationHistoryResource.class, getWiki(), this.space, this.pageName, language))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + history = (History) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, history.getHistorySummaries().size()); + Assert.assertNotEquals(firstVersion, history.getHistorySummaries().get(0).getVersion()); + } finally { + // Cleanup: Delete the page. + executeDelete(uri, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), + TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + } + } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/SpacesResourceIT.java+147 −0 modified@@ -19,9 +19,11 @@ */ package org.xwiki.rest.test; +import java.io.ByteArrayInputStream; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; @@ -42,6 +44,7 @@ import org.xwiki.rest.model.jaxb.Wikis; import org.xwiki.rest.resources.spaces.SpaceAttachmentsResource; import org.xwiki.rest.resources.spaces.SpaceSearchResource; +import org.xwiki.rest.resources.spaces.SpacesResource; import org.xwiki.rest.resources.wikis.WikisResource; import org.xwiki.rest.test.framework.AbstractHttpIT; @@ -146,6 +149,150 @@ public void testAttachments() throws Exception for (Attachment attachment : attachments.getAttachments()) { checkLinks(attachment); } + } + + @Test + public void testSpacesNumberParameter() throws Exception + { + // Setup: Ensure at least 2 spaces exist + DocumentReference ref1 = new DocumentReference(getWiki(), getTestClassName() + "A", "WebHome"); + DocumentReference ref2 = new DocumentReference(getWiki(), getTestClassName() + "B", "WebHome"); + try { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + this.testUtils.rest().savePage(ref1, "content1", "title1"); + this.testUtils.rest().savePage(ref2, "content2", "title2"); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet("%s?number=-1".formatted(buildURI(SpacesResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet("%s?number=1001".formatted(buildURI(SpacesResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet("%s?number=1".formatted(buildURI(SpacesResource.class, getWiki()))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + Spaces spaces = (Spaces) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, spaces.getSpaces().size()); + + String firstName = spaces.getSpaces().get(0).getName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet("%s?number=1&start=1".formatted(buildURI(SpacesResource.class, getWiki()))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + spaces = (Spaces) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, spaces.getSpaces().size()); + Assert.assertNotEquals(firstName, spaces.getSpaces().get(0).getName()); + } finally { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + } + } + + @Test + public void testSpaceSearchNumberParameter() throws Exception + { + // Setup: Ensure at least 2 pages exist for search + String spaceName = getTestClassName(); + DocumentReference ref1 = new DocumentReference(getWiki(), spaceName, getTestMethodName() + "A"); + DocumentReference ref2 = new DocumentReference(getWiki(), spaceName, getTestMethodName() + "B"); + try { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + this.testUtils.rest().savePage(ref1, "searchcontent", "searchtitleA"); + this.testUtils.rest().savePage(ref2, "searchcontent", "searchtitleB"); + + this.solrUtils.waitEmptyQueue(); + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + "%s?q=searchcontent&number=-1".formatted(buildURI(SpaceSearchResource.class, getWiki(), + List.of(spaceName)))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + "%s?q=searchcontent&number=1001".formatted( + buildURI(SpaceSearchResource.class, getWiki(), List.of(spaceName)))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + "%s?q=searchcontent&number=1".formatted( + buildURI(SpaceSearchResource.class, getWiki(), List.of(spaceName)))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + SearchResults results = (SearchResults) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, results.getSearchResults().size()); + + String firstName = results.getSearchResults().get(0).getPageName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet( + "%s?q=searchcontent&number=1&start=1".formatted( + buildURI(SpaceSearchResource.class, getWiki(), List.of(spaceName)))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + results = (SearchResults) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, results.getSearchResults().size()); + Assert.assertNotEquals(firstName, results.getSearchResults().get(0).getPageName()); + } finally { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + } + } + + @Test + public void testSpaceAttachmentsNumberParameter() throws Exception + { + // Setup: Ensure at least 2 attachments exist in the space + String spaceName = getTestClassName(); + DocumentReference ref = new DocumentReference(getWiki(), spaceName, getTestMethodName()); + this.testUtils.rest().delete(ref); + this.testUtils.rest().savePage(ref); + + try { + this.testUtils.rest().attachFile(new AttachmentReference("att1.txt", ref), + new ByteArrayInputStream("content1".getBytes(StandardCharsets.UTF_8)), true); + this.testUtils.rest().attachFile(new AttachmentReference("att2.txt", ref), + new ByteArrayInputStream("content2".getBytes(StandardCharsets.UTF_8)), true); + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + "%s?number=-1".formatted(buildURI(SpaceAttachmentsResource.class, getWiki(), List.of(spaceName)))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + "%s?number=1001".formatted(buildURI(SpaceAttachmentsResource.class, getWiki(), + List.of(spaceName)))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + "%s?number=1".formatted(buildURI(SpaceAttachmentsResource.class, getWiki(), List.of(spaceName)))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + Attachments attachments = (Attachments) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, attachments.getAttachments().size()); + + String firstName = attachments.getAttachments().get(0).getName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet( + "%s?number=1&start=1".formatted(buildURI(SpaceAttachmentsResource.class, getWiki(), + List.of(spaceName)))); + Assert.assertEquals(HttpStatus.SC_OK, getMethod.getStatusCode()); + attachments = (Attachments) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, attachments.getAttachments().size()); + Assert.assertNotEquals(firstName, attachments.getAttachments().get(0).getName()); + } finally { + // Clean up + this.testUtils.rest().delete(ref); + } } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/TagsResourceIT.java+85 −0 modified@@ -19,7 +19,10 @@ */ package org.xwiki.rest.test; +import java.util.Arrays; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import javax.ws.rs.core.MediaType; @@ -200,4 +203,86 @@ public void testPUTTagsFormUrlEncoded() throws Exception } Assert.assertTrue(found); } + + @Test + public void testPagesForTagsMultipleTagsAndPagination() throws Exception + { + // Setup: create three pages with different tags + String tagA = "TagA_" + UUID.randomUUID(); + String tagB = "TagB_" + UUID.randomUUID(); + + String[] pages = { "PageForTagA", "PageForTagB", "PageForTagAB" }; + + // Create test pages + for (String page : pages) { + createPageIfDoesntExist(TestConstants.TEST_SPACE_NAME, page, "Test"); + } + + // Add tags to pages + Tags tags1 = this.objectFactory.createTags(); + tags1.getTags().add(this.objectFactory.createTag().withName(tagA)); + + // Add tagA to page1 + executePutXml( + buildURI(PageTagsResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, pages[0]), + tags1, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + + // Add tagB to page2 + Tags tags2 = this.objectFactory.createTags(); + tags2.getTags().add(this.objectFactory.createTag().withName(tagB)); + executePutXml( + buildURI(PageTagsResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, pages[1]), + tags2, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + + // Add both tags to page3 + Tags tags3 = this.objectFactory.createTags(); + for (String tagName : new String[] { tagA, tagB }) { + tags3.getTags().add(this.objectFactory.createTag().withName(tagName)); + } + executePutXml( + buildURI(PageTagsResource.class, getWiki(), TestConstants.TEST_SPACE_NAME, pages[2]), + tags3, TestUtils.SUPER_ADMIN_CREDENTIALS.getUserName(), TestUtils.SUPER_ADMIN_CREDENTIALS.getPassword()); + + // Query for both tags + String tagQuery = tagA + "," + tagB; + GetMethod getMethod = executeGet( + buildURI(PagesForTagsResource.class, getWiki(), tagQuery)); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + Pages returnedPages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + + // Verify all pages are returned in alphabetical order + List<String> expectedOrder = Arrays.stream(pages).sorted().collect(Collectors.toList()); + List<String> actualOrder = returnedPages.getPageSummaries().stream() + .map(PageSummary::getName) + .toList(); + Assert.assertEquals(expectedOrder, actualOrder); + + // Test pagination: number=1 + getMethod = executeGet( + buildURI(PagesForTagsResource.class, getWiki(), tagQuery) + "?number=1"); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + returnedPages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, returnedPages.getPageSummaries().size()); + Assert.assertEquals(expectedOrder.get(0), returnedPages.getPageSummaries().get(0).getName()); + + // Test pagination: number=1, start=1 + getMethod = executeGet( + buildURI(PagesForTagsResource.class, getWiki(), tagQuery) + "?number=1&start=1"); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + returnedPages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, returnedPages.getPageSummaries().size()); + Assert.assertEquals(expectedOrder.get(1), returnedPages.getPageSummaries().get(0).getName()); + + // Test error: number=-1 + getMethod = executeGet( + buildURI(PagesForTagsResource.class, getWiki(), tagQuery) + "?number=-1"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test error: number=1001 + getMethod = executeGet( + buildURI(PagesForTagsResource.class, getWiki(), tagQuery) + "?number=1001"); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-test/xwiki-platform-rest-test-tests/src/test/it/org/xwiki/rest/test/WikisResourceIT.java+253 −19 modified@@ -19,6 +19,7 @@ */ package org.xwiki.rest.test; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringReader; import java.nio.charset.StandardCharsets; @@ -50,6 +51,7 @@ import org.xwiki.rest.model.jaxb.Wikis; import org.xwiki.rest.resources.pages.PageResource; import org.xwiki.rest.resources.wikis.WikiAttachmentsResource; +import org.xwiki.rest.resources.wikis.WikiChildrenResource; import org.xwiki.rest.resources.wikis.WikiPagesResource; import org.xwiki.rest.resources.wikis.WikiResource; import org.xwiki.rest.resources.wikis.WikiSearchQueryResource; @@ -59,7 +61,6 @@ import org.xwiki.rest.test.framework.AbstractHttpIT; import org.xwiki.test.ui.TestUtils; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -94,7 +95,7 @@ private SearchResults search(int expectedSize, String query) { try { GetMethod getMethod = executeGet(URIUtil.encodeQuery(query)); - assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); SearchResults searchResults = (SearchResults) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); @@ -172,8 +173,8 @@ private void testSearchWikisName(String sourceHint) throws Exception // Ensure that the terminal page is found by its name. int resultSize = searchResults.getSearchResults().size(); - assertEquals(1, resultSize); - assertEquals(this.fullName, searchResults.getSearchResults().get(0).getPageFullName()); + Assert.assertEquals(1, resultSize); + Assert.assertEquals(this.fullName, searchResults.getSearchResults().get(0).getPageFullName()); // Create a non-terminal page with the same "name" but this time as last space. String nonTerminalFullName = String.join(".", nonTerminalSpaces) + "." + "WebHome"; @@ -187,7 +188,7 @@ private void testSearchWikisName(String sourceHint) throws Exception // Ensure that searching by name finds both terminal and non-terminal page. resultSize = searchResults.getSearchResults().size(); - assertEquals(2, resultSize); + Assert.assertEquals(2, resultSize); List<String> foundPages = searchResults.getSearchResults().stream() .map(SearchResult::getPageFullName) .collect(Collectors.toList()); @@ -207,7 +208,7 @@ private void testSearchWikisName(String sourceHint) throws Exception assertTrue(foundPages.contains(this.fullName)); assertTrue(foundPages.contains(nonTerminalFullName)); } else { - assertEquals(List.of(), foundPages); + Assert.assertEquals(List.of(), foundPages); } } finally { resetSearchSource(sourceHint); @@ -216,7 +217,7 @@ private void testSearchWikisName(String sourceHint) throws Exception private void resetSearchSource(String sourceHint) throws Exception { - assertEquals(sourceHint, this.testUtils.executeWikiPlain(""" + Assert.assertEquals(sourceHint, this.testUtils.executeWikiPlain(""" {{groovy}} System.clearProperty("xconf.xwikiproperties.rest.keywordSearchSource") {{/groovy}} @@ -225,7 +226,7 @@ private void resetSearchSource(String sourceHint) throws Exception private void setSearchSource(String sourceHint) throws Exception { - assertEquals("", this.testUtils.executeWikiPlain(""" + Assert.assertEquals("", this.testUtils.executeWikiPlain(""" {{groovy}} System.setProperty("xconf.xwikiproperties.rest.keywordSearchSource", "%s") {{/groovy}} @@ -267,7 +268,7 @@ private void testSearchWikisContentNameTitleSpace(String sourceHint) throws Exce SearchResults searchResults = (SearchResults) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); int resultSize = searchResults.getSearchResults().size(); - assertEquals(1, resultSize); + Assert.assertEquals(1, resultSize); for (SearchResult searchResult : searchResults.getSearchResults()) { checkLinks(searchResult); @@ -280,7 +281,7 @@ private void testSearchWikisContentNameTitleSpace(String sourceHint) throws Exce searchResults = (SearchResults) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); resultSize = searchResults.getSearchResults().size(); - assertEquals(1, resultSize); + Assert.assertEquals(1, resultSize); for (SearchResult searchResult : searchResults.getSearchResults()) { checkLinks(searchResult); @@ -294,7 +295,7 @@ private void testSearchWikisContentNameTitleSpace(String sourceHint) throws Exce searchResults = (SearchResults) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); resultSize = searchResults.getSearchResults().size(); - assertEquals(1, resultSize); + Assert.assertEquals(1, resultSize); for (SearchResult searchResult : searchResults.getSearchResults()) { checkLinks(searchResult); @@ -382,7 +383,7 @@ public void testPages() throws Exception List<PageSummary> pageSummaries = pages.getPageSummaries(); Assert.assertTrue(pageSummaries.size() == 1); PageSummary pageSummary = pageSummaries.get(0); - assertEquals(this.fullName, pageSummary.getFullName()); + Assert.assertEquals(this.fullName, pageSummary.getFullName()); checkLinks(pageSummary); // Get all pages having a document name that contains "WebHome" and a space with an "s" in its name. @@ -395,7 +396,7 @@ public void testPages() throws Exception pageSummaries = pages.getPageSummaries(); Assert.assertTrue(pageSummaries.size() == 1); pageSummary = pageSummaries.get(0); - assertEquals(this.fullName, pageSummary.getFullName()); + Assert.assertEquals(this.fullName, pageSummary.getFullName()); checkLinks(pageSummary); } @@ -473,7 +474,7 @@ public void testHQLQuerySearch() throws Exception SearchResults searchResults = (SearchResults) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); int resultSize = searchResults.getSearchResults().size(); - assertEquals(1, resultSize); + Assert.assertEquals(1, resultSize); Assert.assertEquals(this.fullName, searchResults.getSearchResults().get(0).getPageFullName()); } @@ -490,7 +491,7 @@ public void testHQLQuerySearchWithClassnameAuthenticated() throws Exception SearchResults searchResults = (SearchResults) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); int resultSize = searchResults.getSearchResults().size(); - assertEquals(1, resultSize); + Assert.assertEquals(1, resultSize); assertNotNull(searchResults.getSearchResults().get(0).getObject()); } @@ -505,7 +506,7 @@ public void testHQLQuerySearchWithClassnameNotAuthenticated() throws Exception SearchResults searchResults = (SearchResults) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); int resultSize = searchResults.getSearchResults().size(); - assertEquals(1, resultSize); + Assert.assertEquals(1, resultSize); assertNull(searchResults.getSearchResults().get(0).getObject()); } @@ -524,8 +525,8 @@ public void testSolrSearch() throws Exception SearchResults searchResults = (SearchResults) unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); int resultSize = searchResults.getSearchResults().size(); - assertEquals(1, resultSize); - assertEquals(this.fullName, searchResults.getSearchResults().get(0).getPageFullName()); + Assert.assertEquals(1, resultSize); + Assert.assertEquals(this.fullName, searchResults.getSearchResults().get(0).getPageFullName()); } @Test @@ -542,7 +543,7 @@ public void testGlobalSearch() throws Exception // case there is some race condition on server side SearchResults searchResults = this.testUtils.getDriver().waitUntilCondition(d -> search(1, query)); - assertEquals(this.fullName, searchResults.getSearchResults().get(0).getPageFullName()); + Assert.assertEquals(this.fullName, searchResults.getSearchResults().get(0).getPageFullName()); } @Test @@ -566,4 +567,237 @@ public void testImportXAR() throws Exception Assert.assertEquals("Foo", page.getName()); Assert.assertEquals("Foo", page.getContent()); } + + @Test + public void testAttachmentsNumberParameter() throws Exception + { + // Setup: Ensure at least 2 attachments exist + this.testUtils.rest().delete(this.reference); + + try { + this.testUtils.rest().attachFile(new AttachmentReference(getTestClassName() + "1.txt", this.reference), + new ByteArrayInputStream("attachment content 1".getBytes(StandardCharsets.UTF_8)), true); + this.testUtils.rest().attachFile(new AttachmentReference(getTestClassName() + "2.txt", this.reference), + new ByteArrayInputStream("attachment content 2".getBytes(StandardCharsets.UTF_8)), true); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + String.format("%s?number=-1", buildURI(WikiAttachmentsResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + String.format("%s?number=1001", buildURI(WikiAttachmentsResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + String.format("%s?number=1", buildURI(WikiAttachmentsResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + Attachments attachments = (Attachments) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, attachments.getAttachments().size()); + + String firstName = attachments.getAttachments().get(0).getName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet( + String.format("%s?number=1&start=1", buildURI(WikiAttachmentsResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + attachments = (Attachments) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, attachments.getAttachments().size()); + // Check that we got a different attachment + Assert.assertNotEquals(firstName, attachments.getAttachments().get(0).getName()); + } finally { + // Clean up attachments + this.testUtils.rest().delete(this.reference); + } + } + + @Test + public void testPagesNumberParameter() throws Exception + { + // Setup: Ensure at least 2 pages exist + DocumentReference ref1 = new DocumentReference(this.wikiName, this.spaces, this.pageName + "1"); + DocumentReference ref2 = new DocumentReference(this.wikiName, this.spaces, this.pageName + "2"); + try { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + this.testUtils.rest().savePage(ref1, "content1", "title1"); + this.testUtils.rest().savePage(ref2, "content2", "title2"); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + String.format("%s?number=-1", buildURI(WikiPagesResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + String.format("%s?number=1001", buildURI(WikiPagesResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + String.format("%s?number=1", buildURI(WikiPagesResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + Pages pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, pages.getPageSummaries().size()); + + String firstName = pages.getPageSummaries().get(0).getName(); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet( + String.format("%s?number=1&start=1", buildURI(WikiPagesResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, pages.getPageSummaries().size()); + // Check that we got a different page + Assert.assertNotEquals(firstName, pages.getPageSummaries().get(0).getName()); + } finally { + // Clean up pages + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + } + } + + @Test + public void testWikiChildrenLimitParameter() throws Exception + { + // Setup: Ensure at least 2 top-level pages exist + DocumentReference ref1 = new DocumentReference(this.wikiName, List.of("ChildSpace1"), "WebHome"); + DocumentReference ref2 = new DocumentReference(this.wikiName, List.of("ChildSpace2"), "WebHome"); + try { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + this.testUtils.rest().savePage(ref1, "content1", "title1"); + this.testUtils.rest().savePage(ref2, "content2", "title2"); + + // Test: limit=-1 should return error. + GetMethod getMethod = executeGet("%s?limit=-1".formatted(buildURI(WikiChildrenResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: limit=1001 should return error. + getMethod = executeGet("%s?limit=1001".formatted(buildURI(WikiChildrenResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with limit=1. + getMethod = executeGet("%s?limit=1".formatted(buildURI(WikiChildrenResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + Pages pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, pages.getPageSummaries().size()); + Assert.assertEquals("ChildSpace1.WebHome", pages.getPageSummaries().get(0).getFullName()); + + // Test: pagination with limit=1 and offset=1. + getMethod = executeGet("%s?limit=1&offset=1".formatted(buildURI(WikiChildrenResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + pages = (Pages) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, pages.getPageSummaries().size()); + Assert.assertEquals("ChildSpace2.WebHome", pages.getPageSummaries().get(0).getFullName()); + } finally { + // Clean up pages + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + } + } + + @Test + public void testWikiSearchNumberParameter() throws Exception + { + // Setup: Ensure at least 2 pages exist for search + DocumentReference ref1 = new DocumentReference(this.wikiName, this.spaces, this.pageName + "A"); + DocumentReference ref2 = new DocumentReference(this.wikiName, this.spaces, this.pageName + "B"); + try { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + this.testUtils.rest().savePage(ref1, "searchcontent", "searchtitleA"); + this.testUtils.rest().savePage(ref2, "searchcontent", "searchtitleB"); + + this.solrUtils.waitEmptyQueue(); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + "%s?q=searchcontent&number=-1".formatted(buildURI(WikiSearchResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + "%s?q=searchcontent&number=1001".formatted(buildURI(WikiSearchResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + "%s?q=searchcontent&number=1&scope=content".formatted(buildURI(WikiSearchResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + SearchResults results = (SearchResults) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, results.getSearchResults().size()); + Assert.assertEquals(ref1.getName(), results.getSearchResults().get(0).getPageName()); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet( + "%s?q=searchcontent&number=1&start=1&scope=content".formatted( + buildURI(WikiSearchResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + results = (SearchResults) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, results.getSearchResults().size()); + Assert.assertEquals(ref2.getName(), results.getSearchResults().get(0).getPageName()); + } finally { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + } + } + + @Test + public void testWikiSearchQueryNumberParameter() throws Exception + { + // Setup: Ensure at least 2 pages exist for query search + DocumentReference ref1 = new DocumentReference(this.wikiName, this.spaces, this.pageName + "Q1"); + DocumentReference ref2 = new DocumentReference(this.wikiName, this.spaces, this.pageName + "Q2"); + try { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + this.testUtils.rest().savePage(ref1, "querycontent", "querytitle1"); + this.testUtils.rest().savePage(ref2, "querycontent", "querytitle2"); + + this.solrUtils.waitEmptyQueue(); + + // Test: number=-1 should return error + GetMethod getMethod = executeGet( + "%s?q=querycontent1&number=-1".formatted(buildURI(WikiSearchQueryResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_MINUS_1, getMethod.getResponseBodyAsString()); + + // Test: number=1001 should return error + getMethod = executeGet( + "%s?q=querycontent1&number=1001".formatted(buildURI(WikiSearchQueryResource.class, getWiki()))); + Assert.assertEquals(400, getMethod.getStatusCode()); + Assert.assertEquals(INVALID_LIMIT_1001, getMethod.getResponseBodyAsString()); + + // Test: pagination with number=1 + getMethod = executeGet( + "%s?q=querycontent&number=1&type=solr".formatted(buildURI(WikiSearchQueryResource.class, getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + SearchResults results = (SearchResults) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, results.getSearchResults().size()); + Assert.assertEquals(ref1.getName(), results.getSearchResults().get(0).getPageName()); + + // Test: pagination with number=1 and start=1 + getMethod = executeGet( + "%s?q=querycontent&number=1&start=1&type=solr".formatted(buildURI(WikiSearchQueryResource.class, + getWiki()))); + Assert.assertEquals(getHttpMethodInfo(getMethod), HttpStatus.SC_OK, getMethod.getStatusCode()); + results = (SearchResults) this.unmarshaller.unmarshal(getMethod.getResponseBodyAsStream()); + Assert.assertEquals(1, results.getSearchResults().size()); + Assert.assertEquals(ref2.getName(), results.getSearchResults().get(0).getPageName()); + } finally { + this.testUtils.rest().delete(ref1); + this.testUtils.rest().delete(ref2); + } + } }
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-solr/xwiki-platform-search-solr-rest/src/main/java/org/xwiki/search/solr/internal/rest/WikisSearchQueryResourceImpl.java+3 −1 modified@@ -52,6 +52,8 @@ public class WikisSearchQueryResourceImpl extends BaseSearchResult implements Wi public SearchResults search(String query, Integer number, Integer start, Boolean distinct, String searchWikis, String orderField, String order, Boolean withPrettyNames, String className) throws XWikiRestException { + int limit = validateAndGetLimit(number); + try { SearchResults searchResults = objectFactory.createSearchResults(); searchResults.setTemplate(String.format("%s?%s", uriInfo.getBaseUri().toString(), @@ -63,7 +65,7 @@ public SearchResults search(String query, Integer number, Integer start, Boolean getXWikiContext().getWikiId(), searchWikis, Utils.getXWiki(componentManager).getRightService().hasProgrammingRights( - Utils.getXWikiContext(componentManager)), orderField, order, distinct, number, start, + Utils.getXWikiContext(componentManager)), orderField, order, distinct, limit, start, withPrettyNames, className, uriInfo)); return searchResults;
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-solr/xwiki-platform-search-solr-ui/src/main/resources/Main/SolrSearchMacros.xml+1 −4 modified@@ -836,10 +836,7 @@ $value## #end ## ## Pagination - #set ($rows = $numbertool.toNumber($request.rows).intValue()) - #if ("$!rows" == '') - #set ($rows = 10) - #end + #getAndValidateQueryLimitFromRequest('rows', 10, $rows) #set ($start = $numbertool.toNumber($request.firstIndex).intValue()) #if ("$!start" == '') #set ($start = 0)
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-solr/xwiki-platform-search-solr-ui/src/main/resources/XWiki/SuggestSolrMacros.xml+1 −4 modified@@ -143,10 +143,7 @@ #macro (getSearchSuggestResults $results) #createSearchSuggestQuery($request.query $request.input $query) - #set ($limit = $numbertool.toNumber($request.nb).intValue()) - #if (!$limit) - #set ($limit = 3) - #end + #getAndValidateQueryLimitFromRequest('nb', 3, $limit) #set ($discard = $query.setLimit($limit)) #set ($macro.results = $query.execute()[0].results) #foreach ($result in $macro.results)
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-test/xwiki-platform-search-test-docker/src/test/it/org/xwiki/search/test/ui/SolrSearchIT.java+66 −0 modified@@ -19,6 +19,7 @@ */ package org.xwiki.search.test.ui; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -28,6 +29,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.xwiki.administration.test.po.AdministrationPage; import org.xwiki.administration.test.po.LocalizationAdministrationSectionPage; +import org.xwiki.model.reference.DocumentReference; import org.xwiki.repository.test.SolrTestUtils; import org.xwiki.search.test.po.SolrSearchPage; import org.xwiki.search.test.po.SolrSearchResult; @@ -36,6 +38,8 @@ import org.xwiki.test.docker.junit5.UITest; import org.xwiki.test.ui.TestUtils; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -47,6 +51,13 @@ @UITest class SolrSearchIT { + private static final String EXPECTED_ERROR = + "Error 400 Invalid parameter value 2000 for %s: Must be a positive integer and less than or equal to 1000"; + + private static final String NB_PARAMETER = "nb"; + + private static final String GET_ACTION = "get"; + @Test void verifySpaceFaucetEscaping(TestUtils setup, TestConfiguration testConfiguration) throws Exception { @@ -104,4 +115,59 @@ void verifySearchInLocale(String locale, TestUtils setup, TestConfiguration test sectionPage.clickSave(); } } + + @Test + void searchLimit(TestUtils setup, TestConfiguration testConfiguration, TestReference testReference) throws Exception + { + setup.loginAsSuperAdmin(); + + setup.rest().savePage(testReference, "TestSearchLimit", "Test Search Limit Page"); + + // Create 20 pages. + for (int i = 0; i < 20; i++) { + DocumentReference pageReference = new DocumentReference("Page" + i, testReference.getLastSpaceReference()); + setup.rest().savePage(pageReference, "Content of Page " + i, "Title " + i); + } + + new SolrTestUtils(setup, testConfiguration.getServletEngine()).waitEmptyQueue(); + + SolrSearchPage searchPage = SolrSearchPage.gotoPage(); + searchPage.search("Content of Page"); + assertEquals(10, searchPage.getSearchResults().size(), "The search should return only 10 results by default."); + + searchPage = searchPage.setResultsPerPage(20, true); + assertEquals(20, searchPage.getSearchResults().size(), + "The search should return 20 results when the results per page is set to 20."); + + searchPage = searchPage.setResultsPerPage(2000, false); + assertEquals(EXPECTED_ERROR.formatted("rows"), searchPage.getPageTitle()); + } + + @Test + void suggestService(TestUtils setup, TestConfiguration testConfiguration, TestReference testReference) + throws Exception + { + setup.loginAsSuperAdmin(); + + String testDocumentTitle = "SuggestServiceTestTitle"; + setup.rest().savePage(testReference, "Hello World!", testDocumentTitle); + + new SolrTestUtils(setup, testConfiguration.getServletEngine()).waitEmptyQueue(); + + Map<String, String> parameters = new HashMap<>(); + parameters.put("outputSyntax", "plain"); + parameters.put("query", "__INPUT__"); + parameters.put("input", testDocumentTitle); + + DocumentReference suggestSolrService = new DocumentReference("xwiki", "XWiki", "SuggestSolrService"); + setup.gotoPage(suggestSolrService, GET_ACTION, parameters); + + String json = setup.getDriver().getPageSource(); + assertThat(json, containsString(testDocumentTitle)); + + parameters.put(NB_PARAMETER, "2000"); + setup.gotoPage(suggestSolrService, GET_ACTION, parameters); + + assertEquals(EXPECTED_ERROR.formatted(NB_PARAMETER), setup.getDriver().getTitle()); + } }
xwiki-platform-core/xwiki-platform-search/xwiki-platform-search-test/xwiki-platform-search-test-pageobjects/src/main/java/org/xwiki/search/test/po/SolrSearchPage.java+38 −0 modified@@ -21,12 +21,17 @@ import java.util.List; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.xwiki.test.ui.po.ViewPage; public class SolrSearchPage extends ViewPage { + private static final String ROWS_PARAM = "rows="; + @FindBy(id = "search-page-bar-input") private WebElement searchInput; @@ -71,4 +76,37 @@ public List<SolrSearchResult> getSearchResults() .map(SolrSearchResult::new) .toList(); } + + /** + * Sets the number of results to display per page by directly modifying the URL. + * + * @param resultsPerPage the number of results to display per page + * @param wait if true, waits for the page to reload after setting the results per page + * @return the reloaded page, or the current page if wait is false + * @since 17.7.0RC1 + * @since 17.4.4 + * @since 16.10.11 + */ + public SolrSearchPage setResultsPerPage(int resultsPerPage, boolean wait) + { + String currentUrl = getDriver().getCurrentUrl(); + // Setting the number of results per page isn't supported in the UI, so we modify the URL directly. + // The results per page parameter is called "rows" in Solr. + String url; + if (Strings.CS.contains(currentUrl, ROWS_PARAM)) { + url = RegExUtils.replaceAll(currentUrl, "rows=\\d+", ROWS_PARAM + resultsPerPage); + } else { + url = currentUrl + (StringUtils.contains(currentUrl, '?') ? "&" : "?") + ROWS_PARAM + resultsPerPage; + } + + getUtil().gotoPage(url); + + // Construct a new page to wait for the page to load. + if (wait) { + return new SolrSearchPage(); + } else { + // Return the current page without waiting. + return this; + } + } }
xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-api/src/main/java/org/xwiki/security/internal/DefaultSecurityConfiguration.java+2 −2 modified@@ -43,9 +43,9 @@ public class DefaultSecurityConfiguration extends AbstractSecurityConfiguration private static final String QUERY_ITEMS_LIMIT = AbstractSecurityConfiguration.SECURITY + ".queryItemsLimit"; /** - * Default query items limit is set to 100 because it corresponds to the LiveTable/LiveData max items view limit. + * Default query items limit. */ - private static final int DEFAULT_QUERY_ITEMS_LIMIT = 100; + private static final int DEFAULT_QUERY_ITEMS_LIMIT = 1000; /** * Obtain configuration from the {@code xwiki.properties} file. We currently consider that this is a technical
xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-api/src/main/java/org/xwiki/security/SecurityConfiguration.java+1 −2 modified@@ -33,8 +33,7 @@ public interface SecurityConfiguration /** * @return the number used to control how many items are retrieved through queries (for example inside Velocity * templates). This limit can be customized in the {@code xwiki.properties} file in order to allow - * retrieving more or less items. Default value is {@code 100} (this number corresponds to the - * LiveTable/LiveData max items view limit). This is to avoid DOS attacks. + * retrieving more or less items. Default value is {@code 1000}. This is to avoid DOS attacks. */ int getQueryItemsLimit(); }
xwiki-platform-core/xwiki-platform-tree/xwiki-platform-tree-test-pageobjects/src/main/java/org/xwiki/tree/test/po/TreeElement.java+5 −5 modified@@ -110,13 +110,13 @@ public TreeElement clearSelection() */ public TreeElement waitForIt() { - // Wait for the loading animation container. This element is generated from JavaScript when the tree is - // being initialized, so its presence guarantees that the tree initialization has started. - getDriver().waitUntilElementIsVisible(this.element, By.cssSelector(".jstree-container-ul")); // Wait for the root node to be loaded. getDriver().waitUntilCondition(driver -> - // The tree element is marked as busy while the tree nodes are being loaded. - !Boolean.parseBoolean(this.element.getAttribute("aria-busy")) + // Wait for the loading animation container. This element is generated from JavaScript when the tree is + // being initialized, so its presence guarantees that the tree initialization has started. + getDriver().hasElement(this.element, By.cssSelector(".jstree-container-ul")) + // The tree element is marked as busy while the tree nodes are being loaded. + && !Boolean.parseBoolean(this.element.getAttribute("aria-busy")) // Check if there is any descendant of the element that is marked as busy. && this.element.findElements(By.cssSelector("[aria-busy = 'true']")).isEmpty() );
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/getdeleteddocuments.vm+1 −4 modified@@ -71,10 +71,7 @@ $response.setContentType("application/json") #if (!$offset || $offset < 0) #set($offset = 0) #end -#set ($limit = $numbertool.toNumber($request.get('limit')).intValue()) -#if (!$limit) - #set ($limit = 15) -#end +#getAndValidateQueryLimitFromRequest('limit', 15, $limit) ## ## Build the query ##
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/getdocuments.vm+1 −10 modified@@ -107,16 +107,7 @@ $response.setContentType("application/json") #if (!$offset || $offset < 0) #set($offset = 0) #end -#set ($limit = $numbertool.toNumber($request.get('limit')).intValue()) -#if (!$limit) - #set ($limit = 15) -#else - ## Security protection for guest users: limit the number of items returned to avoid DOS attacks. - #if (!$xcontext.userReference) - #set ($maxLimit = $services.security.getQueryItemsLimit()) - #set ($limit = $mathtool.min($limit, $maxLimit)) - #end -#end +#getAndValidateQueryLimitFromRequest('limit', 15, $limit) ## ## Build the query ##
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/macros.vm+45 −0 modified@@ -2060,6 +2060,48 @@ $displayName## #end ## ## +#** + * Validate the given query limit. Send an error and stop parsing when the limit is invalid + * + * @param limit the limit to validate + * @param parameterName the name of the parameter that shall be mentioned in the error message + * @since 17.7.0RC1 + * @since 17.4.4 + * @since 16.10.11 + *# +#macro(validateQueryLimit $limit $parameterName) + #set ($configuredLimit = $services.security.getQueryItemsLimit()) + #if ($configuredLimit > 0 && ($limit < 0 || $limit > $configuredLimit)) + #set($discard = + $response.sendError(400, + "Invalid parameter value $limit for $parameterName: Must be a positive integer and less than or equal to $configuredLimit")) + #stop + #end +#end +## +## +#** + * Get and validate the limit of a query from a request parameter. + * + * @param parameterName the name of the request parameter + * @param defaultValue the default limit to use when the request parameter isn't set + * @param _limit the limit variable to set + * @since 17.7.0RC1 + * @since 17.4.4 + * @since 16.10.11 + *# +#macro(getAndValidateQueryLimitFromRequest $parameterName $defaultValue $_limit) + #set ($return = $numbertool.toNumber("$!{request.get($parameterName)}").intValue()) + #if (!$return) + #set ($return = $defaultValue) + #else + #validateQueryLimit($return $parameterName) + #end + #set ($_limit = $NULL) + #setVariable("$_limit" $return) +#end +## +## #** * Pagination 2.0 * @@ -2143,11 +2185,14 @@ $displayName## #if (!$parameters.defaultItemsPerPage || $numbertool.toNumber($parameters.defaultItemsPerPage).intValue() <= 0) #set ($parameters.defaultItemsPerPage = 20) #end + ## We don't use #getAndValidateQueryLimitFromRequest here because of the different handling of negative parameter + ## values (using the default vs. sending an error). #set ($parameters.itemsPerPage = "$!{request.get($parameters.itemsPerPageParamName)}") #set ($parameters.itemsPerPage = $numbertool.toNumber($parameters.itemsPerPage).intValue()) #if (!$parameters.itemsPerPage || $parameters.itemsPerPage <= 0) #set ($parameters.itemsPerPage = $parameters.defaultItemsPerPage) #end + #validateQueryLimit($parameters.itemsPerPage, $parameters.itemsPerPageParamName) #end #** * Pagination : display the pagination widget after all parameters have been computed
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/uorgsuggest.vm+1 −4 modified@@ -43,10 +43,7 @@ #set ($query = $query.bindValue('input').anyChars().literal($input).anyChars().query()) #set ($rawResults = $query.execute()) #set ($results = []) - #set ($limit = $numbertool.toNumber($request.limit)) - #if ("$!limit" == '') - #set ($limit = 10) - #end + #getAndValidateQueryLimitFromRequest('limit', 10, $limit) ## Iterate over the results and select the first N results (based on the limit request parameter) that are accessible ## to the current user. #foreach ($rawResult in $rawResults)
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/test/java/org/xwiki/web/GetdeleteddocumentsPageTest.java+2 −0 modified@@ -32,6 +32,7 @@ import org.xwiki.query.script.QueryManagerScriptService; import org.xwiki.script.service.ScriptService; import org.xwiki.security.authorization.Right; +import org.xwiki.security.script.SecurityScriptServiceComponentList; import org.xwiki.template.TemplateManager; import org.xwiki.test.annotation.ComponentList; import org.xwiki.test.page.PageTest; @@ -68,6 +69,7 @@ StoreConfiguration.class }) @UserReferenceComponentList +@SecurityScriptServiceComponentList class GetdeleteddocumentsPageTest extends PageTest { private static final String GETDELETEDDOCUMENTS = "getdeleteddocuments.vm";
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/test/java/org/xwiki/web/GetdocumentsPageTest.java+7 −21 modified@@ -33,9 +33,7 @@ import org.xwiki.query.internal.ScriptQuery; import org.xwiki.query.script.QueryManagerScriptService; import org.xwiki.script.service.ScriptService; -import org.xwiki.security.SecurityConfiguration; -import org.xwiki.security.internal.DefaultSecurityConfiguration; -import org.xwiki.security.script.SecurityScriptService; +import org.xwiki.security.script.SecurityScriptServiceComponentList; import org.xwiki.template.TemplateManager; import org.xwiki.test.annotation.ComponentList; import org.xwiki.test.page.PageTest; @@ -53,6 +51,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; /** @@ -65,9 +64,8 @@ @ComponentList({ XWikiDateTool.class, ModelScriptService.class, - SecurityScriptService.class, - DefaultSecurityConfiguration.class }) +@SecurityScriptServiceComponentList class GetdocumentsPageTest extends PageTest { private static final String GETDOCUMENTS = "getdocuments.vm"; @@ -197,25 +195,13 @@ void dateFilterBetweenTimestamp() throws Exception @Test void preventDOSAttackOnQueryItemsReturned() throws Exception { - this.request.put("limit", "101"); - when(this.queryService.hql(anyString())).thenReturn(this.query); - when(this.query.setLimit(anyInt())).thenReturn(this.query); - when(this.query.setOffset(anyInt())).thenReturn(this.query); - when(this.query.bindValues(any(Map.class))).thenReturn(this.query); - when(this.query.bindValues(any(List.class))).thenReturn(this.query); - - // Simulate the query limit - SecurityConfiguration securityConfiguration = - this.oldcore.getMocker().registerMockComponent(SecurityConfiguration.class); - when(securityConfiguration.getQueryItemsLimit()).thenReturn(100); + this.request.put("limit", "2000"); this.templateManager.render(GETDOCUMENTS); - ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class); - verify(this.query).setLimit(argument.capture()); - - // Verify that even though the guest user is asking for 101 items, we only return 100. - assertEquals(100, argument.getValue()); + // Unfortunately, we cannot verify that an error was sent as the request in the test doesn't store it. + // So we can just verify that no query was performed. + verifyNoInteractions(this.queryService); } /**
xwiki-platform-core/xwiki-platform-wiki/xwiki-platform-wiki-ui/xwiki-platform-wiki-ui-mainwiki/src/main/resources/WikiManager/WikisLiveTableResultsMacros.xml+1 −4 modified@@ -199,10 +199,7 @@ #if (!$offset || $offset < 0) #set($offset = 0) #end - #set($limit = $numbertool.toNumber($request.get('limit')).intValue()) - #if (!$limit) - #set ($limit = 15) - #end + #getAndValidateQueryLimitFromRequest('limit', 15, $limit) ######## WIKI UI MAINWIKI CUSTOMIZATION ######### ## The descriptors are always hidden, so we don't filter on the hidden column. #set($query = $services.query.hql($sql).addFilter('currentlanguage').setLimit($limit).setOffset($offset).bindValues($sqlParams))
xwiki-platform-tools/xwiki-platform-tool-configuration-resources/src/main/resources/xwiki.properties.vm+7 −2 modified@@ -832,10 +832,15 @@ distribution.automaticStartOnWiki=$xwikiPropertiesAutomaticStartOnWiki # security.script.save.checkAuthor = true #-# [Since 13.10] -#-# Prevent against DOS attacks by limiting the number of entries returned by DB queries, for guest users. +#-# Prevent DOS attacks by limiting the number of entries returned by search queries and the REST API. +#-# The query items limit shouldn't be set below 100 (default maximum page size of Live Data/LiveTable). +#-# The query items limit can be set to -1 to disable any limits (not recommended for instances that are reachable +#-# from the internet). +#-# Before XWiki 16.10.11, 17.4.4, and 17.7.0RC1, this limit had a default value of 100 and was only used for guest +#-# users in a single place. #-# #-# The default is: -# security.queryItemsLimit = 100 +# security.queryItemsLimit = 1000 #-# [Since 13.10.1] #-# [Since 14.0RC1]
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
5- github.com/advisories/GHSA-cc84-q3v3-mhgfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-66473ghsaADVISORY
- github.com/xwiki/xwiki-platform/commit/e3c47745195fb445b054537be86f5c01ee69558bghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/security/advisories/GHSA-cc84-q3v3-mhgfghsax_refsource_CONFIRMWEB
- jira.xwiki.org/browse/XWIKI-23355ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.