Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.like.script;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.xwiki.like.internal.DefaultLikeConfiguration;
import org.xwiki.like.internal.DefaultLikeManager;
import org.xwiki.like.internal.LikeConfigurationSource;
import org.xwiki.ratings.internal.DefaultRatingsConfiguration;
import org.xwiki.ratings.internal.DefaultRatingsManagerFactory;
import org.xwiki.ratings.internal.RatingsConfigurationSource;
import org.xwiki.test.annotation.ComponentList;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Pack of default Component implementations that are needed for {@link LikeScriptService}.
*
* @version $Id$
* @since 13.9RC1
* @since 13.4.4
*/
@Documented
@Retention(RUNTIME)
@Target({ TYPE, METHOD, ANNOTATION_TYPE })
@ComponentList({
LikeScriptService.class,
DefaultLikeManager.class,
DefaultRatingsManagerFactory.class,
DefaultLikeConfiguration.class,
LikeConfigurationSource.class,
// Ratings dependencies.
DefaultRatingsConfiguration.class,
RatingsConfigurationSource.class,
})
@Inherited
public @interface LikeScriptServiceComponentList
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,6 @@ class LiveDataIT
"(1) Some pages have a computed title. Filtering and sorting by title will not work as expected for these "
+ "pages.";

private static final String FOOTNOTES_PROPERTY_NOT_VIEWABLE =
"(*) Displayed when some entries require special rights"
+ " to be viewed and thus fields cannot be extracted from them.";

/**
* Test the view and edition of the cells of a live data in table layout with a liveTable source. Creates an XClass
* and two XObjects, then edit the XObjects properties from the live data.
Expand Down Expand Up @@ -183,12 +179,11 @@ void livedataLivetableTableLayout(TestUtils testUtils, TestReference testReferen

liveDataElement = new LiveDataElement("test");
tableLayout = liveDataElement.getTableLayout();
assertEquals(2, tableLayout.countRows());
tableLayout.assertRow(NAME_COLUMN, NAME_LYNDA);
tableLayout.assertRow(NAME_COLUMN, "N/A*");
tableLayout.assertRow(NAME_COLUMN, NAME_NIKOLAY);
assertEquals(2, liveDataElement.countFootnotes());
assertThat(liveDataElement.getFootnotesText(),
containsInAnyOrder(FOOTNOTES_PROPERTY_NOT_VIEWABLE, FOOTNOTE_COMPUTED_TITLE));
assertEquals(1, liveDataElement.countFootnotes());
assertThat(liveDataElement.getFootnotesText(), containsInAnyOrder(FOOTNOTE_COMPUTED_TITLE));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-security-script</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>javax.servlet</groupId>
Expand All @@ -74,5 +80,13 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- Security Script Component List for the Page Tests -->
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-security-script</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,8 @@
#foreach($item in $items)
#gridresult_buildRowJSON($item $rows)
#end
#set($discard = $map.put('rows', $rows))
#set ($discard = $map.put('rows', $rows))
#livetable_filterObfuscated($map)
#end


Expand All @@ -365,64 +366,75 @@
#set($docLanguage = '')
#end
#set ($docReference = $services.model.resolveDocument($docName))
#set ($row = {
'doc_viewable': $services.security.authorization.hasAccess('view', $docReference),
'doc_fullName': $services.model.serialize($docReference, 'local'),
'doc_space': $services.model.serialize($docReference.parent, 'local'),
'doc_location': "#hierarchy($docReference, {'limit': 5, 'plain': false, 'local': true, 'displayTitle': false})",
'doc_url': $xwiki.getURL($docReference),
'doc_space_url': $xwiki.getURL($docReference.parent),
'doc_wiki': $docReference.wikiReference.name,
'doc_wiki_url': $xwiki.getURL($docReference.wikiReference),
'doc_hasadmin': $xwiki.hasAdminRights(),
'doc_hasedit': $services.security.authorization.hasAccess('edit', $docReference),
'doc_hasdelete': $services.security.authorization.hasAccess('delete', $docReference),
'doc_edit_url': $xwiki.getURL($docReference, 'edit'),
'doc_copy_url': $xwiki.getURL($docReference, 'view', 'xpage=copy'),
'doc_delete_url': $xwiki.getURL($docReference, 'delete'),
'doc_rename_url': $xwiki.getURL($docReference, 'view', 'xpage=rename&amp;step=1')
})
#set ($isTranslation = "$!docLanguage" != '' &amp;&amp; $xwiki.getLanguagePreference() != $docLanguage)
## Display the language after the document name so that not all translated documents have the same name displayed.
#set ($row.doc_name = "$docReference.name#if ($isTranslation) ($docLanguage)#end")
#set ($row.doc_hascopy = $row.doc_viewable)
#set ($row.doc_hasrename = $row.doc_hasdelete)
#set ($row.doc_hasrights = $row.doc_hasedit &amp;&amp; $isAdvancedUser)
#if ($docReference.name == 'WebHome')
## For nested pages, use the page administration.
#set ($webPreferencesReference = $services.model.createDocumentReference('WebPreferences', $docReference.lastSpaceReference))
#set ($row.doc_rights_url = $xwiki.getURL($webPreferencesReference, 'admin', 'editor=spaceadmin&amp;section=PageRights'))
#else
## For terminal pages, use the old rights editor.
## TODO: We should create a page administration for terminal pages too.
#set ($row.doc_rights_url = $xwiki.getURL($docReference, 'edit', 'editor=rights'))
#end
#if ($row.doc_viewable)
#set ($itemDoc = $xwiki.getDocument($docReference))
## Handle translations. We need to make sure we display the data associated to the correct document if the returned
## result is a translation.
#if ($isTranslation)
#set ($translatedDoc = $itemDoc.getTranslatedDocument($docLanguage))
#set ($isViewable = $services.security.authorization.hasAccess('view', $docReference))
#if ($isViewable)
#set ($row = {
'doc_viewable': $isViewable,
'doc_fullName': $services.model.serialize($docReference, 'local'),
'doc_space': $services.model.serialize($docReference.parent, 'local'),
'doc_location': "#hierarchy($docReference, {'limit': 5, 'plain': false, 'local': true, 'displayTitle': false})",
'doc_url': $xwiki.getURL($docReference),
'doc_space_url': $xwiki.getURL($docReference.parent),
'doc_wiki': $docReference.wikiReference.name,
'doc_wiki_url': $xwiki.getURL($docReference.wikiReference),
'doc_hasadmin': $xwiki.hasAdminRights(),
'doc_hasedit': $services.security.authorization.hasAccess('edit', $docReference),
'doc_hasdelete': $services.security.authorization.hasAccess('delete', $docReference),
'doc_edit_url': $xwiki.getURL($docReference, 'edit'),
'doc_copy_url': $xwiki.getURL($docReference, 'view', 'xpage=copy'),
'doc_delete_url': $xwiki.getURL($docReference, 'delete'),
'doc_rename_url': $xwiki.getURL($docReference, 'view', 'xpage=rename&amp;step=1')
})
#set ($isTranslation = "$!docLanguage" != '' &amp;&amp; $xwiki.getLanguagePreference() != $docLanguage)
## Display the language after the document name so that not all translated documents have the same name displayed.
#set ($row.doc_name = "$docReference.name#if ($isTranslation) ($docLanguage)#end")
#set ($row.doc_hascopy = $row.doc_viewable)
#set ($row.doc_hasrename = $row.doc_hasdelete)
#set ($row.doc_hasrights = $row.doc_hasedit &amp;&amp; $isAdvancedUser)
#if ($docReference.name == 'WebHome')

## For nested pages, use the page administration.
#set ($webPreferencesReference = $services.model.createDocumentReference(
'WebPreferences', $docReference.lastSpaceReference))
#set ($row.doc_rights_url = $xwiki.getURL($webPreferencesReference, 'admin',
'editor=spaceadmin&amp;section=PageRights'))
#else
#set ($translatedDoc = $itemDoc.translatedDocument)
## For terminal pages, use the old rights editor.
## TODO: We should create a page administration for terminal pages too.
#set ($row.doc_rights_url = $xwiki.getURL($docReference, 'edit', 'editor=rights'))
#end
#set($discard = $itemDoc.use($className))
#set($discard = $row.put('doc_objectCount', $itemDoc.getObjectNumbers($className)))
#set($discard = $row.put('doc_edit_url', $itemDoc.getURL($itemDoc.defaultEditMode)))
#set($discard = $row.put('doc_author_url', $xwiki.getURL($translatedDoc.author)))
#set($discard = $row.put('doc_date', $xwiki.formatDate($translatedDoc.date)))
#set($discard = $row.put('doc_title', $translatedDoc.plainTitle))
#set($rawTitle = $translatedDoc.title)
#if($rawTitle != $row['doc_title'])
#set($discard = $row.put('doc_title_raw', $rawTitle))
#end
#set($discard = $row.put('doc_author', $xwiki.getPlainUserName($translatedDoc.authorReference)))
#set($discard = $row.put('doc_creationDate', $xwiki.formatDate($translatedDoc.creationDate)))
#set($discard = $row.put('doc_creator', $xwiki.getPlainUserName($translatedDoc.creatorReference)))
#set($discard = $row.put('doc_hidden', $translatedDoc.isHidden()))
#foreach($colname in $collist)
#gridresult_buildColumnJSON($colname $row)
#if ($row.doc_viewable)
#set ($itemDoc = $xwiki.getDocument($docReference))
## Handle translations. We need to make sure we display the data associated to the correct document if the returned
## result is a translation.
#if ($isTranslation)
#set ($translatedDoc = $itemDoc.getTranslatedDocument($docLanguage))
#else
#set ($translatedDoc = $itemDoc.translatedDocument)
#end
#set($discard = $itemDoc.use($className))
#set($discard = $row.put('doc_objectCount', $itemDoc.getObjectNumbers($className)))
#set($discard = $row.put('doc_edit_url', $itemDoc.getURL($itemDoc.defaultEditMode)))
#set($discard = $row.put('doc_author_url', $xwiki.getURL($translatedDoc.author)))
#set($discard = $row.put('doc_date', $xwiki.formatDate($translatedDoc.date)))
#set($discard = $row.put('doc_title', $translatedDoc.plainTitle))
#set($rawTitle = $translatedDoc.title)
#if($rawTitle != $row['doc_title'])
#set($discard = $row.put('doc_title_raw', $rawTitle))
#end
#set($discard = $row.put('doc_author', $xwiki.getPlainUserName($translatedDoc.authorReference)))
#set($discard = $row.put('doc_creationDate', $xwiki.formatDate($translatedDoc.creationDate)))
#set($discard = $row.put('doc_creator', $xwiki.getPlainUserName($translatedDoc.creatorReference)))
#set($discard = $row.put('doc_hidden', $translatedDoc.isHidden()))
#foreach($colname in $collist)
#gridresult_buildColumnJSON($colname $row)
#end
#end
#else
#set ($row = {
'doc_viewable': $isViewable,
'doc_fullName': 'obfuscated'
})
#end
#set($discard = $rows.add($row))
#end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@
import org.mockito.Mock;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.script.ModelScriptService;
import org.xwiki.query.Query;
import org.xwiki.query.internal.ScriptQuery;
import org.xwiki.query.script.QueryManagerScriptService;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.script.service.ScriptService;
import org.xwiki.security.authorization.Right;
import org.xwiki.security.script.SecurityScriptServiceComponentList;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.test.page.XWikiSyntax20ComponentList;
import org.xwiki.velocity.tools.EscapeTool;
Expand All @@ -50,11 +52,14 @@
import com.xpn.xwiki.plugin.tag.TagPluginApi;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand All @@ -66,12 +71,14 @@
* @version $Id$
*/
@XWikiSyntax20ComponentList
@SecurityScriptServiceComponentList
@ComponentList({
ModelScriptService.class
})
class LiveTableResultsTest extends PageTest
{
private QueryManagerScriptService queryService;

private ModelScriptService modelService;

private Map<String, Object> results;

@Mock
Expand All @@ -92,10 +99,6 @@ public void setUp() throws Exception
this.queryService = mock(QueryManagerScriptService.class);
this.oldcore.getMocker().registerComponent(ScriptService.class, "query", this.queryService);

// Prepare mock ModelScriptService so that tests can control what the model returns.
this.modelService = mock(ModelScriptService.class);
this.oldcore.getMocker().registerComponent(ScriptService.class, "model", this.modelService);

// The LiveTableResultsMacros page uses the tag plugin for the LT tag cloud feature
TagPluginApi tagPluginApi = mock(TagPluginApi.class);
doReturn(tagPluginApi).when(this.oldcore.getSpyXWiki()).getPluginApi(eq("tag"), any(XWikiContext.class));
Expand Down Expand Up @@ -131,28 +134,20 @@ void plainPageResults() throws Exception
when(this.query.count()).thenReturn(17L);
when(this.query.execute()).thenReturn(Arrays.asList("A.B", "X.Y"));

DocumentReference abReference = new DocumentReference("wiki", "A", "B");
when(this.modelService.resolveDocument("A.B")).thenReturn(abReference);
when(this.modelService.serialize(abReference.getParent(), "local")).thenReturn("A");

DocumentReference xyReference = new DocumentReference("wiki", "X", "Y");
when(this.modelService.resolveDocument("X.Y")).thenReturn(xyReference);
when(this.modelService.serialize(xyReference.getParent(), "local")).thenReturn("X");

renderPage();

assertEquals(17L, getTotalRowCount());
assertEquals(2, getRowCount());
assertEquals(13, getOffset());

List<Map<String, String>> rows = getRows();
List<Map<String, Object>> rows = getRows();
assertEquals(2, rows.size());

Map<String, String> ab = rows.get(0);
Map<String, Object> ab = rows.get(0);
assertEquals("A", ab.get("doc_space"));
assertEquals("B", ab.get("doc_name"));

Map<String, String> xy = rows.get(1);
Map<String, Object> xy = rows.get(1);
assertEquals("X", xy.get("doc_space"));
assertEquals("Y", xy.get("doc_name"));
}
Expand All @@ -167,6 +162,12 @@ void sqlReservedKeywordAsPropertyName() throws Exception
setSort("where", true);
setClassName("My.Class");

when(this.queryService.hql(any())).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.count()).thenReturn(1L);

renderPage();

verify(this.queryService).hql(
Expand All @@ -183,6 +184,12 @@ void sqlReservedKeywordAsPropertyName() throws Exception
@Test
void orderByLocation() throws Exception
{
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.count()).thenReturn(1L);

setSort("doc.location", false);

renderPage();
Expand All @@ -204,11 +211,11 @@ void restrictLocationAndFilterByDocLocation() throws Exception
setLocation("Hello");
setFilter("doc.location", "test");

Query query = mock(Query.class);
when(this.queryService.hql(any(String.class))).thenReturn(query);
when(query.setLimit(anyInt())).thenReturn(query);
when(query.setOffset(anyInt())).thenReturn(query);
when(query.bindValues(anyMap())).thenReturn(query);
when(this.queryService.hql(any(String.class))).thenReturn(this.query);
when(this.query.setLimit(anyInt())).thenReturn(this.query);
when(this.query.setOffset(anyInt())).thenReturn(this.query);
when(this.query.bindValues(anyMap())).thenReturn(this.query);
when(this.query.count()).thenReturn(1L);

renderPage();

Expand Down Expand Up @@ -248,6 +255,8 @@ void filterStringEmptyMatcher() throws Exception
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.count()).thenReturn(1L);

renderPage();

Expand Down Expand Up @@ -296,6 +305,8 @@ void filterStringListEmptyMatcher() throws Exception
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.count()).thenReturn(1L);

renderPage();

Expand Down Expand Up @@ -331,7 +342,7 @@ void filterStringListEmptyMatcher() throws Exception
void filterStringMultipleValues() throws Exception
{
XWikiDocument document = new XWikiDocument(new DocumentReference("xwiki", "Panels", "PanelClass"));
StaticListClass category = document.getXClass().addStaticListField("category");
document.getXClass().addStaticListField("category");
this.xwiki.saveDocument(document, "creates PanelClass", true, this.context);

setColumns("name,description,category");
Expand All @@ -350,6 +361,8 @@ void filterStringMultipleValues() throws Exception
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.count()).thenReturn(1L);

renderPage();

Expand Down Expand Up @@ -393,6 +406,8 @@ void filterStringNoMatcherSpecified() throws Exception
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.count()).thenReturn(1L);

renderPage();

Expand All @@ -413,6 +428,58 @@ void filterStringNoMatcherSpecified() throws Exception
verify(this.query).bindValues(values);
}

@Test
void nonViewableResultsAreObfuscated() throws Exception
{
this.request.put("limit", "2");
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.count()).thenReturn(3L);
when(this.query.execute()).thenReturn(Arrays.asList("XWiki.NotViewable", "XWiki.Viewable"));

when(this.oldcore.getMockContextualAuthorizationManager()
.hasAccess(same(Right.VIEW), eq(new DocumentReference("xwiki", "XWiki", "NotViewable")))).thenReturn(false);

renderPage();

List<Map<String, Object>> rows = getRows();
assertEquals(2, rows.size());
assertEquals(2, getRowCount());
Map<String, Object> obfuscated = rows.get(0);
assertFalse((boolean) obfuscated.get("doc_viewable"));
assertEquals("obfuscated", obfuscated.get("doc_fullName"));

Map<String, Object> viewable = rows.get(1);
assertTrue((boolean) viewable.get("doc_viewable"));
assertEquals("XWiki.Viewable", viewable.get("doc_fullName"));
}

@Test
void removeObfuscatedResultsWhenTotalrowsLowerThanLimit() throws Exception
{
this.request.put("limit", "2");
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.count()).thenReturn(2L);
when(this.query.execute()).thenReturn(Arrays.asList("XWiki.NotViewable", "XWiki.Viewable"));

when(this.oldcore.getMockContextualAuthorizationManager()
.hasAccess(same(Right.VIEW), eq(new DocumentReference("xwiki", "XWiki", "NotViewable")))).thenReturn(false);

renderPage();

List<Map<String, Object>> rows = getRows();
assertEquals(1, rows.size());
assertEquals(1, getRowCount());

Map<String, Object> viewable = rows.get(0);
assertTrue((boolean) viewable.get("doc_viewable"));
assertEquals("XWiki.Viewable", viewable.get("doc_fullName"));
}

//
// Helper methods
Expand Down Expand Up @@ -498,8 +565,8 @@ private Object getOffset()
}

@SuppressWarnings("unchecked")
private List<Map<String, String>> getRows()
private List<Map<String, Object>> getRows()
{
return (List<Map<String, String>>) this.results.get("rows");
return (List<Map<String, Object>>) this.results.get("rows");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2993,6 +2993,7 @@ platform.livetable.tagsHelp=Click on one or more tags to filter the list
platform.livetable.tagsHelpCancel=and click again on a tag to cancel the filter
platform.livetable.environmentCannotLoadTableMessage=The environment prevents the table from loading data.
platform.livetable.docTitleComputedHint=Some pages have a computed title. Filtering and sorting by title will not work as expected for these pages.
platform.livetable.docNotViewable=N/A
platform.livetable.pagesizeLabel=per page of
platform.livetable.selectAll=All
platform.livetable.paginationPage=Page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,23 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>test-jar</id>
<goals>
<goal>test-jar</goal>
</goals>
<configuration>
<includes>
<include>**/DefaultUserComponentList.class</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.user;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.xwiki.configuration.internal.DocumentsConfigurationSource;
import org.xwiki.configuration.internal.SpacesConfigurationSource;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.user.internal.AllGuestConfigurationSource;
import org.xwiki.user.internal.AllSuperAdminConfigurationSource;
import org.xwiki.user.internal.ConfiguredStringUserReferenceSerializer;
import org.xwiki.user.internal.DefaultConfiguredStringUserReferenceResolver;
import org.xwiki.user.internal.DefaultUserConfiguration;
import org.xwiki.user.internal.DefaultUserManager;
import org.xwiki.user.internal.GuestConfigurationSource;
import org.xwiki.user.internal.SecureAllUserPropertiesResolver;
import org.xwiki.user.internal.SecureUserPropertiesResolver;
import org.xwiki.user.internal.SuperAdminConfigurationSource;
import org.xwiki.user.internal.document.CurrentUserReferenceResolver;
import org.xwiki.user.internal.document.DocumentDocumentReferenceUserReferenceResolver;
import org.xwiki.user.internal.document.DocumentDocumentReferenceUserReferenceSerializer;
import org.xwiki.user.internal.document.NormalUserConfigurationSourceAuthorization;
import org.xwiki.user.internal.document.NormalUserPreferencesConfigurationSource;
import org.xwiki.user.internal.document.SecureUserDocumentUserPropertiesResolver;
import org.xwiki.user.internal.document.UserPreferencesConfigurationSource;
import org.xwiki.user.internal.group.DefaultGroupManager;
import org.xwiki.user.internal.group.GroupsCache;
import org.xwiki.user.internal.group.MembersCache;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Pack of default Components that are needed for the User API.
*
* @version $Id$
* @since 13.9RC1
* @since 13.4.4
*/
@Documented
@Retention(RUNTIME)
@Target({ TYPE, METHOD, ANNOTATION_TYPE })
@ComponentList({
// UserReferenceSerializer
DocumentDocumentReferenceUserReferenceSerializer.class,
CurrentUserReferenceResolver.class,
DocumentDocumentReferenceUserReferenceResolver.class,
ConfiguredStringUserReferenceSerializer.class,
// User Script Service
SecureUserPropertiesResolver.class,
SuperAdminConfigurationSource.class,
GuestConfigurationSource.class,
SecureAllUserPropertiesResolver.class,
AllSuperAdminConfigurationSource.class,
DocumentsConfigurationSource.class,
AllGuestConfigurationSource.class,
DefaultUserManager.class,
DefaultConfiguredStringUserReferenceResolver.class,
SecureUserDocumentUserPropertiesResolver.class,
UserPreferencesConfigurationSource.class,
NormalUserPreferencesConfigurationSource.class,
NormalUserConfigurationSourceAuthorization.class,
// User Configuration
DefaultUserConfiguration.class,
// Group Script Service
DefaultGroupManager.class,
GroupsCache.class,
MembersCache.class,
SpacesConfigurationSource.class
})
@Inherited
public @interface DefaultUserComponentList
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-user-script</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-user-default</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-user-default</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<!-- Icon Script Service -->
<dependency>
<groupId>org.xwiki.platform</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,33 +102,40 @@ $response.setContentType("application/json")
#set ($rows = [])
#foreach ($item in $items)
#set ($deletedDocument = $xwiki.getDeletedDocument($item))
#set ($originalDocument = $deletedDocument.getDocument())
#set ($documentReference = $deletedDocument.documentReference)
#set ($viewable = $deletedDocument.canUndelete())
#set ($row = {})
#set ($discard = $row.put('doc_viewable', $viewable))
#set ($discard = $row.put('doc_url', $xwiki.getURL($documentReference, 'view', $escapetool.url({'rev' : "deleted:${item}"}))))
#set ($title = "$!{originalDocument.plainTitle}")
#if ($title == '')
#set ($title = $deletedDocument.fullName)
#if ($viewable)
#set ($originalDocument = $deletedDocument.getDocument())
#set ($documentReference = $deletedDocument.documentReference)
#set ($row = {})
#set ($discard = $row.put('doc_viewable', true))
#set ($discard = $row.put('doc_url', $xwiki.getURL($documentReference, 'view', $escapetool.url({'rev' : "deleted:${item}"}))))
#set ($title = "$!{originalDocument.plainTitle}")
#if ($title == '')
#set ($title = $deletedDocument.fullName)
#end
#if ("$!{deletedDocument.locale}" != '')
#set ($title = "${title} (${deletedDocument.locale})")
#end
#set ($discard = $row.put('doc_name', $title))
#set ($location = "#hierarchy($documentReference, {'limit': 5, 'plain': false, 'local': true, 'displayTitle': false})")
#set ($discard = $row.put('doc_location', $location))
#set ($discard = $row.put('doc_date', $xwiki.formatDate($deletedDocument.date)))
#set ($discard = $row.put('doc_deleter', $xwiki.getUserName($deletedDocument.deleter, false)))
#set ($discard = $row.put('doc_deleter_url', $xwiki.getURL($deletedDocument.deleter)))
#set ($discard = $row.put('doc_locale', $deletedDocument.locale))
#set ($discard = $row.put('doc_hasdelete', $deletedDocument.canDelete()))
#set ($discard = $row.put('doc_delete_url', $xwiki.getURL($documentReference, 'delete', "id=${item}&form_token=$!{services.csrf.getToken()}")))
#set ($discard = $row.put('doc_hasrestore', $deletedDocument.canUndelete()))
#set ($discard = $row.put('doc_restore_url', $xwiki.getURL($documentReference, 'undelete', "id=${item}&form_token=$!{services.csrf.getToken()}")))
#else
#set ($row = {
'doc_viewable': false
})
#end
#if ("$!{deletedDocument.locale}" != '')
#set ($title = "${title} (${deletedDocument.locale})")
#end
#set ($discard = $row.put('doc_name', $title))
#set ($location = "#hierarchy($documentReference, {'limit': 5, 'plain': false, 'local': true, 'displayTitle': false})")
#set ($discard = $row.put('doc_location', $location))
#set ($discard = $row.put('doc_date', $xwiki.formatDate($deletedDocument.date)))
#set ($discard = $row.put('doc_deleter', $xwiki.getUserName($deletedDocument.deleter, false)))
#set ($discard = $row.put('doc_deleter_url', $xwiki.getURL($deletedDocument.deleter)))
#set ($discard = $row.put('doc_locale', $deletedDocument.locale))
#set ($discard = $row.put('doc_hasdelete', $deletedDocument.canDelete()))
#set ($discard = $row.put('doc_delete_url', $xwiki.getURL($documentReference, 'delete', "id=${item}&form_token=$!{services.csrf.getToken()}")))
#set ($discard = $row.put('doc_hasrestore', $deletedDocument.canUndelete()))
#set ($discard = $row.put('doc_restore_url', $xwiki.getURL($documentReference, 'undelete', "id=${item}&form_token=$!{services.csrf.getToken()}")))
#set ($discard = $rows.add($row))
#end
#set ($discard = $map.put('rows', $rows))
#livetable_filterObfuscated($map)
##
## Serialize the JSON
##
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ $response.setContentType("application/json")
#set ($viewable = $xwiki.hasAccessLevel('view', $xcontext.user, "${xcontext.database}:${docName}"))
#set ($row = {'doc_viewable' : $viewable})
#if (!$viewable)
#set ($discard = $row.put('doc_fullName', "${xcontext.database}:${item}"))
#set ($discard = $row.put('doc_fullName', 'obfuscated'))
#else
#set ($itemDoc = $xwiki.getDocument($docName))
## Handle translations. We need to make sure we display the data associated to the correct document if the returned
Expand Down Expand Up @@ -204,6 +204,7 @@ $response.setContentType("application/json")
#set ($discard = $rows.add($row))
#end
#set ($discard = $map.put('rows', $rows))
#livetable_filterObfuscated($map)
##
## Serialize the JSON
##
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
#set ($members = $rm.getAllMatchedMembersNamesForGroup($doc.fullName, $request.member, $limit,
$mathtool.sub($offset, 1), $order))
#else
#set ($count = $rm.countAllMembersNamesForGroup($doc.fullName))
#set ($count = $services.user.group.getMembers($doc.documentReference).size())
#set ($members = $rm.getAllMatchedMembersNamesForGroup($doc.fullName, $NULL, $limit,
$mathtool.sub($offset, 1), $order))
#end
Expand All @@ -40,64 +40,69 @@
'rows': []
})
#foreach ($member in $members)
#set ($memberReference = $services.model.resolveDocument($member))
#set ($showAlias = $hasAdmin || $isAdvancedUser)
#set ($memberDisplay = "#displayUser($memberReference {'showAlias': $showAlias, 'wrapAvatar': true})")
#set ($isUser = $xwiki.getDocument($memberReference).getObject('XWiki.XWikiUsers'))
#set ($isGroup = $xwiki.getDocument($memberReference).getObject('XWiki.XWikiGroups'))
#set ($type = [])
#if ($isUser)
#set ($discard = $type.add($services.localization.render('xe.admin.groups.type.user')))
#end
#if ($isGroup)
#set ($discard = $type.add($services.localization.render('xe.admin.groups.type.group')))
#end
#if ($memberReference.wikiReference.name == $xcontext.mainWikiName)
#set ($scope = $services.localization.render('xe.admin.groups.global'))
#elseif ($memberReference.wikiReference.name == $doc.documentReference.wikiReference.name)
#set ($scope = $services.localization.render('xe.admin.groups.local'))
#else
#set ($scope = $services.wiki.getById($memberReference.wikiReference.name).prettyName)
#if ("$!scope.trim()" == '')
#set ($scope = $memberReference.wikiReference.name)
#if ($xwiki.hasAccessLevel('view', $xcontext.user, $member))
#set ($memberReference = $services.model.resolveDocument($member))
#set ($showAlias = $hasAdmin || $isAdvancedUser)
#set ($memberDisplay = "#displayUser($memberReference {'showAlias': $showAlias, 'wrapAvatar': true})")
#set ($isUser = $xwiki.getDocument($memberReference).getObject('XWiki.XWikiUsers'))
#set ($isGroup = $xwiki.getDocument($memberReference).getObject('XWiki.XWikiGroups'))
#set ($type = [])
#if ($isUser)
#set ($discard = $type.add($services.localization.render('xe.admin.groups.type.user')))
#end
#end
## The following properties are kept for backward compatibility with XWiki versions older than 2.7.2.
#set ($grayed = $memberReference.equals($xcontext.userReference) && $doc.fullName == 'XWiki.XWikiAdminGroup')
#set ($oldProperties = {
'fullname': $member,
'prettyname': "$xwiki.getPlainUserName($member)#if ($hasAdmin || $isAdvancedUser) ($member)#end",
'wikiname': $memberReference.wikiReference.name,
'memberurl': $xwiki.getURL($memberReference),
'docurl': $doc.getURL(),
'grayed': $grayed
})
## The following properties are kept for backward compatibility with XWiki versions older than 10.9
#set ($discard = $oldProperties.putAll({
'member_url': $xwiki.getURL($memberReference),
'_avatar': "#largeUserAvatar($member)",
'_avatar_url': $xwiki.getURL($memberReference)
}))
## Live table data.
#set ($liveTableData = {
'doc_fullName': $member,
'doc_viewable': $xwiki.hasAccessLevel('view', $xcontext.user, $member),
'doc_hasadmin': $xwiki.hasAccessLevel('admin', $xcontext.user, $doc.fullName),
'doc_hasedit': $xwiki.hasAccessLevel('edit', $xcontext.user, $doc.fullName),
'doc_hasdelete': $xwiki.hasAccessLevel('edit', $xcontext.user, $doc.fullName),
'member': $memberDisplay.trim(),
'type': $stringtool.join($type, ', '),
'scope': $scope,
'doc_delete_url': $doc.getURL('view', $escapetool.url({
'xpage': 'deletegroupmember',
#if ($isGroup)
#set ($discard = $type.add($services.localization.render('xe.admin.groups.type.group')))
#end
#if ($memberReference.wikiReference.name == $xcontext.mainWikiName)
#set ($scope = $services.localization.render('xe.admin.groups.global'))
#elseif ($memberReference.wikiReference.name == $doc.documentReference.wikiReference.name)
#set ($scope = $services.localization.render('xe.admin.groups.local'))
#else
#set ($scope = $services.wiki.getById($memberReference.wikiReference.name).prettyName)
#if ("$!scope.trim()" == '')
#set ($scope = $memberReference.wikiReference.name)
#end
#end
## The following properties are kept for backward compatibility with XWiki versions older than 2.7.2.
#set ($grayed = $memberReference.equals($xcontext.userReference) && $doc.fullName == 'XWiki.XWikiAdminGroup')
#set ($oldProperties = {
'fullname': $member,
'ajax': true,
'form_token': $services.csrf.token
'prettyname': "$xwiki.getPlainUserName($member)#if ($hasAdmin || $isAdvancedUser) ($member)#end",
'wikiname': $memberReference.wikiReference.name,
'memberurl': $xwiki.getURL($memberReference),
'docurl': $doc.getURL(),
'grayed': $grayed
})
## The following properties are kept for backward compatibility with XWiki versions older than 10.9
#set ($discard = $oldProperties.putAll({
'member_url': $xwiki.getURL($memberReference),
'_avatar': "#largeUserAvatar($member)",
'_avatar_url': $xwiki.getURL($memberReference)
}))
})
#set ($data = {})
#set ($discard = $data.putAll($oldProperties))
#set ($discard = $data.putAll($liveTableData))
## Live table data.
#set ($liveTableData = {
'doc_fullName': $member,
'doc_viewable': true,
'doc_hasadmin': $xwiki.hasAccessLevel('admin', $xcontext.user, $doc.fullName),
'doc_hasedit': $xwiki.hasAccessLevel('edit', $xcontext.user, $doc.fullName),
'doc_hasdelete': $xwiki.hasAccessLevel('edit', $xcontext.user, $doc.fullName),
'member': $memberDisplay.trim(),
'type': $stringtool.join($type, ', '),
'scope': $scope,
'doc_delete_url': $doc.getURL('view', $escapetool.url({
'xpage': 'deletegroupmember',
'fullname': $member,
'ajax': true,
'form_token': $services.csrf.token
}))
})
#set ($data = {})
#set ($discard = $data.putAll($oldProperties))
#set ($discard = $data.putAll($liveTableData))
#else
#set($data = { 'doc_viewable': false })
#end
#set ($discard = $json.rows.add($data))
#end
#livetable_filterObfuscated($json)
#jsonResponse($json)
Original file line number Diff line number Diff line change
Expand Up @@ -48,39 +48,47 @@
'rows': []
})
#foreach ($group in $groups)
#set ($wikiName = $group.wiki)
#if ($wikiName != $xcontext.mainWikiName || $wikiName == $xcontext.database)
#set ($wikiName = 'local')
#end
## Deprecated properties kept for backward compatibility with XWiki versions older than 10.9
#set ($row = {
'username': $group.documentReference.name,
'fullname': $group.fullName,
'wikiname': $wikiName,
'userurl': $group.getURL(),
'usersaveurl': $group.getURL('save'),
'userinlineurl': $group.getURL('edit', 'xpage=plain'),
'docurl': $xwiki.getURL('XWiki.XWikiPreferences', 'admin', 'section=Groups')
})
#if ($group.wiki == $xcontext.mainWikiName)
#set ($scope = 'global')
#if($services.security.authorization.hasAccess('view', $group.documentReference))
#set ($wikiName = $group.wiki)
#if ($wikiName != $xcontext.mainWikiName || $wikiName == $xcontext.database)
#set ($wikiName = 'local')
#end
## Deprecated properties kept for backward compatibility with XWiki versions older than 10.9
#set ($row = {
'username': $group.documentReference.name,
'fullname': $group.fullName,
'wikiname': $wikiName,
'userurl': $group.getURL(),
'usersaveurl': $group.getURL('save'),
'userinlineurl': $group.getURL('edit', 'xpage=plain'),
'docurl': $xwiki.getURL('XWiki.XWikiPreferences', 'admin', 'section=Groups')
})
#if ($group.wiki == $xcontext.mainWikiName)
#set ($scope = 'global')
#else
#set ($scope = 'local')
#end
#set ($discard = $row.putAll({
'doc_fullName': $group.fullName,
'doc_wiki': $group.wiki,
'doc_url': $group.getURL(),
'doc_viewable': true,
'doc_hasadmin': $xwiki.hasAdminRights(),
'doc_hasedit': $services.security.authorization.hasAccess('edit', $group.documentReference),
'doc_edit_url': $group.getURL('edit'),
'doc_hasdelete': $services.security.authorization.hasAccess('delete', $group.documentReference),
'doc_delete_url': $group.getURL('delete'),
'name': "#displayGroup($group.documentReference {'wrapAvatar': true})",
'members': $services.user.group.getMembers($group.documentReference, false).size(),
'scope': $services.localization.render("xe.admin.groups.$scope")
}))
#else
#set ($scope = 'local')
#set($row = {
'doc_viewable': false,
'doc_fullName': 'obfuscated'
})
#end
#set ($discard = $row.putAll({
'doc_fullName': $group.fullName,
'doc_wiki': $group.wiki,
'doc_url': $group.getURL(),
'doc_viewable': $services.security.authorization.hasAccess('view', $group.documentReference),
'doc_hasadmin': $xwiki.hasAdminRights(),
'doc_hasedit': $services.security.authorization.hasAccess('edit', $group.documentReference),
'doc_edit_url': $group.getURL('edit'),
'doc_hasdelete': $services.security.authorization.hasAccess('delete', $group.documentReference),
'doc_delete_url': $group.getURL('delete'),
'name': "#displayGroup($group.documentReference {'wrapAvatar': true})",
'members': $services.user.group.getMembers($group.documentReference, false).size(),
'scope': $services.localization.render("xe.admin.groups.$scope")
}))
#set ($discard = $data.rows.add($row))
#end
#livetable_filterObfuscated($data)
#jsonResponse($data)
Original file line number Diff line number Diff line change
Expand Up @@ -84,65 +84,73 @@
'rows': []
})
#foreach ($user in $users)
#set ($grayed = $xcontext.user == $user.fullName)
#set ($wikiName = $user.wiki)
#if ($wikiName != $xcontext.mainWikiName || $wikiName == $xcontext.database)
#set ($wikiName = 'local')
#end
#set ($userObject = $user.getObject($userClassName))
## Deprecated properties kept for backward compatibility with XWiki versions older than 10.9
#set ($row = {
'username': $user.documentReference.name,
'fullname': $user.fullName,
'wikiname': $wikiName,
'firstname': $userObject.getValue('first_name'),
'lastname': $userObject.getValue('last_name'),
'userurl': $user.getURL(),
'usersaveurl': $user.getURL('save'),
'userinlineurl': $user.getURL('edit', 'xpage=edituser'),
'docurl': $xwiki.getURL('XWiki.XWikiPreferences', 'admin', 'section=Users'),
'grayed': $grayed
})
#if ($user.wiki == $xcontext.mainWikiName)
#set ($scope = 'global')
#if ($services.security.authorization.hasAccess('view', $user.documentReference))
#set ($grayed = $xcontext.user == $user.fullName)
#set ($wikiName = $user.wiki)
#if ($wikiName != $xcontext.mainWikiName || $wikiName == $xcontext.database)
#set ($wikiName = 'local')
#end
#set ($userObject = $user.getObject($userClassName))
## Deprecated properties kept for backward compatibility with XWiki versions older than 10.9
#set ($row = {
'username': $user.documentReference.name,
'fullname': $user.fullName,
'wikiname': $wikiName,
'firstname': $userObject.getValue('first_name'),
'lastname': $userObject.getValue('last_name'),
'userurl': $user.getURL(),
'usersaveurl': $user.getURL('save'),
'userinlineurl': $user.getURL('edit', 'xpage=edituser'),
'docurl': $xwiki.getURL('XWiki.XWikiPreferences', 'admin', 'section=Users'),
'grayed': $grayed
})
#if ($user.wiki == $xcontext.mainWikiName)
#set ($scope = 'global')
#else
#set ($scope = 'local')
#end
#set ($hasEdit = $services.security.authorization.hasAccess('edit', $user.documentReference))
#set ($disabled = $xwiki.getUser($user.documentReference).isDisabled())
#set ($hasEnable = $hasEdit && $disabled)
## Prevent users from disabling their own accounts.
#set ($hasDisable = $hasEdit && !$disabled && $xcontext.userReference != $user.documentReference)
## Delete user accounts only after they have been disabled. Also prevent users from deleting their own accounts.
#set ($hasDelete = $disabled && $xcontext.userReference != $user.documentReference
&& $services.security.authorization.hasAccess('delete', $user.documentReference))
#set ($discard = $row.putAll({
'doc_fullName': $user.fullName,
'doc_wiki': $user.wiki,
'doc_url': $user.getURL(),
'doc_viewable': true,
'doc_hasadmin': $xwiki.hasAdminRights(),
'doc_hasedit': $hasEdit,
'doc_edit_url': $user.getURL('edit'),
'doc_hasdelete': $hasDelete,
'doc_delete_url': $user.getURL('delete'),
'doc_hasdisable': $hasDisable,
'doc_disable_url': $user.getURL('save', $escapetool.url({
"${userClassName}_0_active": 0,
'comment': $services.localization.render('core.users.disable.saveComment'),
'form_token': $services.csrf.token
})),
'doc_hasenable': $hasEnable,
'doc_enable_url': $user.getURL('save', $escapetool.url({
"${userClassName}_0_active": 1,
'comment': $services.localization.render('core.users.enable.saveComment'),
'form_token': $services.csrf.token
})),
'name': "#displayUserAliasWithAvatar($user.documentReference $disabled)",
'first_name': $userObject.getValue('first_name'),
'last_name': $userObject.getValue('last_name'),
'scope': $services.localization.render("xe.admin.groups.$scope")
}))
#else
#set ($scope = 'local')
#set($row = {
'doc_viewable': false,
'doc_fullName': 'obfuscated'
})
#end
#set ($hasEdit = $services.security.authorization.hasAccess('edit', $user.documentReference))
#set ($disabled = $xwiki.getUser($user.documentReference).isDisabled())
#set ($hasEnable = $hasEdit && $disabled)
## Prevent users from disabling their own accounts.
#set ($hasDisable = $hasEdit && !$disabled && $xcontext.userReference != $user.documentReference)
## Delete user accounts only after they have been disabled. Also prevent users from deleting their own accounts.
#set ($hasDelete = $disabled && $xcontext.userReference != $user.documentReference
&& $services.security.authorization.hasAccess('delete', $user.documentReference))
#set ($discard = $row.putAll({
'doc_fullName': $user.fullName,
'doc_wiki': $user.wiki,
'doc_url': $user.getURL(),
'doc_viewable': $services.security.authorization.hasAccess('view', $user.documentReference),
'doc_hasadmin': $xwiki.hasAdminRights(),
'doc_hasedit': $hasEdit,
'doc_edit_url': $user.getURL('edit'),
'doc_hasdelete': $hasDelete,
'doc_delete_url': $user.getURL('delete'),
'doc_hasdisable': $hasDisable,
'doc_disable_url': $user.getURL('save', $escapetool.url({
"${userClassName}_0_active": 0,
'comment': $services.localization.render('core.users.disable.saveComment'),
'form_token': $services.csrf.token
})),
'doc_hasenable': $hasEnable,
'doc_enable_url': $user.getURL('save', $escapetool.url({
"${userClassName}_0_active": 1,
'comment': $services.localization.render('core.users.enable.saveComment'),
'form_token': $services.csrf.token
})),
'name': "#displayUserAliasWithAvatar($user.documentReference $disabled)",
'first_name': $userObject.getValue('first_name'),
'last_name': $userObject.getValue('last_name'),
'scope': $services.localization.render("xe.admin.groups.$scope")
}))
#set ($discard = $data.rows.add($row))
#end
#livetable_filterObfuscated($data)
#jsonResponse($data)
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,39 @@ $displayName##
#end
#end

#**
* Internal macro - Remove the obfuscated results (i.e., non-viewable rows) from the map when the total number of
* returned rows is larger or equal to the total number of counted rows.
*
* @param map the response map returned to the user. This map is expected to have a totalrows and returnedrows fields
* with numeric values and a rows field containing a arrow of objects containing a doc_viewable boolean
* field
*
* @internal
* @since 13.9RC1
* @since 13.4.4
* @since 12.10.11
*#
##
#macro (livetable_filterObfuscated $map)
## When the total number of rows is not bigger than the limit, we remove the obfuscated results from the response since
## this has no impact on the pagination.
#set ($rows = $map.get('rows'))
#if ($map.get('totalrows') <= $map.get('returnedrows'))
#set ($filteredRows = [])
#foreach ($row in $rows)
#if($row['doc_viewable'])
#set ($discard = $filteredRows.add($row))
#end
#end
#set($discard = $map.put('totalrows', $filteredRows.size()))
#set($discard = $map.put('returnedrows', $filteredRows.size()))
#else
#set ($filteredRows = $rows)
#end
#set ($discard = $map.put('rows', $filteredRows))
#end

#**
* Modifies the passed where clause of the query (and its parameters) to add a "location" filter.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.security.authorization.Right;
import org.xwiki.security.authorization.script.SecurityAuthorizationScriptService;
import org.xwiki.security.authorization.script.internal.RightConverter;
import org.xwiki.security.script.SecurityScriptService;
import org.xwiki.security.script.SecurityScriptServiceComponentList;
import org.xwiki.skinx.internal.async.SkinExtensionAsync;
import org.xwiki.template.TemplateManager;
import org.xwiki.test.annotation.ComponentList;
Expand All @@ -52,11 +50,8 @@
* @version $Id$
*/
@HTML50ComponentList
@SecurityScriptServiceComponentList
@ComponentList({
// Security SS so that $service.security.* calls in the vm work and their behavior controlled.
SecurityScriptService.class,
SecurityAuthorizationScriptService.class,
RightConverter.class,
// SKin Extensions so that $jsx.* and $ssx.* calls in the vm work.
SkinExtensionAsync.class
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.web;

import java.util.List;
import java.util.Map;

import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.query.QueryException;
import org.xwiki.query.internal.ScriptQuery;
import org.xwiki.query.script.QueryManagerScriptService;
import org.xwiki.script.service.ScriptService;
import org.xwiki.template.TemplateManager;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.velocity.tools.EscapeTool;
import org.xwiki.velocity.tools.JSONTool;

import com.xpn.xwiki.doc.XWikiDeletedDocument;
import com.xpn.xwiki.doc.XWikiDeletedDocumentContent;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.internal.store.StoreConfiguration;
import com.xpn.xwiki.store.XWikiRecycleBinStoreInterface;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
* Test the {@code getdeleteddocuments.vm} template. Assert that the returned results are well-formed.
*
* @version $Id$
* @since 13.9RC1
* @since 13.4.4
*/
@ComponentList({
StoreConfiguration.class
})
class GetdeleteddocumentsPageTest extends PageTest
{
private static final String GETDELETEDDOCUMENTS = "getdeleteddocuments.vm";

@Mock
private QueryManagerScriptService queryService;

@Mock
private ScriptQuery query;

@Mock
private XWikiRecycleBinStoreInterface recycleBinStore;

private TemplateManager templateManager;

@BeforeEach
void setUp() throws Exception
{
this.templateManager = this.oldcore.getMocker().getInstance(TemplateManager.class);
this.oldcore.getMocker().registerComponent(ScriptService.class, "query", this.queryService);

this.oldcore.getMocker().registerComponent(ScriptService.class, "query", this.queryService);

this.xwiki.setRecycleBinStore(this.recycleBinStore);

registerVelocityTool("mathtool", new MathTool());
registerVelocityTool("escapetool", new EscapeTool());
registerVelocityTool("numbertool", new NumberTool());
}

@Test
void getDeletedDocumentsObfuscatedResultsAreFiltered() throws Exception
{
defaultQueryMocks();

when(this.query.execute()).thenReturn(asList("1", "2"), singletonList(2));

when(this.recycleBinStore.getDeletedDocument(1L, this.context, true))
.thenReturn(new XWikiDeletedDocument("fullName1", null, null, null, null, null));
XWikiDeletedDocumentContent xWikiDeletedDocumentContent = mock(XWikiDeletedDocumentContent.class);
when(xWikiDeletedDocumentContent.getXWikiDocument(null)).thenReturn(
new XWikiDocument(new DocumentReference("xwiki", "XWiki", "fullName2")));
when(this.recycleBinStore.getDeletedDocument(2L, this.context, true))
.thenReturn(new XWikiDeletedDocument("fullName2", null, null, null, null, xWikiDeletedDocumentContent));

when(this.xwiki.getRightService().hasAccessLevel("admin", "XWiki.XWikiGuest", "fullName2", this.context))
.thenReturn(true);

Map<String, Object> results = getJsonResultMap();

List<Map<String, Object>> rows = (List<Map<String, Object>>) results.get("rows");
assertEquals(1, rows.size());
assertTrue((Boolean) rows.get(0).get("doc_viewable"));
assertEquals("fullName2", rows.get(0).get("doc_name"));
verify(this.queryService).hql("SELECT ddoc.id FROM XWikiDeletedDocument as ddoc WHERE 1=1 ");
verify(this.queryService).hql("SELECT COUNT(ddoc.id) FROM XWikiDeletedDocument as ddoc WHERE 1=1");
}

@Test
void getDeletedDocumentsOfuscatedResultsAreNotFiltered() throws Exception
{
this.request.put("limit", "1");

defaultQueryMocks();

when(this.query.execute()).thenReturn(asList("1"), singletonList(2));

when(this.recycleBinStore.getDeletedDocument(1L, this.context, true))
.thenReturn(new XWikiDeletedDocument("fullName1", null, null, null, null, null));

when(this.xwiki.getRightService().hasAccessLevel("admin", "XWiki.XWikiGuest", "fullName2", this.context))
.thenReturn(true);

Map<String, Object> results = getJsonResultMap();

List<Map<String, Object>> rows = (List<Map<String, Object>>) results.get("rows");
assertEquals(1, rows.size());
assertFalse((Boolean) rows.get(0).get("doc_viewable"));
verify(this.queryService).hql("SELECT ddoc.id FROM XWikiDeletedDocument as ddoc WHERE 1=1 ");
verify(this.queryService).hql("SELECT COUNT(ddoc.id) FROM XWikiDeletedDocument as ddoc WHERE 1=1");
}

private void defaultQueryMocks() throws QueryException
{
when(this.queryService.hql(any())).thenReturn(this.query, this.query);
when(this.query.setLimit(anyInt())).thenReturn(this.query);
when(this.query.setOffset(anyInt())).thenReturn(this.query);
when(this.query.bindValues(anyList())).thenReturn(this.query, this.query);
when(this.query.setWiki(any())).thenReturn(this.query, this.query);
}

private Map<String, Object> getJsonResultMap() throws Exception
{
JSONTool jsonTool = mock(JSONTool.class);
registerVelocityTool("jsontool", jsonTool);

this.templateManager.render(GETDELETEDDOCUMENTS);

ArgumentCaptor<Object> argument = ArgumentCaptor.forClass(Object.class);
verify(jsonTool).serialize(argument.capture());

return (Map<String, Object>) argument.getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.web;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.xwiki.model.script.ModelScriptService;
import org.xwiki.query.internal.ScriptQuery;
import org.xwiki.query.script.QueryManagerScriptService;
import org.xwiki.script.service.ScriptService;
import org.xwiki.template.TemplateManager;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.velocity.internal.XWikiDateTool;
import org.xwiki.velocity.tools.EscapeTool;
import org.xwiki.velocity.tools.JSONTool;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
* Test the {@code getdocuments.vm} template. Assert that the returned results are well-formed.
*
* @version $Id$
* @since 13.9RC1
* @since 13.4.4
*/
@ComponentList({
XWikiDateTool.class,
ModelScriptService.class,
})
class GetdocumentsPageTest extends PageTest
{
private static final String GETDOCUMENTS = "getdocuments.vm";

@Mock
private QueryManagerScriptService queryService;

@Mock
private ScriptQuery query;

private TemplateManager templateManager;

@BeforeEach
void setUp() throws Exception
{
this.templateManager = this.oldcore.getMocker().getInstance(TemplateManager.class);
this.oldcore.getMocker().registerComponent(ScriptService.class, "query", this.queryService);

registerVelocityTool("jsontool", new JSONTool());
registerVelocityTool("mathtool", new MathTool());
registerVelocityTool("escapetool", new EscapeTool());
registerVelocityTool("numbertool", new NumberTool());
}

@Test
void removeObuscatedResultsWhenTotalrowsLowerThanLimit() throws Exception
{
when(this.oldcore.getMockRightService().hasAccessLevel(eq("view"), any(), any(), any()))
.thenReturn(false, true);
this.request.put("limit", "2");
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);
when(this.query.count()).thenReturn(3L);
when(this.query.execute()).thenReturn(Arrays.asList("XWiki.NotViewable", "XWiki.Viewable"));

Map<String, Object> result = getJsonResultMap();

List<Map<String, Object>> rows = (List<Map<String, Object>>) result.get("rows");
assertEquals(2, rows.size());
assertEquals(2, result.get("returnedrows"));
Map<String, Object> obfuscated = rows.get(0);
assertFalse((boolean) obfuscated.get("doc_viewable"));
assertEquals("obfuscated", obfuscated.get("doc_fullName"));

Map<String, Object> viewable = rows.get(1);
assertTrue((boolean) viewable.get("doc_viewable"));
assertEquals("xwiki:XWiki.Viewable", viewable.get("doc_fullName"));
}

@Test
void nonViewableResultsAreObfuscated() throws Exception
{
when(this.oldcore.getMockRightService().hasAccessLevel(eq("view"), any(), any(), any())).thenReturn(false,
true);
this.request.put("limit", "2");
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);
when(this.query.count()).thenReturn(2L);
when(this.query.execute()).thenReturn(Arrays.asList("XWiki.NotViewable", "XWiki.Viewable"));

Map<String, Object> result = getJsonResultMap();

List<Map<String, Object>> rows = (List<Map<String, Object>>) result.get("rows");
assertEquals(1, rows.size());
assertEquals(1, result.get("returnedrows"));

Map<String, Object> viewable = rows.get(0);
assertTrue((boolean) viewable.get("doc_viewable"));
assertEquals("xwiki:XWiki.Viewable", viewable.get("doc_fullName"));
}

private Map<String, Object> getJsonResultMap() throws Exception
{
JSONTool jsonTool = mock(JSONTool.class);
registerVelocityTool("jsontool", jsonTool);

this.templateManager.render(GETDOCUMENTS);

ArgumentCaptor<Object> argument = ArgumentCaptor.forClass(Object.class);
verify(jsonTool).serialize(argument.capture());

return (Map<String, Object>) argument.getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.web;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.xwiki.model.internal.reference.converter.EntityReferenceConverter;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.script.ModelScriptService;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.script.service.ScriptService;
import org.xwiki.template.TemplateManager;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.user.DefaultUserComponentList;
import org.xwiki.user.internal.group.AbstractGroupCache.GroupCacheEntry;
import org.xwiki.user.internal.group.MembersCache;
import org.xwiki.user.script.GroupScriptService;
import org.xwiki.user.script.UserScriptService;
import org.xwiki.velocity.tools.EscapeTool;
import org.xwiki.velocity.tools.JSONTool;

import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.plugin.rightsmanager.RightsManagerPlugin;
import com.xpn.xwiki.user.api.XWikiGroupService;

import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
* Test the {@code getgroupmembers.vm} template. Assert that the returned results are well-formed.
*
* @version $Id$
* @since 13.9RC1
* @since 13.4.4
*/
@ComponentList({
ModelScriptService.class,
EntityReferenceConverter.class,
UserScriptService.class,
GroupScriptService.class
})
@DefaultUserComponentList
class GetgroupmembersPageTest extends PageTest
{
private static final String GETGROUPMEMBERS = "getgroupmembers.vm";

private TemplateManager templateManager;

private XWikiGroupService groupService;

private MembersCache membersCache;

@BeforeEach
void setUp() throws Exception
{
setOutputSyntax(Syntax.PLAIN_1_0);

this.templateManager = this.oldcore.getMocker().getInstance(TemplateManager.class);

// Enable the Rights Manager plugin.
this.oldcore.getSpyXWiki().getPluginManager().addPlugin("rightsmanager", RightsManagerPlugin.class.getName(),
this.oldcore.getXWikiContext());

this.groupService = this.context.getWiki().getGroupService(this.context);

// Override the members cache component with a mock.
this.membersCache = this.componentManager.registerMockComponent(MembersCache.class);

// Make sure User and Group script services load properly.
this.componentManager.getInstance(ScriptService.class, "user");
this.componentManager.getInstance(ScriptService.class, "user.group");

registerVelocityTool("numbertool", new NumberTool());
registerVelocityTool("mathtool", new MathTool());
registerVelocityTool("stringutils", new StringUtils());
registerVelocityTool("stringtool", new StringUtils());
registerVelocityTool("escapetool", new EscapeTool());
}

@Test
void removeObuscatedResultsWhenTotalrowsLowerThanLimit() throws Exception
{
this.request.put("limit", "10");
this.request.put("offset", "1");

DocumentReference groupDocumentReference = new DocumentReference("xwiki", "XWiki", "group");

XWikiDocument groupDoc = this.xwiki.getDocument(groupDocumentReference, this.context);
this.context.setDoc(groupDoc);

this.xwiki.createUser("U1", Collections.emptyMap(), this.context);
this.xwiki.createUser("U2", Collections.emptyMap(), this.context);

when(this.xwiki.getRightService().hasAccessLevel("view", "XWiki.XWikiGuest", "U2", this.context))
.thenAnswer(invocationOnMock -> false);

GroupCacheEntry groupCacheEntry = mock(GroupCacheEntry.class);
when(this.membersCache.getCacheEntry(groupDocumentReference, true)).thenReturn(groupCacheEntry);
when(groupCacheEntry.getAll()).thenReturn(Arrays.asList(
new DocumentReference("xwiki", "XWiki", "U1"),
new DocumentReference("xwiki", "XWiki", "U2")
));

when(this.groupService.getAllMatchedMembersNamesForGroup("XWiki.group", null, 10, 0, true, this.context))
.thenReturn(asList(
"U1",
"U2"));
Map<String, Object> results = getJsonResultMap();
List<Map<String, Object>> rows = (List<Map<String, Object>>) results.get("rows");
assertEquals(1, rows.size());
assertEquals("U1", rows.get(0).get("doc_fullName"));
}

@Test
void nonViewableResultsAreObfuscated() throws Exception
{
this.request.put("limit", "2");
this.request.put("offset", "1");

DocumentReference groupDocumentReference = new DocumentReference("xwiki", "XWiki", "group");

XWikiDocument groupDoc = this.xwiki.getDocument(groupDocumentReference, this.context);
this.context.setDoc(groupDoc);

this.xwiki.createUser("U1", Collections.emptyMap(), this.context);
this.xwiki.createUser("U2", Collections.emptyMap(), this.context);

when(this.xwiki.getRightService().hasAccessLevel("view", "XWiki.XWikiGuest", "U2", this.context))
.thenAnswer(invocationOnMock -> false);

GroupCacheEntry groupCacheEntry = mock(GroupCacheEntry.class);
when(this.membersCache.getCacheEntry(groupDocumentReference, true)).thenReturn(groupCacheEntry);
when(groupCacheEntry.getAll()).thenReturn(Arrays.asList(
new DocumentReference("xwiki", "XWiki", "U1"),
new DocumentReference("xwiki", "XWiki", "U2"),
new DocumentReference("xwiki", "XWiki", "U3")
));

when(this.groupService.getAllMatchedMembersNamesForGroup("XWiki.group", null, 2, 0, true, this.context))
.thenReturn(asList(
"U1",
"U2"));
Map<String, Object> results = getJsonResultMap();
List<Map<String, Object>> rows = (List<Map<String, Object>>) results.get("rows");
assertEquals(2, rows.size());
assertEquals("U1", rows.get(0).get("doc_fullName"));
assertTrue((Boolean) rows.get(0).get("doc_viewable"));
assertFalse((Boolean) rows.get(1).get("doc_viewable"));
}

private Map<String, Object> getJsonResultMap() throws Exception
{
JSONTool jsonTool = mock(JSONTool.class);
registerVelocityTool("jsontool", jsonTool);

this.templateManager.render(GETGROUPMEMBERS);

ArgumentCaptor<Object> argument = ArgumentCaptor.forClass(Object.class);
verify(jsonTool).serialize(argument.capture());

return (Map<String, Object>) argument.getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.web;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.security.script.SecurityScriptServiceComponentList;
import org.xwiki.template.TemplateManager;
import org.xwiki.test.page.PageTest;
import org.xwiki.velocity.tools.JSONTool;

import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.plugin.rightsmanager.RightsManagerPlugin;
import com.xpn.xwiki.user.api.XWikiGroupService;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
* Test the {@code getgroups.vm} template. Assert that the returned results are well-formed.
*
* @version $Id$
* @since 13.9RC1
* @since 13.4.4
*/
@SecurityScriptServiceComponentList
class GetgroupsPageTest extends PageTest
{
private static final String GETGROUPS = "getgroups.vm";

private TemplateManager templateManager;

private XWikiGroupService groupService;

private ContextualAuthorizationManager contextualAuthorizationManager;

@BeforeEach
void setUp() throws Exception
{
this.templateManager = this.oldcore.getMocker().getInstance(TemplateManager.class);

// Enable the Rights Manager plugin.
this.oldcore.getSpyXWiki().getPluginManager().addPlugin("rightsmanager", RightsManagerPlugin.class.getName(),
this.oldcore.getXWikiContext());

this.groupService = this.context.getWiki().getGroupService(this.context);

this.contextualAuthorizationManager = this.componentManager.getInstance(ContextualAuthorizationManager.class);

registerVelocityTool("numbertool", new NumberTool());
registerVelocityTool("mathtool", new MathTool());
}

@Test
void removeObuscatedResultsWhenTotalrowsLowerThanLimit() throws Exception
{
this.request.put("wiki", "local");
this.request.put("limit", "10");
this.request.put("offset", "1");

DocumentReference group1DocumentReference = new DocumentReference("xwiki", "XWiki", "G1");
DocumentReference group2DocumentReference = new DocumentReference("xwiki", "XWiki", "G2");
when(this.contextualAuthorizationManager.hasAccess(Right.VIEW, group2DocumentReference)).thenReturn(false);
XWikiDocument group1 = this.xwiki.getDocument(group1DocumentReference, this.context);
XWikiDocument group2 = this.xwiki.getDocument(group2DocumentReference, this.context);


doReturn(Arrays.asList(group1, group2))
.when(this.groupService)
.getAllMatchedGroups(eq(null), eq(true), eq(10), eq(0), any(), eq(this.context));
when(this.groupService.countAllMatchedGroups(any(), eq(this.context))).thenReturn(2);

Map<String, Object> results = getJsonResultMap();
List<Map<String, Object>> rows = (List<Map<String, Object>>) results.get("rows");
assertEquals(1, rows.size());
assertEquals("XWiki.G1", rows.get(0).get("doc_fullName"));
}

@Test
void nonViewableResultsAreObfuscated() throws Exception
{
this.request.put("wiki", "local");
this.request.put("limit", "2");
this.request.put("offset", "1");

DocumentReference group1DocumentReference = new DocumentReference("xwiki", "XWiki", "G1");
DocumentReference group2DocumentReference = new DocumentReference("xwiki", "XWiki", "G2");
when(this.contextualAuthorizationManager.hasAccess(Right.VIEW, group2DocumentReference)).thenReturn(false);
XWikiDocument group1 = this.xwiki.getDocument(group1DocumentReference, this.context);
XWikiDocument group2 = this.xwiki.getDocument(group2DocumentReference, this.context);


doReturn(Arrays.asList(group1, group2))
.when(this.groupService)
.getAllMatchedGroups(eq(null), eq(true), eq(2), eq(0), any(), eq(this.context));
when(this.groupService.countAllMatchedGroups(any(), eq(this.context))).thenReturn(10);

Map<String, Object> results = getJsonResultMap();
List<Map<String, Object>> rows = (List<Map<String, Object>>) results.get("rows");
assertEquals(2, rows.size());
assertEquals("XWiki.G1", rows.get(0).get("doc_fullName"));
assertTrue((Boolean) rows.get(0).get("doc_viewable"));
assertEquals("obfuscated", rows.get(1).get("doc_fullName"));
assertFalse((Boolean) rows.get(1).get("doc_viewable"));
}

private Map<String, Object> getJsonResultMap() throws Exception
{
JSONTool jsonTool = mock(JSONTool.class);
registerVelocityTool("jsontool", jsonTool);

this.templateManager.render(GETGROUPS);

ArgumentCaptor<Object> argument = ArgumentCaptor.forClass(Object.class);
verify(jsonTool).serialize(argument.capture());

return (Map<String, Object>) argument.getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.web;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.velocity.tools.generic.MathTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.security.script.SecurityScriptServiceComponentList;
import org.xwiki.template.TemplateManager;
import org.xwiki.test.page.PageTest;
import org.xwiki.velocity.tools.JSONTool;

import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.plugin.rightsmanager.RightsManagerPlugin;
import com.xpn.xwiki.user.api.XWikiGroupService;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
* Test the {@code getusers.vm} template. Assert that the returned results are well-formed.
*
* @version $Id$
* @since 13.9RC1
* @since 13.4.4
*/
@SecurityScriptServiceComponentList
class GetusersPageTest extends PageTest
{
private static final String GETUSERS = "getusers.vm";

private TemplateManager templateManager;

private XWikiGroupService groupService;

private ContextualAuthorizationManager contextualAuthorizationManager;

@BeforeEach
void setUp() throws Exception
{
this.templateManager = this.oldcore.getMocker().getInstance(TemplateManager.class);

// Enable the Rights Manager plugin.
this.oldcore.getSpyXWiki().getPluginManager().addPlugin("rightsmanager", RightsManagerPlugin.class.getName(),
this.oldcore.getXWikiContext());

this.groupService = this.context.getWiki().getGroupService(this.context);

this.contextualAuthorizationManager = this.componentManager.getInstance(ContextualAuthorizationManager.class);

registerVelocityTool("numbertool", new NumberTool());
registerVelocityTool("mathtool", new MathTool());
}

@Test
void removeObuscatedResultsWhenTotalrowsLowerThanLimit() throws Exception
{
this.request.put("wiki", "local");
this.request.put("limit", "10");
this.request.put("offset", "1");

DocumentReference user1DocumentReference = new DocumentReference("xwiki", "XWiki", "U1");
DocumentReference user2DocumentReference = new DocumentReference("xwiki", "XWiki", "U2");
when(this.contextualAuthorizationManager.hasAccess(Right.VIEW, user2DocumentReference)).thenReturn(false);
XWikiDocument user1 = this.xwiki.getDocument(user1DocumentReference, this.context);
XWikiDocument user2 = this.xwiki.getDocument(user2DocumentReference, this.context);


doReturn(Arrays.asList(user1, user2))
.when(this.groupService)
.getAllMatchedUsers(eq(null), eq(true), eq(10), eq(0), any(), eq(this.context));
when(this.groupService.countAllMatchedUsers(any(), eq(this.context))).thenReturn(2);

Map<String, Object> results = getJsonResultMap();
List<Map<String, Object>> rows = (List<Map<String, Object>>) results.get("rows");
assertEquals(1, rows.size());
assertEquals("XWiki.U1", rows.get(0).get("doc_fullName"));
}

@Test
void nonViewableResultsAreObfuscated() throws Exception
{
this.request.put("wiki", "local");
this.request.put("limit", "2");
this.request.put("offset", "1");

DocumentReference user1DocumentReference = new DocumentReference("xwiki", "XWiki", "U1");
DocumentReference user2DocumentReference = new DocumentReference("xwiki", "XWiki", "U2");
when(this.contextualAuthorizationManager.hasAccess(Right.VIEW, user2DocumentReference)).thenReturn(false);
XWikiDocument user1 = this.xwiki.getDocument(user1DocumentReference, this.context);
XWikiDocument user2 = this.xwiki.getDocument(user2DocumentReference, this.context);


doReturn(Arrays.asList(user1, user2))
.when(this.groupService)
.getAllMatchedUsers(eq(null), eq(true), eq(2), eq(0), any(), eq(this.context));
when(this.groupService.countAllMatchedUsers(any(), eq(this.context))).thenReturn(10);

Map<String, Object> results = getJsonResultMap();
List<Map<String, Object>> rows = (List<Map<String, Object>>) results.get("rows");
assertEquals(2, rows.size());
assertEquals("XWiki.U1", rows.get(0).get("doc_fullName"));
assertTrue((Boolean) rows.get(0).get("doc_viewable"));
assertEquals("obfuscated", rows.get(1).get("doc_fullName"));
assertFalse((Boolean) rows.get(1).get("doc_viewable"));
}

private Map<String, Object> getJsonResultMap() throws Exception
{
JSONTool jsonTool = mock(JSONTool.class);
registerVelocityTool("jsontool", jsonTool);

this.templateManager.render(GETUSERS);

ArgumentCaptor<Object> argument = ArgumentCaptor.forClass(Object.class);
verify(jsonTool).serialize(argument.capture());

return (Map<String, Object>) argument.getValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,21 @@

import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xwiki.test.page.PageTest;
import org.xwiki.velocity.VelocityManager;
import org.xwiki.velocity.tools.EscapeTool;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Integration tests for {@code macros.vm}.
Expand Down Expand Up @@ -95,4 +102,42 @@ void addLivetableLocationFilterWhenFilteringWebHomeAndCustomWhere() throws Excep
+ "$whereQL $whereParams"));
assertEquals("SOMETHING []", writer.toString().trim());
}

@Test
void livetableFilterObfuscatedTotalGreaterThanReturnedRows() throws Exception
{
Map<Object, Object> mapParameter = new HashMap<>();
mapParameter.put("totalrows", 10);
mapParameter.put("returnedrows", 5);
List<String> dummyRows = asList("a", "b", "c");
mapParameter.put("rows", dummyRows);
this.velocityManager.getVelocityContext().put("map", mapParameter);
this.velocityManager.evaluate(new StringWriter(), "livetable",
new StringReader("#livetable_filterObfuscated($map)"));
Map<Object, Object> map = (Map<Object, Object>) this.velocityManager.getVelocityContext().get("map");
assertEquals(10, map.get("totalrows"));
assertEquals(5, map.get("returnedrows"));
assertSame(dummyRows, map.get("rows"));
}

@Test
void livetableFilterObfuscatedTotalLowerThanReturnedRows() throws Exception
{
HashMap<Object, Object> mapParameter = new HashMap<>();
mapParameter.put("totalrows", 2);
mapParameter.put("returnedrows", 2);
List<Map<Object, Object>> dummyRows = asList(
singletonMap("doc_viewable", true),
singletonMap("doc_viewable", false)
);
mapParameter.put("rows", dummyRows);
this.velocityManager.getVelocityContext().put("map", mapParameter);
this.velocityManager.evaluate(new StringWriter(), "livetable",
new StringReader("#livetable_filterObfuscated($map)"));
Map<String, Object> map = (Map<String, Object>) this.velocityManager.getVelocityContext().get("map");
assertEquals(1, map.get("totalrows"));
assertEquals(1, map.get("returnedrows"));
assertEquals(1, ((List<?>) map.get("rows")).size());
assertTrue(((List<Map<String, Boolean>>) map.get("rows")).get(0).get("doc_viewable"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -353,10 +353,26 @@ XWiki.widgets.LiveTable = Class.create({
if (descriptor.type === 'hidden') {
return;
}
// The column's display name to be used when displaying the reponsive version.
// The column's display name to be used when displaying the responsive version.
var displayName = descriptor.displayName || column;
var fieldName = column.replace(/^doc\./, 'doc_');
if (column === '_actions') {
// When a cell is part of a non-viewable row (except for the actions that simply stays empty), we display a
// message indicating that the content is not present because the current user does not have the right to view it.
if (!row['doc_viewable'] && !row[fieldName]) {
const notViewableCellMessage = $services.localization.render('platform.livetable.docNotViewable');
var td = new Element('td', {
'class': [
fieldName,
'link' + (descriptor.link || ''),
'type' + (descriptor.type || '')
].join(' '),
'data-title': displayName
});
if (column !== '_actions') {
td.update(notViewableCellMessage + "<sup>*</sup>");
}
tr.appendChild(td);
} else if (column === '_actions') {
var adminActions = ['admin', 'rename', 'rights'];
var td = new Element('td', {
'class': 'actions',
Expand Down Expand Up @@ -444,9 +460,6 @@ XWiki.widgets.LiveTable = Class.create({
container.innerHTML = row[fieldName] || '';
} else if (row[fieldName] !== undefined && row[fieldName] !== null) {
var text = row[fieldName] + '';
if (fieldName === 'doc_name' && !row['doc_viewable']) {
text += '*';
}
if (showFilterNote && fieldName === 'doc_title' && row['doc_title_raw'] !== undefined) {
container.addClassName('docTitleComputed');
}
Expand Down