This is an automated email from the ASF dual-hosted git repository. reta pushed a commit to branch 3.5.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/3.5.x-fixes by this push: new cb52151 CXF-8636: Swagger2Feature: Can't set url in UI through SwaggerUiConfig (#906) cb52151 is described below commit cb521517edfb65e4ff53a37c86a15c72b86c507d Author: Andriy Redko <drr...@gmail.com> AuthorDate: Mon Feb 14 12:08:21 2022 -0500 CXF-8636: Swagger2Feature: Can't set url in UI through SwaggerUiConfig (#906) (cherry picked from commit 90fd6d465c7b9bbe108aa6c744744ce19fecd93d) --- 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 8f48e4e..641058d 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 5ecdc7b..0e4b836 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -207,7 +207,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)); } }