AWS-S3 configurable temporary signed URL support

Introduces AWSS3BlobRequestSigner, which reuses the
RequestAuthorizeSignature filter for most of the heavy lifting.
Other implementation details based on [1].

Tested with AWSS3BlobSignerLiveTest, in particular,
testSign(Get|Put)UrlWithTime.

Closes JCLOUDS-200

[1] http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html
This commit is contained in:
Diwaker Gupta 2013-07-22 09:30:22 -07:00 committed by Andrew Gaul
parent 48b499c636
commit c64c7423cd
6 changed files with 217 additions and 11 deletions

View File

@ -44,13 +44,13 @@ import com.google.common.reflect.Invokable;
*/
@Singleton
public class S3BlobRequestSigner<T extends S3AsyncClient> implements BlobRequestSigner {
private final RestAnnotationProcessor processor;
private final BlobToObject blobToObject;
private final BlobToHttpGetOptions blob2HttpGetOptions;
protected final RestAnnotationProcessor processor;
protected final BlobToObject blobToObject;
protected final BlobToHttpGetOptions blob2HttpGetOptions;
private final Invokable<?, ?> getMethod;
private final Invokable<?, ?> deleteMethod;
private final Invokable<?, ?> createMethod;
protected final Invokable<?, ?> getMethod;
protected final Invokable<?, ?> deleteMethod;
protected final Invokable<?, ?> createMethod;
@Inject
public S3BlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject,

View File

@ -50,6 +50,7 @@ import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.internal.SignatureWire;
import org.jclouds.http.utils.Queries;
import org.jclouds.logging.Logger;
import org.jclouds.rest.RequestSigner;
import org.jclouds.s3.util.S3Utils;
@ -133,6 +134,13 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
}
HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
// Only add the Authorization header if the query string doesn't already contain
// the 'Signature' parameter, otherwise S3 will fail the request complaining about
// duplicate authentication methods. The 'Signature' parameter will be added for signed URLs
// with expiration.
if (Queries.queryParser().apply(request.getEndpoint().getQuery()).containsKey("Signature")) {
return request;
}
request = request.toBuilder()
.replaceHeader(HttpHeaders.AUTHORIZATION, authTag + " " + creds.get().identity + ":" + signature).build();
return request;

View File

@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.aws.s3.blobstore;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.blobstore.util.BlobStoreUtils.cleanRequest;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.jclouds.aws.s3.AWSS3AsyncClient;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
import org.jclouds.date.DateService;
import org.jclouds.date.TimeStamp;
import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpRequest;
import org.jclouds.reflect.Invocation;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.s3.blobstore.S3BlobRequestSigner;
import org.jclouds.s3.blobstore.functions.BlobToObject;
import org.jclouds.s3.filters.RequestAuthorizeSignature;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.net.HttpHeaders;
import com.google.inject.Inject;
import com.google.inject.Provider;
/**
* @author Diwaker Gupta
*/
public class AWSS3BlobRequestSigner extends S3BlobRequestSigner<AWSS3AsyncClient> {
private final RequestAuthorizeSignature authSigner;
private final String identity;
private final DateService dateService;
private final Provider<String> timeStampProvider;
@Inject
public AWSS3BlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject,
BlobToHttpGetOptions blob2HttpGetOptions, Class<AWSS3AsyncClient> interfaceClass,
@org.jclouds.location.Provider Supplier<Credentials> credentials,
RequestAuthorizeSignature authSigner, @TimeStamp Provider<String> timeStampProvider,
DateService dateService) throws SecurityException, NoSuchMethodException {
super(processor, blobToObject, blob2HttpGetOptions, interfaceClass);
this.authSigner = authSigner;
this.identity = credentials.get().identity;
this.dateService = dateService;
this.timeStampProvider = timeStampProvider;
}
@Override
public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
checkNotNull(container, "container");
checkNotNull(name, "name");
HttpRequest request = processor.apply(Invocation.create(getMethod, ImmutableList.<Object> of(container, name)));
return cleanRequest(signForTemporaryAccess(request, timeInSeconds));
}
@Override
public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) {
checkNotNull(container, "container");
checkNotNull(blob, "blob");
HttpRequest request = processor.apply(Invocation.create(createMethod,
ImmutableList.<Object>of(container, blobToObject.apply(blob))));
return cleanRequest(signForTemporaryAccess(request, timeInSeconds));
}
private HttpRequest signForTemporaryAccess(HttpRequest request, long timeInSeconds) {
// Update the 'DATE' header
String dateString = request.getFirstHeaderOrNull(HttpHeaders.DATE);
if (dateString == null) {
dateString = timeStampProvider.get();
}
Date date = dateService.rfc1123DateParse(dateString);
String expiration = String.valueOf(TimeUnit.MILLISECONDS.toSeconds(date.getTime()) + timeInSeconds);
HttpRequest.Builder<?> builder = request.toBuilder().replaceHeader(HttpHeaders.DATE, expiration);
final String signature = authSigner.sign(authSigner.createStringToSign(builder.build()));
return builder.addQueryParam("Signature", signature)
.addQueryParam(HttpHeaders.EXPIRES, expiration)
.addQueryParam("AWSAccessKeyId", identity)
.build();
}
}

View File

@ -16,8 +16,8 @@
*/
package org.jclouds.aws.s3.blobstore.config;
import org.jclouds.aws.s3.AWSS3AsyncClient;
import org.jclouds.aws.s3.blobstore.AWSS3AsyncBlobStore;
import org.jclouds.aws.s3.blobstore.AWSS3BlobRequestSigner;
import org.jclouds.aws.s3.blobstore.AWSS3BlobStore;
import org.jclouds.aws.s3.blobstore.strategy.AsyncMultipartUploadStrategy;
import org.jclouds.aws.s3.blobstore.strategy.MultipartUploadStrategy;
@ -25,12 +25,10 @@ import org.jclouds.aws.s3.blobstore.strategy.internal.ParallelMultipartUploadStr
import org.jclouds.aws.s3.blobstore.strategy.internal.SequentialMultipartUploadStrategy;
import org.jclouds.blobstore.BlobRequestSigner;
import org.jclouds.s3.blobstore.S3AsyncBlobStore;
import org.jclouds.s3.blobstore.S3BlobRequestSigner;
import org.jclouds.s3.blobstore.S3BlobStore;
import org.jclouds.s3.blobstore.config.S3BlobStoreContextModule;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
/**
*
@ -50,7 +48,6 @@ public class AWSS3BlobStoreContextModule extends S3BlobStoreContextModule {
@Override
protected void bindRequestSigner() {
bind(BlobRequestSigner.class).to(new TypeLiteral<S3BlobRequestSigner<AWSS3AsyncClient>>() {
});
bind(BlobRequestSigner.class).to(AWSS3BlobRequestSigner.class);
}
}

View File

@ -0,0 +1,92 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.aws.s3.blobstore;
import static org.testng.Assert.assertEquals;
import org.jclouds.aws.s3.config.AWSS3RestClientModule;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.date.TimeStamp;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.s3.blobstore.S3BlobSignerExpectTest;
import org.testng.annotations.Test;
import com.google.common.base.Supplier;
import com.google.inject.Module;
/**
* @author Diwaker Gupta
*/
@Test(groups = "unit", testName = "AWSS3BlobSignerExpectTest")
public class AWSS3BlobSignerExpectTest extends S3BlobSignerExpectTest {
public AWSS3BlobSignerExpectTest() {
provider = "aws-s3";
}
@Override
protected HttpRequest getBlobWithTime() {
return HttpRequest.builder().method("GET")
.endpoint("https://container.s3.amazonaws.com/name" +
"?Signature=Y4Ac4sZfBemGZmgfG78F7IX%20IFg%3D&Expires=1212683902&AWSAccessKeyId=identity")
.addHeader("Host", "container.s3.amazonaws.com")
.addHeader("Date", "Thu, 05 Jun 2008 16:38:19 GMT").build();
}
@Test
public void testSignGetBlobWithTime() {
BlobStore getBlobWithTime = requestsSendResponses(init());
HttpRequest compare = getBlobWithTime();
assertEquals(getBlobWithTime.getContext().getSigner().signGetBlob(container, name, 3l /* seconds */),
compare);
}
@Override
protected HttpRequest putBlobWithTime() {
return HttpRequest.builder().method("PUT")
.endpoint("https://container.s3.amazonaws.com/name" +
"?Signature=genkB2vLxe3AWV/bPvRTMqQts7E%3D&Expires=1212683902&AWSAccessKeyId=identity")
.addHeader("Expect", "100-continue")
.addHeader("Host", "container.s3.amazonaws.com")
.addHeader("Date", "Thu, 05 Jun 2008 16:38:19 GMT").build();
}
@Test
public void testSignPutBlobWithTime() throws Exception {
BlobStore signPutBloblWithTime = requestsSendResponses(init());
Blob blob = signPutBloblWithTime.blobBuilder(name).payload(text).contentType("text/plain").build();
HttpRequest compare = putBlobWithTime();
compare.setPayload(blob.getPayload());
assertEquals(signPutBloblWithTime.getContext().getSigner().signPutBlob(container, blob, 3l /* seconds */),
compare);
}
@Override
protected Module createModule() {
return new TestAWSS3RestClientModule();
}
@ConfiguresRestClient
private static final class TestAWSS3RestClientModule extends AWSS3RestClientModule {
@Override
@TimeStamp
protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
return "Thu, 05 Jun 2008 16:38:19 GMT";
}
}
}

View File

@ -16,6 +16,9 @@
*/
package org.jclouds.aws.s3.blobstore.integration;
import java.util.Properties;
import org.jclouds.Constants;
import org.jclouds.s3.blobstore.integration.S3BlobSignerLiveTest;
import org.testng.annotations.Test;
@ -28,4 +31,12 @@ public class AWSS3BlobSignerLiveTest extends S3BlobSignerLiveTest {
public AWSS3BlobSignerLiveTest() {
provider = "aws-s3";
}
@Override
protected Properties setupProperties() {
Properties overrides = super.setupProperties();
overrides.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER, "true");
overrides.setProperty(Constants.PROPERTY_SESSION_INTERVAL, "1");
return overrides;
}
}