mirror of https://github.com/apache/jclouds.git
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:
parent
48b499c636
commit
c64c7423cd
|
@ -44,13 +44,13 @@ import com.google.common.reflect.Invokable;
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class S3BlobRequestSigner<T extends S3AsyncClient> implements BlobRequestSigner {
|
public class S3BlobRequestSigner<T extends S3AsyncClient> implements BlobRequestSigner {
|
||||||
private final RestAnnotationProcessor processor;
|
protected final RestAnnotationProcessor processor;
|
||||||
private final BlobToObject blobToObject;
|
protected final BlobToObject blobToObject;
|
||||||
private final BlobToHttpGetOptions blob2HttpGetOptions;
|
protected final BlobToHttpGetOptions blob2HttpGetOptions;
|
||||||
|
|
||||||
private final Invokable<?, ?> getMethod;
|
protected final Invokable<?, ?> getMethod;
|
||||||
private final Invokable<?, ?> deleteMethod;
|
protected final Invokable<?, ?> deleteMethod;
|
||||||
private final Invokable<?, ?> createMethod;
|
protected final Invokable<?, ?> createMethod;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public S3BlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject,
|
public S3BlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject,
|
||||||
|
|
|
@ -50,6 +50,7 @@ import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpRequestFilter;
|
import org.jclouds.http.HttpRequestFilter;
|
||||||
import org.jclouds.http.HttpUtils;
|
import org.jclouds.http.HttpUtils;
|
||||||
import org.jclouds.http.internal.SignatureWire;
|
import org.jclouds.http.internal.SignatureWire;
|
||||||
|
import org.jclouds.http.utils.Queries;
|
||||||
import org.jclouds.logging.Logger;
|
import org.jclouds.logging.Logger;
|
||||||
import org.jclouds.rest.RequestSigner;
|
import org.jclouds.rest.RequestSigner;
|
||||||
import org.jclouds.s3.util.S3Utils;
|
import org.jclouds.s3.util.S3Utils;
|
||||||
|
@ -133,6 +134,13 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
|
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()
|
request = request.toBuilder()
|
||||||
.replaceHeader(HttpHeaders.AUTHORIZATION, authTag + " " + creds.get().identity + ":" + signature).build();
|
.replaceHeader(HttpHeaders.AUTHORIZATION, authTag + " " + creds.get().identity + ":" + signature).build();
|
||||||
return request;
|
return request;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,8 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.aws.s3.blobstore.config;
|
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.AWSS3AsyncBlobStore;
|
||||||
|
import org.jclouds.aws.s3.blobstore.AWSS3BlobRequestSigner;
|
||||||
import org.jclouds.aws.s3.blobstore.AWSS3BlobStore;
|
import org.jclouds.aws.s3.blobstore.AWSS3BlobStore;
|
||||||
import org.jclouds.aws.s3.blobstore.strategy.AsyncMultipartUploadStrategy;
|
import org.jclouds.aws.s3.blobstore.strategy.AsyncMultipartUploadStrategy;
|
||||||
import org.jclouds.aws.s3.blobstore.strategy.MultipartUploadStrategy;
|
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.aws.s3.blobstore.strategy.internal.SequentialMultipartUploadStrategy;
|
||||||
import org.jclouds.blobstore.BlobRequestSigner;
|
import org.jclouds.blobstore.BlobRequestSigner;
|
||||||
import org.jclouds.s3.blobstore.S3AsyncBlobStore;
|
import org.jclouds.s3.blobstore.S3AsyncBlobStore;
|
||||||
import org.jclouds.s3.blobstore.S3BlobRequestSigner;
|
|
||||||
import org.jclouds.s3.blobstore.S3BlobStore;
|
import org.jclouds.s3.blobstore.S3BlobStore;
|
||||||
import org.jclouds.s3.blobstore.config.S3BlobStoreContextModule;
|
import org.jclouds.s3.blobstore.config.S3BlobStoreContextModule;
|
||||||
|
|
||||||
import com.google.inject.Scopes;
|
import com.google.inject.Scopes;
|
||||||
import com.google.inject.TypeLiteral;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -50,7 +48,6 @@ public class AWSS3BlobStoreContextModule extends S3BlobStoreContextModule {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void bindRequestSigner() {
|
protected void bindRequestSigner() {
|
||||||
bind(BlobRequestSigner.class).to(new TypeLiteral<S3BlobRequestSigner<AWSS3AsyncClient>>() {
|
bind(BlobRequestSigner.class).to(AWSS3BlobRequestSigner.class);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.aws.s3.blobstore.integration;
|
package org.jclouds.aws.s3.blobstore.integration;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.jclouds.Constants;
|
||||||
import org.jclouds.s3.blobstore.integration.S3BlobSignerLiveTest;
|
import org.jclouds.s3.blobstore.integration.S3BlobSignerLiveTest;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@ -28,4 +31,12 @@ public class AWSS3BlobSignerLiveTest extends S3BlobSignerLiveTest {
|
||||||
public AWSS3BlobSignerLiveTest() {
|
public AWSS3BlobSignerLiveTest() {
|
||||||
provider = "aws-s3";
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue