Skip to content

Commit 4385761

Browse files
authored
Merge pull request #11 from elreydetodo/mmeyer-fixes-for-rc2
Fix Java 7 compile; README updates; signed header order
2 parents 5efb3d3 + c0265d4 commit 4385761

File tree

10 files changed

+213
-101
lines changed

10 files changed

+213
-101
lines changed

README.md

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ build one with its internal builder:
3636
```java
3737
Request request = Request.builder()
3838
.method("POST")
39-
.uriWithQuery(URI.create("https://localhost/service/v2/users"))
40-
.body("{ field: \"foo\" }")
41-
.header("Content-Type", "application/json")
39+
.uriWithQuery(URI.create("https://akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net/billing-usage/v1/reportSources"))
40+
.body("{ \"field\": \"field value\" }")
41+
.header("X-Some-Signed-Header", "header value")
42+
.header("X-Some-Other-Signed-Header", "header value 2")
4243
.build();
4344
```
4445

46+
NOTE: You only need to include headers in your `Request` that will be included
47+
in the EdgeGrid request signature. Many APIs do not require any headers to be
48+
signed.
49+
4550
`EdgeGridV1Signer` is an implementation of the EdgeGrid V1 Signing Algorithm.
4651
You can use `EdgeGridV1Signer#getSignature(Request, ClientCredential)` to
4752
generate the `Authorization` header for an EdgeGrid request:
@@ -100,16 +105,16 @@ Sign your REST-assured request specification with a defined client credential:
100105

101106
```java
102107
given()
103-
.baseUri("https://endpoint.net")
104-
.filter(new RestAssuredEdgeGridFilter(credential))
108+
.baseUri("https://akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net")
109+
.filter(new RestAssuredEdgeGridFilter(clientCredential))
105110
.when()
106-
.get("/service/v2/users")
111+
.get("/billing-usage/v1/reportSources")
107112
.then()
108113
.statusCode(200);
109114
```
110115

111116
REST-assured request specifications *must* contain a relative path in `get(path)`, `post
112-
(path)` etc.
117+
(path)` etc.
113118

114119
## Usage with Google HTTP Client Library for Java
115120

@@ -130,22 +135,24 @@ Sign your HTTP request with a defined client credential:
130135
```java
131136
HttpTransport httpTransport = new ApacheHttpTransport();
132137
HttpRequestFactory requestFactory = httpTransport.createRequestFactory();
133-
URI uri = URI.create("https://endpoint.net/billing-usage/v1/reportSources");
138+
URI uri = URI.create("https://akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net/billing-usage/v1/reportSources");
134139
HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(uri));
135140

136-
GoogleHttpClientEdgeGridRequestSigner requestSigner = new GoogleHttpClientEdgeGridRequestSigner(credential);
141+
GoogleHttpClientEdgeGridRequestSigner requestSigner = new GoogleHttpClientEdgeGridRequestSigner(clientCredential);
137142
requestSigner.sign(request);
138143
request.execute();
139144
```
140145

141-
This, however, requires remembering to sign explicitly every request. Alternatively, you may create <code>HttpRequestFactory</code>
142-
that will be doing it for yourself:
146+
This, however, requires remembering to sign explicitly every request.
147+
Alternately, you may create an `HttpRequestFactory` that will automatically
148+
sign requests via an Interceptor:
143149

144150
```java
145-
private HttpRequestFactory createSigningRequestFactory(HttpTransport httpTransport) {
151+
private HttpRequestFactory createSigningRequestFactory() {
152+
HttpTransport httpTransport = new ApacheHttpTransport();
146153
return httpTransport.createRequestFactory(new HttpRequestInitializer() {
147154
public void initialize(HttpRequest request) throws IOException {
148-
request.setInterceptor(new GoogleHttpClientEdgeGridInterceptor(credential));
155+
request.setInterceptor(new GoogleHttpClientEdgeGridInterceptor(clientCredentialProvider));
149156
}
150157
});
151158
}
@@ -154,14 +161,19 @@ private HttpRequestFactory createSigningRequestFactory(HttpTransport httpTranspo
154161
And then
155162

156163
```java
157-
HttpTransport httpTransport = new ApacheHttpTransport();
158-
HttpRequestFactory requestFactory = createSigningRequestFactory(httpTransport);
159-
URI uri = URI.create("https://endpoint.net/billing-usage/v1/reportSources");
164+
HttpRequestFactory requestFactory = createSigningRequestFactory();
165+
URI uri = URI.create("https://akaa-baseurl-xxxxxxxxxxx-xxxxxxxxxxxxx.luna.akamaiapis.net/billing-usage/v1/reportSources");
160166
HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(uri));
161167

162168
request.execute();
163169
```
164170

171+
NOTE: In this example we have used a `ClientCredentialProvider` rather than
172+
a more simple `ClientCredential`. `ClientCredentialProvider` provides a
173+
mechanism to construct a `ClientCredential` at the time of a request based on
174+
any logic you may want. For example, your own implementation could read
175+
credentials from a database or other secret store.
176+
165177
## Releases
166178

167179
2.0:
@@ -178,12 +190,12 @@ A number of similar libraries for signing requests exist for popular
178190
programming languages:
179191

180192
* There are two Python bindings: a [command line tool similar to curl][1] and a [Python library][2].
181-
* [Ruby binding][2]
182-
* [Perl binding][3]
183-
* [Powershell binding][4]
184-
* [NodeJS binding][5]
185-
* [C# binding][6]
186-
* [Go binding][7]
193+
* [Ruby binding][3]
194+
* [Perl binding][4]
195+
* [Powershell binding][5]
196+
* [NodeJS binding][6]
197+
* [C# binding][7]
198+
* [Go binding][8]
187199

188200
[1]: https://github.com/akamai-open/edgegrid-curl
189201
[2]: https://github.com/akamai-open/AkamaiOPEN-edgegrid-python

edgegrid-signer-core/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
<groupId>ch.qos.logback</groupId>
2424
<artifactId>logback-classic</artifactId>
2525
</dependency>
26+
<dependency>
27+
<groupId>commons-codec</groupId>
28+
<artifactId>commons-codec</artifactId>
29+
</dependency>
2630
<dependency>
2731
<groupId>org.apache.commons</groupId>
2832
<artifactId>commons-configuration2</artifactId>

edgegrid-signer-core/src/main/java/com/akamai/edgegrid/signer/ClientCredential.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
package com.akamai.edgegrid.signer;
1818

19-
import java.util.HashSet;
2019
import java.util.Objects;
2120
import java.util.Set;
21+
import java.util.TreeSet;
2222

2323
import org.apache.commons.lang3.Validate;
2424
import org.apache.commons.lang3.builder.Builder;
@@ -42,7 +42,7 @@ public class ClientCredential implements Comparable<ClientCredential> {
4242
private String accessToken;
4343
private String clientSecret;
4444
private String clientToken;
45-
private Set<String> headersToSign;
45+
private TreeSet<String> headersToSign;
4646
private String host;
4747
private Integer maxBodySize;
4848

@@ -132,7 +132,8 @@ public static class ClientCredentialBuilder implements Builder<ClientCredential>
132132
private String accessToken;
133133
private String clientSecret;
134134
private String clientToken;
135-
private Set<String> headersToSign = new HashSet<>();
135+
// NOTE: Headers are expected to be in order, so we pre-sort them here by using TreeSet.
136+
private TreeSet<String> headersToSign = new TreeSet<>();
136137
private String host;
137138
private Integer maxBodySize;
138139

edgegrid-signer-core/src/main/java/com/akamai/edgegrid/signer/EdgeGridV1Signer.java

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@
1616

1717
package com.akamai.edgegrid.signer;
1818

19+
import java.net.URI;
20+
import java.nio.charset.StandardCharsets;
21+
import java.security.InvalidKeyException;
22+
import java.security.MessageDigest;
23+
import java.security.NoSuchAlgorithmException;
24+
import java.text.SimpleDateFormat;
25+
import java.util.ArrayList;
26+
import java.util.Date;
27+
import java.util.List;
28+
import java.util.Map;
29+
import java.util.TimeZone;
30+
import java.util.UUID;
31+
import java.util.regex.Matcher;
32+
import java.util.regex.Pattern;
33+
34+
import javax.crypto.Mac;
35+
import javax.crypto.spec.SecretKeySpec;
36+
37+
import org.apache.commons.codec.binary.Base64;
1938
import org.apache.commons.lang3.StringEscapeUtils;
2039
import org.apache.commons.lang3.StringUtils;
2140
import org.apache.commons.lang3.Validate;
@@ -27,18 +46,6 @@
2746
import com.akamai.edgegrid.signer.Request.RequestBuilder;
2847
import com.akamai.edgegrid.signer.exceptions.RequestSigningException;
2948

30-
import javax.crypto.Mac;
31-
import javax.crypto.spec.SecretKeySpec;
32-
import java.net.URI;
33-
import java.nio.charset.StandardCharsets;
34-
import java.security.InvalidKeyException;
35-
import java.security.MessageDigest;
36-
import java.security.NoSuchAlgorithmException;
37-
import java.text.SimpleDateFormat;
38-
import java.util.*;
39-
import java.util.regex.Matcher;
40-
import java.util.regex.Pattern;
41-
4249

4350
/**
4451
* <p>
@@ -85,8 +92,6 @@ public class EdgeGridV1Signer {
8592

8693
private static final Logger log = LoggerFactory.getLogger(EdgeGridV1Signer.class);
8794

88-
private final Base64.Encoder base64 = Base64.getEncoder();
89-
9095
/**
9196
* Creates signer with default configuration.
9297
*/
@@ -172,7 +177,7 @@ String getSignature(Request request, ClientCredential credential, long timestamp
172177
String timeStamp = formatTimeStamp(timestamp);
173178
String authData = getAuthData(credential, timeStamp, nonce);
174179
String signature = getSignature(request, credential, timeStamp, authData);
175-
log.debug(String.format("Signature: '%s'", signature));
180+
log.debug("Signature: {}", signature);
176181

177182
return getAuthorizationHeaderValue(authData, signature);
178183
}
@@ -181,23 +186,21 @@ private String getSignature(Request request, ClientCredential credential, String
181186
String authData) throws RequestSigningException {
182187
String signingKey = getSigningKey(timeStamp, credential.getClientSecret());
183188
String canonicalizedRequest = getCanonicalizedRequest(request, credential);
184-
log.debug(String.format("Canonicalized request: '%s'",
185-
StringEscapeUtils.escapeJava(canonicalizedRequest)));
189+
log.debug("Canonicalized request: {}", StringEscapeUtils.escapeJava(canonicalizedRequest));
186190
String dataToSign = getDataToSign(canonicalizedRequest, authData);
187-
log.debug(String.format("Data to sign: '%s'",
188-
StringEscapeUtils.escapeJava(dataToSign)));
191+
log.debug("Data to sign: {}", StringEscapeUtils.escapeJava(dataToSign));
189192

190193
return signAndEncode(dataToSign, signingKey);
191194
}
192195

193196
private String signAndEncode(String stringToSign, String signingKey) throws RequestSigningException {
194197
byte[] signatureBytes = sign(stringToSign, signingKey);
195-
return base64.encodeToString(signatureBytes);
198+
return Base64.encodeBase64String(signatureBytes);
196199
}
197200

198201
private String getSigningKey(String timeStamp, String clientSecret) throws RequestSigningException {
199202
byte[] signingKeyBytes = sign(timeStamp, clientSecret);
200-
return base64.encodeToString(signingKeyBytes);
203+
return Base64.encodeBase64String(signingKeyBytes);
201204
}
202205

203206
private String getDataToSign(String canonicalizedRequest, String authData) {
@@ -236,15 +239,15 @@ private String getCanonicalizedRequest(Request request, ClientCredential credent
236239
sb.append(request.getMethod().toUpperCase());
237240
sb.append('\t');
238241

239-
String scheme = StringUtils.defaultString(request.getUriWithQuery().getScheme(), "https");
242+
String scheme = StringUtils.defaultString(request.getUri().getScheme(), "https");
240243
sb.append(scheme.toLowerCase());
241244
sb.append('\t');
242245

243246
String host = credential.getHost();
244247
sb.append(host.toLowerCase());
245248
sb.append('\t');
246249

247-
String relativePath = getRelativePathWithQuery(request.getUriWithQuery());
250+
String relativePath = getRelativePathWithQuery(request.getUri());
248251
String relativeUrl = canonicalizeUri(relativePath);
249252
sb.append(relativeUrl);
250253
sb.append('\t');
@@ -272,6 +275,7 @@ private byte[] getHash(byte[] requestBody, int offset, int len) throws RequestSi
272275

273276
private String canonicalizeHeaders(Map<String, String> requestHeaders, ClientCredential credential) {
274277
List<String> headers = new ArrayList<>();
278+
// NOTE: Headers are expected to be in order. ClientCredential#headersToSign is a TreeSet.
275279
for (String headerName : credential.getHeadersToSign()) {
276280
String headerValue = requestHeaders.get(headerName);
277281
if (StringUtils.isBlank(headerValue)) {
@@ -304,18 +308,21 @@ private String getContentHash(String requestMethod, byte[] requestBody, int maxB
304308

305309
int lengthToHash = requestBody.length;
306310
if (lengthToHash > maxBodySize) {
307-
log.info(String.format("Content length '%d' is larger than the max '%d'. " +
308-
"Using first '%d' bytes for computing the hash.", lengthToHash, maxBodySize, maxBodySize));
311+
log.info("Content length '{}' exceeds signing length of '{}'. Less than the entire message will be signed.",
312+
lengthToHash,
313+
maxBodySize);
309314
lengthToHash = maxBodySize;
310315
} else {
311-
log.debug(String.format("Content (Base64): %s", base64.encodeToString(requestBody)));
316+
if (log.isTraceEnabled()) {
317+
log.trace("Content (Base64): {}", Base64.encodeBase64String(requestBody));
318+
}
312319
}
313320

314321
byte[] digestBytes = getHash(requestBody, 0, lengthToHash);
315-
log.debug(String.format("Content hash (Base64): %s", base64.encodeToString(digestBytes)));
322+
log.debug("Content hash (Base64): {}", Base64.encodeBase64String(digestBytes));
316323

317324
// (mgawinec) I removed support for non-retryable content, that used to reset the content for downstream handlers
318-
return base64.encodeToString(digestBytes);
325+
return Base64.encodeBase64String(digestBytes);
319326
}
320327

321328
}

0 commit comments

Comments
 (0)