CVE-2023-34467: Exposure of Resource to Wrong Sphere (CWE-668)
value: XWiki Platform is a generic wiki platform. Starting in version 3.5-milestone-1 and prior to versions 14.4.8, 14.10.4, and 15.0-rc-1, the mail obfuscation configuration was not fully taken into account. While the mail displayed to the end user was obfuscated, the rest response was also containing the mail unobfuscated and users were able to filter and sort on the unobfuscated, allowing them to infer the mail content. The consequence was the possibility to retrieve the email addresses of all users even when obfuscated. This has been patched in XWiki 14.4.8, 14.10.4, and 15.0-rc-1.
CWE description: The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.
CVSS v3 base score: 7.5
Published: 2023-06-23T17:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: XML
Files changed: xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/pom.xml, xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml, xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java, xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/pom.xml, xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/src/test/java/org/xwiki/mail/script/MailScriptServiceComponentList.java
Commit date: 2023-01-13 16:53:48 +0100
Commit message: XWIKI-20333: Improved filtering and sorting of emails in Livetable

Diff: diff --git a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/pom.xml b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/pom.xml
index 94dd1d7f0fa45..b288369ccd763 100644
--- a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/pom.xml
@@ -60,10 +60,16 @@
       <groupId>org.xwiki.platform</groupId>
       <artifactId>xwiki-platform-security-authorization-script</artifactId>
       <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-mail-script</artifactId>
+      <version>${project.version}</version>
+      <scope>runtime</scope>
+    </dependency>
     <!-- Test Dependencies -->
     <dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>javax.servlet-api</artifactId>
       <scope>test</scope>
@@ -86,7 +92,15 @@
       <artifactId>xwiki-platform-security-authorization-script</artifactId>
       <version>${project.version}</version>
       <type>test-jar</type>
       <scope>test</scope>
     </dependency>
+    <!-- Mail Script Service Component List for the Page Tests -->
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-mail-script</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml
index c2162cafe9831..066634d25dbc7 100644
--- a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml
+++ b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/main/resources/XWiki/LiveTableResultsMacros.xml
@@ -74,11 +74,14 @@
   #set($tablelist = [])
   #foreach($colname in $collist)
     ## If a classname is defined and the class field corresponding to the column name,
     ## we check the type of the field and skip it if it's Password.
     #if ($className != '' &amp;&amp; $class.get($colname))
-      #if ($class.get($colname).classType != 'Password')
+      #set ($isPasswordType = $class.get($colname).classType == 'Password')
+      #set ($isEmailType = $class.get($colname).classType == 'Email')
+      #set ($emailObfuscated = $services.mail.general.shouldObfuscate())
+      #if (!($isPasswordType || ($isEmailType &amp;&amp; $emailObfuscated)))
         #livetable_addColumnToQuery($colname)
       #end
     #else
       #livetable_addColumnToQuery($colname)
     #end
@@ -486,10 +489,12 @@
     #end
     #set($fieldObject = $itemDoc.getFirstObject($colname))
     #set($fieldProperty = $fieldObject.getProperty($colname))
     #if ($fieldProperty.getPropertyClass().classType == 'Password')
       #set($fieldValue = '********')
+    #elseif ($fieldProperty.getPropertyClass().classType == 'Email' &amp;&amp; $services.mail.general.shouldObfuscate())
+      #set ($fieldValue = $services.mail.general.obfuscate("$!fieldProperty.getValue()"))
     #else
       #set($fieldValue = "$!fieldProperty.getValue()")
     #end
     #set($fieldDisplayValue = "$!itemDoc.display($colname, 'view')")
     #if($fieldDisplayValue == '')
diff --git a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java
index 64d7cc9c12713..b9f8c8e906174 100644
--- a/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java
+++ b/xwiki-platform-core/xwiki-platform-livetable/xwiki-platform-livetable-ui/src/test/java/org/xwiki/livetable/LiveTableResultsTest.java
@@ -22,36 +22,46 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import org.apache.commons.lang3.StringUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.xwiki.mail.GeneralMailConfiguration;
+import org.xwiki.mail.MailSender;
+import org.xwiki.mail.script.MailScriptServiceComponentList;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.model.script.ModelScriptService;
 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.junit5.mockito.MockComponent;
 import org.xwiki.test.page.PageTest;
 import org.xwiki.test.page.XWikiSyntax20ComponentList;
 import org.xwiki.velocity.tools.JSONTool;
 
 import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
 import com.xpn.xwiki.objects.classes.BaseClass;
 import com.xpn.xwiki.objects.classes.StaticListClass;
 import com.xpn.xwiki.plugin.tag.TagPluginApi;
 
 import static java.util.Collections.emptyList;
+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;
@@ -61,18 +71,20 @@
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.xwiki.rendering.syntax.Syntax.XWIKI_2_1;
 
 /**
  * Unit tests for the {@code LiveTableResults} page.
- * 
+ *
  * @version $Id$
  */
 @XWikiSyntax20ComponentList
 @SecurityScriptServiceComponentList
+@MailScriptServiceComponentList
 @ComponentList({
     ModelScriptService.class
 })
 class LiveTableResultsTest extends PageTest
 {
@@ -81,10 +93,13 @@ class LiveTableResultsTest extends PageTest
     private Map<String, Object> results;
 
     @Mock
     private ScriptQuery query;
 
+    @MockComponent
+    private GeneralMailConfiguration generalMailConfiguration;
+
     @BeforeEach
     @SuppressWarnings("deprecation")
     public void setUp() throws Exception
     {
         // The LiveTableResultsMacros page expects that the HTTP query is done with the "get" action and asking for
@@ -101,10 +116,14 @@ public void setUp() throws Exception
         // 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));
 
         loadPage(new DocumentReference("xwiki", "XWiki", "LiveTableResultsMacros"));
+
+        // Mock this component as we are not interested in mail sending in this test suite.
+        this.componentManager.registerMockComponent(MailSender.class);
+        this.componentManager.registerMockComponent(ScriptService.class, "mail.storage");
     }
 
     @Test
     void plainPageResults() throws Exception
     {
@@ -529,10 +548,103 @@ void cleanupAccessToPasswordFields() throws Exception
             + "where obj.name=doc.fullName "
             + "and obj.className = :className "
             + "and doc.fullName not in (:classTemplate1, :classTemplate2)  ");
     }
 
+    private static Stream<Arguments> provideObfuscateEmails()
+    {
+        return Stream.of(
+            Arguments.of(
+                true,
+                ", BaseObject as obj , StringProperty as prop_name  "
+                    + "where obj.name=doc.fullName "
+                    + "and obj.className = :className "
+                    + "and doc.fullName not in (:classTemplate1, :classTemplate2)  "
+                    + "and obj.id = prop_name.id.id "
+                    + "and prop_name.id.name = :prop_name_id_name "
+                    + "and (upper(prop_name.value) like upper(:prop_name_value_1)) ",
+                Map.of(
+                    "className", "Space.MyClass",
+                    "classTemplate1", "Space.MyClassTemplate",
+                    "classTemplate2", "Space.MyTemplate",
+                    "prop_name_id_name", "name",
+                    "prop_name_value_1", "%filtername%"
+                ),
+                "t...@mail.com",
+                "t...@mail.com"
+            ),
+            Arguments.of(
+                false,
+                ", BaseObject as obj , StringProperty as prop_mail, StringProperty as prop_name  "
+                    + "where obj.name=doc.fullName "
+                    + "and obj.className = :className "
+                    + "and doc.fullName not in (:classTemplate1, :classTemplate2)  "
+                    + "and obj.id = prop_mail.id.id "
+                    + "and prop_mail.id.name = :prop_mail_id_name "
+                    + "and (upper(prop_mail.value) like upper(:prop_mail_value_1)) "
+                    + "and obj.id = prop_name.id.id "
+                    + "and prop_name.id.name = :prop_name_id_name "
+                    + "and (upper(prop_name.value) like upper(:prop_name_value_1)) ",
+                Map.of(
+                    "className", "Space.MyClass",
+                    "classTemplate1", "Space.MyClassTemplate",
+                    "classTemplate2", "Space.MyTemplate",
+                    "prop_mail_id_name", "mail",
+                    "prop_mail_value_1", "%filtermail%",
+                    "prop_name_id_name", "name",
+                    "prop_name_value_1", "%filtername%"),
+                "<a href=\"mailto:test@mail.com\">test@mail.com</a>",
+                "test@mail.com"
+            )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("provideObfuscateEmails")
+    void obfuscateEmails(boolean obfuscate, String expectedHql, Map<String, Object> expectedBindValues,
+        String expectedMail, String expectedMailValue) throws Exception
+    {
+        // TODO: We mock the mail configuration as it relies on document that are loaded through xar files from external
+        //  modules, which is currently not possible (would it be loaded using a mandatory document initializer, it 
+        //  would work though).
+        when(this.generalMailConfiguration.shouldObfuscate()).thenReturn(obfuscate);
+
+        DocumentReference myClassReference = new DocumentReference("xwiki", "Space", "MyClass");
+        XWikiDocument xClassDocument = new XWikiDocument(myClassReference);
+        xClassDocument.getXClass().addEmailField("mail", "Email", 100);
+        xClassDocument.getXClass().addTextField("name", "Name", 100);
+        this.xwiki.saveDocument(xClassDocument, this.context);
+
+        XWikiDocument xObjectDocument = new XWikiDocument(new DocumentReference("xwiki", "Space", "MyObject"));
+        xObjectDocument.setSyntax(XWIKI_2_1);
+        BaseObject baseObject = xObjectDocument.newXObject(myClassReference, this.context);
+        baseObject.set("mail", "test@mail.com", this.context);
+        baseObject.set("name", "testName", this.context);
+        this.xwiki.saveDocument(xObjectDocument, this.context);
+
+        setColumns("mail,name");
+        setClassName("Space.MyClass");
+        setFilter("mail", "filtermail");
+        setFilter("name", "filtername");
+
+        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);
+        when(this.query.execute()).thenReturn(singletonList("Space.MyObject"));
+
+        renderPage();
+
+        List<Map<String, Object>> rows = getRows();
+        assertEquals(expectedMail, StringUtils.trim((String) rows.get(0).get("mail")));
+        assertEquals(expectedMailValue, rows.get(0).get("mail_value"));
+
+        verify(this.queryService).hql(expectedHql);
+        verify(this.query).bindValues(expectedBindValues);
+    }
+
     //
     // Helper methods
     //
 
     @SuppressWarnings("unchecked")
diff --git a/xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/pom.xml b/xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/pom.xml
index dd636a7405a33..03e0bedcfc2d2 100644
--- a/xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/pom.xml
@@ -58,6 +58,27 @@
       <artifactId>xwiki-commons-tool-test-component</artifactId>
       <version>${commons.version}</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
+  <build>
+    <plugins>
+      <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>**/MailScriptServiceComponentList.class</include>
+              </includes>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
 </project>
diff --git a/xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/src/test/java/org/xwiki/mail/script/MailScriptServiceComponentList.java b/xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/src/test/java/org/xwiki/mail/script/MailScriptServiceComponentList.java
new file mode 100644
index 0000000000000..d162e29c92ae7
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-mail/xwiki-platform-mail-script/src/test/java/org/xwiki/mail/script/MailScriptServiceComponentList.java
@@ -0,0 +1,69 @@
+/*
+ * 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.mail.script;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.xwiki.mail.internal.DefaultEmailAddressObfuscator;
+import org.xwiki.mail.internal.DefaultSessionFactory;
+import org.xwiki.mail.internal.InternetAddressConverter;
+import org.xwiki.mail.internal.configuration.DefaultMailSenderConfiguration;
+import org.xwiki.mail.internal.configuration.GeneralMailConfigClassDocumentConfigurationSource;
+import org.xwiki.mail.internal.configuration.MainWikiGeneralMailConfigClassDocumentConfigurationSource;
+import org.xwiki.mail.internal.configuration.MainWikiSendMailConfigClassDocumentConfigurationSource;
+import org.xwiki.mail.internal.configuration.SendMailConfigClassDocumentConfigurationSource;
+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 components list for {@link MailScriptService}.
+ *
+ * @version $Id$
+ * @since 15.0RC1
+ * @since 14.4.8
+ * @since 14.10.3
+ */
+@Documented
+@Retention(RUNTIME)
+@Target({ TYPE, METHOD, ANNOTATION_TYPE })
+@ComponentList({
+    MailScriptService.class,
+    MailSenderScriptService.class,
+    DefaultSessionFactory.class,
+    DefaultMailSenderConfiguration.class,
+    SendMailConfigClassDocumentConfigurationSource.class,
+    MainWikiSendMailConfigClassDocumentConfigurationSource.class,
+    GeneralMailConfigClassDocumentConfigurationSource.class,
+    MainWikiGeneralMailConfigClassDocumentConfigurationSource.class,
+    DefaultEmailAddressObfuscator.class,
+    GeneralMailScriptService.class,
+    InternetAddressConverter.class
+})
+@Inherited
+public @interface MailScriptServiceComponentList
+{
+}

Vulnerable code:       #if ($class.get($colname).classType != 'Password')
 * 
Fixed code:     <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-mail-script</artifactId>
      <version>${project.version}</version>
      <scope>runtime</scope>
    </dependency>
    <!-- Mail Script Service Component List for the Page Tests -->
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-mail-script</artifactId>
      <version>${project.version}</version>
      <type>test-jar</type>
      <scope>test</scope>
    </dependency>
      #set ($isPasswordType = $class.get($colname).classType == 'Password')
      #set ($isEmailType = $class.get($colname).classType == 'Email')
      #set ($emailObfuscated = $services.mail.general.shouldObfuscate())
      #if (!($isPasswordType || ($isEmailType &amp;&amp; $emailObfuscated)))
    #elseif ($fieldProperty.getPropertyClass().classType == 'Email' &amp;&amp; $services.mail.general.shouldObfuscate())
      #set ($fieldValue = $services.mail.general.obfuscate("$!fieldProperty.getValue()"))
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.xwiki.mail.GeneralMailConfiguration;
import org.xwiki.mail.MailSender;
import org.xwiki.mail.script.MailScriptServiceComponentList;
import org.xwiki.test.junit5.mockito.MockComponent;
import com.xpn.xwiki.objects.BaseObject;
import static java.util.Collections.singletonList;
import static org.xwiki.rendering.syntax.Syntax.XWIKI_2_1;
 *
@MailScriptServiceComponentList
    @MockComponent
    private GeneralMailConfiguration generalMailConfiguration;


        // Mock this component as we are not interested in mail sending in this test suite.
        this.componentManager.registerMockComponent(MailSender.class);
        this.componentManager.registerMockComponent(ScriptService.class, "mail.storage");
    private static Stream<Arguments> provideObfuscateEmails()
    {
        return Stream.of(
            Arguments.of(
                true,
                ", BaseObject as obj , StringProperty as prop_name  "
                    + "where obj.name=doc.fullName "
                    + "and obj.className = :className "
                    + "and doc.fullName not in (:classTemplate1, :classTemplate2)  "
                    + "and obj.id = prop_name.id.id "
                    + "and prop_name.id.name = :prop_name_id_name "
                    + "and (upper(prop_name.value) like upper(:prop_name_value_1)) ",
                Map.of(
                    "className", "Space.MyClass",
                    "classTemplate1", "Space.MyClassTemplate",
                    "classTemplate2", "Space.MyTemplate",
                    "prop_name_id_name", "name",
                    "prop_name_value_1", "%filtername%"
                ),
                "t...@mail.com",
                "t...@mail.com"
            ),
            Arguments.of(
                false,
                ", BaseObject as obj , StringProperty as prop_mail, StringProperty as prop_name  "
                    + "where obj.name=doc.fullName "
                    + "and obj.className = :className "
                    + "and doc.fullName not in (:classTemplate1, :classTemplate2)  "
                    + "and obj.id = prop_mail.id.id "
                    + "and prop_mail.id.name = :prop_mail_id_name "
                    + "and (upper(prop_mail.value) like upper(:prop_mail_value_1)) "
                    + "and obj.id = prop_name.id.id "
                    + "and prop_name.id.name = :prop_name_id_name "
                    + "and (upper(prop_name.value) like upper(:prop_name_value_1)) ",
                Map.of(
                    "className", "Space.MyClass",
                    "classTemplate1", "Space.MyClassTemplate",
                    "classTemplate2", "Space.MyTemplate",
                    "prop_mail_id_name", "mail",
                    "prop_mail_value_1", "%filtermail%",
                    "prop_name_id_name", "name",
                    "prop_name_value_1", "%filtername%"),
                "<a href=\"mailto:test@mail.com\">test@mail.com</a>",
                "test@mail.com"
            )
        );
    }

    @ParameterizedTest
    @MethodSource("provideObfuscateEmails")
    void obfuscateEmails(boolean obfuscate, String expectedHql, Map<String, Object> expectedBindValues,
        String expectedMail, String expectedMailValue) throws Exception
    {
        // TODO: We mock the mail configuration as it relies on document that are loaded through xar files from external
        //  modules, which is currently not possible (would it be loaded using a mandatory document initializer, it 
        //  would work though).
        when(this.generalMailConfiguration.shouldObfuscate()).thenReturn(obfuscate);

        DocumentReference myClassReference = new DocumentReference("xwiki", "Space", "MyClass");
        XWikiDocument xClassDocument = new XWikiDocument(myClassReference);
        xClassDocument.getXClass().addEmailField("mail", "Email", 100);
        xClassDocument.getXClass().addTextField("name", "Name", 100);
        this.xwiki.saveDocument(xClassDocument, this.context);

        XWikiDocument xObjectDocument = new XWikiDocument(new DocumentReference("xwiki", "Space", "MyObject"));
        xObjectDocument.setSyntax(XWIKI_2_1);
        BaseObject baseObject = xObjectDocument.newXObject(myClassReference, this.context);
        baseObject.set("mail", "test@mail.com", this.context);
        baseObject.set("name", "testName", this.context);
        this.xwiki.saveDocument(xObjectDocument, this.context);

        setColumns("mail,name");
        setClassName("Space.MyClass");
        setFilter("mail", "filtermail");
        setFilter("name", "filtername");

        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);
        when(this.query.execute()).thenReturn(singletonList("Space.MyObject"));

        renderPage();

        List<Map<String, Object>> rows = getRows();
        assertEquals(expectedMail, StringUtils.trim((String) rows.get(0).get("mail")));
        assertEquals(expectedMailValue, rows.get(0).get("mail_value"));

        verify(this.queryService).hql(expectedHql);
        verify(this.query).bindValues(expectedBindValues);
    }

  <build>
    <plugins>
      <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>**/MailScriptServiceComponentList.class</include>
              </includes>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
/*
 * 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.mail.script;

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

import org.xwiki.mail.internal.DefaultEmailAddressObfuscator;
import org.xwiki.mail.internal.DefaultSessionFactory;
import org.xwiki.mail.internal.InternetAddressConverter;
import org.xwiki.mail.internal.configuration.DefaultMailSenderConfiguration;
import org.xwiki.mail.internal.configuration.GeneralMailConfigClassDocumentConfigurationSource;
import org.xwiki.mail.internal.configuration.MainWikiGeneralMailConfigClassDocumentConfigurationSource;
import org.xwiki.mail.internal.configuration.MainWikiSendMailConfigClassDocumentConfigurationSource;
import org.xwiki.mail.internal.configuration.SendMailConfigClassDocumentConfigurationSource;
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 components list for {@link MailScriptService}.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.3
 */
@Documented
@Retention(RUNTIME)
@Target({ TYPE, METHOD, ANNOTATION_TYPE })
@ComponentList({
    MailScriptService.class,
    MailSenderScriptService.class,
    DefaultSessionFactory.class,
    DefaultMailSenderConfiguration.class,
    SendMailConfigClassDocumentConfigurationSource.class,
    MainWikiSendMailConfigClassDocumentConfigurationSource.class,
    GeneralMailConfigClassDocumentConfigurationSource.class,
    MainWikiGeneralMailConfigClassDocumentConfigurationSource.class,
    DefaultEmailAddressObfuscator.class,
    GeneralMailScriptService.class,
    InternetAddressConverter.class
})
@Inherited
public @interface MailScriptServiceComponentList
{
}