From d329e2da5d41d511e302cfd8ad31fec0a7ec3e8e Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Thu, 10 Sep 2015 00:30:11 +0200 Subject: [PATCH 1/2] Use -u 'username:password' for basic auth in the curl request snippet Previously, when a request was made that used basic auth, the curl snippet would configure the authentication header with the Base64-encoded header. This commit updates the snippet to use the more human-friendly -u option to provide the username and password in place of the authentication header. Closes gh-122 --- spring-restdocs-core/build.gradle | 1 + .../restdocs/curl/CurlRequestSnippet.java | 24 ++++++++++++++++++- .../curl/CurlRequestSnippetTests.java | 12 ++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/spring-restdocs-core/build.gradle b/spring-restdocs-core/build.gradle index 5f5be34f..335edf28 100644 --- a/spring-restdocs-core/build.gradle +++ b/spring-restdocs-core/build.gradle @@ -27,6 +27,7 @@ dependencies { compile 'junit:junit' compile 'org.springframework:spring-webmvc' compile 'javax.servlet:javax.servlet-api' + compile 'commons-codec:commons-codec:1.10' compile files(jmustacheRepackJar) jarjar 'com.googlecode.jarjar:jarjar:1.3' jmustache 'com.samskivert:jmustache@jar' diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java index 018cb0f1..7cebe503 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java @@ -29,12 +29,14 @@ import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.TemplatedSnippet; +import org.springframework.util.Base64Utils; import org.springframework.util.StringUtils; /** * A {@link Snippet} that documents the curl command for a request. * * @author Andy Wilkinson + * @author Paul-Christian Volkmer * @see CurlDocumentation#curlRequest() * @see CurlDocumentation#curlRequest(Map) */ @@ -73,6 +75,7 @@ public class CurlRequestSnippet extends TemplatedSnippet { StringWriter command = new StringWriter(); PrintWriter printer = new PrintWriter(command); writeOptionToIncludeHeadersInOutput(printer); + writeHttpBasicAuthorization(operation.getRequest(), printer); writeHttpMethodIfNecessary(operation.getRequest(), printer); writeHeaders(operation.getRequest(), printer); writePartsIfNecessary(operation.getRequest(), printer); @@ -92,9 +95,24 @@ public class CurlRequestSnippet extends TemplatedSnippet { } } + private void writeHttpBasicAuthorization(OperationRequest request, PrintWriter writer) { + for (Entry> entry : request.getHeaders().entrySet()) { + for (String header : entry.getValue()) { + if (isAuthBasicHeader(entry.getKey(), header)) { + String auth = new String(Base64Utils.decodeFromString(header.replace("Basic", "").trim())); + writer.print(String.format(" -u '%s'", auth)); + break; + } + } + } + } + private void writeHeaders(OperationRequest request, PrintWriter writer) { for (Entry> entry : request.getHeaders().entrySet()) { for (String header : entry.getValue()) { + if (isAuthBasicHeader(entry.getKey(), header)) { + continue; + } writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); } } @@ -142,4 +160,8 @@ public class CurlRequestSnippet extends TemplatedSnippet { || HttpMethod.POST.equals(request.getMethod()); } -} \ No newline at end of file + private boolean isAuthBasicHeader(String key, String value) { + return ("Authorization".equals(key) && value.startsWith("Basic")); + } + +} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java index 96d6927a..8f69267a 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java @@ -36,6 +36,7 @@ import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.test.ExpectedSnippet; import org.springframework.restdocs.test.OperationBuilder; +import org.springframework.util.Base64Utils; /** * Tests for {@link CurlRequestSnippet} @@ -44,6 +45,7 @@ import org.springframework.restdocs.test.OperationBuilder; * @author Yann Le Guern * @author Dmitriy Mayboroda * @author Jonathan Pearlin + * @author Paul-Christian Volkmer */ public class CurlRequestSnippetTests { @@ -257,4 +259,14 @@ public class CurlRequestSnippetTests { .request("http://localhost/foo").build()); } + @Test + public void httpBasicAuthorizationHeader() throws IOException { + this.snippet.expectCurlRequest("get-request") + .withContents(codeBlock("bash").content("$ curl 'http://localhost/foo' -i -u 'user:secret'")); + new CurlRequestSnippet().document(new OperationBuilder("get-request", this.snippet.getOutputDirectory()) + .request("http://localhost/foo") + .header(HttpHeaders.AUTHORIZATION, "Basic " + Base64Utils.encodeToString("user:secret".getBytes())) + .build()); + } + } From ce26bd4b1ad1f66e784fe60621ecbe06fadc7777 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 16 Sep 2015 17:39:24 -0400 Subject: [PATCH 2/2] Polish contribution - Make commons-codec an optional dependency - Isolate knowledge of basic auth header to code that produces the -u option Closes gh-120 --- build.gradle | 1 + spring-restdocs-core/build.gradle | 2 +- .../restdocs/curl/CurlRequestSnippet.java | 50 +++++++++---------- .../curl/CurlRequestSnippetTests.java | 13 +++-- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index b593dfb9..a849cd98 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,7 @@ subprojects { dependencies { dependency 'com.fasterxml.jackson.core:jackson-databind:2.4.6' dependency 'com.samskivert:jmustache:1.10' + dependency 'commons-codec:commons-codec:1.10' dependency 'javax.servlet:javax.servlet-api:3.1.0' dependency 'javax.validation:validation-api:1.1.0.Final' dependency 'junit:junit:4.12' diff --git a/spring-restdocs-core/build.gradle b/spring-restdocs-core/build.gradle index 335edf28..2de27164 100644 --- a/spring-restdocs-core/build.gradle +++ b/spring-restdocs-core/build.gradle @@ -27,11 +27,11 @@ dependencies { compile 'junit:junit' compile 'org.springframework:spring-webmvc' compile 'javax.servlet:javax.servlet-api' - compile 'commons-codec:commons-codec:1.10' compile files(jmustacheRepackJar) jarjar 'com.googlecode.jarjar:jarjar:1.3' jmustache 'com.samskivert:jmustache@jar' optional 'javax.validation:validation-api' + optional 'commons-codec:commons-codec' testCompile 'org.mockito:mockito-core' testCompile 'org.hamcrest:hamcrest-core' testCompile 'org.hamcrest:hamcrest-library' diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java index 7cebe503..0802bdf4 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequest; @@ -74,10 +75,10 @@ public class CurlRequestSnippet extends TemplatedSnippet { private String getOptions(Operation operation) { StringWriter command = new StringWriter(); PrintWriter printer = new PrintWriter(command); - writeOptionToIncludeHeadersInOutput(printer); - writeHttpBasicAuthorization(operation.getRequest(), printer); + writeIncludeHeadersInOutputOption(printer); + HttpHeaders headers = writeUserOptionIfNecessary(operation.getRequest(), printer); writeHttpMethodIfNecessary(operation.getRequest(), printer); - writeHeaders(operation.getRequest(), printer); + writeHeaders(headers, printer); writePartsIfNecessary(operation.getRequest(), printer); writeContent(operation.getRequest(), printer); @@ -85,34 +86,37 @@ public class CurlRequestSnippet extends TemplatedSnippet { return command.toString(); } - private void writeOptionToIncludeHeadersInOutput(PrintWriter writer) { + private void writeIncludeHeadersInOutputOption(PrintWriter writer) { writer.print("-i"); } + private HttpHeaders writeUserOptionIfNecessary(OperationRequest request, + PrintWriter writer) { + HttpHeaders headers = new HttpHeaders(); + headers.putAll(request.getHeaders()); + String authorization = headers.getFirst(HttpHeaders.AUTHORIZATION); + if (isAuthorizationBasicHeader(authorization)) { + String credentials = new String(Base64Utils.decodeFromString(authorization + .substring(5).trim())); + writer.print(String.format(" -u '%s'", credentials)); + headers.remove(HttpHeaders.AUTHORIZATION); + } + return headers; + } + + private boolean isAuthorizationBasicHeader(String header) { + return header != null && header.startsWith("Basic"); + } + private void writeHttpMethodIfNecessary(OperationRequest request, PrintWriter writer) { if (!HttpMethod.GET.equals(request.getMethod())) { writer.print(String.format(" -X %s", request.getMethod())); } } - private void writeHttpBasicAuthorization(OperationRequest request, PrintWriter writer) { - for (Entry> entry : request.getHeaders().entrySet()) { + private void writeHeaders(HttpHeaders headers, PrintWriter writer) { + for (Entry> entry : headers.entrySet()) { for (String header : entry.getValue()) { - if (isAuthBasicHeader(entry.getKey(), header)) { - String auth = new String(Base64Utils.decodeFromString(header.replace("Basic", "").trim())); - writer.print(String.format(" -u '%s'", auth)); - break; - } - } - } - } - - private void writeHeaders(OperationRequest request, PrintWriter writer) { - for (Entry> entry : request.getHeaders().entrySet()) { - for (String header : entry.getValue()) { - if (isAuthBasicHeader(entry.getKey(), header)) { - continue; - } writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); } } @@ -160,8 +164,4 @@ public class CurlRequestSnippet extends TemplatedSnippet { || HttpMethod.POST.equals(request.getMethod()); } - private boolean isAuthBasicHeader(String key, String value) { - return ("Authorization".equals(key) && value.startsWith("Basic")); - } - } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java index 8f69267a..2e15a116 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java @@ -260,12 +260,15 @@ public class CurlRequestSnippetTests { } @Test - public void httpBasicAuthorizationHeader() throws IOException { - this.snippet.expectCurlRequest("get-request") - .withContents(codeBlock("bash").content("$ curl 'http://localhost/foo' -i -u 'user:secret'")); - new CurlRequestSnippet().document(new OperationBuilder("get-request", this.snippet.getOutputDirectory()) + public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException { + this.snippet.expectCurlRequest("basic-auth").withContents( + codeBlock("bash").content( + "$ curl 'http://localhost/foo' -i -u 'user:secret'")); + new CurlRequestSnippet().document(new OperationBuilder("basic-auth", this.snippet + .getOutputDirectory()) .request("http://localhost/foo") - .header(HttpHeaders.AUTHORIZATION, "Basic " + Base64Utils.encodeToString("user:secret".getBytes())) + .header(HttpHeaders.AUTHORIZATION, + "Basic " + Base64Utils.encodeToString("user:secret".getBytes())) .build()); }