diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java index 73cfb051c6..ba1db7ddef 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java @@ -21,9 +21,9 @@ import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import io.swagger.v3.parser.urlresolver.exceptions.HostDeniedException; import io.swagger.v3.parser.util.DeserializationUtils; +import io.swagger.v3.parser.util.OpenAPIDeserializer; import io.swagger.v3.parser.util.PathUtils; import io.swagger.v3.parser.util.RefUtils; -import io.swagger.v3.parser.util.OpenAPIDeserializer; import org.apache.commons.lang3.StringUtils; import java.io.File; @@ -72,6 +72,7 @@ public class ResolverCache { private Set resolveValidationMessages; private final ParseOptions parseOptions; protected boolean openapi31; + private final PermittedUrlsChecker permittedUrlsChecker; /* * a map that stores original external references, and their associated renamed @@ -94,6 +95,7 @@ public ResolverCache(OpenAPI openApi, List auths, String par this.rootPath = parentFileLocation; this.resolveValidationMessages = resolveValidationMessages; this.parseOptions = parseOptions; + this.permittedUrlsChecker = new PermittedUrlsChecker(parseOptions.getRemoteRefAllowList(), parseOptions.getRemoteRefBlockList()); if(parentFileLocation != null) { if(parentFileLocation.startsWith("http") || parentFileLocation.startsWith("jar")) { @@ -153,13 +155,13 @@ public T loadRef(String ref, RefFormat refFormat, Class expectedType) { } if(parentDirectory != null) { - contents = RefUtils.readExternalRef(file, refFormat, auths, parentDirectory); + contents = RefUtils.readExternalRef(file, refFormat, auths, parentDirectory, permittedUrlsChecker); } else if(rootPath != null && rootPath.startsWith("http")) { - contents = RefUtils.readExternalUrlRef(file, refFormat, auths, rootPath); + contents = RefUtils.readExternalUrlRef(file, refFormat, auths, rootPath, permittedUrlsChecker); } else if (rootPath != null) { - contents = RefUtils.readExternalClasspathRef(file, refFormat, auths, rootPath); + contents = RefUtils.readExternalClasspathRef(file, refFormat, auths, rootPath, permittedUrlsChecker); } externalFileCache.put(file, contents); @@ -382,9 +384,6 @@ private Object getFromMap(String ref, Map map, Pattern pattern) { protected void checkUrlIsPermitted(String refSet) { try { - PermittedUrlsChecker permittedUrlsChecker = new PermittedUrlsChecker(parseOptions.getRemoteRefAllowList(), - parseOptions.getRemoteRefBlockList()); - permittedUrlsChecker.verify(refSet); } catch (HostDeniedException e) { throw new RuntimeException(e.getMessage()); diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/ReferenceVisitor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/ReferenceVisitor.java index a36f9bbc8d..c0ca355386 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/ReferenceVisitor.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/ReferenceVisitor.java @@ -15,7 +15,6 @@ import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.parser.core.models.AuthorizationValue; import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; -import io.swagger.v3.parser.urlresolver.exceptions.HostDeniedException; import io.swagger.v3.parser.util.RemoteUrl; import org.apache.commons.lang3.StringUtils; import org.slf4j.LoggerFactory; @@ -35,6 +34,7 @@ public class ReferenceVisitor extends AbstractVisitor { protected OpenAPI31Traverser openAPITraverser; protected Reference reference; protected DereferencerContext context; + private PermittedUrlsChecker permittedUrlsChecker; public ReferenceVisitor( Reference reference, @@ -59,6 +59,8 @@ public ReferenceVisitor( this.visited = visited; this.visitedMap = visitedMap; this.context = context; + this.permittedUrlsChecker = new PermittedUrlsChecker(context.getParseOptions().getRemoteRefAllowList(), + context.getParseOptions().getRemoteRefBlockList()); } public String toBaseURI(String uri) throws Exception{ @@ -83,7 +85,7 @@ public Reference toReference(String uri) throws Exception{ return ref; } - public Reference toSchemaReference(String baseUri, JsonNode node) throws Exception{ + public Reference toSchemaReference(String baseUri, JsonNode node) { Map referenceSet = this.reference.getReferenceSet(); if (referenceSet.containsKey(baseUri)) { return referenceSet.get(baseUri); @@ -193,20 +195,21 @@ public Header visitHeader(Header header){ } @Override - public String readHttp(String uri, List auths) throws Exception { + public String readHttp(String uri, List auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception { if(context.getParseOptions().isSafelyResolveURL()){ - checkUrlIsPermitted(uri); + permittedUrlsChecker.verify(uri); + return RemoteUrl.urlToString(uri, auths, permittedUrlsChecker); } return RemoteUrl.urlToString(uri, auths); } public T resolveRef(T visiting, String ref, Class clazz, BiFunction traverseFunction){ try { - Reference reference = toReference(ref); + Reference referenceObject = toReference(ref); String fragment = ReferenceUtils.getFragment(ref); - JsonNode node = ReferenceUtils.jsonPointerEvaluate(fragment, reference.getJsonNode(), ref); - T resolved = openAPITraverser.deserializeFragment(node, clazz, ref, fragment, reference.getMessages()); - ReferenceVisitor visitor = new ReferenceVisitor(reference, openAPITraverser, this.visited, this.visitedMap, context); + JsonNode node = ReferenceUtils.jsonPointerEvaluate(fragment, referenceObject.getJsonNode(), ref); + T resolved = openAPITraverser.deserializeFragment(node, clazz, ref, fragment, referenceObject.getMessages()); + ReferenceVisitor visitor = new ReferenceVisitor(referenceObject, openAPITraverser, this.visited, this.visitedMap, context); return traverseFunction.apply(resolved, visitor); } catch (Exception e) { @@ -226,13 +229,13 @@ public Schema resolveSchemaRef(Schema visiting, String ref, List inherit } baseURI = ReferenceUtils.resolve(ref, baseURI); baseURI = ReferenceUtils.toBaseURI(baseURI); - Reference reference = null; + Reference referenceObject; boolean isAnchor = false; if (this.reference.getReferenceSet().containsKey(baseURI)) { - reference = this.reference.getReferenceSet().get(baseURI); + referenceObject = this.reference.getReferenceSet().get(baseURI); } else { - JsonNode node = null; + JsonNode node; try { node = parse(baseURI, this.reference.getAuths()); } catch (Exception e) { @@ -240,25 +243,25 @@ public Schema resolveSchemaRef(Schema visiting, String ref, List inherit baseURI = toBaseURI(ref); node = parse(baseURI, this.reference.getAuths()); } - reference = toSchemaReference(baseURI, node); + referenceObject = toSchemaReference(baseURI, node); } String fragment = ReferenceUtils.getFragment(ref); - JsonNode evaluatedNode = null; + JsonNode evaluatedNode; try { - evaluatedNode = ReferenceUtils.jsonPointerEvaluate(fragment, reference.getJsonNode(), ref); + evaluatedNode = ReferenceUtils.jsonPointerEvaluate(fragment, referenceObject.getJsonNode(), ref); } catch (RuntimeException e) { // maybe anchor - evaluatedNode = findAnchor(reference.getJsonNode(), fragment); + evaluatedNode = findAnchor(referenceObject.getJsonNode(), fragment); if (evaluatedNode == null) { throw new RuntimeException("Could not find " + fragment + " in contents of " + ref); } isAnchor = true; } - Schema resolved = openAPITraverser.deserializeFragment(evaluatedNode, Schema.class, ref, fragment, reference.getMessages()); + Schema resolved = openAPITraverser.deserializeFragment(evaluatedNode, Schema.class, ref, fragment, referenceObject.getMessages()); if (isAnchor) { resolved.$anchor(null); } - ReferenceVisitor visitor = new ReferenceVisitor(reference, openAPITraverser, this.visited, this.visitedMap, context); + ReferenceVisitor visitor = new ReferenceVisitor(referenceObject, openAPITraverser, this.visited, this.visitedMap, context); return openAPITraverser.traverseSchema(resolved, visitor, inheritedIds); } catch (Exception e) { LOGGER.error("Error resolving schema " + ref, e); @@ -313,13 +316,6 @@ public JsonNode parse(String absoluteUri, List auths) throws } } - return deserializeIntoTree(readURI(absoluteUri, auths)); - } - - protected void checkUrlIsPermitted(String refSet) throws HostDeniedException { - PermittedUrlsChecker permittedUrlsChecker = new PermittedUrlsChecker(context.getParseOptions().getRemoteRefAllowList(), - context.getParseOptions().getRemoteRefBlockList()); - - permittedUrlsChecker.verify(refSet); + return deserializeIntoTree(readURI(absoluteUri, auths, permittedUrlsChecker)); } } diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/Visitor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/Visitor.java index 7e1f26375d..560ab63b96 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/Visitor.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/Visitor.java @@ -17,6 +17,7 @@ import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.parser.core.models.AuthorizationValue; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import io.swagger.v3.parser.util.ClasspathHelper; import io.swagger.v3.parser.util.RemoteUrl; import org.apache.commons.io.IOUtils; @@ -68,18 +69,18 @@ default String readFile(String path) throws Exception { } } - default String readClasspath(String classPath) throws Exception { + default String readClasspath(String classPath) { return ClasspathHelper.loadFileFromClasspath(classPath); } - default String readHttp(String uri, List auths) throws Exception { - return RemoteUrl.urlToString(uri, auths); + default String readHttp(String uri, List auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception { + return RemoteUrl.urlToString(uri, auths, permittedUrlsChecker); } - default String readURI(String absoluteUri, List auths) throws Exception { + default String readURI(String absoluteUri, List auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception { URI resolved = new URI(absoluteUri); if (StringUtils.isNotBlank(resolved.getScheme())) { if (resolved.getScheme().startsWith("http")) { - return readHttp(absoluteUri, auths); + return readHttp(absoluteUri, auths, permittedUrlsChecker); } else if (resolved.getScheme().startsWith("file")) { return readFile(resolved.getPath()); } else if (resolved.getScheme().startsWith("classpath")) { diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java index 561a3b5679..1ea303994b 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java @@ -3,10 +3,8 @@ import io.swagger.v3.parser.core.models.AuthorizationValue; import io.swagger.v3.parser.models.RefFormat; import io.swagger.v3.parser.processors.ExternalRefProcessor; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; - -import static java.nio.charset.StandardCharsets.UTF_8; import java.io.FileInputStream; import java.io.IOException; @@ -18,6 +16,8 @@ import java.util.List; import java.util.Optional; +import static java.nio.charset.StandardCharsets.UTF_8; + public class RefUtils { private static final String REFERENCE_SEPARATOR = "#/"; @@ -50,10 +50,10 @@ public static String computeDefinitionName(String ref) { final String[] split = plausibleName.split("\\."); // Fix for issue-1621 and issue-1865 //validate number of dots - if(split.length > 2) { + if (split.length > 2) { //Remove dot so ref can be interpreted as internal and relative in Swagger-Core schema class 'set$ref' plausibleName = String.join("", Arrays.copyOf(split, split.length - 1)); - }else{ + } else { plausibleName = split[0]; } } @@ -66,9 +66,9 @@ public static Optional getExternalPath(String ref) { return Optional.empty(); } return Optional.of(ref.split(REFERENCE_SEPARATOR)) - .filter(it -> it.length == 2) - .map(it -> it[0]) - .filter(it -> !it.isEmpty()); + .filter(it -> it.length == 2) + .map(it -> it[0]) + .filter(it -> !it.isEmpty()); } public static boolean isAnExternalRefFormat(RefFormat refFormat) { @@ -76,14 +76,16 @@ public static boolean isAnExternalRefFormat(RefFormat refFormat) { } public static RefFormat computeRefFormat(String ref) { - RefFormat result = RefFormat.INTERNAL; + RefFormat result; ref = mungedRef(ref); - if(ref.startsWith("http")||ref.startsWith("https")) { + if (ref.startsWith("http") || ref.startsWith("https")) { result = RefFormat.URL; - } else if(ref.startsWith(REFERENCE_SEPARATOR)) { + } else if (ref.startsWith(REFERENCE_SEPARATOR)) { result = RefFormat.INTERNAL; - } else if(ref.startsWith(".") || ref.startsWith("/") || ref.indexOf(REFERENCE_SEPARATOR) > 0) { + } else if (ref.startsWith(".") || ref.startsWith("/") || ref.indexOf(REFERENCE_SEPARATOR) > 0) { result = RefFormat.RELATIVE; + } else { + result = RefFormat.INTERNAL; } return result; @@ -103,7 +105,7 @@ public static String mungedRef(String refString) { public static String readExternalUrlRef(String file, RefFormat refFormat, List auths, - String rootPath) { + String rootPath, PermittedUrlsChecker permittedUrlsChecker) { if (!RefUtils.isAnExternalRefFormat(refFormat)) { throw new RuntimeException("Ref is not external"); @@ -113,12 +115,12 @@ public static String readExternalUrlRef(String file, RefFormat refFormat, List auths, - String rootPath) { + String rootPath, PermittedUrlsChecker permittedUrlsChecker) { if (!RefUtils.isAnExternalRefFormat(refFormat)) { throw new RuntimeException("Ref is not external"); @@ -139,7 +141,7 @@ public static String readExternalClasspathRef(String file, RefFormat refFormat, try { if (refFormat == RefFormat.URL) { - result = RemoteUrl.urlToString(file, auths); + result = RemoteUrl.urlToString(file, auths, permittedUrlsChecker); } else { //its assumed to be a relative ref String pathRef = ExternalRefProcessor.join(rootPath, file); @@ -155,24 +157,32 @@ public static String readExternalClasspathRef(String file, RefFormat refFormat, } public static String buildUrl(String rootPath, String relativePath) { - if(rootPath == null || relativePath == null) { - return null; - } - - try { - int until = rootPath.lastIndexOf("/")+1; - String root = rootPath.substring(0, until); - URL rootUrl = new URL(root); - URL finalUrl = new URL(rootUrl, relativePath); - return finalUrl.toString(); - } - catch(Exception e) { - throw new RuntimeException(e); - } + if (rootPath == null || relativePath == null) { + return null; + } + + try { + int until = rootPath.lastIndexOf("/") + 1; + String root = rootPath.substring(0, until); + URL rootUrl = new URL(root); + URL finalUrl = new URL(rootUrl, relativePath); + return finalUrl.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } } + /** + * @deprecated - new readExternalRef with PermittedUrlsChecker is preferred because it prevents bypassing security checks + */ + @Deprecated public static String readExternalRef(String file, RefFormat refFormat, List auths, Path parentDirectory) { + return readExternalRef(file, refFormat, auths, parentDirectory, null); + } + + public static String readExternalRef(String file, RefFormat refFormat, List auths, + Path parentDirectory, PermittedUrlsChecker permittedUrlsChecker) { if (!RefUtils.isAnExternalRefFormat(refFormat)) { throw new RuntimeException("Ref is not external"); @@ -182,12 +192,12 @@ public static String readExternalRef(String file, RefFormat refFormat, List true; + + return connection -> { + if (connection instanceof HttpsURLConnection) { + final HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; + httpsConnection.setSSLSocketFactory(sf); + httpsConnection.setHostnameVerifier(trustAllNames); + httpsConnection.setConnectTimeout(CONNECTION_TIMEOUT); + httpsConnection.setReadTimeout(READ_TIMEOUT); + httpsConnection.setInstanceFollowRedirects(false); } }; - - return new ConnectionConfigurator() { - - @Override - public void process(URLConnection connection) { - if (connection instanceof HttpsURLConnection) { - final HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; - httpsConnection.setSSLSocketFactory(sf); - httpsConnection.setHostnameVerifier(trustAllNames); - httpsConnection.setConnectTimeout(CONNECTION_TIMEOUT); - httpsConnection.setReadTimeout(READ_TIMEOUT); - } - } - }; - } catch (NoSuchAlgorithmException e) { - LOGGER.error("Not Supported", e); - } catch (KeyManagementException e) { + } catch (NoSuchAlgorithmException | KeyManagementException e) { LOGGER.error("Not Supported", e); } } - return new ConnectionConfigurator() { - - @Override - public void process(URLConnection connection) { - connection.setConnectTimeout(CONNECTION_TIMEOUT); - connection.setReadTimeout(READ_TIMEOUT); - } + return connection -> { + connection.setConnectTimeout(CONNECTION_TIMEOUT); + connection.setReadTimeout(READ_TIMEOUT); }; } public static String cleanUrl(String url) { - String result = null; - try { - result =url.replaceAll("\\{", "%7B"). - replaceAll("\\}", "%7D"). - replaceAll(" ", "%20"); - }catch (Exception t){ - t.printStackTrace(); + if (url == null) { + return null; } - return result; + return url.replace("{", "%7B") + .replace("}", "%7D") + .replace(" ", "%20"); } public static String urlToString(String url, List auths) throws Exception { - InputStream is = null; - BufferedReader br = null; + return urlToString(url, auths, null); + } + + public static String urlToString(String url, List auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception { try { URLConnection conn; - do { + int redirectCount = 0; + + while (redirectCount <= MAX_REDIRECTS) { + //redirect count > 0 means we are checking only redirections + if (redirectCount > 0 && permittedUrlsChecker != null) { + permittedUrlsChecker.verify(url); + } final URL inUrl = new URL(cleanUrl(url)); final List query = new ArrayList<>(); final List header = new ArrayList<>(); - if (auths != null && auths.size() > 0) { - for (AuthorizationValue auth : auths) { - if (auth.getUrlMatcher()!= null) { - if (auth.getUrlMatcher().test(inUrl)) { - if ("query".equals(auth.getType())) { - appendValue(inUrl, auth, query); - } else if ("header".equals(auth.getType())) { - appendValue(inUrl, auth, header); - } - } - } - } - } - if (!query.isEmpty()) { - final URI inUri = inUrl.toURI(); - final StringBuilder newQuery = new StringBuilder(inUri.getQuery() == null ? "" : inUri.getQuery()); - for (AuthorizationValue item : query) { - if (newQuery.length() > 0) { - newQuery.append("&"); - } - newQuery.append(URLEncoder.encode(item.getKeyName(), UTF_8.name())).append("=") - .append(URLEncoder.encode(item.getValue(), UTF_8.name())); - } - conn = new URI(inUri.getScheme(), inUri.getAuthority(), inUri.getPath(), newQuery.toString(), - inUri.getFragment()).toURL().openConnection(); - } else { - conn = inUrl.openConnection(); - } + filterAndAssignAuthValues(auths, inUrl, query, header); + conn = prepareConnection(query, inUrl); CONNECTION_CONFIGURATOR.process(conn); - for (AuthorizationValue item : header) { - conn.setRequestProperty(item.getKeyName(), item.getValue()); - } + setRequestHeaders(header, conn); - conn.setRequestProperty("Accept", ACCEPT_HEADER_VALUE); - conn.setRequestProperty("User-Agent", USER_AGENT_HEADER_VALUE); conn.connect(); - url = ((HttpURLConnection) conn).getHeaderField("Location"); - } while ((301 == ((HttpURLConnection) conn).getResponseCode())||(302 == ((HttpURLConnection) conn).getResponseCode()) - || (307 == ((HttpURLConnection) conn).getResponseCode())||(308 == ((HttpURLConnection) conn).getResponseCode())); - InputStream in = conn.getInputStream(); - - StringBuilder contents = new StringBuilder(); - - BufferedReader input = new BufferedReader(new InputStreamReader(in, UTF_8)); + HttpURLConnection httpConn = (HttpURLConnection) conn; - for (int i = 0; i != -1; i = input.read()) { - char c = (char) i; - if (!Character.isISOControl(c)) { - contents.append((char) i); - } - if (c == '\n') { - contents.append('\n'); + if (isRedirect(httpConn)) { + url = conn.getHeaderField("Location"); + redirectCount++; + if (url == null) { + throw new IOException("Redirect response missing 'Location' header"); + } + } else { + return readResponse(conn); } } - - in.close(); - - return contents.toString(); + throw new IOException("Too many redirects (> " + MAX_REDIRECTS + ")"); } catch (javax.net.ssl.SSLProtocolException e) { LOGGER.warn("there is a problem with the target SSL certificate"); LOGGER.warn("**** you may want to run with -Djsse.enableSNIExtension=false\n\n"); - LOGGER.error("unable to read", e); + LOGGER.error("unable to read {}", e.getMessage()); throw e; } catch (Exception e) { - LOGGER.error("unable to read", e); + LOGGER.error("unable to read {}", e.getMessage()); throw e; - } finally { - if (is != null) { - is.close(); + } + } + + private static void filterAndAssignAuthValues(List auths, URL inUrl, List query, List header) { + if (auths != null && !auths.isEmpty()) { + for (AuthorizationValue auth : auths) { + if (auth.getUrlMatcher() != null && auth.getUrlMatcher().test(inUrl)) { + if ("query".equals(auth.getType())) { + appendValue(inUrl, auth, query); + } else if ("header".equals(auth.getType())) { + appendValue(inUrl, auth, header); + } + } } - if (br != null) { - br.close(); + } + } + + private static String readResponse(URLConnection conn) throws IOException { + + try (InputStream in = conn.getInputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(in, UTF_8))) { + + StringBuilder contents = new StringBuilder(); + int ch; + while ((ch = input.read()) != -1) { + char c = (char) ch; + if (!Character.isISOControl(c) || c == '\n') { + contents.append(c); + } } + return contents.toString(); } } + private static void setRequestHeaders(List header, URLConnection conn) { + for (AuthorizationValue item : header) { + conn.setRequestProperty(item.getKeyName(), item.getValue()); + } + + conn.setRequestProperty("Accept", ACCEPT_HEADER_VALUE); + conn.setRequestProperty("User-Agent", USER_AGENT_HEADER_VALUE); + } + + private static URLConnection prepareConnection(List query, URL inUrl) throws URISyntaxException, IOException { + URLConnection conn; + if (!query.isEmpty()) { + final URI inUri = inUrl.toURI(); + final StringBuilder newQuery = new StringBuilder(inUri.getQuery() == null ? "" : inUri.getQuery()); + for (AuthorizationValue item : query) { + if (newQuery.length() > 0) { + newQuery.append("&"); + } + newQuery.append(URLEncoder.encode(item.getKeyName(), UTF_8.name())).append("=") + .append(URLEncoder.encode(item.getValue(), UTF_8.name())); + } + conn = new URI(inUri.getScheme(), inUri.getAuthority(), inUri.getPath(), newQuery.toString(), + inUri.getFragment()).toURL().openConnection(); + } else { + conn = inUrl.openConnection(); + } + return conn; + } + + private static boolean isRedirect(HttpURLConnection conn) throws IOException { + int code = conn.getResponseCode(); + return code == 301 || code == 302 || code == 303 || code == 307 || code == 308; + } + private static void appendValue(URL url, AuthorizationValue value, Collection to) { - if (value instanceof ManagedValue) { - if (!((ManagedValue) value).process(url)) { + if (value instanceof ManagedValue && !((ManagedValue) value).process(url)) { return; } - } to.add(value); } diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ExternalRefProcessorTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ExternalRefProcessorTest.java index 10c5f6437a..4c490cb25b 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ExternalRefProcessorTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ExternalRefProcessorTest.java @@ -11,11 +11,11 @@ import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; import io.swagger.v3.parser.models.RefFormat; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import io.swagger.v3.parser.util.RemoteUrl; import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; -import mockit.Expectations; import org.testng.Assert; import org.testng.annotations.Test; @@ -24,9 +24,6 @@ import java.util.List; import java.util.Map; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; @@ -35,42 +32,33 @@ public class ExternalRefProcessorTest { @Injectable - ResolverCache cache; + ResolverCache cache; @Injectable - OpenAPI openAPI; + OpenAPI openAPI; - @Injectable Schema mockedModel; + @Injectable + Schema mockedModel; @Test - public void testProcessRefToExternalDefinition_NoNameConflict() throws Exception { + public void testProcessRefToExternalDefinition_NoNameConflict() { final String ref = "http://my.company.com/path/to/file.json#/foo/bar"; final RefFormat refFormat = RefFormat.URL; new Expectations() {{ - cache.getRenamedRef(ref); - times = 1; - result = null; + times = 1; + + cache.getRenamedRef(ref); cache.loadRef(ref, refFormat, Schema.class); - times = 1; result = mockedModel; - openAPI.getComponents(); - times = 1; - result = new Components(); - - openAPI.getComponents().getSchemas(); - times = 1; - result = null; cache.putRenamedRef(ref, "bar"); - openAPI.getComponents().addSchemas("bar", mockedModel); times=1; + openAPI.getComponents().addSchemas("bar", mockedModel); - cache.addReferencedKey("bar"); - times = 1; - result = null; + cache.addReferencedKey("bar"); }}; String newRef = new ExternalRefProcessor(cache, openAPI).processRefToExternalSchema(ref, refFormat); @@ -78,140 +66,132 @@ public void testProcessRefToExternalDefinition_NoNameConflict() throws Exception } - @Test - public void testNestedExternalRefs(){ - final RefFormat refFormat = RefFormat.URL; - - //Swagger test instance - OpenAPI testedOpenAPI = new OpenAPI(); - - //Start with customer, add address property to it - final String customerURL = "http://my.company.com/path/to/customer.json#/definitions/Customer"; - - final Schema customerModel = new Schema(); - Map custProps = new HashMap<>(); - Schema address = new Schema(); - final String addressURL = "http://my.company.com/path/to/address.json#/definitions/Address"; - address.set$ref(addressURL); - custProps.put("Address", address); - - //Create a 'local' reference to something in #/definitions, this should be ignored a no longer result in a null pointer exception - final String loyaltyURL = "#/definitions/LoyaltyScheme"; - - Schema loyaltyProp = new Schema(); - loyaltyProp.set$ref(loyaltyURL); - loyaltyProp.setName("LoyaltyCardNumber"); - List required = new ArrayList<>(); - required.add("LoyaltyCardNumber"); - loyaltyProp.setRequired(required); - - custProps.put("Loyalty", loyaltyProp); - customerModel.setProperties(custProps); - - //create address model, add Contact Ref Property to it - final Schema addressModel = new Schema(); - Map addressProps = new HashMap<>(); - Schema contact = new Schema(); - final String contactURL = "http://my.company.com/path/to/Contact.json#/definitions/Contact"; - contact.set$ref(contactURL); - addressProps.put("Contact", contact); - addressModel.setProperties(addressProps); - - - //Create contact model, with basic type property - final Schema contactModel = new Schema(); - Schema contactProp = new StringSchema(); - contactProp.setName("PhoneNumber"); - List requiredList = new ArrayList<>(); - requiredList.add("PhoneNumber"); - contactProp.setRequired(requiredList); - Map contactProps = new HashMap<>(); - contactProps.put("PhoneNumber", contactProp); - contactModel.setProperties(contactProps); - - new Expectations(){{ - cache.loadRef(customerURL, refFormat, Schema.class); - result = customerModel; - times = 1; - - cache.loadRef(addressURL, refFormat, Schema.class); - result = addressModel; - times = 1; - - cache.loadRef(contactURL, refFormat, Schema.class); - result = contactModel; - times = 1; - }}; - - new ExternalRefProcessor(cache, testedOpenAPI).processRefToExternalSchema(customerURL, refFormat); - - assertThat(testedOpenAPI.getComponents().getSchemas().get("Customer"), notNullValue()); - assertThat(testedOpenAPI.getComponents().getSchemas().get("Contact"), notNullValue()); - assertThat(testedOpenAPI.getComponents().getSchemas().get("Address"), notNullValue()); + public void testNestedExternalRefs() { + final RefFormat refFormat = RefFormat.URL; + + //Swagger test instance + OpenAPI testedOpenAPI = new OpenAPI(); + + //Start with customer, add address property to it + final String customerURL = "http://my.company.com/path/to/customer.json#/definitions/Customer"; + + final Schema customerModel = new Schema(); + Map custProps = new HashMap<>(); + Schema address = new Schema(); + final String addressURL = "http://my.company.com/path/to/address.json#/definitions/Address"; + address.set$ref(addressURL); + custProps.put("Address", address); + + //Create a 'local' reference to something in #/definitions, this should be ignored a no longer result in a null pointer exception + final String loyaltyURL = "#/definitions/LoyaltyScheme"; + + Schema loyaltyProp = new Schema(); + loyaltyProp.set$ref(loyaltyURL); + loyaltyProp.setName("LoyaltyCardNumber"); + List required = new ArrayList<>(); + required.add("LoyaltyCardNumber"); + loyaltyProp.setRequired(required); + + custProps.put("Loyalty", loyaltyProp); + customerModel.setProperties(custProps); + + //create address model, add Contact Ref Property to it + final Schema addressModel = new Schema(); + Map addressProps = new HashMap<>(); + Schema contact = new Schema(); + final String contactURL = "http://my.company.com/path/to/Contact.json#/definitions/Contact"; + contact.set$ref(contactURL); + addressProps.put("Contact", contact); + addressModel.setProperties(addressProps); + + + //Create contact model, with basic type property + final Schema contactModel = new Schema(); + Schema contactProp = new StringSchema(); + contactProp.setName("PhoneNumber"); + List requiredList = new ArrayList<>(); + requiredList.add("PhoneNumber"); + contactProp.setRequired(requiredList); + Map contactProps = new HashMap<>(); + contactProps.put("PhoneNumber", contactProp); + contactModel.setProperties(contactProps); + + new Expectations() {{ + cache.loadRef(customerURL, refFormat, Schema.class); + result = customerModel; + times = 1; + + cache.loadRef(addressURL, refFormat, Schema.class); + result = addressModel; + + cache.loadRef(contactURL, refFormat, Schema.class); + result = contactModel; + }}; + + new ExternalRefProcessor(cache, testedOpenAPI).processRefToExternalSchema(customerURL, refFormat); + + assertNotNull(testedOpenAPI.getComponents().getSchemas().get("Customer")); + assertNotNull(testedOpenAPI.getComponents().getSchemas().get("Contact")); + assertNotNull(testedOpenAPI.getComponents().getSchemas().get("Address")); } - @Mocked - RemoteUrl remoteUrl; - - - @Test - public void testRelativeRefIncludingUrlRef() - throws Exception { - final RefFormat refFormat = RefFormat.RELATIVE; - - final String url = "https://my.example.remote.url.com/globals.yaml"; - - final String expectedResult = "components:\n" + - " schemas:\n" + - " link-object:\n" + - " type: object\n" + - " additionalProperties:\n" + - " \"$ref\": \"#/components/schemas/rel-data\"\n" + - " rel-data:\n" + - " type: object\n" + - " required:\n" + - " - href\n" + - " properties:\n" + - " href:\n" + - " type: string\n" + - " note:\n" + - " type: string\n" + - " result:\n" + - " type: object\n" + - " properties:\n" + - " name:\n" + - " type: string\n" + - " _links:\n" + - " \"$ref\": \"#/components/schemas/link-object\"\n" + - ""; - List auths = null; - - new Expectations() {{ - RemoteUrl.urlToString(url, auths); - times = 1; - result = expectedResult; - }}; - - OpenAPI mockedOpenAPI = new OpenAPI(); - mockedOpenAPI.setComponents(new Components()); - mockedOpenAPI.getComponents().setSchemas(new HashMap<>()); - ResolverCache mockedResolverCache = new ResolverCache(mockedOpenAPI, null, null); - - ExternalRefProcessor processor = new ExternalRefProcessor(mockedResolverCache, mockedOpenAPI); - - processor.processRefToExternalSchema("./relative-with-url/relative-with-url.yaml#/relative-with-url", refFormat); - assertThat(((Schema) mockedOpenAPI.getComponents().getSchemas().get("relative-with-url").getProperties().get("Foo")).get$ref(), - is("https://my.example.remote.url.com/globals.yaml#/components/schemas/link-object") - ); - assertThat(mockedOpenAPI.getComponents().getSchemas().keySet().contains("link-object"), is(true)); - assertThat(mockedOpenAPI.getComponents().getSchemas().keySet().contains("rel-data"), is(true)); - // assert that ref is relative ref is resolved. and the file path is from root yaml file. - assertThat(((Schema) mockedOpenAPI.getComponents().getSchemas().get("relative-with-url").getProperties().get("Bar")).get$ref(), - is("./relative-with-url/relative-with-local.yaml#/relative-same-file") - ); - } + @Mocked + RemoteUrl remoteUrl; + + + @Test + public void testRelativeRefIncludingUrlRef() throws Exception { + final RefFormat refFormat = RefFormat.RELATIVE; + + final String url = "https://my.example.remote.url.com/globals.yaml"; + + final String expectedResult = "components:\n" + + " schemas:\n" + + " link-object:\n" + + " type: object\n" + + " additionalProperties:\n" + + " \"$ref\": \"#/components/schemas/rel-data\"\n" + + " rel-data:\n" + + " type: object\n" + + " required:\n" + + " - href\n" + + " properties:\n" + + " href:\n" + + " type: string\n" + + " note:\n" + + " type: string\n" + + " result:\n" + + " type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n" + + " _links:\n" + + " \"$ref\": \"#/components/schemas/link-object\"\n"; + + new Expectations() {{ + RemoteUrl.urlToString(url, null, (PermittedUrlsChecker) any); + times = 1; + result = expectedResult; + }}; + + OpenAPI mockedOpenAPI = new OpenAPI(); + mockedOpenAPI.setComponents(new Components()); + mockedOpenAPI.getComponents().setSchemas(new HashMap<>()); + ResolverCache mockedResolverCache = new ResolverCache(mockedOpenAPI, null, null); + + ExternalRefProcessor processor = new ExternalRefProcessor(mockedResolverCache, mockedOpenAPI); + + processor.processRefToExternalSchema("./relative-with-url/relative-with-url.yaml#/relative-with-url", refFormat); + assertEquals(((Schema) mockedOpenAPI.getComponents().getSchemas().get("relative-with-url").getProperties().get("Foo")).get$ref(), + "https://my.example.remote.url.com/globals.yaml#/components/schemas/link-object"); + assertTrue(mockedOpenAPI.getComponents().getSchemas().containsKey("link-object")); + assertTrue(mockedOpenAPI.getComponents().getSchemas().containsKey("rel-data")); + // assert that ref is relative ref is resolved. and the file path is from root yaml file. + assertEquals(((Schema) mockedOpenAPI.getComponents().getSchemas().get("relative-with-url").getProperties().get("Bar")).get$ref(), + "./relative-with-url/relative-with-local.yaml#/relative-same-file"); + } @Test public void testHandleComposedSchemasInArrayItems() { @@ -245,14 +225,14 @@ public void testHandleInlineRefsInComposedSchemas() { assertTrue(components.containsKey("ResponseAnyOf")); assertTrue(components.containsKey("Product")); - Schema allOfInlineProduct = (Schema)((Schema)components.get("ResponseAllOf").getAllOf().get(1)).getProperties().get("product"); - assertThat(allOfInlineProduct.get$ref(), is("#/components/schemas/Product")); + Schema allOfInlineProduct = (Schema) ((Schema) components.get("ResponseAllOf").getAllOf().get(1)).getProperties().get("product"); + assertEquals(allOfInlineProduct.get$ref(), "#/components/schemas/Product"); - Schema oneOfInlineProduct = (Schema)((Schema)components.get("ResponseOneOf").getOneOf().get(1)).getProperties().get("product"); - assertThat(oneOfInlineProduct.get$ref(), is("#/components/schemas/Product")); + Schema oneOfInlineProduct = (Schema) ((Schema) components.get("ResponseOneOf").getOneOf().get(1)).getProperties().get("product"); + assertEquals(oneOfInlineProduct.get$ref(), "#/components/schemas/Product"); - Schema anyOfInlineProduct = (Schema)((Schema)components.get("ResponseAnyOf").getAnyOf().get(1)).getProperties().get("product"); - assertThat(anyOfInlineProduct.get$ref(), is("#/components/schemas/Product")); + Schema anyOfInlineProduct = (Schema) ((Schema) components.get("ResponseAnyOf").getAnyOf().get(1)).getProperties().get("product"); + assertEquals(anyOfInlineProduct.get$ref(), "#/components/schemas/Product"); } @Test diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/CustomOpenAPIDereferencer.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/CustomOpenAPIDereferencer.java index 529ef973da..345ccf4577 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/CustomOpenAPIDereferencer.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/CustomOpenAPIDereferencer.java @@ -8,6 +8,7 @@ import io.swagger.v3.parser.reference.ReferenceVisitor; import io.swagger.v3.parser.reference.Traverser; import io.swagger.v3.parser.reference.Visitor; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import java.util.HashMap; import java.util.HashSet; @@ -40,7 +41,7 @@ public CustomVisitor( } @Override - public String readHttp(String uri, List auths) throws Exception { + public String readHttp(String uri, List auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception { if (uri.startsWith("http://example.com/custom")) { return "openapi: 3.1.0\n" + @@ -57,7 +58,7 @@ public String readHttp(String uri, List auths) throws Except " '200':\n" + " description: OK"; } else { - return super.readHttp(uri, auths); + return super.readHttp(uri, auths, permittedUrlsChecker); } } } diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/LocalReferenceTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/LocalReferenceTest.java index 8e936f6e68..70c993ac79 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/LocalReferenceTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/LocalReferenceTest.java @@ -1,12 +1,12 @@ package io.swagger.v3.parser.test; - import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.parser.OpenAPIV3Parser; import io.swagger.v3.parser.core.models.ParseOptions; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import io.swagger.v3.parser.util.RemoteUrl; import mockit.Expectations; import mockit.Mocked; @@ -14,6 +14,7 @@ import java.io.File; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; @@ -22,34 +23,34 @@ import static org.testng.Assert.assertTrue; public class LocalReferenceTest { + @Mocked - public RemoteUrl remoteUrl = new RemoteUrl(); + public RemoteUrl remoteUrl; - static String issue_454_yaml, issue_454_components_yaml; + private static String issue_454_yaml, issue_454_components_yaml; static { try { issue_454_yaml = readFile("src/test/resources/nested-network-references/issue-454.yaml"); issue_454_components_yaml = readFile("src/test/resources/nested-network-references/issue-454-components.yaml"); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } } @Test - public void testAuth() throws Exception { + public void testAuth() throws Exception { new Expectations() {{ - remoteUrl.urlToString("https://remote-server.com/issue-454.yaml", new ArrayList<>()); + RemoteUrl.urlToString("https://remote-server.com/issue-454.yaml", new ArrayList<>()); result = issue_454_yaml; - remoteUrl.urlToString("https://remote-components.com/issue-454-components", new ArrayList<>()); + RemoteUrl.urlToString("https://remote-components.com/issue-454-components", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_454_components_yaml; }}; ParseOptions options = new ParseOptions(); options.setResolve(true); OpenAPI swagger = new OpenAPIV3Parser().read("https://remote-server.com/issue-454.yaml", - null, options); + null, options); assertNotNull(swagger.getComponents().getSchemas().get("ErrorModel")); assertNotNull(swagger.getComponents().getSchemas().get("ModelWithNestedProperties")); @@ -67,7 +68,7 @@ public void testAuth() throws Exception { assertTrue(nestedModel.getProperties().get("name") instanceof StringSchema); } - static String readFile(String name) throws Exception { - return new String(Files.readAllBytes(new File(name).toPath()), Charset.forName("UTF-8")); + private static String readFile(String name) throws Exception { + return new String(Files.readAllBytes(new File(name).toPath()), StandardCharsets.UTF_8); } } diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NetworkReferenceTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NetworkReferenceTest.java index 96ed91bad6..bc0d4a4aaa 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NetworkReferenceTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NetworkReferenceTest.java @@ -1,7 +1,6 @@ package io.swagger.v3.parser.test; - import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.media.Schema; @@ -12,6 +11,7 @@ import io.swagger.v3.parser.core.models.AuthorizationValue; import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import io.swagger.v3.parser.util.RemoteUrl; import mockit.Expectations; import mockit.Mocked; @@ -19,7 +19,7 @@ import org.testng.annotations.Test; import java.io.File; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -30,7 +30,7 @@ public class NetworkReferenceTest { @Mocked - public RemoteUrl remoteUrl = new RemoteUrl(); + public RemoteUrl remoteUrl; private static String issue_323_yaml, issue_323_events_yaml, issue_323_paging_yaml, issue_323_bar_yaml; private static String issue_328_yaml, issue_328_events_yaml, issue_328_paging_yaml, issue_328_bar_yaml; @@ -39,55 +39,55 @@ public class NetworkReferenceTest { private static String issue_407_json; private static String issue_411_server, issue_411_components; private static String issue_742_json; - private static String issue_443_yaml,issue_443_ref_yaml ; + private static String issue_443_yaml, issue_443_ref_yaml; static { try { - issue_323_yaml = readFile("src/test/resources/nested-file-references/issue-323.yaml"); - issue_323_events_yaml = readFile("src/test/resources/nested-file-references/eventsCase9.yaml"); - issue_323_paging_yaml = readFile("src/test/resources/nested-file-references/common/pagingWithFolderRef.yaml"); - issue_323_bar_yaml = readFile("src/test/resources/nested-file-references/common/common2/bar.yaml"); - - issue_328_yaml = readFile("src/test/resources/nested-file-references/issue-328.yaml"); - issue_328_events_yaml = readFile("src/test/resources/nested-file-references/issue-328-events.yaml"); - issue_328_paging_yaml = readFile("src/test/resources/nested-file-references/common/issue-328-paging.yaml"); - issue_328_bar_yaml = readFile("src/test/resources/nested-file-references/common/common2/issue-328-bar.yaml"); - - issue_330_yaml = readFile("src/test/resources/nested-network-references/issue-330.yaml"); - issue_330_paging_yaml = readFile("src/test/resources/nested-network-references/common/issue-330-paging.yaml"); - issue_330_users_yaml = readFile("src/test/resources/nested-network-references/common/issue-330-users.yaml"); + issue_323_yaml = readFile("src/test/resources/nested-file-references/issue-323.yaml"); + issue_323_events_yaml = readFile("src/test/resources/nested-file-references/eventsCase9.yaml"); + issue_323_paging_yaml = readFile("src/test/resources/nested-file-references/common/pagingWithFolderRef.yaml"); + issue_323_bar_yaml = readFile("src/test/resources/nested-file-references/common/common2/bar.yaml"); + + issue_328_yaml = readFile("src/test/resources/nested-file-references/issue-328.yaml"); + issue_328_events_yaml = readFile("src/test/resources/nested-file-references/issue-328-events.yaml"); + issue_328_paging_yaml = readFile("src/test/resources/nested-file-references/common/issue-328-paging.yaml"); + issue_328_bar_yaml = readFile("src/test/resources/nested-file-references/common/common2/issue-328-bar.yaml"); + + issue_330_yaml = readFile("src/test/resources/nested-network-references/issue-330.yaml"); + issue_330_paging_yaml = readFile("src/test/resources/nested-network-references/common/issue-330-paging.yaml"); + issue_330_users_yaml = readFile("src/test/resources/nested-network-references/common/issue-330-users.yaml"); issue_330_entities_yaml = readFile("src/test/resources/nested-network-references/common/issue-330-entities.yaml"); - issue_335_json = readFile("src/test/resources/nested-file-references/issue-335.json"); - issue_335_bar_json = readFile("src/test/resources/nested-file-references/issue-335-bar.json"); + issue_335_json = readFile("src/test/resources/nested-file-references/issue-335.json"); + issue_335_bar_json = readFile("src/test/resources/nested-file-references/issue-335-bar.json"); - issue_407_json = readFile("src/test/resources/petstore.yaml"); + issue_407_json = readFile("src/test/resources/petstore.yaml"); - issue_411_server = readFile("src/test/resources/nested-network-references/issue-411-server.yaml"); - issue_411_components = readFile("src/test/resources/nested-network-references/issue-411-remote2.yaml"); + issue_411_server = readFile("src/test/resources/nested-network-references/issue-411-server.yaml"); + issue_411_components = readFile("src/test/resources/nested-network-references/issue-411-remote2.yaml"); - issue_742_json = readFile("src/test/resources/issue-742.json"); - issue_443_yaml = readFile("src/test/resources/swos-443/root.yaml"); - issue_443_ref_yaml = readFile("src/test/resources/swos-443/ref.yaml"); - } - catch(Exception e) { + issue_742_json = readFile("src/test/resources/issue-742.json"); + issue_443_yaml = readFile("src/test/resources/swos-443/root.yaml"); + issue_443_ref_yaml = readFile("src/test/resources/swos-443/ref.yaml"); + } catch (Exception e) { e.printStackTrace(); } } @Test(description = "option true, adds Original Location to messages when ref is remote") - public void testValidateExternalRefsTrueRemote() throws Exception{ + public void testValidateExternalRefsTrueRemote() throws Exception { ParseOptions options = new ParseOptions(); options.setValidateExternalRefs(true); options.setResolve(true); new Expectations() { { - remoteUrl.urlToString("http://localhost:8080/swos-443/root.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/swos-443/root.yaml", new ArrayList<>()); result = issue_443_yaml; - remoteUrl.urlToString("http://localhost:8080/swos-443/ref.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/swos-443/ref.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_443_ref_yaml; - }}; + } + }; SwaggerParseResult result = new OpenAPIV3Parser().readLocation("http://localhost:8080/swos-443/root.yaml", null, options); OpenAPI openAPI = result.getOpenAPI(); @@ -119,16 +119,16 @@ public void testValidateExternalRefsTrueRemote() throws Exception{ @Test public void testIssue323() throws Exception { new Expectations() {{ - remoteUrl.urlToString("http://localhost:8080/nested-file-references/issue-323.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/nested-file-references/issue-323.yaml", new ArrayList<>()); result = issue_323_yaml; - remoteUrl.urlToString("http://localhost:8080/nested-file-references/eventsCase9.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/nested-file-references/eventsCase9.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_323_events_yaml; - remoteUrl.urlToString("http://localhost:8080/nested-file-references/common/pagingWithFolderRef.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/nested-file-references/common/pagingWithFolderRef.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_323_paging_yaml; - remoteUrl.urlToString("http://localhost:8080/nested-file-references/common/common2/bar.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/nested-file-references/common/common2/bar.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_323_bar_yaml; }}; @@ -149,16 +149,16 @@ public void testIssue323() throws Exception { @Test public void testIssue328() throws Exception { new Expectations() {{ - remoteUrl.urlToString("http://localhost:8080/resources/swagger/issue-328.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/resources/swagger/issue-328.yaml", new ArrayList<>()); result = issue_328_yaml; - remoteUrl.urlToString("http://localhost:8080/resources/swagger/issue-328-events.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/resources/swagger/issue-328-events.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_328_events_yaml; - remoteUrl.urlToString("http://localhost:8080/resources/swagger/common/issue-328-paging.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/resources/swagger/common/issue-328-paging.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_328_paging_yaml; - remoteUrl.urlToString("http://localhost:8080/resources/swagger/common/common2/issue-328-bar.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://localhost:8080/resources/swagger/common/common2/issue-328-bar.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_328_bar_yaml; }}; @@ -178,16 +178,16 @@ public void testIssue328() throws Exception { @Test public void testIssue330() throws Exception { new Expectations() {{ - remoteUrl.urlToString("http://server1/resources/swagger.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://server1/resources/swagger.yaml", new ArrayList<>()); result = issue_330_yaml; - remoteUrl.urlToString("http://server1/resources/common/paging.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://server1/resources/common/paging.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_330_paging_yaml; - remoteUrl.urlToString("http://server1/resources/common/users.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://server1/resources/common/users.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_330_users_yaml; - remoteUrl.urlToString("http://server2/resources/common/entities.yaml", new ArrayList<>()); + RemoteUrl.urlToString("http://server2/resources/common/entities.yaml", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_330_entities_yaml; }}; ParseOptions options = new ParseOptions(); @@ -208,10 +208,10 @@ public void testIssue330() throws Exception { @Test public void testIssue335() throws Exception { new Expectations() {{ - remoteUrl.urlToString("http://server1/resources/swagger.json", new ArrayList<>()); + RemoteUrl.urlToString("http://server1/resources/swagger.json", new ArrayList<>()); result = issue_335_json; - remoteUrl.urlToString("http://server1/resources/Bar.json", new ArrayList<>()); + RemoteUrl.urlToString("http://server1/resources/Bar.json", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_335_bar_json; }}; @@ -231,7 +231,7 @@ public void testIssue335() throws Exception { @Test public void testPathReference() throws Exception { new Expectations() {{ - remoteUrl.urlToString("http://petstore.swagger.io/v2/swagger.json", new ArrayList<>()); + RemoteUrl.urlToString("http://petstore.swagger.io/v2/swagger.json", new ArrayList<>(), (PermittedUrlsChecker) any); result = issue_407_json; }}; @@ -251,21 +251,21 @@ public void testPathReference() throws Exception { final SwaggerParseResult result = parser.readContents(yaml); Assert.assertNotNull(result.getOpenAPI()); - assertTrue(result.getMessages().size() == 0); - assertTrue(result.getOpenAPI().getComponents().getSchemas().size() == 3); + assertEquals(result.getMessages().size(), 0); + assertEquals(result.getOpenAPI().getComponents().getSchemas().size(), 3); } @Test public void testIssue411() throws Exception { - final List< AuthorizationValue > auths = new ArrayList<>(); + final List auths = new ArrayList<>(); AuthorizationValue auth = new AuthorizationValue("Authorization", "OMG_SO_SEKR3T", "header"); auths.add(auth); new Expectations() {{ - remoteUrl.urlToString("http://remote1/resources/swagger.yaml", auths); + RemoteUrl.urlToString("http://remote1/resources/swagger.yaml", auths); result = issue_411_server; - remoteUrl.urlToString("http://remote2/resources/foo", auths); + RemoteUrl.urlToString("http://remote2/resources/foo", auths, (PermittedUrlsChecker) any); result = issue_411_components; }}; @@ -278,9 +278,9 @@ public void testIssue411() throws Exception { OpenAPI swagger = result.getOpenAPI(); assertNotNull(swagger.getPaths().get("/health")); PathItem health = swagger.getPaths().get("/health"); - assertTrue(health.getGet().getParameters().size() == 0); + assertEquals(health.getGet().getParameters().size(), 0); Schema responseRef = health.getGet().getResponses().get("200").getContent().get("*/*").getSchema(); - assertTrue(responseRef.get$ref() != null); + assertNotNull(responseRef.get$ref()); assertEquals(responseRef.get$ref(), "#/components/schemas/Success"); @@ -299,7 +299,7 @@ public void testIssue411() throws Exception { assertNotNull(error); Schema errorProp = error.getContent().get("*/*").getSchema(); assertNotNull(errorProp); - assertTrue(errorProp.get$ref() != null); + assertNotNull(errorProp.get$ref()); assertEquals(errorProp.get$ref(), "#/components/schemas/Error"); assertTrue(swagger.getComponents().getSchemas().get("Error") instanceof Schema); @@ -307,10 +307,10 @@ public void testIssue411() throws Exception { @Test public void testIssue742() throws Exception { - final List< AuthorizationValue > auths = new ArrayList<>(); + final List auths = new ArrayList<>(); new Expectations() {{ - remoteUrl.urlToString("http://www.example.io/one/two/swagger.json", auths); + RemoteUrl.urlToString("http://www.example.io/one/two/swagger.json", auths); result = issue_742_json; }}; @@ -321,18 +321,18 @@ public void testIssue742() throws Exception { Assert.assertNotNull(result.getOpenAPI()); Assert.assertNotNull(result.getOpenAPI().getServers()); - Assert.assertEquals(result.getOpenAPI().getServers().size(), 4); - Assert.assertEquals(result.getOpenAPI().getServers().get(0).getDescription(), "An absolute path"); - Assert.assertEquals(result.getOpenAPI().getServers().get(0).getUrl(), "https://api.absolute.org/v2"); - Assert.assertEquals(result.getOpenAPI().getServers().get(1).getDescription(), "Server relative to root path"); - Assert.assertEquals(result.getOpenAPI().getServers().get(1).getUrl(), "http://www.example.io/api/v2"); - Assert.assertEquals(result.getOpenAPI().getServers().get(2).getDescription(), "Server relative path 1"); - Assert.assertEquals(result.getOpenAPI().getServers().get(2).getUrl(), "http://www.example.io/one/two/path/v2"); - Assert.assertEquals(result.getOpenAPI().getServers().get(3).getDescription(), "Server relative path 2"); - Assert.assertEquals(result.getOpenAPI().getServers().get(3).getUrl(), "http://www.example.io/one/v2"); + Assert.assertEquals(4, result.getOpenAPI().getServers().size()); + Assert.assertEquals("An absolute path", result.getOpenAPI().getServers().get(0).getDescription()); + Assert.assertEquals("https://api.absolute.org/v2", result.getOpenAPI().getServers().get(0).getUrl()); + Assert.assertEquals("Server relative to root path", result.getOpenAPI().getServers().get(1).getDescription()); + Assert.assertEquals("http://www.example.io/api/v2", result.getOpenAPI().getServers().get(1).getUrl()); + Assert.assertEquals("Server relative path 1", result.getOpenAPI().getServers().get(2).getDescription()); + Assert.assertEquals("http://www.example.io/one/two/path/v2", result.getOpenAPI().getServers().get(2).getUrl()); + Assert.assertEquals("Server relative path 2", result.getOpenAPI().getServers().get(3).getDescription()); + Assert.assertEquals("http://www.example.io/one/v2", result.getOpenAPI().getServers().get(3).getUrl()); } static String readFile(String name) throws Exception { - return new String(Files.readAllBytes(new File(name).toPath()), Charset.forName("UTF-8")); + return new String(Files.readAllBytes(new File(name).toPath()), StandardCharsets.UTF_8); } } diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/RelativeReferenceTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/RelativeReferenceTest.java index b23becd6e4..79fa60b5f2 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/RelativeReferenceTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/RelativeReferenceTest.java @@ -4,18 +4,17 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.parameters.RequestBody; import io.swagger.v3.parser.OpenAPIV3Parser; -import io.swagger.v3.parser.core.models.AuthorizationValue; import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import io.swagger.v3.parser.util.RemoteUrl; import mockit.Expectations; import mockit.Mocked; - import org.testng.Assert; import org.testng.annotations.Test; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import static org.testng.Assert.assertNotNull; @@ -26,43 +25,43 @@ public class RelativeReferenceTest { @Mocked RemoteUrl remoteUrl; - static final String spec = + private static final String SPEC = "openapi: 3.0.0\n" + - "servers:\n" + - " - url: /\n" + - " - url: https://localhost:8080/{version}\n" + - " description: The local server\n" + - " variables:\n" + - " version:\n" + - " default: v2\n" + - " enum:\n" + - " - v1\n" + - " - v2\n"+ - "info:\n" + - " description: It works.\n" + - " version: 1.0.0\n" + - " title: My API\n" + - "paths:\n" + - " /samplePath:\n" + - " $ref: './path/samplePath.yaml'"; - static final String samplePath = + "servers:\n" + + " - url: /\n" + + " - url: https://localhost:8080/{version}\n" + + " description: The local server\n" + + " variables:\n" + + " version:\n" + + " default: v2\n" + + " enum:\n" + + " - v1\n" + + " - v2\n" + + "info:\n" + + " description: It works.\n" + + " version: 1.0.0\n" + + " title: My API\n" + + "paths:\n" + + " /samplePath:\n" + + " $ref: './path/samplePath.yaml'"; + private static final String SAMPLE_PATH = "get:\n" + - " responses:\n" + - " '200':\n" + - " description: It works\n" + - " requestBody:\n" + - " content:\n" + - " application/json:\n" + - " schema:\n" + - " type: object\n" + - " required: true"; + " responses:\n" + + " '200':\n" + + " description: It works\n" + + " requestBody:\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " type: object\n" + + " required: true"; @Test public void testIssueServerUrlValidation() throws Exception { new Expectations() {{ - RemoteUrl.urlToString("http://foo.bar.com/swagger.json", Arrays.asList(new AuthorizationValue[]{})); + RemoteUrl.urlToString("http://foo.bar.com/swagger.json", Collections.emptyList()); times = 1; - result = spec; + result = SPEC; }}; SwaggerParseResult swaggerParseResult = new OpenAPIV3Parser().readLocation("http://foo.bar.com/swagger.json", null, new ParseOptions()); @@ -73,13 +72,13 @@ public void testIssueServerUrlValidation() throws Exception { @Test public void testIssue213() throws Exception { new Expectations() {{ - RemoteUrl.urlToString("http://foo.bar.com/swagger.json", Arrays.asList(new AuthorizationValue[]{})); + RemoteUrl.urlToString("http://foo.bar.com/swagger.json", Collections.emptyList()); times = 1; - result = spec; + result = SPEC; - RemoteUrl.urlToString("http://foo.bar.com/path/samplePath.yaml", Arrays.asList(new AuthorizationValue[]{})); + RemoteUrl.urlToString("http://foo.bar.com/path/samplePath.yaml", Collections.emptyList(), (PermittedUrlsChecker) any); times = 1; - result = samplePath; + result = SAMPLE_PATH; }}; @@ -133,25 +132,21 @@ public void testIssue409() { " minLength: 6\n" + " maxLength: 254"; - OpenAPI swagger = (new OpenAPIV3Parser().readContents(yaml,null, null)).getOpenAPI(); + OpenAPI swagger = (new OpenAPIV3Parser().readContents(yaml, null, null)).getOpenAPI(); assertNotNull(swagger.getComponents().getSchemas().get("ID")); } @Test public void testResolveRelativePaths() { - ParseOptions options = new ParseOptions(); + ParseOptions options = new ParseOptions(); options.setResolve(true); SwaggerParseResult parseResult = new OpenAPIV3Parser().readLocation("/relative-references-example/openapi.yaml", null, options); - Assert.assertNotNull(parseResult.getOpenAPI()); - - HashSet validationMessages = new HashSet<>(null != parseResult.getMessages() ? parseResult.getMessages() : new ArrayList<>()); - + Assert.assertNotNull(parseResult.getOpenAPI()); - //validationMessages.forEach(msg->System.out.println(msg)); - //OpenAPI specification = parseResult.getOpenAPI(); - Assert.assertTrue(validationMessages.isEmpty(), validationMessages.toString()); + HashSet validationMessages = new HashSet<>(null != parseResult.getMessages() ? parseResult.getMessages() : new ArrayList<>()); + Assert.assertTrue(validationMessages.isEmpty(), validationMessages.toString()); } @Test diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/ResolverCacheTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/ResolverCacheTest.java index 0fa9b08aab..843725c9fa 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/ResolverCacheTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/ResolverCacheTest.java @@ -1,13 +1,6 @@ package io.swagger.v3.parser.test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; @@ -20,20 +13,22 @@ import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; import io.swagger.v3.parser.models.RefFormat; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import io.swagger.v3.parser.util.DeserializationUtils; import io.swagger.v3.parser.util.RefUtils; - -import org.apache.commons.lang3.tuple.Pair; -import org.testng.annotations.Test; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; - - import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; public class ResolverCacheTest { @@ -57,8 +52,13 @@ public class ResolverCacheTest { @Injectable DeserializationUtils deserializationUtils; + @BeforeMethod + public void init() { + openAPI = new OpenAPI(); + } + @Test - public void testMock() throws Exception { + public void testMock() throws JsonProcessingException { final RefFormat format = RefFormat.URL; final String ref = "http://my.company.com/path/to/file.json"; @@ -69,7 +69,7 @@ public void testMock() throws Exception { parseOptions.setValidateExternalRefs(true); new Expectations(deserializationUtils) {{ - RefUtils.readExternalUrlRef(ref, format, auths, "http://my.company.com/path/parent.json"); + RefUtils.readExternalUrlRef(ref, format, auths, "http://my.company.com/path/parent.json", (PermittedUrlsChecker) any); times = 1; result = contentsOfExternalFile; @@ -87,7 +87,7 @@ public void testMock() throws Exception { } @Test - public void testLoadExternalRef_NoDefinitionPath() throws Exception { + public void testLoadExternalRef_NoDefinitionPath() throws JsonProcessingException { final RefFormat format = RefFormat.URL; final String ref = "http://my.company.com/path/to/file.json"; @@ -98,7 +98,7 @@ public void testLoadExternalRef_NoDefinitionPath() throws Exception { parseOptions.setValidateExternalRefs(true); new Expectations(deserializationUtils) {{ - RefUtils.readExternalUrlRef(ref, format, auths, "http://my.company.com/path/parent.json"); + RefUtils.readExternalUrlRef(ref, format, auths, "http://my.company.com/path/parent.json", (PermittedUrlsChecker) any); times = 1; result = contentsOfExternalFile; @@ -111,8 +111,8 @@ public void testLoadExternalRef_NoDefinitionPath() throws Exception { Schema firstActualResult = cache.loadRef(ref, RefFormat.URL, Schema.class); - assertEquals(contentsOfExternalFile, cache.getExternalFileCache().get(ref)); - assertEquals(((Schema)cache.getResolutionCache().get(ref)).getType(), "string"); + assertEquals(cache.getExternalFileCache().get(ref), contentsOfExternalFile); + assertEquals(((Schema) cache.getResolutionCache().get(ref)).getType(), "string"); assertEquals(firstActualResult.getType(), "string"); //requesting the same ref a second time should not result in reading the external file again @@ -122,66 +122,65 @@ public void testLoadExternalRef_NoDefinitionPath() throws Exception { } @Test - public void testLoadExternalRefWithEscapedCharacters() throws Exception { + public void testLoadExternalRefWithEscapedCharacters() { final RefFormat format = RefFormat.URL; final String ref = "http://my.company.com/path/to/main.yaml"; final String contentsOfExternalFile = "openAPI: \"2.0\"\n" + - "\n" + - "info:\n" + - " version: 1.0.0\n" + - " title: Path include test case child\n" + - "\n" + - "paths:\n" + - " /foo~bar~1:\n" + - " get:\n" + - " responses:\n" + - " 200:\n" + - " description: \"Request successful\"\n"; + "\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Path include test case child\n" + + "\n" + + "paths:\n" + + " /foo~bar~1:\n" + + " get:\n" + + " responses:\n" + + " 200:\n" + + " description: \"Request successful\"\n"; new Expectations() {{ - RefUtils.readExternalUrlRef(ref, format, auths, "http://my.company.com/path/parent.json"); + RefUtils.readExternalUrlRef(ref, format, auths, "http://my.company.com/path/parent.json", (PermittedUrlsChecker) any); times = 1; result = contentsOfExternalFile; }}; ResolverCache cache = new ResolverCache(openAPI, auths, "http://my.company.com/path/parent.json"); - PathItem path = cache.loadRef(ref+"#/paths/~1foo~0bar~01", RefFormat.URL, PathItem.class); + PathItem path = cache.loadRef(ref + "#/paths/~1foo~0bar~01", RefFormat.URL, PathItem.class); assertNotNull(path); } @Test - public void testLoadExternalRefResponseWithNoContent() throws Exception { + public void testLoadExternalRefResponseWithNoContent() { final RefFormat format = RefFormat.URL; final String ref = "http://my.company.com/path/to/main.yaml"; final String contentsOfExternalFile = "openapi: 3.0.0\n" + - "\n" + - "info:\n" + - " version: 1.0.0\n" + - " title: Response include test case child\n" + - "\n" + - "components:\n" + - " responses:\n" + - " 200:\n" + - " description: Success\n"; + "\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Response include test case child\n" + + "\n" + + "components:\n" + + " responses:\n" + + " 200:\n" + + " description: Success\n"; new Expectations() {{ - RefUtils.readExternalUrlRef(ref, format, auths, "http://my.company.com/path/parent.json"); + RefUtils.readExternalUrlRef(ref, format, auths, "http://my.company.com/path/parent.json", (PermittedUrlsChecker) any); times = 1; result = contentsOfExternalFile; }}; ResolverCache cache = new ResolverCache(openAPI, auths, "http://my.company.com/path/parent.json"); - ApiResponse response = cache.loadRef(ref+"#/components/responses/200", RefFormat.URL, ApiResponse.class); + ApiResponse response = cache.loadRef(ref + "#/components/responses/200", RefFormat.URL, ApiResponse.class); assertNotNull(response); assertEquals(response.getDescription(), "Success"); assertNull(response.getContent()); } @Test - public void testLoadInternalParameterRef() throws Exception { - OpenAPI openAPI = new OpenAPI(); + public void testLoadInternalParameterRef() { openAPI.components(new Components().addParameters("foo", mockedParameter)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -193,8 +192,7 @@ public void testLoadInternalParameterRef() throws Exception { } @Test - public void testLoadInternalParameterRefWithSpaces() throws Exception { - OpenAPI openAPI = new OpenAPI(); + public void testLoadInternalParameterRefWithSpaces() { openAPI.components(new Components().addParameters("foo bar", mockedParameter)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -203,8 +201,7 @@ public void testLoadInternalParameterRefWithSpaces() throws Exception { } @Test - public void testLoadInternalDefinitionRef() throws Exception { - OpenAPI openAPI = new OpenAPI(); + public void testLoadInternalDefinitionRef() { openAPI.components(new Components().addSchemas("foo", mockedModel)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -216,8 +213,7 @@ public void testLoadInternalDefinitionRef() throws Exception { } @Test - public void testLoadInternalDefinitionRefWithSpaces() throws Exception { - OpenAPI openAPI = new OpenAPI(); + public void testLoadInternalDefinitionRefWithSpaces() { openAPI.components(new Components().addSchemas("foo bar", mockedModel)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -226,8 +222,7 @@ public void testLoadInternalDefinitionRefWithSpaces() throws Exception { } @Test - public void testLoadInternalDefinitionRefWithEscapedCharacters() throws Exception { - OpenAPI openAPI = new OpenAPI(); + public void testLoadInternalDefinitionRefWithEscapedCharacters() { openAPI.components(new Components().addSchemas("foo~bar/baz~1", mockedModel)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -236,8 +231,7 @@ public void testLoadInternalDefinitionRefWithEscapedCharacters() throws Exceptio } @Test - public void testLoadInternalResponseRef() throws Exception { - OpenAPI openAPI = new OpenAPI(); + public void testLoadInternalResponseRef() { openAPI.components(new Components().addResponses("foo", mockedResponse)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -248,8 +242,7 @@ public void testLoadInternalResponseRef() throws Exception { } @Test - public void testLoadInternalResponseRefWithSpaces() throws Exception { - OpenAPI openAPI = new OpenAPI(); + public void testLoadInternalResponseRefWithSpaces() { openAPI.components(new Components().addResponses("foo bar", mockedResponse)); ResolverCache cache = new ResolverCache(openAPI, auths, null); @@ -258,25 +251,11 @@ public void testLoadInternalResponseRefWithSpaces() throws Exception { } @Test - public void testRenameCache() throws Exception { + public void testRenameCache() { ResolverCache cache = new ResolverCache(openAPI, auths, null); assertNull(cache.getRenamedRef("foo")); cache.putRenamedRef("foo", "bar"); assertEquals(cache.getRenamedRef("foo"), "bar"); } - - private Pair constructJsonTree(String... properties) { - JsonNodeFactory factory = new JsonNodeFactory(true); - final ObjectNode parent = factory.objectNode(); - ObjectNode current = parent; - - for (String property : properties) { - current = current.putObject(property); - } - - current.put("key", "value"); - - return Pair.of((JsonNode) parent, (JsonNode) current); - } } diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/PermittedUrlsCheckerAllowLocal.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/PermittedUrlsCheckerAllowLocal.java new file mode 100644 index 0000000000..01aff67b3c --- /dev/null +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/PermittedUrlsCheckerAllowLocal.java @@ -0,0 +1,13 @@ +package io.swagger.v3.parser.util; + +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; + +import java.net.InetAddress; + +class PermittedUrlsCheckerAllowLocal extends PermittedUrlsChecker { + @Override + protected boolean isRestrictedIpRange(InetAddress ip) { + // Allow all IPs for testing purposes + return false; + } +} diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RefUtilsTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RefUtilsTest.java index 84428db730..9872dbea10 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RefUtilsTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RefUtilsTest.java @@ -5,6 +5,7 @@ import io.swagger.v3.parser.core.models.AuthorizationValue; import io.swagger.v3.parser.models.RefFormat; import io.swagger.v3.parser.processors.ExternalRefProcessor; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; import mockit.Injectable; import mockit.Mocked; import mockit.Expectations; @@ -43,13 +44,16 @@ public class RefUtilsTest { AuthorizationValue auth1; @Injectable AuthorizationValue auth2; + + private PermittedUrlsChecker permittedUrlsChecker = new PermittedUrlsChecker(); + public RefUtilsTest() { List auths = new ArrayList<>(); auths.add(auth1); auths.add(auth2); } @Test - public void testComputeDefinitionName() throws Exception { + public void testComputeDefinitionName() { //URL refs doComputeDefinitionNameTestCase("http://my.company.com/path/to/file.json", "file"); doComputeDefinitionNameTestCase("http://my.company.com/path/to/file.json#/foo", "foo"); @@ -87,18 +91,8 @@ private void doComputeDefinitionNameTestCase(String ref, String expectedDefiniti assertEquals(expectedDefinitionName, RefUtils.computeDefinitionName(ref)); } - private Map createMap(String... keys) { - Map definitionMap = new HashMap<>(); - - for (String key : keys) { - definitionMap.put(key, new Schema()); - } - - return definitionMap; - } - @Test - public void testIsAnExternalRefFormat() throws Exception { + public void testIsAnExternalRefFormat() { final RefFormat[] values = RefFormat.values(); for (RefFormat value : values) { @@ -118,7 +112,8 @@ public void testIsAnExternalRefFormat() throws Exception { } - @Mocked RemoteUrl remoteUrl; + @Mocked + RemoteUrl remoteUrl; @Test public void testReadExternalRef_UrlFormat() throws Exception { @@ -127,13 +122,13 @@ public void testReadExternalRef_UrlFormat() throws Exception { final String expectedResult = "really good json"; new Expectations() {{ - RemoteUrl.urlToString(url, auths); + RemoteUrl.urlToString(url, auths, permittedUrlsChecker); times = 1; result = expectedResult; }}; - String actualResult = RefUtils.readExternalRef(url, RefFormat.URL, auths, null); - assertEquals(expectedResult, actualResult); + String actualResult = RefUtils.readExternalRef(url, RefFormat.URL, auths, null, permittedUrlsChecker); + assertEquals(actualResult, expectedResult); } @Rule @@ -145,12 +140,12 @@ public void testReadExternalRef_UrlFormat_ExceptionThrown() throws Exception { final String url = "http://my.company.com/path/to/file.json"; new Expectations() {{ - RemoteUrl.urlToString(url, auths); + RemoteUrl.urlToString(url, auths, permittedUrlsChecker); times = 1; result = new Exception(); }}; thrown.expect(RuntimeException.class); - RefUtils.readExternalRef(url, RefFormat.URL, auths, null); + RefUtils.readExternalRef(url, RefFormat.URL, auths, null, permittedUrlsChecker); } @Mocked IOUtils ioUtils; @@ -164,6 +159,7 @@ public File tempFile(String name) throws Exception{ f.createNewFile(); return f; } + @Test public void testReadExternalRef_RelativeFileFormat( ) throws Exception { @@ -180,8 +176,8 @@ public void testReadExternalRef_RelativeFileFormat( }}; - String actualResult = RefUtils.readExternalRef(filePath, RefFormat.RELATIVE, auths, parentDirectory); - assertEquals(expectedResult, actualResult); + String actualResult = RefUtils.readExternalRef(filePath, RefFormat.RELATIVE, auths, parentDirectory, permittedUrlsChecker); + assertEquals(actualResult, expectedResult); } @@ -211,7 +207,6 @@ private void setupRelativeFileExpectations(@Injectable final Path parentDirector public void testReadExternalRef_RelativeFileFormat_ExceptionThrown() throws Exception { final String filePath = "file.json"; File file = tempFile(filePath); - final String expectedResult = "really good json"; setupRelativeFileExpectations(parentDirectory, pathToUse, file, filePath); @@ -229,16 +224,16 @@ public boolean matches(Object o) { return ((Exception)o).getCause().getClass().equals(IOException.class); } }); - RefUtils.readExternalRef(filePath, RefFormat.RELATIVE, auths, parentDirectory); + RefUtils.readExternalRef(filePath, RefFormat.RELATIVE, auths, parentDirectory, permittedUrlsChecker); } @Test - public void testReadExternalRef_InternalFormat() throws Exception { + public void testReadExternalRef_InternalFormat() { final String file = "#/defintiions/foo"; try { - RefUtils.readExternalRef(file, RefFormat.INTERNAL, auths, null); + RefUtils.readExternalRef(file, RefFormat.INTERNAL, auths, null, permittedUrlsChecker); fail("Should have thrown an exception"); } catch (RuntimeException e) { @@ -302,50 +297,38 @@ public void testPathJoinIssue1745() { @Test public void shouldReturnEmptyExternalPathForInternalReference() { - // given String ref = "#/components/test"; - // when Optional externalPath = RefUtils.getExternalPath(ref); - // then - assertThat(externalPath.isPresent(), is(false)); + assertFalse(externalPath.isPresent()); } @Test public void shouldReturnEmptyExternalPathForNullReference() { - // given String ref = null; - // when Optional externalPath = RefUtils.getExternalPath(ref); - // then - assertThat(externalPath.isPresent(), is(false)); + assertFalse(externalPath.isPresent()); } @Test public void shouldReturnEmptyExternalPathForEmptyReference() { - // given String ref = ""; - // when Optional externalPath = RefUtils.getExternalPath(ref); - // then - assertThat(externalPath.isPresent(), is(false)); + assertFalse(externalPath.isPresent()); } @Test public void shouldReturnEmptyExternalPathForInvalidReference() { - // given String ref = "test"; - // when Optional externalPath = RefUtils.getExternalPath(ref); - // then - assertThat(externalPath.isPresent(), is(false)); + assertFalse(externalPath.isPresent()); } @Test @@ -357,8 +340,8 @@ public void shouldReturnExternalPathForFileReference() { Optional externalPath = RefUtils.getExternalPath(ref); // then - assertThat(externalPath.isPresent(), is(true)); - assertThat(externalPath.get(), equalTo("test.yaml")); + assertTrue(externalPath.isPresent()); + assertEquals(externalPath.get(), "test.yaml"); } @Test @@ -370,8 +353,8 @@ public void shouldReturnExternalPathForHttpReference() { Optional externalPath = RefUtils.getExternalPath(ref); // then - assertThat(externalPath.isPresent(), is(true)); - assertThat(externalPath.get(), equalTo("http://localhost/schema.json")); + assertTrue(externalPath.isPresent()); + assertEquals(externalPath.get(), "http://localhost/schema.json"); } } diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RemoteUrlTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RemoteUrlTest.java index 9f2a1b68d7..b664a20b1d 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RemoteUrlTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RemoteUrlTest.java @@ -2,16 +2,19 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import io.swagger.v3.parser.core.models.AuthorizationValue; +import io.swagger.v3.parser.urlresolver.exceptions.HostDeniedException; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import javax.net.ssl.HttpsURLConnection; +import java.io.IOException; import java.net.SocketTimeoutException; import java.net.URL; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; @@ -35,15 +38,21 @@ public class RemoteUrlTest { @AfterMethod - public void tearDown() throws Exception { + public void tearDown() { wireMockServer.stop(); } @BeforeMethod - public void setUp() throws Exception { - wireMockServer = new WireMockServer(WIRE_MOCK_PORT); + public void setUp() { + System.setProperty("io.swagger.v3.parser.util.RemoteUrl.trustAll", "true"); + wireMockServer = new WireMockServer( + WireMockConfiguration.options() + .port(0) // disables HTTP + .httpsPort(WIRE_MOCK_PORT) // enables HTTPS + ); wireMockServer.start(); - WireMock.configureFor(WIRE_MOCK_PORT); + WireMock.configureFor("https", LOCALHOST, WIRE_MOCK_PORT); // Use HTTPS for WireMock + } @Test @@ -62,7 +71,7 @@ public void testReadARemoteUrl() throws Exception { assertEquals(actualBody, expectedBody); verify(getRequestedFor(urlEqualTo("/v2/pet/1")) - .withHeader("Accept", equalTo(EXPECTED_ACCEPTS_HEADER))); + .withHeader("Accept", equalTo(EXPECTED_ACCEPTS_HEADER))); } @Test @@ -73,13 +82,13 @@ public void testAuthorizationHeader() throws Exception { final String headerName = "Authorization"; final String headerValue = "foobar"; final AuthorizationValue authorizationValue = new AuthorizationValue(headerName, headerValue, "header"); - final String actualBody = RemoteUrl.urlToString(getUrl(), Arrays.asList(authorizationValue)); + final String actualBody = RemoteUrl.urlToString(getUrl(), Collections.singletonList(authorizationValue)); assertEquals(actualBody, expectedBody); verify(getRequestedFor(urlEqualTo("/v2/pet/1")) - .withHeader("Accept", equalTo(EXPECTED_ACCEPTS_HEADER)) - .withHeader(headerName, equalTo(headerValue)) + .withHeader("Accept", equalTo(EXPECTED_ACCEPTS_HEADER)) + .withHeader(headerName, equalTo(headerValue)) ); } @@ -91,14 +100,14 @@ public void testAuthorizationHeaderWithMatchingUrl() throws Exception { final String headerName = "Authorization"; final String headerValue = "foobar"; final AuthorizationValue authorizationValue = new AuthorizationValue(headerName, headerValue, "header", - url -> url.toString().startsWith("http://localhost")); - final String actualBody = RemoteUrl.urlToString(getUrl(), Arrays.asList(authorizationValue)); + url -> url.toString().startsWith("https://localhost")); + final String actualBody = RemoteUrl.urlToString(getUrl(), Collections.singletonList(authorizationValue)); assertEquals(actualBody, expectedBody); verify(getRequestedFor(urlEqualTo("/v2/pet/1")) - .withHeader("Accept", equalTo(EXPECTED_ACCEPTS_HEADER)) - .withHeader(headerName, equalTo(headerValue)) + .withHeader("Accept", equalTo(EXPECTED_ACCEPTS_HEADER)) + .withHeader(headerName, equalTo(headerValue)) ); } @@ -110,33 +119,32 @@ public void testAuthorizationHeaderWithNonMatchingUrl() throws Exception { final String headerValue = "foobar"; String authorization = "Authorization"; final AuthorizationValue authorizationValue = new AuthorizationValue(authorization, - headerValue, "header", u -> false); - final String actualBody = RemoteUrl.urlToString(getUrl(), Arrays.asList(authorizationValue)); + headerValue, "header", u -> false); + final String actualBody = RemoteUrl.urlToString(getUrl(), Collections.singletonList(authorizationValue)); assertEquals(actualBody, expectedBody); List requests = WireMock.findAll(getRequestedFor(urlEqualTo("/v2/pet/1"))); - assertEquals(1, requests.size()); + assertEquals(requests.size(), 1); assertFalse(requests.get(0).containsHeader(authorization)); } private String getUrl() { - return String.format("http://%s:%d/v2/pet/1", LOCALHOST, WIRE_MOCK_PORT); + return String.format("https://%s:%d/v2/pet/1", LOCALHOST, WIRE_MOCK_PORT); } private String setupStub() { final String expectedBody = "a really good body"; stubFor(get(urlEqualTo("/v2/pet/1")) .willReturn(aResponse() - .withBody(expectedBody) - .withHeader("Content-Type", "application/json") + .withBody(expectedBody) + .withHeader("Content-Type", "application/json") )); return expectedBody; } @Test public void testConnectionTimeoutEnforced() throws Exception { - System.setProperty("io.swagger.v3.parser.util.RemoteUrl.trustAll", "true"); RemoteUrl.ConnectionConfigurator configurator = RemoteUrl.createConnectionConfigurator(); URL url = new URL("https://10.255.255.1"); // non-routable IP to simulate timeout HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); @@ -152,4 +160,56 @@ public void testConnectionTimeoutEnforced() throws Exception { assertTrue(duration <= CONNECTION_TIMEOUT + 2000, "Timeout was not enforced properly (took too long)"); } } + + @Test(expectedExceptions = IOException.class) + public void testTooManyRedirectsThrowsException() throws Exception { + String nextPath = "/redirect"; + // Chain 6 redirects + for (int i = 0; i < 6; i++) { + String target = "/redirect" + (i + 1); + stubFor(get(urlEqualTo(nextPath)) + .willReturn(aResponse() + .withStatus(302) + .withHeader("Location", wireMockServer.baseUrl() + target))); + nextPath = target; + } + + // Add stub for /redirect6 to return a 200 OK response, but it's not expected to be reached + stubFor(get(urlEqualTo(nextPath)) + .willReturn(aResponse() + .withStatus(200) + .withBody("Final destination"))); + + String startUrl = String.format("https://%s:%d/redirect", LOCALHOST, WIRE_MOCK_PORT); + + try { + RemoteUrl.urlToString(startUrl, null, new PermittedUrlsCheckerAllowLocal()); + } catch (IOException e) { + assertTrue(e.getMessage().contains("Too many redirects")); + throw e; + } + } + + @Test(expectedExceptions = HostDeniedException.class) + public void testRedirectWithForbiddenProtocolThrowsException() throws Exception { + String nextPath = "/redirect"; + for (int i = 0; i < 3; i++) { + String target = "/redirect" + (i + 1); + stubFor(get(urlEqualTo(nextPath)) + .willReturn(aResponse() + .withStatus(302) + .withHeader("Location", "ftp://localhost/" + target))); + nextPath = target; + } + + // Add stub for /redirect3 to return a 200 OK response, but it's not expected to be reached + stubFor(get(urlEqualTo(nextPath)) + .willReturn(aResponse() + .withStatus(200) + .withBody("Final destination"))); + + String startUrl = String.format("https://%s:%d/redirect", LOCALHOST, WIRE_MOCK_PORT); + + RemoteUrl.urlToString(startUrl, null, new PermittedUrlsCheckerAllowLocal()); + } } diff --git a/pom.xml b/pom.xml index fd370e495c..c54dd54dd4 100644 --- a/pom.xml +++ b/pom.xml @@ -55,20 +55,13 @@ maven-surefire-plugin ${surefire-version} - none:none -javaagent:"${settings.localRepository}"/org/jmockit/jmockit/${jmockit-version}/jmockit-${jmockit-version}.jar --add-opens java.base/java.lang=ALL-UNNAMED -Djdk.attach.allowAttachSelf + + true +