CVE-2022-23618 (published 2022-02-09T21:15Z)

XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. In affected versions there is no protection against URL redirection to untrusted sites, in particular some well known parameters (xredirect) can be used to perform url redirections. This problem has been patched in XWiki 12.10.7 and XWiki 13.3RC1. Users are advised to update. There are no known workarounds for this issue.

Severity: MEDIUM
CVSS v2 base score: 5.8 (AV:N/AC:M/Au:N/C:P/I:P/A:N)
CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-601
cwe_name: URL Redirection to Untrusted Site ('Open Redirect')
description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
extended_description: An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance. Whether this issue poses a vulnerability will be subject to the intended behavior of the application. For example, a search engine might intentionally provide redirects to arbitrary URLs.
         

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: Simon Urli
author_date: 2021-04-15 11:35:55+02:00
msg: XWIKI-10309: Check URL domains based on a whitelist (#1592)

Introduce a new property for listing the trusted domains and API to
check an URL against that list and the aliases used in subwikis.

  * Add new property url.trustedDomains in xwiki.properties
  * Add new API in URLConfiguration to retrieve this configuration value
  * Create a new URLSecurityManager responsible to check if an URL can
    be trusted based on this property and on the subwikis configurations
  * Introduce a new listener to invalidate the cache of
    URLSecurityManager whenever a XWikiServerClass xobject is
added/updated/deleted
  * Move URL API implementations to URL default module
  * Add a new property url.enableTrustedDomains as a global switch off the
    checks on domains to avoid breaking behaviours on existing instances
  * Add a constant property in URLSecurityManager to be set in
    ExecutionContext to allow temporary switch off the check for
extensions
  * Use both those switches in DefaultURLSecurityManager to prevent
    performing the check when needed
repository: repo_url: https://github.com/xwiki/xwiki-platform
repo_name: xwiki/xwiki-platform
description: The XWiki platform
repo_language: Java
owner: xwiki
file_changes: filename: MyFormAuthenticator.java
change_type: ModificationType.MODIFY
diff: @@ -41,6 +41,8 @@
 import org.xwiki.security.authentication.AuthenticationFailureManager;
 import com.xpn.xwiki.internal.user.UserAuthenticatedEventNotifier;
 
+import com.xpn.xwiki.web.XWikiResponse;
+
 public class MyFormAuthenticator extends FormAuthenticator implements XWikiAuthenticator
 {
     private static final Logger LOGGER = LoggerFactory.getLogger(MyFormAuthenticator.class);
@@ -244,8 +246,8 @@ public boolean processLogin(String username, String password, String rememberme,
 
             Boolean bAjax = (Boolean) context.get("ajax");
             if ((bAjax == null) || (!bAjax.booleanValue())) {
-                String continueToURL = getContinueToURL(request);
                 // This is the url that the user was initially accessing before being prompted for login.
+                String continueToURL = getContinueToURL(request);
                 response.sendRedirect(response.encodeRedirectURL(continueToURL));
             }
         } else {

programming_language: Java
filename: XWikiAction.java
change_type: ModificationType.MODIFY
diff: @@ -981,6 +981,12 @@ protected boolean sendGlobalRedirect(XWikiResponse response, String url, XWikiCo
         return false;
     }
 
+    /**
+     * Perform a redirect to the given URL.
+     * @param response the response to use to perform the redirect
+     * @param url the location of the redirect
+     * @throws XWikiException in case of IOException when performing the redirect.
+     */
     protected void sendRedirect(XWikiResponse response, String url) throws XWikiException
     {
         try {

programming_language: Java
filename: XWikiServletResponse.java
change_type: ModificationType.MODIFY
diff: @@ -21,8 +21,10 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.net.URL;
 import java.util.Collection;
 import java.util.Locale;
+import java.util.regex.Pattern;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.Cookie;
@@ -31,10 +33,12 @@
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.xwiki.url.URLSecurityManager;
 
 public class XWikiServletResponse implements XWikiResponse
 {
     private static final Logger LOGGER = LoggerFactory.getLogger(XWikiServletResponse.class);
+    private static final Pattern ABSOLUTE_URL_PATTERN = Pattern.compile("[a-z0-9]+://.*");
 
     private HttpServletResponse response;
 
@@ -66,9 +70,25 @@ public void sendRedirect(String redirect) throws IOException
             LOGGER.warn("Possible HTTP Response Splitting attack, attempting to redirect to [{}]", redirect);
             return;
         }
+
+        // check for trusted domains, only if the given location is an absolute URL.
+        if (ABSOLUTE_URL_PATTERN.matcher(redirect).matches()) {
+            if (!getURLSecurityManager().isDomainTrusted(new URL(redirect))) {
+                LOGGER.warn(
+                    "Possible phishing attack, attempting to redirect to [{}], this request has been blocked. "
+                        + "If the request was legitimate, add the domain related to this request in the list "
+                        + "of trusted domains in the configuration.", redirect);
+                return;
+            }
+        }
         this.response.sendRedirect(redirect);
     }
 
+    private URLSecurityManager getURLSecurityManager()
+    {
+        return Utils.getComponent(URLSecurityManager.class);
+    }
+
     @Override
     public void setContentType(String type)
     {

programming_language: Java
filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -35,6 +35,7 @@
     <!-- Sorted Alphabetically -->
     <module>xwiki-platform-url-api</module>
     <module>xwiki-platform-url-container</module>
+    <module>xwiki-platform-url-default</module>
     <module>xwiki-platform-url-schemes</module>
   </modules>
 </project>

programming_language: HTML
filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -32,9 +32,7 @@
   <packaging>jar</packaging>
   <description>Allows configuration of the URL scheme used by XWiki to parse/serialize URLs</description>
   <properties>
-    <!-- The reason for this low TPC value is because this module is tested using integration tests in the various
-         URL Scheme modules -->
-    <xwiki.jacoco.instructionRatio>0.45</xwiki.jacoco.instructionRatio>
+    <xwiki.jacoco.instructionRatio>0.85</xwiki.jacoco.instructionRatio>
     <!-- Name to display by the Extension Manager -->
     <xwiki.extension.name>URL API</xwiki.extension.name>
   </properties>

programming_language: HTML
filename: URLConfiguration.java
change_type: ModificationType.MODIFY
diff: @@ -19,7 +19,11 @@
  */
 package org.xwiki.url;
 
+import java.util.Collections;
+import java.util.List;
+
 import org.xwiki.component.annotation.Role;
+import org.xwiki.stability.Unstable;
 
 /**
  * Configuration options for the URL module.
@@ -47,4 +51,30 @@ default boolean useResourceLastModificationDate()
     {
         return true;
     }
+
+    /**
+     * Specify the list of domains that are considered as trusted by the administrators of the wiki: those domains can
+     * be used safely for redirections from the wiki or for performing other requests on them.
+     * @return the list of trusted domains that can be used in the wiki.
+     * @since 13.3RC1
+     * @since 12.10.7
+     */
+    @Unstable
+    default List<String> getTrustedDomains()
+    {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Define if the trusted domains check should be performed or not. This option is provided only to allow bypassing
+     * security checks globally on the wiki in case of problems.
+     * @return {@code true} if the security check on domains should be performed. {@code false} otherwise.
+     * @since 13.3RC1
+     * @since 12.10.7
+     */
+    @Unstable
+    default boolean isTrustedDomainsEnabled()
+    {
+        return true;
+    }
 }

programming_language: Java
filename: URLSecurityManager.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,56 @@
+/*
+ * 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.url;
+
+import java.net.URL;
+
+import org.xwiki.component.annotation.Role;
+import org.xwiki.stability.Unstable;
+
+/**
+ * Dedicated component to perform security checks on URLs.
+ *
+ * @version $Id$
+ * @since 13.3RC1
+ * @since 12.10.7
+ */
+@Role
+@Unstable
+public interface URLSecurityManager
+{
+    /**
+     * Constant to be used in {@link org.xwiki.context.ExecutionContext} with the value {@code "true"} to bypass a
+     * check of {@link #isDomainTrusted(URL)}.
+     */
+    String BYPASS_DOMAIN_SECURITY_CHECK_CONTEXT_PROPERTY = "bypassDomainSecurityCheck";
+
+    /**
+     * Check if the given {@link URL} can be trusted based on the trusted domains of the wiki.
+     * This method check on both the list of trusted domains given by the configuration
+     * (see {@link URLConfiguration#getTrustedDomains()}) and the list of aliases used by the wiki descriptors.
+     * Note that this method always returns {@code true} if {@link URLConfiguration#isTrustedDomainsEnabled()} returns
+     * {@code true}. Also the method will return {@code true} whenever the {@link org.xwiki.context.ExecutionContext}
+     * contains a property named {@link #BYPASS_DOMAIN_SECURITY_CHECK_CONTEXT_PROPERTY} with the value {@code "true"}.
+     *
+     * @param urlToCheck the URL for which we want to know if the domain is trusted or not.
+     * @return {@code true} if the URL domain can be trusted or if the check is skipped, {@code false} otherwise
+     */
+    boolean isDomainTrusted(URL urlToCheck);
+}

programming_language: Java
filename: pom.xml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,63 @@
+<?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/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.xwiki.platform</groupId>
+    <artifactId>xwiki-platform-url</artifactId>
+    <version>13.3-SNAPSHOT</version>
+  </parent>
+  <artifactId>xwiki-platform-url-default</artifactId>
+  <name>XWiki Platform - URL - Default</name>
+  <packaging>jar</packaging>
+  <description>Default implementations of the API defined in xwiki-platform-url-api</description>
+  <properties>
+    <!-- The reason for this low TPC value is because this module is tested using integration tests in the various
+    URL Scheme modules -->
+    <xwiki.jacoco.instructionRatio>0.31</xwiki.jacoco.instructionRatio>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-url-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-oldcore</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-wiki-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <!-- Testing Dependencies -->
+    <dependency>
+      <groupId>org.xwiki.commons</groupId>
+      <artifactId>xwiki-commons-tool-test-component</artifactId>
+      <version>${commons.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>

programming_language: HTML
filename: DefaultURLConfiguration.java
change_type: ModificationType.RENAME
diff: @@ -19,6 +19,9 @@
  */
 package org.xwiki.url.internal;
 
+import java.util.Collections;
+import java.util.List;
+
 import javax.inject.Inject;
 import javax.inject.Provider;
 import javax.inject.Singleton;
@@ -60,4 +63,16 @@ public boolean useResourceLastModificationDate()
     {
         return this.configuration.get().getProperty(PREFIX + "useResourceLastModificationDate", true);
     }
+
+    @Override
+    public List<String> getTrustedDomains()
+    {
+        return this.configuration.get().getProperty(PREFIX + "trustedDomains", Collections.emptyList());
+    }
+
+    @Override
+    public boolean isTrustedDomainsEnabled()
+    {
+        return this.configuration.get().getProperty(PREFIX + "trustedDomainsEnabled", true);
+    }
 }

programming_language: Java
filename: DefaultURLSecurityManager.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,137 @@
+/*
+ * 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.url.internal;
+
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.slf4j.Logger;
+import org.xwiki.component.annotation.Component;
+import org.xwiki.context.Execution;
+import org.xwiki.url.URLConfiguration;
+import org.xwiki.url.URLSecurityManager;
+import org.xwiki.wiki.descriptor.WikiDescriptor;
+import org.xwiki.wiki.descriptor.WikiDescriptorManager;
+import org.xwiki.wiki.manager.WikiManagerException;
+
+/**
+ * Default implementation of {@link URLSecurityManager}.
+ * This implementation keeps a HashSet in memory containing the trusted domains defined in the configuration and
+ * for all subwikis. Use {@link #invalidateCache()} to compute back this hashset.
+ *
+ * @version $Id$
+ * @since 13.3RC1
+ * @since 12.10.7
+ */
+@Component
+@Singleton
+public class DefaultURLSecurityManager implements URLSecurityManager
+{
+    private static final Pattern ACCEPTED_DOMAIN_PATTERN = Pattern.compile("([^.]+\\.[^.]+)+");
+    private static final char DOT = '.';
+
+    @Inject
+    private URLConfiguration urlConfiguration;
+
+    @Inject
+    private WikiDescriptorManager wikiDescriptorManager;
+
+    @Inject
+    private Execution execution;
+
+    @Inject
+    private Logger logger;
+
+    private Set<String> trustedDomains;
+
+    private void computeTrustedDomains()
+    {
+        Set<String> domains;
+        domains = new HashSet<>(this.urlConfiguration.getTrustedDomains());
+
+        try {
+            for (WikiDescriptor wikiDescriptor : wikiDescriptorManager.getAll()) {
+                domains.addAll(wikiDescriptor.getAliases());
+            }
+        } catch (WikiManagerException e) {
+            logger.warn("Error while getting wiki descriptor to fill list of trusted domains: [{}]. "
+                + "The subwikis won't be taken into account for the list of trusted domains.",
+                ExceptionUtils.getRootCauseMessage(e));
+        }
+        this.trustedDomains = new HashSet<>();
+
+        for (String domain : domains) {
+            if (ACCEPTED_DOMAIN_PATTERN.matcher(domain).matches()) {
+                this.trustedDomains.add(domain);
+            } else {
+                logger.warn("The domain [{}] specified in the trusted domains configuration won't be taken into "
+                    + "account since it doesn't respect the documented format.", domain);
+            }
+        }
+    }
+
+    @Override
+    public boolean isDomainTrusted(URL urlToCheck)
+    {
+        if (this.urlConfiguration.isTrustedDomainsEnabled()) {
+            if (this.trustedDomains == null) {
+                computeTrustedDomains();
+            }
+
+            String host = urlToCheck.getHost();
+
+            while (StringUtils.contains(host, DOT)) {
+                if (trustedDomains.contains(host)) {
+                    return true;
+                } else {
+                    host = host.substring(host.indexOf(DOT) + 1);
+                }
+            }
+
+            Object bypassCheckProperty = execution.getContext()
+                .getProperty(URLSecurityManager.BYPASS_DOMAIN_SECURITY_CHECK_CONTEXT_PROPERTY);
+            boolean bypassCheck = bypassCheckProperty != null && Boolean.parseBoolean(bypassCheckProperty.toString());
+
+            if (bypassCheck) {
+                logger.info("Domain of URL [{}] does not belong to the list of trusted domains but it's considered as "
+                    + "trusted since the check has been bypassed.", urlToCheck);
+            }
+
+            return bypassCheck;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Invalidate the set of trusted domains: this should mainly be used when a subwiki is added/edited/deleted.
+     */
+    public void invalidateCache()
+    {
+        this.trustedDomains = null;
+    }
+}

programming_language: Java
filename: XWikiServerClassListener.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,84 @@
+/*
+ * 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.url.internal;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.xwiki.component.annotation.Component;
+import org.xwiki.model.reference.LocalDocumentReference;
+import org.xwiki.observation.AbstractEventListener;
+import org.xwiki.observation.event.Event;
+import org.xwiki.url.URLSecurityManager;
+
+import com.xpn.xwiki.internal.event.XObjectAddedEvent;
+import com.xpn.xwiki.internal.event.XObjectDeletedEvent;
+import com.xpn.xwiki.internal.event.XObjectUpdatedEvent;
+
+/**
+ * Listener for changes on XWikiServerClass xobjects to ensure the {@link URLSecurityManager} cache is invalidated
+ * in case of change on XWikiServerClass objects.
+ *
+ * @version $Id$
+ * @since 13.3RC1
+ * @since 12.10.7
+ */
+@Component
+@Singleton
+@Named(XWikiServerClassListener.NAME)
+public class XWikiServerClassListener extends AbstractEventListener
+{
+    /**
+     * Name of the listener.
+     */
+    public static final String NAME = "org.xwiki.url.internal.XWikiServerClassListener";
+
+    private static final LocalDocumentReference XWIKISERVER_CLASS =
+        new LocalDocumentReference("XWiki", "XWikiServerClass");
+
+    private static final List<Event> EVENTS = Arrays.asList(
+        new XObjectAddedEvent(XWIKISERVER_CLASS),
+        new XObjectDeletedEvent(XWIKISERVER_CLASS),
+        new XObjectUpdatedEvent(XWIKISERVER_CLASS)
+    );
+
+    @Inject
+    private URLSecurityManager securityManager;
+
+    /**
+     * Default constructor.
+     */
+    public XWikiServerClassListener()
+    {
+        super(NAME, EVENTS);
+    }
+
+    @Override
+    public void onEvent(Event event, Object source, Object data)
+    {
+        if (this.securityManager instanceof DefaultURLSecurityManager) {
+            ((DefaultURLSecurityManager) this.securityManager).invalidateCache();
+        }
+    }
+}

programming_language: Java
filename: components.txt
change_type: ModificationType.RENAME
diff: @@ -1,3 +1,5 @@
+org.xwiki.url.internal.XWikiServerClassListener
+org.xwiki.url.internal.DefaultURLSecurityManager
 org.xwiki.url.internal.DefaultURLConfiguration
 org.xwiki.url.internal.DefaultResourceReferenceResolver
 org.xwiki.url.internal.DefaultResourceReferenceSerializer

programming_language: Scala
filename: DefaultURLSecurityManagerTest.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,197 @@
+/*
+ * 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.url.internal;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.xwiki.context.Execution;
+import org.xwiki.context.ExecutionContext;
+import org.xwiki.test.LogLevel;
+import org.xwiki.test.junit5.LogCaptureExtension;
+import org.xwiki.test.junit5.mockito.ComponentTest;
+import org.xwiki.test.junit5.mockito.InjectMockComponents;
+import org.xwiki.test.junit5.mockito.MockComponent;
+import org.xwiki.url.URLConfiguration;
+import org.xwiki.url.URLSecurityManager;
+import org.xwiki.wiki.descriptor.WikiDescriptor;
+import org.xwiki.wiki.descriptor.WikiDescriptorManager;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link DefaultURLSecurityManager}.
+ *
+ * @version $Id$
+ * @since 13.3RC1
+ * @since 12.10.7
+ */
+@ComponentTest
+class DefaultURLSecurityManagerTest
+{
+    @InjectMockComponents
+    private DefaultURLSecurityManager urlSecurityManager;
+
+    @MockComponent
+    private URLConfiguration urlConfiguration;
+
+    @MockComponent
+    private WikiDescriptorManager wikiDescriptorManager;
+
+    @MockComponent
+    private Execution execution;
+    
+    private ExecutionContext executionContext;
+
+    @RegisterExtension
+    LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.INFO);
+    
+    @BeforeEach
+    void setup()
+    {
+        this.executionContext = mock(ExecutionContext.class);
+        when(this.execution.getContext()).thenReturn(this.executionContext);
+        when(this.urlConfiguration.isTrustedDomainsEnabled()).thenReturn(true);
+    }
+
+    @Test
+    void isDomainTrusted() throws Exception
+    {
+        when(urlConfiguration.getTrustedDomains()).thenReturn(Arrays.asList(
+            "foo.acme.org",
+            "com" // this should not be taken into account
+        ));
+
+        WikiDescriptor wikiDescriptor1 = mock(WikiDescriptor.class);
+        when(wikiDescriptor1.getAliases()).thenReturn(Arrays.asList(
+            "www.xwiki.org",
+            "something.bar.com"
+        ));
+
+        WikiDescriptor wikiDescriptor2 = mock(WikiDescriptor.class);
+        when(wikiDescriptor2.getAliases()).thenReturn(Collections.singletonList(
+            "enterprise.eu"
+        ));
+
+        when(this.wikiDescriptorManager.getAll()).thenReturn(Arrays.asList(wikiDescriptor1, wikiDescriptor2));
+
+        assertThat("www.xwiki.org is trusted", this.urlSecurityManager
+            .isDomainTrusted(new URL("http://www.xwiki.org/xwiki/bin/view/XWiki/Login")));
+        assertThat("www.xwiki.org is trusted", this.urlSecurityManager
+            .isDomainTrusted(new URL("https://www.xwiki.org/xwiki/bin/view/XWiki/Login")));
+        assertThat("www.xwiki.com is not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://www.xwiki.com/xwiki/bin/view/XWiki/Login")));
+        assertThat("xwiki.org is not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://xwiki.org/xwiki/bin/view/XWiki/Login")));
+        assertThat("foo.acme.org is trusted", this.urlSecurityManager
+            .isDomainTrusted(new URL("https://foo.acme.org/something/else")));
+        assertThat("bar.foo.acme.org is trusted since foo.acme.org is", this.urlSecurityManager
+            .isDomainTrusted(new URL("https://bar.foo.acme.org/something/else")));
+        assertThat("buz.bar.foo.acme.org is trusted since foo.acme.org is", this.urlSecurityManager
+            .isDomainTrusted(new URL("https://buz.bar.foo.acme.org/something/else")));
+        assertThat("acme.org is not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://acme.org/something/else")));
+        assertThat("www.acme.org is not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://www.acme.org/something/else")));
+        assertThat("something.bar.thing.com is not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://something.bar.thing.com")));
+        assertThat("bar.thing.com is not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://bar.thing.com")));
+        assertThat("something.bar.com is tristed", this.urlSecurityManager
+            .isDomainTrusted(new URL("https://something.bar.com")));
+        assertThat("enterprise.eu is trusted", this.urlSecurityManager
+            .isDomainTrusted(new URL("https://enterprise.eu/xwiki/")));
+        assertThat("enterprise.eu. is not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://enterprise.eu./xwiki/")));
+
+        assertEquals("The domain [com] specified in the trusted domains configuration won't be taken into account "
+                + "since it doesn't respect the documented format.",
+            logCapture.getMessage(0));
+    }
+
+    @Test
+    void invalidateCache() throws Exception
+    {
+        when(urlConfiguration.getTrustedDomains()).thenReturn(Collections.singletonList(
+            "xwiki.org"
+        ));
+        assertThat("www.xwiki.org is trusted", this.urlSecurityManager
+            .isDomainTrusted(new URL("http://www.xwiki.org")));
+        assertThat("foo.acme.org is not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://foo.acme.org/something/else")));
+
+        when(urlConfiguration.getTrustedDomains()).thenReturn(Collections.singletonList(
+            "foo.acme.org"
+        ));
+
+        // the asserts are still the same because we rely on cached values
+        assertThat("www.xwiki.org is still trusted", this.urlSecurityManager
+            .isDomainTrusted(new URL("http://www.xwiki.org")));
+        assertThat("foo.acme.org is still not trusted", !this.urlSecurityManager
+            .isDomainTrusted(new URL("https://foo.acme.org/something/else")));
+
+        // after invalidation the cache has been recomputed.
+        this.urlSecurityManager.invalidateCache();
+        assertThat("www.xwiki.org is not trusted anymore", !this.urlSecurityManager
+            .isDomainTrusted(new URL("http://www.xwiki.org")));
+        assertThat("foo.acme.org is trusted now", this.urlSecurityManager
+            .isDomainTrusted(new URL("https://foo.acme.org/something/else")));
+    }
+
+    @Test
+    void isDomainTrustedWhenCheckSkipped() throws MalformedURLException
+    {
+        when(urlConfiguration.getTrustedDomains()).thenReturn(Collections.singletonList(
+            "foo.acme.org"
+        ));
+        when(urlConfiguration.isTrustedDomainsEnabled()).thenReturn(false);
+        assertThat("Any domain can be trusted when check is skipped: check with www.xwiki.org",
+            this.urlSecurityManager.isDomainTrusted(new URL("http://www.xwiki.org")));
+        assertThat("Any domain can be trusted when check is skipped: check with www.bar.eu",
+            this.urlSecurityManager.isDomainTrusted(new URL("http://www.bar.eu")));
+        assertThat("Any domain can be trusted when check is skipped: check with foo.acme.org",
+            this.urlSecurityManager.isDomainTrusted(new URL("http://foo.acme.org")));
+
+        when(urlConfiguration.isTrustedDomainsEnabled()).thenReturn(true);
+        assertThat("www.xwiki.org should not be trusted",
+            !this.urlSecurityManager.isDomainTrusted(new URL("http://www.xwiki.org")));
+        assertThat("www.bar.eu should not be trusted",
+            !this.urlSecurityManager.isDomainTrusted(new URL("http://www.bar.eu")));
+        assertThat("foo.acme.org should be trusted",
+            this.urlSecurityManager.isDomainTrusted(new URL("http://foo.acme.org")));
+
+        when(this.executionContext.getProperty(URLSecurityManager.BYPASS_DOMAIN_SECURITY_CHECK_CONTEXT_PROPERTY))
+            .thenReturn(true);
+        assertThat("www.xwiki.org should be trusted when check is bypassed",
+            this.urlSecurityManager.isDomainTrusted(new URL("http://www.xwiki.org")));
+
+        assertEquals("Domain of URL [http://www.xwiki.org] does not belong to the list of trusted domains but "
+                + "it's considered as trusted since the check has been bypassed.",
+            logCapture.getMessage(0));
+    }
+}

programming_language: Java
filename: RelativeExtendedURLTest.java
change_type: ModificationType.RENAME
diff: @@ -17,7 +17,7 @@
  * 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.url;
+package org.xwiki.url.internal;
 
 import java.util.Arrays;
 

programming_language: Java
filename: URLExecutionContextInitializerTest.java
change_type: ModificationType.RENAME
diff: @@ -17,13 +17,14 @@
  * 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.url;
+package org.xwiki.url.internal;
 
 import org.junit.jupiter.api.Test;
 import org.xwiki.context.ExecutionContext;
 import org.xwiki.test.junit5.mockito.ComponentTest;
 import org.xwiki.test.junit5.mockito.InjectMockComponents;
 import org.xwiki.test.junit5.mockito.MockComponent;
+import org.xwiki.url.URLConfiguration;
 import org.xwiki.url.internal.URLExecutionContextInitializer;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;

programming_language: Java
filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -39,7 +39,7 @@
   <dependencies>
     <dependency>
       <groupId>org.xwiki.platform</groupId>
-      <artifactId>xwiki-platform-url-api</artifactId>
+      <artifactId>xwiki-platform-url-default</artifactId>
       <version>${project.version}</version>
     </dependency>
 

programming_language: HTML
filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -38,7 +38,7 @@
   <dependencies>
     <dependency>
       <groupId>org.xwiki.platform</groupId>
-      <artifactId>xwiki-platform-url-api</artifactId>
+      <artifactId>xwiki-platform-url-default</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>

programming_language: HTML
filename: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -46,7 +46,7 @@
     </dependency>
     <dependency>
       <groupId>org.xwiki.platform</groupId>
-      <artifactId>xwiki-platform-url-api</artifactId>
+      <artifactId>xwiki-platform-url-default</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>

programming_language: HTML
filename: xwiki.properties.vm
change_type: ModificationType.MODIFY
diff: @@ -744,6 +744,25 @@ extension.versioncheck.environment.enabled=$xwikiPropertiesEnvironmentVersionChe
 #-# The default is:
 # url.useResourceLastModificationDate=true
 
+#-# [Since 13.3RC1]
+#-# [Since 12.10.7]
+#-# Define a list of trusted domains that can be used in the wiki for performing requests or redirections even if
+#-# the wiki does not use it. Domains are listed without http and separated with a comma in the list. Subdomains can be
+#-# specified.
+#-# Example of accepted value: foo.acme.org,enterprise.org
+#-#
+#-# By default the list of trusted domains is empty:
+# url.trustedDomains=
+
+#-# [Since 13.3RC1]
+#-# [Since 12.10.7]
+#-# Allow to enable or disable checks performed on domains by taking into account the list of trusted domains.
+#-# Disable this property only if you experienced some issues on your wiki: some security check won't be performed when
+#-# this property is set to false.
+#-#
+#-# By default this property is set to true:
+# url.trustedDomainsEnabled=true
+
 #-------------------------------------------------------------------------------------
 # Attachment
 #-------------------------------------------------------------------------------------

programming_language: Ruby