diff --git a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobRequestSigner.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobRequestSigner.java index 9162f75a03..d6402afc59 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobRequestSigner.java +++ b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobRequestSigner.java @@ -44,13 +44,13 @@ import com.google.common.reflect.Invokable; */ @Singleton public class S3BlobRequestSigner 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, diff --git a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java index 885b2eeb7b..ca85401f6c 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java +++ b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java @@ -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; diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSigner.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSigner.java new file mode 100644 index 0000000000..b0b98241bf --- /dev/null +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobRequestSigner.java @@ -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 { + private final RequestAuthorizeSignature authSigner; + private final String identity; + private final DateService dateService; + private final Provider timeStampProvider; + + @Inject + public AWSS3BlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject, + BlobToHttpGetOptions blob2HttpGetOptions, Class interfaceClass, + @org.jclouds.location.Provider Supplier credentials, + RequestAuthorizeSignature authSigner, @TimeStamp Provider 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. 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.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(); + } +} diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/config/AWSS3BlobStoreContextModule.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/config/AWSS3BlobStoreContextModule.java index cb454bb016..501645c888 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/config/AWSS3BlobStoreContextModule.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/config/AWSS3BlobStoreContextModule.java @@ -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>() { - }); + bind(BlobRequestSigner.class).to(AWSS3BlobRequestSigner.class); } } diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/AWSS3BlobSignerExpectTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/AWSS3BlobSignerExpectTest.java new file mode 100644 index 0000000000..d041abd912 --- /dev/null +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/AWSS3BlobSignerExpectTest.java @@ -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 cache) { + return "Thu, 05 Jun 2008 16:38:19 GMT"; + } + } +} diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobSignerLiveTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobSignerLiveTest.java index d0f86f6b18..0a3ec9111e 100644 --- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobSignerLiveTest.java +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3BlobSignerLiveTest.java @@ -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; + } }