mirror of https://github.com/apache/jclouds.git
allow filtering to be chained by returning the request on filter()
git-svn-id: http://jclouds.googlecode.com/svn/trunk@1854 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
parent
c1c6ab959b
commit
fbb7740f17
|
@ -23,14 +23,16 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.aws.s3.filters;
|
package org.jclouds.aws.s3.filters;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import org.jclouds.aws.s3.reference.S3Constants;
|
import org.jclouds.aws.s3.reference.S3Constants;
|
||||||
import org.jclouds.http.HttpException;
|
import org.jclouds.http.HttpException;
|
||||||
|
@ -53,8 +55,8 @@ import com.google.inject.name.Named;
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class RequestAuthorizeSignature implements HttpRequestFilter {
|
public class RequestAuthorizeSignature implements HttpRequestFilter {
|
||||||
private final String[] firstHeadersToSign = new String[] {
|
private final String[] firstHeadersToSign = new String[] { "Content-MD5",
|
||||||
"Content-MD5", HttpHeaders.CONTENT_TYPE, HttpHeaders.DATE };
|
HttpHeaders.CONTENT_TYPE, HttpHeaders.DATE };
|
||||||
|
|
||||||
private final String accessKey;
|
private final String accessKey;
|
||||||
private final String secretKey;
|
private final String secretKey;
|
||||||
|
@ -100,18 +102,16 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
|
||||||
timeStamp = new AtomicReference<String>(createNewStamp());
|
timeStamp = new AtomicReference<String>(createNewStamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void filter(HttpRequest request) throws HttpException {
|
public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||||
|
replaceDateHeader(request);
|
||||||
String toSign = createStringToSign(request);
|
String toSign = createStringToSign(request);
|
||||||
|
calculateAndReplaceAuthHeader(request, toSign);
|
||||||
addAuthHeader(request, toSign);
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String createStringToSign(HttpRequest request) {
|
public String createStringToSign(HttpRequest request) {
|
||||||
StringBuilder buffer = new StringBuilder();
|
StringBuilder buffer = new StringBuilder();
|
||||||
// re-sign the request
|
// re-sign the request
|
||||||
removeOldHeaders(request);
|
|
||||||
addDateHeader(request);
|
|
||||||
appendMethod(request, buffer);
|
appendMethod(request, buffer);
|
||||||
appendHttpHeaders(request, buffer);
|
appendHttpHeaders(request, buffer);
|
||||||
appendAmzHeaders(request, buffer);
|
appendAmzHeaders(request, buffer);
|
||||||
|
@ -120,27 +120,30 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeOldHeaders(HttpRequest request) {
|
private void calculateAndReplaceAuthHeader(HttpRequest request, String toSign)
|
||||||
request.getHeaders().removeAll(HttpHeaders.AUTHORIZATION);
|
throws HttpException {
|
||||||
request.getHeaders().removeAll(HttpHeaders.DATE);
|
String signature = signString(toSign);
|
||||||
|
request.getHeaders().replaceValues(HttpHeaders.AUTHORIZATION,
|
||||||
|
Collections.singletonList("AWS " + accessKey + ":" + signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAuthHeader(HttpRequest request, String toSign) throws HttpException {
|
public String signString(String toSign) {
|
||||||
String signature;
|
String signature;
|
||||||
try {
|
try {
|
||||||
signature = HttpUtils.hmacSha1Base64(toSign, secretKey.getBytes());
|
signature = HttpUtils.hmacSha1Base64(toSign, secretKey.getBytes());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new HttpException("error signing request", e);
|
throw new HttpException("error signing request", e);
|
||||||
}
|
}
|
||||||
request.getHeaders().put(HttpHeaders.AUTHORIZATION, "AWS " + accessKey + ":" + signature);
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendMethod(HttpRequest request, StringBuilder toSign) {
|
private void appendMethod(HttpRequest request, StringBuilder toSign) {
|
||||||
toSign.append(request.getMethod()).append("\n");
|
toSign.append(request.getMethod()).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDateHeader(HttpRequest request) {
|
private void replaceDateHeader(HttpRequest request) {
|
||||||
request.getHeaders().put(HttpHeaders.DATE, timestampAsHeaderString());
|
request.getHeaders().replaceValues(HttpHeaders.DATE,
|
||||||
|
Collections.singletonList(timestampAsHeaderString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendAmzHeaders(HttpRequest request, StringBuilder toSign) {
|
private void appendAmzHeaders(HttpRequest request, StringBuilder toSign) {
|
||||||
|
@ -174,7 +177,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void appendUriPath(HttpRequest request, StringBuilder toSign) {
|
void appendUriPath(HttpRequest request, StringBuilder toSign) {
|
||||||
|
|
||||||
toSign.append(request.getEndpoint().getPath());
|
toSign.append(request.getEndpoint().getRawPath());
|
||||||
|
|
||||||
// ...however, there are a few exceptions that must be included in the signed URI.
|
// ...however, there are a few exceptions that must be included in the signed URI.
|
||||||
if (request.getEndpoint().getQuery() != null) {
|
if (request.getEndpoint().getQuery() != null) {
|
||||||
|
|
|
@ -33,22 +33,60 @@ import org.jclouds.aws.s3.reference.S3Constants;
|
||||||
import org.jclouds.http.HttpMethod;
|
import org.jclouds.http.HttpMethod;
|
||||||
import org.jclouds.http.HttpRequest;
|
import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.util.DateService;
|
import org.jclouds.util.DateService;
|
||||||
|
import org.testng.annotations.BeforeClass;
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.name.Names;
|
import com.google.inject.name.Names;
|
||||||
|
|
||||||
@Test(groups = "unit", testName = "s3.RequestAuthorizeSignatureTest")
|
@Test(groups = "unit", testName = "s3.RequestAuthorizeSignatureTest")
|
||||||
public class RequestAuthorizeSignatureTest {
|
public class RequestAuthorizeSignatureTest {
|
||||||
|
|
||||||
|
private Injector injector;
|
||||||
|
private RequestAuthorizeSignature filter;
|
||||||
|
|
||||||
|
@DataProvider(parallel = true)
|
||||||
|
public Object[][] dataProvider() {
|
||||||
|
return new Object[][] {
|
||||||
|
{ new HttpRequest(HttpMethod.GET, URI.create("http://s3.amazonaws.com:80")) },
|
||||||
|
{ new HttpRequest(
|
||||||
|
HttpMethod.GET,
|
||||||
|
URI
|
||||||
|
.create("http://adriancole.s3int5.s3-external-3.amazonaws.com:80/testObject")) },
|
||||||
|
{ new HttpRequest(HttpMethod.GET, URI.create("http://s3.amazonaws.com:80/?acl"))
|
||||||
|
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE this test is dependent on how frequently the timestamp updates. At the time of writing,
|
||||||
|
* this was once per second. If this timestamp update interval is increased, it could make this
|
||||||
|
* test appear to hang for a long time.
|
||||||
|
*/
|
||||||
|
@Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 3000)
|
||||||
|
void testIdempotent(HttpRequest request) {
|
||||||
|
filter.filter(request);
|
||||||
|
String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION);
|
||||||
|
String date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
|
||||||
|
int iterations = 1;
|
||||||
|
while (filter.filter(request).getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) {
|
||||||
|
iterations++;
|
||||||
|
assertEquals(signature, request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION));
|
||||||
|
}
|
||||||
|
System.out.printf("%s: %d iterations before the timestamp updated %n", Thread.currentThread()
|
||||||
|
.getName(), iterations);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAppendBucketNameHostHeader() {
|
void testAppendBucketNameHostHeader() {
|
||||||
URI host = URI.create("http://s3.amazonaws.com:80");
|
URI host = URI.create("http://s3.amazonaws.com:80");
|
||||||
HttpRequest request = new HttpRequest(HttpMethod.GET, host);
|
HttpRequest request = new HttpRequest(HttpMethod.GET, host);
|
||||||
request.getHeaders().put(HttpHeaders.HOST, "adriancole.s3int5.s3.amazonaws.com");
|
request.getHeaders().put(HttpHeaders.HOST, "adriancole.s3int5.s3.amazonaws.com");
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
createFilter().appendBucketName(request, builder);
|
filter.appendBucketName(request, builder);
|
||||||
assertEquals(builder.toString(), "/adriancole.s3int5");
|
assertEquals(builder.toString(), "/adriancole.s3int5");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +95,7 @@ public class RequestAuthorizeSignatureTest {
|
||||||
URI host = URI.create("http://s3.amazonaws.com:80/?acl");
|
URI host = URI.create("http://s3.amazonaws.com:80/?acl");
|
||||||
HttpRequest request = new HttpRequest(HttpMethod.GET, host);
|
HttpRequest request = new HttpRequest(HttpMethod.GET, host);
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
createFilter().appendUriPath(request, builder);
|
filter.appendUriPath(request, builder);
|
||||||
assertEquals(builder.toString(), "/?acl");
|
assertEquals(builder.toString(), "/?acl");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +107,7 @@ public class RequestAuthorizeSignatureTest {
|
||||||
HttpRequest request = new HttpRequest(HttpMethod.GET, host);
|
HttpRequest request = new HttpRequest(HttpMethod.GET, host);
|
||||||
request.getHeaders().put(HttpHeaders.HOST, "s3.amazonaws.com");
|
request.getHeaders().put(HttpHeaders.HOST, "s3.amazonaws.com");
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
createFilter().appendBucketName(request, builder);
|
filter.appendBucketName(request, builder);
|
||||||
assertEquals(builder.toString(), "");
|
assertEquals(builder.toString(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,13 +116,12 @@ public class RequestAuthorizeSignatureTest {
|
||||||
URI host = URI.create("http://adriancole.s3int5.s3-external-3.amazonaws.com:80");
|
URI host = URI.create("http://adriancole.s3int5.s3-external-3.amazonaws.com:80");
|
||||||
HttpRequest request = new HttpRequest(HttpMethod.GET, host);
|
HttpRequest request = new HttpRequest(HttpMethod.GET, host);
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
createFilter().appendBucketName(request, builder);
|
filter.appendBucketName(request, builder);
|
||||||
assertEquals(builder.toString(), "/adriancole.s3int5");
|
assertEquals(builder.toString(), "/adriancole.s3int5");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testUpdatesOnlyOncePerSecond() throws NoSuchMethodException, InterruptedException {
|
void testUpdatesOnlyOncePerSecond() throws NoSuchMethodException, InterruptedException {
|
||||||
RequestAuthorizeSignature filter = createFilter();
|
|
||||||
// filter.createNewStamp();
|
// filter.createNewStamp();
|
||||||
String timeStamp = filter.timestampAsHeaderString();
|
String timeStamp = filter.timestampAsHeaderString();
|
||||||
// replay(filter);
|
// replay(filter);
|
||||||
|
@ -96,8 +133,13 @@ public class RequestAuthorizeSignatureTest {
|
||||||
// verify(filter);
|
// verify(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestAuthorizeSignature createFilter() {
|
/**
|
||||||
return Guice.createInjector(new AbstractModule() {
|
* before class, as we need to ensure that the filter is threadsafe.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@BeforeClass
|
||||||
|
protected void createFilter() {
|
||||||
|
injector = Guice.createInjector(new AbstractModule() {
|
||||||
|
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_ACCESSKEYID)).to(
|
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_ACCESSKEYID)).to(
|
||||||
|
@ -107,7 +149,8 @@ public class RequestAuthorizeSignatureTest {
|
||||||
bind(DateService.class);
|
bind(DateService.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
}).getInstance(RequestAuthorizeSignature.class);
|
});
|
||||||
|
filter = injector.getInstance(RequestAuthorizeSignature.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -25,9 +25,9 @@ package org.jclouds.http;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* // TODO: Adrian: Document this!
|
* // TODO: Adrian: Document this!
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
public interface HttpRequestFilter {
|
public interface HttpRequestFilter {
|
||||||
void filter(HttpRequest request) throws HttpException;
|
HttpRequest filter(HttpRequest request) throws HttpException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,10 +101,11 @@ public interface IntegrationTestClient {
|
||||||
Future<String> downloadFilter(@PathParam("id") String id, @HeaderParam("filterme") String header);
|
Future<String> downloadFilter(@PathParam("id") String id, @HeaderParam("filterme") String header);
|
||||||
|
|
||||||
static class Filter implements HttpRequestFilter {
|
static class Filter implements HttpRequestFilter {
|
||||||
public void filter(HttpRequest request) throws HttpException {
|
public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||||
if (request.getHeaders().containsKey("filterme")) {
|
if (request.getHeaders().containsKey("filterme")) {
|
||||||
request.getHeaders().put("test", "test");
|
request.getHeaders().put("test", "test");
|
||||||
}
|
}
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import static org.testng.Assert.assertEquals;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -60,6 +61,7 @@ import org.jclouds.util.DateService;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.mortbay.jetty.HttpHeaders;
|
import org.mortbay.jetty.HttpHeaders;
|
||||||
import org.testng.annotations.BeforeClass;
|
import org.testng.annotations.BeforeClass;
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
@ -201,14 +203,16 @@ public class JaxrsAnnotationProcessorTest {
|
||||||
|
|
||||||
static class TestRequestFilter1 implements HttpRequestFilter {
|
static class TestRequestFilter1 implements HttpRequestFilter {
|
||||||
|
|
||||||
public void filter(HttpRequest request) throws HttpException {
|
public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TestRequestFilter2 implements HttpRequestFilter {
|
static class TestRequestFilter2 implements HttpRequestFilter {
|
||||||
|
|
||||||
public void filter(HttpRequest request) throws HttpException {
|
public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -586,13 +590,22 @@ public class JaxrsAnnotationProcessorTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCreateGetRequest() throws SecurityException, NoSuchMethodException {
|
@DataProvider(name = "strings")
|
||||||
|
public Object[][] createData() {
|
||||||
|
return new Object[][] { { "apples" }, { "sp ace" }, { "unic¿de" }, { "qu?stion" } };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "strings")
|
||||||
|
public void testCreateGetRequest(String key) throws SecurityException, NoSuchMethodException,
|
||||||
|
UnsupportedEncodingException {
|
||||||
Method method = TestRequest.class.getMethod("get", String.class, String.class);
|
Method method = TestRequest.class.getMethod("get", String.class, String.class);
|
||||||
URI endpoint = URI.create("http://localhost");
|
URI endpoint = URI.create("http://localhost");
|
||||||
HttpRequest httpMethod = factory.create(TestRequest.class).createRequest(endpoint, method,
|
HttpRequest httpMethod = factory.create(TestRequest.class).createRequest(endpoint, method,
|
||||||
new Object[] { "1", "localhost" });
|
new Object[] { key, "localhost" });
|
||||||
assertEquals(httpMethod.getEndpoint().getHost(), "localhost");
|
assertEquals(httpMethod.getEndpoint().getHost(), "localhost");
|
||||||
assertEquals(httpMethod.getEndpoint().getPath(), "/1");
|
String expectedPath = "/" + URLEncoder.encode(key, "UTF-8").replaceAll("\\+", "%20");
|
||||||
|
assertEquals(httpMethod.getEndpoint().getRawPath(), expectedPath);
|
||||||
|
assertEquals(httpMethod.getEndpoint().getPath(), "/" + key);
|
||||||
assertEquals(httpMethod.getMethod(), HttpMethod.GET);
|
assertEquals(httpMethod.getMethod(), HttpMethod.GET);
|
||||||
assertEquals(httpMethod.getHeaders().size(), 1);
|
assertEquals(httpMethod.getHeaders().size(), 1);
|
||||||
assertEquals(httpMethod.getHeaders().get(HttpHeaders.HOST), Collections
|
assertEquals(httpMethod.getHeaders().get(HttpHeaders.HOST), Collections
|
||||||
|
|
|
@ -88,9 +88,10 @@ public class AuthenticateRequest implements HttpRequestFilter {
|
||||||
authToken = new AtomicReference<String>();
|
authToken = new AtomicReference<String>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void filter(HttpRequest request) throws HttpException {
|
public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||||
request.getHeaders().replaceValues(RackspaceHeaders.AUTH_TOKEN,
|
request.getHeaders().replaceValues(RackspaceHeaders.AUTH_TOKEN,
|
||||||
Collections.singletonList(getAuthToken()));
|
Collections.singletonList(getAuthToken()));
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue