CVE-2022-23618: URL Redirection to Untrusted Site ('Open Redirect') (CWE-601)
value: 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.
CWE 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.
CVSS v3 base score: 6.1
Published: 2022-02-09T21:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: Java
Files changed: xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java, xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java, xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiServletResponse.java, xwiki-platform-core/xwiki-platform-url/pom.xml, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/pom.xml, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLConfiguration.java, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLSecurityManager.java, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/pom.xml, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLConfiguration.java, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLSecurityManager.java, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/XWikiServerClassListener.java, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/resources/META-INF/components.txt, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/DefaultURLSecurityManagerTest.java, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/RelativeExtendedURLTest.java, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/URLExecutionContextInitializerTest.java, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-filesystem/pom.xml, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-reference/pom.xml, xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-standard/pom.xml, xwiki-platform-tools/xwiki-platform-tool-configuration-resources/src/main/resources/xwiki.properties.vm
Commit date: 2021-04-15 11:35:55 +0200
Commit message: 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
Diff: diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java
index 975a6b247c58..64ca52c24e66 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java
@@ -39,10 +39,12 @@
 import org.slf4j.LoggerFactory;
 import org.xwiki.container.servlet.filters.SavedRequestManager;
 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);
 
     private UserAuthenticatedEventNotifier userAuthenticatedEventNotifier;
@@ -242,12 +244,12 @@ public boolean processLogin(String username, String password, String rememberme,
 
             this.getUserAuthenticatedEventNotifier().notify(principal.getName());
 
             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 {
             // login failed
             // set response status and forward to error page
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java
index abd20026a364..6d5e63d78fcd 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java
@@ -979,10 +979,16 @@ 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 {
             if (url != null) {
                 response.sendRedirect(response.encodeRedirectURL(url));
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiServletResponse.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiServletResponse.java
index 1eb143e37c63..a35e1dcedd4d 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiServletResponse.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiServletResponse.java
@@ -19,24 +19,28 @@
  */
 package com.xpn.xwiki.web;
 
 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;
 import javax.servlet.http.HttpServletResponse;
 
 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;
 
     public XWikiServletResponse(HttpServletResponse response)
     {
@@ -64,13 +68,29 @@ public void sendRedirect(String redirect) throws IOException
         }
         if (StringUtils.containsAny(redirect, '\r', '\n')) {
             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)
     {
         this.response.setContentType(type);
     }
diff --git a/xwiki-platform-core/xwiki-platform-url/pom.xml b/xwiki-platform-core/xwiki-platform-url/pom.xml
index b2ccf1c3b346..0b51b09c0c03 100644
--- a/xwiki-platform-core/xwiki-platform-url/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-url/pom.xml
@@ -33,8 +33,9 @@
   <description>XWiki Platform - URL - Parent POM</description>
   <modules>
     <!-- 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>
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/pom.xml b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/pom.xml
index 9f5924720162..1c95bcd62f89 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/pom.xml
@@ -30,13 +30,11 @@
   <artifactId>xwiki-platform-url-api</artifactId>
   <name>XWiki Platform - URL - API</name>
   <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>
   <dependencies>
     <dependency>
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLConfiguration.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLConfiguration.java
index c82ef1b6689b..935e2fa7d6ad 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLConfiguration.java
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLConfiguration.java
@@ -17,11 +17,15 @@
  * 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.util.Collections;
+import java.util.List;
+
 import org.xwiki.component.annotation.Role;
+import org.xwiki.stability.Unstable;
 
 /**
  * Configuration options for the URL module.
  *
  * @version $Id$
@@ -45,6 +49,32 @@
      */
     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;
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLSecurityManager.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLSecurityManager.java
new file mode 100644
index 000000000000..833f1c87727f
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLSecurityManager.java
@@ -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);
+}
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/pom.xml b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/pom.xml
new file mode 100644
index 000000000000..5aca17a92ec0
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/pom.xml
@@ -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>
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/AbstractExtendedURLResourceReferenceSerializer.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/AbstractExtendedURLResourceReferenceSerializer.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/AbstractExtendedURLResourceReferenceSerializer.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/AbstractExtendedURLResourceReferenceSerializer.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/AbstractExtendedURLResourceTypeResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/AbstractExtendedURLResourceTypeResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/AbstractExtendedURLResourceTypeResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/AbstractExtendedURLResourceTypeResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/AbstractParentResourceReferenceResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/AbstractParentResourceReferenceResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/AbstractParentResourceReferenceResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/AbstractParentResourceReferenceResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/AbstractResourceReferenceResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/AbstractResourceReferenceResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/AbstractResourceReferenceResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/AbstractResourceReferenceResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultResourceReferenceResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultResourceReferenceResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultResourceReferenceResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultResourceReferenceResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultResourceReferenceSerializer.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultResourceReferenceSerializer.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultResourceReferenceSerializer.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultResourceReferenceSerializer.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultResourceTypeResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultResourceTypeResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultResourceTypeResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultResourceTypeResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultStringResourceTypeResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultStringResourceTypeResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultStringResourceTypeResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultStringResourceTypeResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultURLConfiguration.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLConfiguration.java
similarity index 84%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultURLConfiguration.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLConfiguration.java
index 17c09000e097..5a830e0544c9 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultURLConfiguration.java
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLConfiguration.java
@@ -17,10 +17,13 @@
  * 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.Collections;
+import java.util.List;
+
 import javax.inject.Inject;
 import javax.inject.Provider;
 import javax.inject.Singleton;
 
 import org.xwiki.component.annotation.Component;
@@ -58,6 +61,18 @@ public String getURLFormatId()
     @Override
     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);
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultURLContextManager.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLContextManager.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/DefaultURLContextManager.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLContextManager.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLSecurityManager.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLSecurityManager.java
new file mode 100644
index 000000000000..e346e58e36e6
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLSecurityManager.java
@@ -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;
+    }
+}
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/GenericResourceReferenceResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/GenericResourceReferenceResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/GenericResourceReferenceResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/GenericResourceReferenceResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/GenericStringResourceTypeResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/GenericStringResourceTypeResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/GenericStringResourceTypeResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/GenericStringResourceTypeResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/ParentResourceReference.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/ParentResourceReference.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/ParentResourceReference.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/ParentResourceReference.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/RelativeExtendedURL.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/RelativeExtendedURL.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/RelativeExtendedURL.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/RelativeExtendedURL.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/URLExecutionContextInitializer.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/URLExecutionContextInitializer.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/URLExecutionContextInitializer.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/URLExecutionContextInitializer.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/URLStringEntityReferenceResolver.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/URLStringEntityReferenceResolver.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/URLStringEntityReferenceResolver.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/URLStringEntityReferenceResolver.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/URLStringEntityReferenceSerializer.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/URLStringEntityReferenceSerializer.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/URLStringEntityReferenceSerializer.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/URLStringEntityReferenceSerializer.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/URLSymbolScheme.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/URLSymbolScheme.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/internal/URLSymbolScheme.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/URLSymbolScheme.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/XWikiServerClassListener.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/XWikiServerClassListener.java
new file mode 100644
index 000000000000..5d59b0da67e0
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/XWikiServerClassListener.java
@@ -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();
+        }
+    }
+}
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/resources/META-INF/components.txt
similarity index 86%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/resources/META-INF/components.txt
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/resources/META-INF/components.txt
index 956d6bbc6c8c..ba55fdab668d 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/resources/META-INF/components.txt
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/resources/META-INF/components.txt
@@ -1,5 +1,7 @@
+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
 org.xwiki.url.internal.DefaultResourceTypeResolver
 org.xwiki.url.internal.GenericResourceReferenceResolver
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/internal/AbstractParentResourceReferenceResolverTest.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/AbstractParentResourceReferenceResolverTest.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/internal/AbstractParentResourceReferenceResolverTest.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/AbstractParentResourceReferenceResolverTest.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/internal/DefaultURLConfigurationTest.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/DefaultURLConfigurationTest.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/internal/DefaultURLConfigurationTest.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/DefaultURLConfigurationTest.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/DefaultURLSecurityManagerTest.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/DefaultURLSecurityManagerTest.java
new file mode 100644
index 000000000000..9cabd5908f79
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/DefaultURLSecurityManagerTest.java
@@ -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));
+    }
+}
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/RelativeExtendedURLTest.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/RelativeExtendedURLTest.java
similarity index 97%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/RelativeExtendedURLTest.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/RelativeExtendedURLTest.java
index 5e8c78012cd5..2e6b8b0250d3 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/RelativeExtendedURLTest.java
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/RelativeExtendedURLTest.java
@@ -15,11 +15,11 @@
  * 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;
+package org.xwiki.url.internal;
 
 import java.util.Arrays;
 
 import org.junit.jupiter.api.Test;
 import org.xwiki.url.internal.RelativeExtendedURL;
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/URLExecutionContextInitializerTest.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/URLExecutionContextInitializerTest.java
similarity index 96%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/URLExecutionContextInitializerTest.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/URLExecutionContextInitializerTest.java
index 7e67564e2228..06997ede5d78 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/URLExecutionContextInitializerTest.java
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/URLExecutionContextInitializerTest.java
@@ -15,17 +15,18 @@
  * 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;
+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;
 import static org.mockito.Mockito.when;
 
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/internal/URLStringEntityReferenceResolverTest.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/URLStringEntityReferenceResolverTest.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/internal/URLStringEntityReferenceResolverTest.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/URLStringEntityReferenceResolverTest.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/internal/URLStringEntityReferenceSerializerTest.java b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/URLStringEntityReferenceSerializerTest.java
similarity index 100%
rename from xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/test/java/org/xwiki/url/internal/URLStringEntityReferenceSerializerTest.java
rename to xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/test/java/org/xwiki/url/internal/URLStringEntityReferenceSerializerTest.java
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-filesystem/pom.xml b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-filesystem/pom.xml
index f1a301cd1063..21499358f87d 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-filesystem/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-filesystem/pom.xml
@@ -37,11 +37,11 @@
     <xwiki.extension.name>Filesystem URL Scheme</xwiki.extension.name>
   </properties>
   <dependencies>
     <dependency>
       <groupId>org.xwiki.platform</groupId>
-      <artifactId>xwiki-platform-url-api</artifactId>
+      <artifactId>xwiki-platform-url-default</artifactId>
       <version>${project.version}</version>
     </dependency>
 
     <!-- Testing Dependencies -->
     <dependency>
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-reference/pom.xml b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-reference/pom.xml
index 1eb99169993c..09c7e6882cef 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-reference/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-reference/pom.xml
@@ -36,11 +36,11 @@
     <xwiki.extension.name>Reference URL Scheme</xwiki.extension.name>
   </properties>
   <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>
       <groupId>org.xwiki.platform</groupId>
       <artifactId>xwiki-platform-url-scheme-standard</artifactId>
diff --git a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-standard/pom.xml b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-standard/pom.xml
index 5c55e548a00a..66eba33ccb6c 100644
--- a/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-standard/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-schemes/xwiki-platform-url-scheme-standard/pom.xml
@@ -44,11 +44,11 @@
       <artifactId>xwiki-commons-configuration-api</artifactId>
       <version>${commons.version}</version>
     </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>
       <groupId>org.xwiki.platform</groupId>
       <artifactId>xwiki-platform-wiki-api</artifactId>
diff --git a/xwiki-platform-tools/xwiki-platform-tool-configuration-resources/src/main/resources/xwiki.properties.vm b/xwiki-platform-tools/xwiki-platform-tool-configuration-resources/src/main/resources/xwiki.properties.vm
index 09ea2e7e464c..036b05b49e4e 100644
--- a/xwiki-platform-tools/xwiki-platform-tool-configuration-resources/src/main/resources/xwiki.properties.vm
+++ b/xwiki-platform-tools/xwiki-platform-tool-configuration-resources/src/main/resources/xwiki.properties.vm
@@ -742,10 +742,29 @@ extension.versioncheck.environment.enabled=$xwikiPropertiesEnvironmentVersionChe
 #-# it might imply the need to force-reload some resources in the browser, when migrating.
 #-#
 #-# 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
 #-------------------------------------------------------------------------------------
 
 #-# [Since 5.2M2]

Vulnerable code:                 String continueToURL = getContinueToURL(request);
    <!-- 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>
package org.xwiki.url;
package org.xwiki.url;
      <artifactId>xwiki-platform-url-api</artifactId>
      <artifactId>xwiki-platform-url-api</artifactId>
      <artifactId>xwiki-platform-url-api</artifactId>
Fixed code: import com.xpn.xwiki.web.XWikiResponse;

                String continueToURL = getContinueToURL(request);
    /**
     * 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.
     */
import java.net.URL;
import java.util.regex.Pattern;
import org.xwiki.url.URLSecurityManager;
    private static final Pattern ABSOLUTE_URL_PATTERN = Pattern.compile("[a-z0-9]+://.*");

        // 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;
            }
        }
    private URLSecurityManager getURLSecurityManager()
    {
        return Utils.getComponent(URLSecurityManager.class);
    }

    <module>xwiki-platform-url-default</module>
    <xwiki.jacoco.instructionRatio>0.85</xwiki.jacoco.instructionRatio>
import java.util.Collections;
import java.util.List;

import org.xwiki.stability.Unstable;

    /**
     * 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;
    }
/*
 * 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);
}
<?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>
import java.util.Collections;
import java.util.List;


    @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);
    }
/*
 * 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;
    }
}
/*
 * 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();
        }
    }
}
org.xwiki.url.internal.XWikiServerClassListener
org.xwiki.url.internal.DefaultURLSecurityManager
/*
 * 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));
    }
}
package org.xwiki.url.internal;
package org.xwiki.url.internal;
import org.xwiki.url.URLConfiguration;
      <artifactId>xwiki-platform-url-default</artifactId>
      <artifactId>xwiki-platform-url-default</artifactId>
      <artifactId>xwiki-platform-url-default</artifactId>
#-# [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