You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2022/02/14 17:08:29 UTC

[cxf] branch master updated: CXF-8636: Swagger2Feature: Can't set url in UI through SwaggerUiConfig (#906)

This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/master by this push:
     new 90fd6d4  CXF-8636: Swagger2Feature: Can't set url in UI through SwaggerUiConfig (#906)
90fd6d4 is described below

commit 90fd6d465c7b9bbe108aa6c744744ce19fecd93d
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Mon Feb 14 12:08:21 2022 -0500

    CXF-8636: Swagger2Feature: Can't set url in UI through SwaggerUiConfig (#906)
---
 distribution/src/main/release/samples/pom.xml      |  2 +-
 parent/pom.xml                                     |  2 +-
 .../cxf/jaxrs/swagger/ui/SwaggerUiConfig.java      | 15 +++++
 .../cxf/jaxrs/swagger/ui/SwaggerUiService.java     | 73 +++++++++++++++++-----
 ... => SwaggerUiConfigurationQueryConfigTest.java} | 23 ++-----
 .../description/SwaggerUiConfigurationTest.java    | 18 ++++++
 6 files changed, 98 insertions(+), 35 deletions(-)

diff --git a/distribution/src/main/release/samples/pom.xml b/distribution/src/main/release/samples/pom.xml
index 6b992f8..fe0d6e6 100644
--- a/distribution/src/main/release/samples/pom.xml
+++ b/distribution/src/main/release/samples/pom.xml
@@ -35,7 +35,7 @@
         <cxf.jetty9.version>9.4.45.v20220203</cxf.jetty9.version>
         <cxf.netty.version>4.1.74.Final</cxf.netty.version>
         <cxf.httpcomponents.client.version>4.5.13</cxf.httpcomponents.client.version>
-        <cxf.swagger.ui.version>4.1.2</cxf.swagger.ui.version>
+        <cxf.swagger.ui.version>4.5.0</cxf.swagger.ui.version>
         <cxf.tika.version>2.2.1</cxf.tika.version>
         <cxf.tomcat.version>9.0.58</cxf.tomcat.version>
         <graalvm.version>21.1.0</graalvm.version>
diff --git a/parent/pom.xml b/parent/pom.xml
index 861c138..c800c94 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -205,7 +205,7 @@
         <cxf.spring.security.version>5.6.1</cxf.spring.security.version>
         <cxf.spring.version>5.3.15</cxf.spring.version>
         <cxf.stax-ex.version>1.8.3</cxf.stax-ex.version>
-        <cxf.swagger.ui.version>4.1.2</cxf.swagger.ui.version>
+        <cxf.swagger.ui.version>4.5.0</cxf.swagger.ui.version>
         <cxf.swagger.v3.version>2.1.13</cxf.swagger.v3.version>
         <cxf.swagger2.version>1.6.5</cxf.swagger2.version>
         <cxf.swagger2.guava.version>31.0.1-jre</cxf.swagger2.guava.version>
diff --git a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiConfig.java b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiConfig.java
index 940bdbd..446a39b 100644
--- a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiConfig.java
+++ b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiConfig.java
@@ -63,6 +63,8 @@ public class SwaggerUiConfig {
     private String validatorUrl;
     // Controls whether the "Try it out" section should be enabled by default.
     private Boolean tryItOutEnabled;
+    // Enables overriding configuration parameters via URL search params.
+    private Boolean queryConfigEnabled;
     
     public String getConfigUrl() {
         return configUrl;
@@ -242,6 +244,11 @@ public class SwaggerUiConfig {
         return this;
     }
 
+    public SwaggerUiConfig queryConfigEnabled(boolean enabled) {
+        setQueryConfigEnabled(enabled);
+        return this;
+    }
+
     public SwaggerUiConfig filter(final String f) {
         setFilter(f);
         return this;
@@ -252,6 +259,14 @@ public class SwaggerUiConfig {
         return this;
     }
 
+    public Boolean isQueryConfigEnabled() {
+        return queryConfigEnabled;
+    }
+
+    public void setQueryConfigEnabled(Boolean queryConfigEnabled) {
+        this.queryConfigEnabled = queryConfigEnabled;
+    }
+
     public Map<String, String> getConfigParameters() {
         final Map<String, String> params = new TreeMap<>();
         
diff --git a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiService.java b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiService.java
index 615a665..bfccf12 100644
--- a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiService.java
+++ b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiService.java
@@ -20,9 +20,12 @@
 package org.apache.cxf.jaxrs.swagger.ui;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.NotFoundException;
@@ -34,11 +37,15 @@ import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.helpers.IOUtils;
+
 
 @Path("api-docs")
 public class SwaggerUiService {
     private static final String FAVICON = "favicon";
     private static final Map<String, String> DEFAULT_MEDIA_TYPES;
+    private static final Pattern URL_PATTERN = Pattern.compile("url[:]\\s*[\"]([^\"]+)[\"][,]");
 
     static {
         DEFAULT_MEDIA_TYPES = new HashMap<>();
@@ -101,22 +108,42 @@ public class SwaggerUiService {
             //    http://localhost:8080/services/helloservice/api-docs?url=/services/helloservice/openapi.json
             //
             // in case the "url" configuration parameter is provided for Swagger UI.
-            if (config != null && uriInfo.getQueryParameters().isEmpty() && path.endsWith("/index.html")) {
-                final Map<String, String> params = config.getConfigParameters();
-                
-                if (params != null && !params.isEmpty()) {
-                    final UriBuilder builder = params
-                        .entrySet()
-                        .stream()
-                        .reduce(
-                            uriInfo.getRequestUriBuilder(), 
-                            (b, e) -> b.queryParam(e.getKey(), e.getValue()),
-                            (left, right) -> left
-                        );
-                    return Response.temporaryRedirect(builder.build()).build();
+            if (config != null && path.endsWith("/index.html")) {
+                if (uriInfo.getQueryParameters().isEmpty()) {
+                    final Map<String, String> params = config.getConfigParameters();
+                    
+                    if (params != null && !params.isEmpty()) {
+                        final UriBuilder builder = params
+                            .entrySet()
+                            .stream()
+                            .reduce(
+                                uriInfo.getRequestUriBuilder(), 
+                                (b, e) -> b.queryParam(e.getKey(), e.getValue()),
+                                (left, right) -> left
+                            );
+                        return Response.temporaryRedirect(builder.build()).build();
+                    }
+                }
+
+                // Since Swagger UI 4.1.3, passing the default URL as query parameter, 
+                // e.g. `?url=swagger.json` is disabled by default due to security concerns.
+                if (config.isQueryConfigEnabled() == null || !config.isQueryConfigEnabled()) {
+                    final String url = config.getUrl();
+                    if (!StringUtils.isEmpty(url)) {
+                        try (InputStream in = resourceURL.openStream()) {
+                            final String index = replaceUrl(IOUtils.readStringFromStream(in), url);
+                            final ResponseBuilder rb = Response.ok(index);
+
+                            if (mediaType != null) {
+                                rb.type(mediaType);
+                            }
+
+                            return rb.build();
+                        }
+                    }
                 }
             }
-            
+
             ResponseBuilder rb = Response.ok(resourceURL.openStream());
             if (mediaType != null) {
                 rb.type(mediaType);
@@ -126,5 +153,23 @@ public class SwaggerUiService {
             throw new NotFoundException(ex);
         }
     }
+
+    /**
+     * Replaces the URL inside Swagger UI index.html file. The implementation does not attempt to 
+     * read the file and parse it as valid HTML but uses straightforward approach by looking for 
+     * the URL pattern of the SwaggerUIBundle initialization and replacing it.
+     * @param index index.html file content
+     * @param replacement replacement URL 
+     * @return index.html file content with replaced URL
+     */
+    protected String replaceUrl(final String index, final String replacement) {
+        final Matcher matcher = URL_PATTERN.matcher(index);
+
+        if (matcher.find()) {
+            return index.substring(0, matcher.start(1)) + replacement + index.substring(matcher.end(1)); 
+        }
+
+        return index;
+    }
 }
 
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationQueryConfigTest.java
similarity index 83%
copy from systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java
copy to systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationQueryConfigTest.java
index 5904c35..3e64231 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationQueryConfigTest.java
@@ -43,8 +43,8 @@ import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-public class SwaggerUiConfigurationTest extends AbstractClientServerTestBase {
-    private static final String PORT = allocatePort(SwaggerUiConfigurationTest.class);
+public class SwaggerUiConfigurationQueryConfigTest extends AbstractClientServerTestBase {
+    private static final String PORT = allocatePort(SwaggerUiConfigurationQueryConfigTest.class);
 
     public static class Server extends AbstractServerTestServerBase {
 
@@ -57,7 +57,7 @@ public class SwaggerUiConfigurationTest extends AbstractClientServerTestBase {
             sf.setProvider(new JacksonJsonProvider());
             final Swagger2Feature feature = new Swagger2Feature();
             feature.setRunAsFilter(false);
-            feature.setSwaggerUiConfig(new SwaggerUiConfig().url("/swagger.json"));
+            feature.setSwaggerUiConfig(new SwaggerUiConfig().url("/swagger.json").queryConfigEnabled(true));
             sf.setFeatures(Arrays.asList(feature));
             sf.setAddress("http://localhost:" + PORT + "/");
             return sf.create();
@@ -76,22 +76,6 @@ public class SwaggerUiConfigurationTest extends AbstractClientServerTestBase {
     }
 
     @Test
-    public void testUiRootResourceRedirect() {
-        // Test that Swagger UI resources do not interfere with
-        // application-specific ones and are accessible.
-        final String url = "http://localhost:" + getPort() + "/api-docs";
-
-        WebClient uiClient = WebClient
-            .create(url)
-            .accept("*/*");
-
-        try (Response response = uiClient.get()) {
-            assertThat(response.getStatus(), equalTo(Response.Status.TEMPORARY_REDIRECT.getStatusCode()));
-            assertThat(response.getHeaderString("Location"), equalTo(url + "?url=/swagger.json"));
-        }
-    }
-
-    @Test
     public void testUiRootResource() {
         // Test that Swagger UI resources do not interfere with
         // application-specific ones and are accessible.
@@ -103,6 +87,7 @@ public class SwaggerUiConfigurationTest extends AbstractClientServerTestBase {
         try (Response response = uiClient.get()) {
             String html = response.readEntity(String.class);
             assertThat(html, containsString("<!-- HTML"));
+            assertThat(html, containsString("url: \"https://petstore.swagger.io/v2/swagger.json\","));
             assertThat(response.getMediaType(), equalTo(MediaType.TEXT_HTML_TYPE));
         }
     }
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java
index 5904c35..fe821e5 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java
@@ -103,6 +103,24 @@ public class SwaggerUiConfigurationTest extends AbstractClientServerTestBase {
         try (Response response = uiClient.get()) {
             String html = response.readEntity(String.class);
             assertThat(html, containsString("<!-- HTML"));
+            assertThat(html, containsString("url: \"/swagger.json\","));
+            assertThat(response.getMediaType(), equalTo(MediaType.TEXT_HTML_TYPE));
+        }
+    }
+
+    @Test
+    public void testUiRootResourcePicksUrlFromConfigurationOnly() {
+        // Test that Swagger UI URL is picked from configuration only and 
+        // never from the query string (when query config is disabled).
+        WebClient uiClient = WebClient
+            .create("http://localhost:" + getPort() + "/api-docs")
+            .query("url", "http://malicious.site/swagger.json")
+            .accept("*/*");
+
+        try (Response response = uiClient.get()) {
+            String html = response.readEntity(String.class);
+            assertThat(html, containsString("<!-- HTML"));
+            assertThat(html, containsString("url: \"/swagger.json\","));
             assertThat(response.getMediaType(), equalTo(MediaType.TEXT_HTML_TYPE));
         }
     }