CVE-2023-35150 (Java, published 2023-06-23T17:15Z)
Description: value: XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. Starting in version 2.40m-2 and prior to versions 14.4.8, 14.10.4, and 15.0, any user with view rights on any document can execute code with programming rights, leading to remote code execution by crafting an url with a dangerous payload. The problem has been patched in XWiki 15.0, 14.10.4 and 14.4.8.
Severity: nan (CVSS v3 base score: 8)
Weakness: CWE-94 \u2014 Improper Control of Generation of Code ('Code Injection')
The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
Repository: https://github.com/xwiki/xwiki-platform
Files changed:
xwiki-platform-core/pom.xml
xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/main/java/org/xwiki/extension/xar/internal/DefaultXARExtensionIndex.java
xwiki-platform-core/xwiki-platform-extension/xwiki-platform-extension-handlers/xwiki-platform-extension-handler-xar/src/main/resources/META-INF/components.txt
xwiki-platform-core/xwiki-platform-index/pom.xml
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-api/pom.xml
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-api/src/main/java/org/xwiki/index/TaskConsumerScriptService.java
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-api/src/main/resources/META-INF/components.txt
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-api/src/test/java/org/xwiki/index/TaskConsumerScriptServiceTest.java
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-default/pom.xml
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-default/src/main/java/org/xwiki/index/internal/TaskApplicationReadyListener.java
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-default/src/main/java/org/xwiki/index/migration/R140300000XWIKI19614DataMigration.java
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-default/src/main/resources/META-INF/components.txt
xwiki-platform-core/xwiki-platform-index/xwiki-platform-index-default/src/test/java/org/xwiki/index/migration/R140300000XWIKI19614DataMigrationTest.java
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/main/resources/Invitation/InvitationCommon.xml
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/main/resources/Invitation/InvitationConfig.xml
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/main/resources/Invitation/InvitationGuestActions.xml
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/main/resources/Invitation/InvitationMailClass.xml
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/main/resources/Invitation/InvitationMemberActions.xml
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/main/resources/Invitation/InvitationMembersCommon.xml
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/test/java/org/xwiki/invitation/InvitationCommonPageTest.java
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/test/java/org/xwiki/invitation/InvitationConfigPageTest.java
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/test/java/org/xwiki/invitation/InvitationGuestActionsPageTest.java
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/test/java/org/xwiki/invitation/InvitationMailClassPageTest.java
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/test/java/org/xwiki/invitation/InvitationMemberActionsPageTest.java
xwiki-platform-core/xwiki-platform-invitation/xwiki-platform-invitation-ui/src/test/java/org/xwiki/invitation/InvitationMembersCommonPageTest.java
xwiki-platform-core/xwiki-platform-minimaldependencies/pom.xml
xwiki-platform-core/xwiki-platform-oldcore/pom.xml
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/internal/extension/XARExtensionIndex.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/internal/migration/AbstractDocumentsMigration.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/internal/migration/InvitationInternalDocumentParameterEscapingFixer.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/internal/migration/InvitationInternalDocumentParameterEscapingTaskConsumer.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/org/xwiki/internal/migration/R150000000XWIKI20285DataMigration.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt
xwiki-platform-core/xwiki-platform-oldcore/src/test/java/org/xwiki/internal/migration/InvitationInternalDocumentParameterEscapingFixerTest.java
xwiki-platform-tools/xwiki-platform-tool-packager-plugin/pom.xml
Commit message: XWIKI-20285: Improve escaping of the Invitation Application

- Introduce an internal role (DefaultXARExtensionIndex) to know if a document is provided by an extension
- Split index-api to index-api/index-default to be able to use the api in oldcore
- Fix the escaping in invitation-ui
- Provide an automatic fix during upgrade for potential escaping issues in generated code


Vulnerable code:
    <xwiki.jacoco.instructionRatio>0.85</xwiki.jacoco.instructionRatio>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-query-manager</artifactId>
      <version>${project.version}</version>
    </dependency>
    <!-- Required to have access to XWikiDocumentIndexingTask. -->
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-oldcore</artifactId>
      <version>${project.version}</version>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <scope>test</scope>
    </dependency>
  <build>
    <plugins>
      <plugin>
        <!-- Apply the Checkstyle configurations defined in the top level pom.xml file -->
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <executions>
          <execution>
            <!-- Specify the "default" execution id so that the "blocker" one is always executed -->
            <id>default</id>
            <configuration>
              <failsOnError>true</failsOnError>
              <suppressionsLocation>${basedir}/src/checkstyle/checkstyle-suppressions.xml</suppressionsLocation>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
import javax.inject.Provider;

import com.xpn.xwiki.XWikiContext;
    private Provider<XWikiContext> xcontextProvider;
        return this.taskManager.getQueueSizePerType(this.xcontextProvider.get().getWikiId());
org.xwiki.index.internal.DefaultTasksManager
org.xwiki.index.internal.TasksStore
org.xwiki.index.internal.TaskExecutor
org.xwiki.index.internal.TaskApplicationReadyListener
org.xwiki.index.internal.DefaultLinksTaskConsumer
org.xwiki.index.internal.listener.LinksUpdateListener
org.xwiki.index.migration.R140300000XWIKI19614DataMigration
org.xwiki.index.migration.R140300001XWIKI19571DataMigration
import javax.inject.Provider;

import org.mockito.Mock;

import com.xpn.xwiki.XWikiContext;
    private Provider<XWikiContext> xcontextProvider;
    @Mock
    private XWikiContext xcontext;
        when(this.xcontextProvider.get()).thenReturn(this.xcontext);
        when(this.xcontext.getWikiId()).thenReturn("xwiki");
import org.xwiki.component.phase.InitializationException;
    public void initialize() throws InitializationException
import javax.inject.Inject;
import javax.inject.Provider;
import org.xwiki.index.TaskManager;
import org.xwiki.query.QueryManager;
import com.xpn.xwiki.store.migration.hibernate.HibernateDataMigration;
// TODO: Implement DataMigration once XWIKI-19399 is fixed.
public class R140300000XWIKI19614DataMigration implements HibernateDataMigration
    @Inject
    private QueryManager queryManager;

    @Inject
    private TaskManager taskManager;

    @Inject
    private Provider<XWikiContext> contextProvider;

    public void migrate() throws DataMigrationException
        XWikiContext context = this.contextProvider.get();
        // No need to migrate if the wiki does not support backlinks.
            String wikiId = context.getWikiId();
                List<Long> ids =
                    this.queryManager.createQuery("SELECT doc.id FROM XWikiDocument doc", Query.HQL).setWiki(wikiId)
                        .execute();
                for (Long id : ids) {
                    this.taskManager.addTask(wikiId, id, LINKS_TASK_TYPE);
                }
                    String.format("Failed retrieve the list of all the documents for wiki [%s].", wikiId), e);
    public boolean shouldExecute(XWikiDBVersion startupVersion)
        return true;
    public String getPreHibernateLiquibaseChangeLog()
        // TODO: Remove once XWIKI-19399 is fixed.
        return null;
    }

    @Override
    public String getLiquibaseChangeLog()
    {
        // TODO: Remove once XWIKI-19399 is fixed.
        return null;
import javax.inject.Provider;

    @MockComponent
    private Provider<XWikiContext> contextProvider;
        when(this.contextProvider.get()).thenReturn(this.context);
        when(this.queryManager.createQuery("SELECT doc.id FROM XWikiDocument doc",
        when(this.query.execute()).thenReturn(List.of(42L, 43L));
        verify(this.taskManager).addTask("wiki1", 42L,  "links");
        assertEquals("Failed retrieve the list of all the documents for wiki [wiki1].", queryException.getMessage());
        assertEquals(QueryException.class, queryException.getCause().getClass());
  {{info}}$services.localization.render('xe.invitation.internalDocument', ["${doc.getSpace()}.WebHome"]){{/info}}
      #set($configDocContent = '{{velo' + 'city}}{{info}}$services.localization.render(''xe.invitation.internalDocument'', ["'
                               + "$!config.get('mainPage')" + '"]){{/info}}{{/velo' + 'city}}')
    #set($emailContainerContent = '{{velo' + 'city}}{{info}}$services.localization.render(''xe.invitation.internalDocument'', ["'
                                  + "$config.get('emailContainer')" + '"]){{/info}}{{/velo' + 'city}}')
<xwikidoc version="1.4" reference="Invitation.InvitationConfig" locale="">
  <syntaxId>xwiki/2.0</syntaxId>
<xwikidoc version="1.3" reference="Invitation.InvitationGuestActions" locale="">
  <syntaxId>xwiki/2.0</syntaxId>
  <content>{{include reference="Invitation.InvitationCommon" /}}
  {{info}}$services.localization.render('xe.invitation.internalDocument', ["${doc.getSpace()}.WebHome"]){{/info}}
<xwikidoc version="1.3" reference="Invitation.InvitationMailClass" locale="">
  <syntaxId>xwiki/2.0</syntaxId>
  <content>{{velocity}}{{info}}$services.localization.render('xe.invitation.internalDocument', ["${doc.getSpace()}.WebHome"]){{/info}}{{/velocity}}</content>
      <size>30</size>
<xwikidoc version="1.3" reference="Invitation.InvitationMemberActions" locale="">
  <syntaxId>xwiki/2.0</syntaxId>
  <content>{{include reference="Invitation.InvitationCommon" /}}
{{include reference="Invitation.InvitationMembersCommon" /}}
    {{info}}$services.localization.render('xe.invitation.internalDocument', ["${doc.getSpace()}.WebHome"]){{/info}}
<xwikidoc version="1.3" reference="Invitation.InvitationMembersCommon" locale="">
  <syntaxId>xwiki/2.0</syntaxId>
  {{info}}$services.localization.render('xe.invitation.internalDocument', ["${doc.getSpace()}.WebHome"]){{/info}}
            new DocumentReference("xwiki", "<script>console.log</script>", "InvitationCommon"), this.context));
        assertEquals("xe.invitation.internalDocument [<script>console\\.log</script>.WebHome]",
      <artifactId>xwiki-platform-index-api</artifactId>

Fixed code:
            <revapi.differences>
              <justification>Moved to xwiki-platform-index-default</justification>
              <criticality>allowed</criticality>
              <differences>
                <item>
                  <ignore>true</ignore>
                  <code>java.class.removed</code>
                  <old>class org.xwiki.index.migration.R140300000XWIKI19614DataMigration</old>
                </item>
                <item>
                  <ignore>true</ignore>
                  <code>java.class.removed</code>
                  <old>class org.xwiki.index.migration.R140300001XWIKI19571DataMigration</old>
                </item>
              </differences>
            </revapi.differences>
/*
 * 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.extension.xar.internal;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.xwiki.component.annotation.Component;
import org.xwiki.extension.repository.InstalledExtensionRepository;
import org.xwiki.extension.xar.internal.handler.XarExtensionHandler;
import org.xwiki.extension.xar.internal.repository.XarInstalledExtensionRepository;
import org.xwiki.internal.extension.XARExtensionIndex;
import org.xwiki.model.reference.DocumentReference;

/**
 * Default implementation of {@link XARExtensionIndex}. Uses {@link XarInstalledExtensionRepository} to implement the
 * operations.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@Component
@Singleton
public class DefaultXARExtensionIndex implements XARExtensionIndex
{
    @Inject
    @Named(XarExtensionHandler.TYPE)
    private InstalledExtensionRepository installedXARs;

    @Override
    public boolean isExtensionDocument(DocumentReference documentReference)
    {
        return !((XarInstalledExtensionRepository) this.installedXARs).getXarInstalledExtensions(documentReference)
            .isEmpty();
    }
}
org.xwiki.extension.xar.internal.DefaultXARExtensionIndex
    <module>xwiki-platform-index-default</module>
    <xwiki.jacoco.instructionRatio>0.04</xwiki.jacoco.instructionRatio>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-stability</artifactId>
      <version>${commons.version}</version>
import org.xwiki.wiki.descriptor.WikiDescriptorManager;
    private WikiDescriptorManager wikiDescriptorManager;
        return this.taskManager.getQueueSizePerType(this.wikiDescriptorManager.getCurrentWikiId());
import org.xwiki.wiki.descriptor.WikiDescriptorManager;
    private WikiDescriptorManager wikiDescriptorManager;
        when(this.wikiDescriptorManager.getCurrentWikiId()).thenReturn("xwiki");
<?xml version="1.0" encoding="UTF-8"?>

<!--
 * 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.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.xwiki.platform</groupId>
    <artifactId>xwiki-platform-index</artifactId>
    <version>15.1-SNAPSHOT</version>
  </parent>
  <artifactId>xwiki-platform-index-default</artifactId>
  <name>XWiki Platform - Index - Default</name>
  <description>Default implementation of the API to queue asynchronous tasks dedicated to the analysis of wiki pages.</description>
  <packaging>jar</packaging>
  <properties>
    <xwiki.jacoco.instructionRatio>0.84</xwiki.jacoco.instructionRatio>
    <!-- Name to display by the Extension Manager -->
    <xwiki.extension.name>Default implementation of the Index API</xwiki.extension.name>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-index-api</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-component-api</artifactId>
      <version>${commons.version}</version>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-oldcore</artifactId>
      <version>${project.version}</version>
    </dependency>
    <!-- Test dependencies. -->
    <dependency>
      <groupId>org.xwiki.commons</groupId>
      <artifactId>xwiki-commons-tool-test-component</artifactId>
      <version>${commons.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <!-- Apply the Checkstyle configurations defined in the top level pom.xml file -->
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <executions>
          <execution>
            <!-- Specify the "default" execution id so that the "blocker" one is always executed -->
            <id>default</id>
            <configuration>
              <failsOnError>true</failsOnError>
              <suppressionsLocation>${basedir}/src/checkstyle/checkstyle-suppressions.xml</suppressionsLocation>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
    public void initialize()
import org.xwiki.internal.migration.AbstractDocumentsMigration;
import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.doc.XWikiDocument;
public class R140300000XWIKI19614DataMigration extends AbstractDocumentsMigration
    protected String getTaskType()
    {
        return LINKS_TASK_TYPE;
    }

    @Override
    protected List<String> selectDocuments() throws DataMigrationException
        List<String> documents;
        XWikiContext context = getXWikiContext();
        XWiki wiki = getXWikiContext().getWiki();
                documents = wiki.getStore().getQueryManager()
                    .createQuery("SELECT doc.fullName FROM XWikiDocument doc", Query.HQL)
                    .setWiki(context.getWikiId())
                    .execute();
                    String.format("Failed retrieve the list of all the documents for wiki [%s].", wiki.getName()), e);
        } else {
            documents = List.of();
        return documents;
    protected void logBeforeQueuingTask(XWikiDocument document)
        // No logs here as it would be too verbose (all documents of the wiki are queued).
    protected void logBeforeQueuingTasks(List<XWikiDocument> documents)
        XWikiContext context = getXWikiContext();
        if (context.getWiki().hasBacklinks(context)) {
            super.logBeforeQueuingTasks(documents);
        } else {
            this.logger.info("Skipped because backlinks are not supported on [{}]", context.getWikiId());
        }
org.xwiki.index.internal.DefaultTasksManager
org.xwiki.index.internal.TasksStore
org.xwiki.index.internal.TaskExecutor
org.xwiki.index.internal.TaskApplicationReadyListener
org.xwiki.index.internal.DefaultLinksTaskConsumer
org.xwiki.index.internal.listener.LinksUpdateListener
org.xwiki.index.migration.R140300000XWIKI19614DataMigration
org.xwiki.index.migration.R140300001XWIKI19571DataMigration
import org.junit.jupiter.api.extension.RegisterExtension;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.test.LogLevel;
import org.xwiki.test.junit5.LogCaptureExtension;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.store.XWikiStoreInterface;
import ch.qos.logback.classic.Level;

import static org.mockito.Mockito.mock;
    @Mock
    private Execution execution;

    @MockComponent
    private DocumentReferenceResolver<String> resolver;

    @RegisterExtension
    LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.INFO);
        ExecutionContext executionContext = mock(ExecutionContext.class);
        XWikiStoreInterface xWikiStoreInterface = mock(XWikiStoreInterface.class);

        when(this.execution.getContext()).thenReturn(executionContext);
        when(executionContext.getProperty("xwikicontext")).thenReturn(this.context);
        when(this.wiki.getName()).thenReturn("wiki1");
        when(this.wiki.getStore()).thenReturn(xWikiStoreInterface);
        when(xWikiStoreInterface.getQueryManager()).thenReturn(this.queryManager);

        when(this.queryManager.createQuery("SELECT doc.fullName FROM XWikiDocument doc",
        DocumentReference doc42 = new DocumentReference("xwiki", "XWiki", "Doc42");
        DocumentReference doc43 = new DocumentReference("xwiki", "XWiki", "Doc43");
        XWikiDocument xWikiDocument42 = mock(XWikiDocument.class);
        XWikiDocument xWikiDocument43 = mock(XWikiDocument.class);

        when(this.query.execute()).thenReturn(List.of("xwiki.XWiki.Doc42", "xwiki.XWiki.Doc43"));
        when(this.resolver.resolve("xwiki.XWiki.Doc42")).thenReturn(doc42);
        when(this.resolver.resolve("xwiki.XWiki.Doc43")).thenReturn(doc43);
        when(this.wiki.getDocument(doc42, this.context)).thenReturn(xWikiDocument42);
        when(this.wiki.getDocument(doc43, this.context)).thenReturn(xWikiDocument43);
        when(xWikiDocument42.getId()).thenReturn(42L);
        when(xWikiDocument43.getId()).thenReturn(43L);
        verify(this.taskManager).addTask("wiki1", 42L, "links");

        assertEquals("[2] documents queued to task [links]", this.logCapture.getMessage(0));
        assertEquals(Level.INFO, this.logCapture.getLogEvent(0).getLevel());
        assertEquals("Data migration R140300000XWIKI19614 failed", queryException.getMessage());
        assertEquals("Failed retrieve the list of all the documents for wiki [wiki1].",
            queryException.getCause().getMessage());
        assertEquals(DataMigrationException.class, queryException.getCause().getClass());
        assertEquals(QueryException.class, queryException.getCause().getCause().getClass());
        assertEquals("Skipped because backlinks are not supported on [wiki1]", this.logCapture.getMessage(0));
        assertEquals(Level.INFO, this.logCapture.getLogEvent(0).getLevel());
  #set ($linkTarget = "${doc.getSpace()}.WebHome")
  #set ($linkTarget = $services.rendering.escape($linkTarget, 'xwiki/2.1'))
  {{info}}$services.localization.render('xe.invitation.internalDocument', [$linkTarget]){{/info}}
      #set($configDocContent = 
        '{{velocity}}{{info}}$services.localization.render(''xe.invitation.internalDocument'','
          + '[$services.rendering.escape("' + "$!config.get('mainPage')" 
          + '", ''xwiki/2.1'')]){{/info}}{{/velocity}}')
    #set($emailContainerContent = 
      '{{velocity}}{{info}}$services.localization.render(''xe.invitation.internalDocument'','
        + '[$services.rendering.escape("' + "$config.get('emailContainer')" 
        + '", ''xwiki/2.1'')]){{/info}}{{/velocity}}')
<xwikidoc version="1.5" reference="Invitation.InvitationConfig" locale="">
  <syntaxId>xwiki/2.1</syntaxId>
<xwikidoc version="1.5" reference="Invitation.InvitationGuestActions" locale="">
  <syntaxId>xwiki/2.1</syntaxId>
  <content>{{include reference="Invitation.InvitationCommon"/}}
  #set ($linkTarget = "${doc.getSpace()}.WebHome")
  #set ($linkTarget = $services.rendering.escape($linkTarget, 'xwiki/2.1'))
  {{info}}$services.localization.render('xe.invitation.internalDocument', [$linkTarget]){{/info}}
<xwikidoc version="1.5" reference="Invitation.InvitationMailClass" locale="">
  <syntaxId>xwiki/2.1</syntaxId>
  <content>{{velocity}}
  #set ($linkTarget = "${doc.getSpace()}.WebHome")
  #set ($linkTarget = $services.rendering.escape($linkTarget, 'xwiki/2.1'))
  {{info}}$services.localization.render('xe.invitation.internalDocument', [$linkTarget]){{/info}}
{{/velocity}}</content>
      <size>30</size>
<xwikidoc version="1.5" reference="Invitation.InvitationMemberActions" locale="">
  <syntaxId>xwiki/2.1</syntaxId>
  <content>{{include reference="Invitation.InvitationCommon"/}}
{{include reference="Invitation.InvitationMembersCommon"/}}
    #set ($linkTarget = "${doc.getSpace()}.WebHome")
    #set ($linkTarget = $services.rendering.escape($linkTarget, 'xwiki/2.1'))
    {{info}}$services.localization.render('xe.invitation.internalDocument', [$linkTarget]){{/info}}
<xwikidoc version="1.5" reference="Invitation.InvitationMembersCommon" locale="">
  <syntaxId>xwiki/2.1</syntaxId>
  #set ($linkTarget = "${doc.getSpace()}.WebHome")
  #set ($linkTarget = $services.rendering.escape($linkTarget, 'xwiki/2.1'))
  {{info}}$services.localization.render('xe.invitation.internalDocument', [$linkTarget]){{/info}}
            new DocumentReference("xwiki", "]]  {{noscript/}}", "InvitationCommon"), this.context));
        assertEquals("xe.invitation.internalDocument []] {{noscript/}}.WebHome]",
/*
 * 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.invitation;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.rendering.RenderingScriptServiceComponentList;
import org.xwiki.rendering.internal.configuration.DefaultExtendedRenderingConfiguration;
import org.xwiki.rendering.internal.configuration.RenderingConfigClassDocumentConfigurationSource;
import org.xwiki.rendering.internal.macro.message.InfoMessageMacro;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.HTML50ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.test.page.TestNoScriptMacro;
import org.xwiki.test.page.XWikiSyntax21ComponentList;

import com.xpn.xwiki.doc.XWikiDocument;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test of {@code Invitation.InvitationConfig}.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@HTML50ComponentList
@XWikiSyntax21ComponentList
@RenderingScriptServiceComponentList
@ComponentList({
    InfoMessageMacro.class,
    TestNoScriptMacro.class,
    // Start - Required in addition of RenderingScriptServiceComponentList
    DefaultExtendedRenderingConfiguration.class,
    RenderingConfigClassDocumentConfigurationSource.class,
    // End - Required in additional of RenderingScriptServiceComponentList
})
class InvitationConfigPageTest extends PageTest
{
    private static final DocumentReference INVITATION_CONFIG_DOCUMENT_REFERENCE =
        new DocumentReference("xwiki", "Invitation", "InvitationConfig");

    @Test
    void escapeInfoMessageInternalDocumentParameter() throws Exception
    {
        XWikiDocument invitationGuestActionsDocument = loadPage(INVITATION_CONFIG_DOCUMENT_REFERENCE);

        // Set up the current doc in the context so that $doc is bound in scripts
        this.context.setDoc(
            this.xwiki.getDocument(new DocumentReference("xwiki", "]] {{noscript/}}", "Page"), this.context));

        Document document = Jsoup.parse(invitationGuestActionsDocument.getRenderedContent(this.context));
        Element infomessage = document.selectFirst(".infomessage");
        assertEquals("xe.invitation.internalDocument [Invitation.WebHome]", infomessage.text());
    }
}
/*
 * 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.invitation;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.rendering.RenderingScriptServiceComponentList;
import org.xwiki.rendering.internal.configuration.DefaultExtendedRenderingConfiguration;
import org.xwiki.rendering.internal.configuration.RenderingConfigClassDocumentConfigurationSource;
import org.xwiki.rendering.internal.macro.message.InfoMessageMacro;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.HTML50ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.test.page.TestNoScriptMacro;
import org.xwiki.test.page.XWikiSyntax21ComponentList;

import com.xpn.xwiki.doc.XWikiDocument;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test of {@code Invitation.InvitationGuestActions}.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@HTML50ComponentList
@XWikiSyntax21ComponentList
@RenderingScriptServiceComponentList
@ComponentList({
    InfoMessageMacro.class,
    TestNoScriptMacro.class,
    // Start - Required in addition of RenderingScriptServiceComponentList
    DefaultExtendedRenderingConfiguration.class,
    RenderingConfigClassDocumentConfigurationSource.class,
    // End - Required in additional of RenderingScriptServiceComponentList
})
class InvitationGuestActionsPageTest extends PageTest
{
    private static final DocumentReference INVITATION_COMMON_DOCUMENT_REFERENCE =
        new DocumentReference("xwiki", "Invitation", "InvitationCommon");

    private static final DocumentReference INVITATION_GUEST_ACTIONS_DOCUMENT_REFERENCE =
        new DocumentReference("xwiki", "Invitation", "InvitationGuestActions");

    @BeforeEach
    void setUp() throws Exception
    {
        loadPage(INVITATION_COMMON_DOCUMENT_REFERENCE);
    }

    @Test
    void escapeInfoMessageInternalDocumentParameter() throws Exception
    {
        XWikiDocument invitationGuestActionsDocument = loadPage(INVITATION_GUEST_ACTIONS_DOCUMENT_REFERENCE);

        // Set up the current doc in the context so that $doc is bound in scripts
        this.context.setDoc(
            this.xwiki.getDocument(new DocumentReference("xwiki", "]] {{noscript/}}", "Page"), this.context));

        Document document = Jsoup.parse(invitationGuestActionsDocument.getRenderedContent(this.context));
        Element infomessage = document.selectFirst(".infomessage");
        assertEquals("xe.invitation.internalDocument []] {{noscript/}}.WebHome]", infomessage.text());
    }
}
/*
 * 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.invitation;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.rendering.RenderingScriptServiceComponentList;
import org.xwiki.rendering.internal.configuration.DefaultExtendedRenderingConfiguration;
import org.xwiki.rendering.internal.configuration.RenderingConfigClassDocumentConfigurationSource;
import org.xwiki.rendering.internal.macro.message.InfoMessageMacro;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.HTML50ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.test.page.TestNoScriptMacro;
import org.xwiki.test.page.XWikiSyntax21ComponentList;

import com.xpn.xwiki.doc.XWikiDocument;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test of {@code Invitation.InvitationMailClass}.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@HTML50ComponentList
@XWikiSyntax21ComponentList
@RenderingScriptServiceComponentList
@ComponentList({
    InfoMessageMacro.class,
    TestNoScriptMacro.class,
    // Start - Required in addition of RenderingScriptServiceComponentList
    DefaultExtendedRenderingConfiguration.class,
    RenderingConfigClassDocumentConfigurationSource.class,
    // End - Required in additional of RenderingScriptServiceComponentList
})
class InvitationMailClassPageTest extends PageTest
{
    private static final DocumentReference INVITATION_MAIL_CLASS_DOCUMENT_REFERENCE =
        new DocumentReference("xwiki", "Invitation", "InvitationMailClass");

    @Test
    void escapeInfoMessageInternalDocumentParameter() throws Exception
    {
        XWikiDocument invitationGuestActionsDocument = loadPage(INVITATION_MAIL_CLASS_DOCUMENT_REFERENCE);

        // Set up the current doc in the context so that $doc is bound in scripts
        this.context.setDoc(
            this.xwiki.getDocument(new DocumentReference("xwiki", "]] {{noscript/}}", "Page"), this.context));

        Document document = Jsoup.parse(invitationGuestActionsDocument.getRenderedContent(this.context));
        Element infomessage = document.selectFirst(".infomessage");
        assertEquals("xe.invitation.internalDocument []] {{noscript/}}.WebHome]", infomessage.text());
    }
}
/*
 * 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.invitation;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.rendering.RenderingScriptServiceComponentList;
import org.xwiki.rendering.internal.configuration.DefaultExtendedRenderingConfiguration;
import org.xwiki.rendering.internal.configuration.RenderingConfigClassDocumentConfigurationSource;
import org.xwiki.rendering.internal.macro.message.InfoMessageMacro;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.HTML50ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.test.page.TestNoScriptMacro;
import org.xwiki.test.page.XWikiSyntax21ComponentList;

import com.xpn.xwiki.doc.XWikiDocument;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test of {@code Invitation.InvitationMemberActions}.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@HTML50ComponentList
@XWikiSyntax21ComponentList
@RenderingScriptServiceComponentList
@ComponentList({
    InfoMessageMacro.class,
    TestNoScriptMacro.class,
    // Start - Required in addition of RenderingScriptServiceComponentList
    DefaultExtendedRenderingConfiguration.class,
    RenderingConfigClassDocumentConfigurationSource.class,
    // End - Required in additional of RenderingScriptServiceComponentList
})
class InvitationMemberActionsPageTest extends PageTest
{
    private static final DocumentReference INVITATION_COMMON_DOCUMENT_REFERENCE =
        new DocumentReference("xwiki", "Invitation", "InvitationCommon");

    private static final DocumentReference INVITATION_MEMBER_ACTIONS_DOCUMENT_REFERENCE =
        new DocumentReference("xwiki", "Invitation", "InvitationMemberActions");

    @BeforeEach
    void setUp() throws Exception
    {
        loadPage(INVITATION_COMMON_DOCUMENT_REFERENCE);
    }

    @Test
    void escapeInfoMessageInternalDocumentParameter() throws Exception
    {
        XWikiDocument invitationGuestActionsDocument = loadPage(INVITATION_MEMBER_ACTIONS_DOCUMENT_REFERENCE);

        // Set a non-guest user as otherwise the rendering stops early.
        this.context.setUserReference(new DocumentReference("xwiki", "XWiki", "U1"));

        // Set up the current doc in the context so that $doc is bound in scripts
        this.context.setDoc(
            this.xwiki.getDocument(new DocumentReference("xwiki", "]] {{noscript/}}", "Page"), this.context));

        Document document = Jsoup.parse(invitationGuestActionsDocument.getRenderedContent(this.context));
        Element infomessage = document.selectFirst(".infomessage");
        assertEquals("xe.invitation.internalDocument []] {{noscript/}}.WebHome]", infomessage.text());
    }
}
/*
 * 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.invitation;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.jupiter.api.Test;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.rendering.RenderingScriptServiceComponentList;
import org.xwiki.rendering.internal.configuration.DefaultExtendedRenderingConfiguration;
import org.xwiki.rendering.internal.configuration.RenderingConfigClassDocumentConfigurationSource;
import org.xwiki.rendering.internal.macro.message.InfoMessageMacro;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.page.HTML50ComponentList;
import org.xwiki.test.page.PageTest;
import org.xwiki.test.page.TestNoScriptMacro;
import org.xwiki.test.page.XWikiSyntax21ComponentList;

import com.xpn.xwiki.doc.XWikiDocument;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test of {@code Invitation.InvitationMembersCommon}.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@HTML50ComponentList
@XWikiSyntax21ComponentList
@RenderingScriptServiceComponentList
@ComponentList({
    InfoMessageMacro.class,
    TestNoScriptMacro.class,
    // Start - Required in addition of RenderingScriptServiceComponentList
    DefaultExtendedRenderingConfiguration.class,
    RenderingConfigClassDocumentConfigurationSource.class,
    // End - Required in additional of RenderingScriptServiceComponentList
})
class InvitationMembersCommonPageTest extends PageTest
{
    private static final DocumentReference INVITATION_MEMBER_COMMON_DOCUMENT_REFERENCE =
        new DocumentReference("xwiki", "Invitation", "InvitationMembersCommon");

    @Test
    void escapeInfoMessageInternalDocumentParameter() throws Exception
    {
        XWikiDocument invitationGuestActionsDocument = loadPage(INVITATION_MEMBER_COMMON_DOCUMENT_REFERENCE);

        // Set a non-guest user as otherwise the rendering stops early.
        this.context.setUserReference(new DocumentReference("xwiki", "XWiki", "U1"));

        // Set up the current doc in the context so that $doc is bound in scripts
        this.context.setDoc(
            this.xwiki.getDocument(new DocumentReference("xwiki", "]] {{noscript/}}", "InvitationMembersCommon"),
                this.context));

        Document document = Jsoup.parse(invitationGuestActionsDocument.getRenderedContent(this.context));
        assertEquals("xe.invitation.internalDocument []] {{noscript/}}.WebHome]",
            document.selectFirst(".infomessage").text());
    }
}
    <!-- The default document task analysis implementation -->
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-index-default</artifactId>
      <version>${project.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-index-api</artifactId>
      <version>${project.version}</version>
    </dependency>
/*
 * 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.internal.extension;

import org.xwiki.component.annotation.Role;
import org.xwiki.model.reference.DocumentReference;

/**
 * Provides operation to access to information relative to XARs provided by extensions.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@Role
public interface XARExtensionIndex
{
    /**
     * @param documentReference a document reference
     * @return {@code true} if a given document reference is provided by an extension
     */
    boolean isExtensionDocument(DocumentReference documentReference);
}
/*
 * 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.internal.migration;

import java.util.List;
import java.util.Optional;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.xwiki.index.TaskManager;
import org.xwiki.model.reference.DocumentReferenceResolver;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.store.migration.DataMigrationException;
import com.xpn.xwiki.store.migration.hibernate.AbstractHibernateDataMigration;

import static java.util.stream.Collectors.toList;

/**
 * Allow to easily queue a document analysis task on a set documents to migrate. Sub-classes need to implement two
 * methods:
 * <ul>
 *     <li>{@link #selectDocuments()}: return the list of document ids to queue for migration</li>
 *     <li>{@link #getTaskType()}: the type of the task to queue documents to</li>
 * </ul>
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
public abstract class AbstractDocumentsMigration extends AbstractHibernateDataMigration
{
    @Inject
    protected Logger logger;

    @Inject
    protected DocumentReferenceResolver<String> documentReferenceResolver;

    @Inject
    private TaskManager taskManager;

    @Override
    protected void hibernateMigrate() throws DataMigrationException
    {
        List<XWikiDocument> documents = selectDocuments()
            .stream()
            .map(this::convert)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(toList());
        logBeforeQueuingTasks(documents);
        for (XWikiDocument document : documents) {
            logBeforeQueuingTask(document);
            this.taskManager.addTask(this.getXWikiContext().getWikiId(), document.getId(), getTaskType());
        }
    }

    private Optional<XWikiDocument> convert(String documentReference)
    {
        XWikiContext context = getXWikiContext();
        try {
            return Optional.of(
                context.getWiki().getDocument(this.documentReferenceResolver.resolve(documentReference), context));
        } catch (XWikiException e) {
            this.logger.error("Failed to resolve [{}]", documentReference, e);
            return Optional.empty();
        }
    }

    /**
     * @return the id of the task type to queue documents to
     */
    protected abstract String getTaskType();

    /**
     * @return the list of document ids to migrate
     */
    protected abstract List<String> selectDocuments() throws DataMigrationException;

    /**
     * Prints an info log with the number of queued documents and the type of the task.
     *
     * @param documents the full list of documents that will be queued
     */
    protected void logBeforeQueuingTasks(List<XWikiDocument> documents)
    {
        this.logger.info("[{}] documents queued to task [{}]", documents.size(), getTaskType());
    }

    /**
     * Prints an info logs with an individual document and well as its queued task.
     *
     * @param document a individual document that will be queued
     */
    protected void logBeforeQueuingTask(XWikiDocument document)
    {
        this.logger.info("document [{}] queued to task [{}]", document, getTaskType());
    }
}
/*
 * 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.internal.migration;

import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;

import javax.inject.Singleton;

import org.xwiki.component.annotation.Component;
import org.xwiki.rendering.syntax.Syntax;

import static java.util.regex.Matcher.quoteReplacement;

/**
 * Fix a content by looking for localization of {@code xe.invitation.internalDocument} and escaping its parameter. The
 * translation fixed by this method is initially introduced by the invitation application but the fix is localed in
 * oldcore so that the fix is applied on pages even if the invitation application has been uninstalled.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@Component(roles = InvitationInternalDocumentParameterEscapingFixer.class)
@Singleton
public class InvitationInternalDocumentParameterEscapingFixer
{
    private static final Pattern PATTERN = Pattern.compile("(\\{\\{info}}"
        + "\\$services\\.localization\\.render\\('xe\\.invitation\\.internalDocument', \\[)(\"[^\"]+\")(]\\)\\"
        + "{\\{/info}})");

    /**
     * @param content the context to fix
     * @param syntax the syntax of the context to fix (xwiki/2.0 or xwiki/2.1)
     * @return the context with the fix applied, or {@link Optional#empty()} if nothing needs to be fixed
     */
    public Optional<String> fix(String content, Syntax syntax)
    {
        String escapedContent = PATTERN.matcher(content).replaceAll(matchResult -> {
            if (matchResult.group(2).contains("services.rendering.escape")) {
                return matchResult.group();
            } else {
                // Concatenate the various groups of the regex while wrapping the localization argument with a
                // call to the escaping script service.
                String format = String.format("%s$services.rendering.escape(%s, '%s')%s", matchResult.group(1),
                    matchResult.group(2), syntax.toIdString(), matchResult.group(3));
                return quoteReplacement(format);
            }
        });

        if (Objects.equals(escapedContent, content)) {
            return Optional.empty();
        } else {
            return Optional.of(escapedContent);
        }
    }
}
/*
 * 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.internal.migration;

import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.index.IndexException;
import org.xwiki.index.TaskConsumer;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.rendering.syntax.Syntax;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;

/**
 * Apply {@link InvitationInternalDocumentParameterEscapingFixer} on the documents queued by
 * {@link R150000000XWIKI20285DataMigration} if applicable, and log the skipped ones. This translation key is initially
 * introduced by the invitation application but the fix is localed in oldcore so that the fix is applied on pages even
 * if the invitation application has been uninstalled.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@Component
@Singleton
@Named(InvitationInternalDocumentParameterEscapingTaskConsumer.HINT)
public class InvitationInternalDocumentParameterEscapingTaskConsumer implements TaskConsumer
{
    /**
     * This task consumer hint.
     */
    public static final String HINT = "internal-document-parameter-escaping";

    @Inject
    private Logger logger;

    @Inject
    private Provider<XWikiContext> contextProvider;

    @Inject
    private InvitationInternalDocumentParameterEscapingFixer invitationInternalDocumentParameterEscapingFixer;

    @Override
    public void consume(DocumentReference documentReference, String version) throws IndexException
    {
        try {
            XWikiContext context = this.contextProvider.get();
            task(context.getWiki().getDocument(documentReference, context));
        } catch (XWikiException e) {
            throw new IndexException(String.format("Failed to resolve document [%s]", documentReference), e);
        }
    }

    private void task(XWikiDocument document)
    {
        Syntax syntax = document.getSyntax();
        if (List.of(Syntax.XWIKI_2_1, Syntax.XWIKI_2_0).contains(syntax)) {
            try {
                this.invitationInternalDocumentParameterEscapingFixer.fix(document.getContent(), document.getSyntax())
                    .ifPresent(content -> {
                        document.setContent(content);
                        try {
                            XWikiContext context = this.contextProvider.get();
                            context.getWiki().saveDocument(document, "Automatic bad escaping fix.", true, context);
                            this.logger.info("[{}] successfully fixed.", document);
                        } catch (XWikiException e) {
                            this.logger.error("Failed to save document [{}]", document, e);
                        }
                    });
            } catch (Exception e) {
                this.logger.error("Unexpected error while fixing [{}]", document, e);
            }
        } else {
            this.logger.warn(
                "[{}] skipped because escaping for syntax [{}] is not supported. It is advised to review this file.",
                document, syntax);
        }
    }
}
/*
 * 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.internal.migration;

import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.xwiki.component.annotation.Component;
import org.xwiki.internal.extension.XARExtensionIndex;
import org.xwiki.query.QueryException;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.store.migration.DataMigrationException;
import com.xpn.xwiki.store.migration.XWikiDBVersion;

import static org.xwiki.query.Query.XWQL;

/**
 * Search for documents with a known invalid content produced by code generation (i.e., that cannot be fixed by editing
 * a xar file), and apply an automatic escaping fix.
 *
 * @version $Id$
 * @since 15.0RC1
 * @since 14.4.8
 * @since 14.10.4
 */
@Component
@Named("R150000000XWIKI20285")
@Singleton
public class R150000000XWIKI20285DataMigration extends AbstractDocumentsMigration
{
    @Inject
    private XARExtensionIndex installedXARs;

    @Override
    public String getDescription()
    {
        return "Patch the InvitationConfig documents with improper escaping.";
    }

    @Override
    public XWikiDBVersion getVersion()
    {
        return new XWikiDBVersion(150000000);
    }

    @Override
    protected String getTaskType()
    {
        return InvitationInternalDocumentParameterEscapingTaskConsumer.HINT;
    }

    @Override
    protected List<String> selectDocuments() throws DataMigrationException
    {
        XWiki wiki = getXWikiContext().getWiki();
        try {
            // We select potentially impacted documents using like wildcards. This selection might lead to false 
            // positive that wild be filtered out by the more accurate regex in
            // InvitationInternalDocumentParameterEscapingFixer.
            return wiki.getStore().getQueryManager()
                .createQuery("where doc.content "
                    + "like '%{{info}}%services.localization.render%xe.invitation.internalDocument%{{/info}}%'", XWQL)
                .<String>execute()
                .stream()
                // Exclude document that are provided by extensions, because they are fixed using the usual xar upgrade
                // mechanism.
                .filter(documentReference -> !this.installedXARs.isExtensionDocument(
                    this.documentReferenceResolver.resolve(documentReference)))
                .collect(Collectors.toList());
        } catch (QueryException e) {
            throw new DataMigrationException(
                String.format("Failed retrieve the list of all the documents for wiki [%s].", wiki.getName()), e);
        }
    }
}
org.xwiki.internal.migration.R150000000XWIKI20285DataMigration
org.xwiki.internal.migration.InvitationInternalDocumentParameterEscapingFixer
org.xwiki.internal.migration.InvitationInternalDocumentParameterEscapingTaskConsumer
/*
 * 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.internal.migration;

import java.util.Optional;
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.rendering.syntax.Syntax;
import org.xwiki.test.junit5.mockito.ComponentTest;
import org.xwiki.test.junit5.mockito.InjectMockComponents;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test of {@link InvitationInternalDocumentParameterEscapingFixer}.
 *
 * @version $Id$
 */
@ComponentTest
class InvitationInternalDocumentParameterEscapingFixerTest
{
    @InjectMockComponents
    private InvitationInternalDocumentParameterEscapingFixer fixer;

    public static Stream<Arguments> fixSource()
    {
        return Stream.of(
            Arguments.of(
                "nothing to change 1\n"
                    + "{{info}}$services.localization.render('xe.invitation.internalDocument', [$noChange]){{/info}}"
                    + "nothing to change 2\n"
                    + "{{info}}$services.localization.render('xe.invitation.internalDocument', [\"$change\"]){{/info}}"
                    + "nothing to change 3",
                Optional.of(
                    "nothing to change 1\n"
                        + "{{info}}$services.localization.render('xe.invitation.internalDocument', [$noChange]){{/info}}nothing to change 2\n"
                        + "{{info}}$services.localization.render('xe.invitation.internalDocument', [$services.rendering.escape(\"$change\", 'xwiki/2.1')]){{/info}}nothing to change 3"
                )
            ),
            Arguments.of(
                "nothing to change 1\n"
                    + "{{info}}$services.localization.render('xe.invitation.internalDocument', [$noChange]){{/info}}"
                    + "nothing to change 2\n"
                    + "nothing to change 3",
                Optional.empty()
            )
        );
    }

    @ParameterizedTest
    @MethodSource("fixSource")
    void fix(String value, Optional<String> expected)
    {
        assertEquals(expected, this.fixer.fix(value, Syntax.XWIKI_2_1));
    }
}
      <artifactId>xwiki-platform-index-default</artifactId>