From 080d142ae1699f48b0908148c7c46ffbe3e21a68 Mon Sep 17 00:00:00 2001 From: Andrei Savu Date: Sun, 16 Sep 2012 17:07:17 +0300 Subject: [PATCH] Generic support for Swift TempUrl middleware --- .../cloudfiles/CloudFilesAsyncClient.java | 16 --- .../jclouds/cloudfiles/CloudFilesClient.java | 21 --- .../CloudFilesBlobRequestSigner.java | 124 ------------------ .../CloudFilesBlobStoreContextModule.java | 22 +--- .../reference/CloudFilesHeaders.java | 2 - .../cloudfiles/CloudFilesClientTest.java | 72 ---------- .../blobstore/CloudFilesBlobSignerTest.java | 83 ------------ .../integration/CloudFilesBlobLiveTest.java | 6 +- .../CloudFilesBlobSignerLiveTest.java | 64 --------- .../swift/CommonSwiftAsyncClient.java | 3 +- .../openstack/swift}/TemporaryUrlKey.java | 2 +- .../blobstore/SwiftBlobRequestSigner.java | 69 ++++++++-- .../config/SwiftBlobStoreContextModule.java | 32 ++++- .../swift/extensions/TemporaryUrlKeyApi.java | 51 +++++++ .../extensions/TemporaryUrlKeyAsyncApi.java | 60 +++++++++ .../ParseTemporaryUrlKeyFromHeaders.java | 13 +- .../swift/reference/SwiftHeaders.java | 3 + .../swift/CommonSwiftClientTest.java | 25 +++- .../blobstore/SwiftBlobRequestSignerTest.java | 48 +++++-- .../integration/SwiftBlobLiveTest.java | 5 + .../integration/SwiftBlobSignerLiveTest.java | 7 +- .../swift/internal/StubSwiftAsyncClient.java | 4 +- .../internal/BaseBlobSignerLiveTest.java | 105 ++++++++++++--- 23 files changed, 377 insertions(+), 460 deletions(-) delete mode 100644 apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobRequestSigner.java delete mode 100644 apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/CloudFilesClientTest.java delete mode 100644 apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobSignerTest.java rename apis/{cloudfiles/src/main/java/org/jclouds/cloudfiles => swift/src/main/java/org/jclouds/openstack/swift}/TemporaryUrlKey.java (97%) create mode 100644 apis/swift/src/main/java/org/jclouds/openstack/swift/extensions/TemporaryUrlKeyApi.java create mode 100644 apis/swift/src/main/java/org/jclouds/openstack/swift/extensions/TemporaryUrlKeyAsyncApi.java rename apis/{cloudfiles/src/main/java/org/jclouds/cloudfiles => swift/src/main/java/org/jclouds/openstack/swift}/functions/ParseTemporaryUrlKeyFromHeaders.java (71%) diff --git a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesAsyncClient.java b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesAsyncClient.java index aa3c9aaf8d..6e2ca5ab5d 100644 --- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesAsyncClient.java +++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesAsyncClient.java @@ -39,7 +39,6 @@ import org.jclouds.cloudfiles.binders.BindIterableToHeadersWithPurgeCDNObjectEma import org.jclouds.cloudfiles.domain.ContainerCDNMetadata; import org.jclouds.cloudfiles.functions.ParseCdnUriFromHeaders; import org.jclouds.cloudfiles.functions.ParseContainerCDNMetadataFromHeaders; -import org.jclouds.cloudfiles.functions.ParseTemporaryUrlKeyFromHeaders; import org.jclouds.cloudfiles.options.ListCdnContainerOptions; import org.jclouds.cloudfiles.reference.CloudFilesHeaders; import org.jclouds.openstack.filters.AuthenticateRequest; @@ -202,19 +201,4 @@ public interface CloudFilesAsyncClient extends CommonSwiftAsyncClient { @Headers(keys = CloudFilesHeaders.CDN_WEBSITE_ERROR, values = "{error}") ListenableFuture setCDNStaticWebsiteError(@PathParam("container") String container, @PathParam("error") String error); - - /** - * @see CloudFilesClient#getTemporaryUrlKey - */ - @HEAD - @Path("/") - @ResponseParser(ParseTemporaryUrlKeyFromHeaders.class) - ListenableFuture getTemporaryUrlKey(); - - /** - * @see CloudFilesClient#setTemporaryUrlKey - */ - @POST - @Path("/") - ListenableFuture setTemporaryUrlKey(@HeaderParam(CloudFilesHeaders.ACCOUNT_TEMPORARY_URL_KEY) String key); } diff --git a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesClient.java b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesClient.java index 23e3dd3abb..8a40c3ed23 100644 --- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesClient.java +++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesClient.java @@ -105,25 +105,4 @@ public interface CloudFilesClient extends CommonSwiftClient { * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Set_Error_Pages_for_Static_Website-dle4005.html */ boolean setCDNStaticWebsiteError(String container, String error); - - /** - * Retrieve the key used to generate Temporary object access URLs - * - * @see - * @return shared secret key - */ - String getTemporaryUrlKey(); - - /** - * To create a Temporary URL you must first set a key as account metadata. - * - * Once the key is set, you should not change it while you still want others to be - * able to access your temporary URL. If you change it, the TempURL becomes invalid - * (within 60 seconds, which is the cache time for a key) and others will not be allowed - * to access it. - * - * @see - * @param temporaryUrlKey - */ - void setTemporaryUrlKey(String temporaryUrlKey); } diff --git a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobRequestSigner.java b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobRequestSigner.java deleted file mode 100644 index b6b9d7136a..0000000000 --- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobRequestSigner.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.jclouds.cloudfiles.blobstore; - -import com.google.common.base.Throwables; -import com.google.inject.Provider; -import org.jclouds.blobstore.BlobRequestSigner; -import org.jclouds.blobstore.domain.Blob; -import org.jclouds.blobstore.functions.BlobToHttpGetOptions; -import org.jclouds.cloudfiles.CloudFilesAsyncClient; -import org.jclouds.cloudfiles.TemporaryUrlKey; -import org.jclouds.crypto.Crypto; -import org.jclouds.crypto.CryptoStreams; -import org.jclouds.date.TimeStamp; -import org.jclouds.http.HttpRequest; -import org.jclouds.http.options.GetOptions; -import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; -import org.jclouds.openstack.swift.blobstore.functions.BlobToObject; -import org.jclouds.openstack.swift.domain.SwiftObject; -import org.jclouds.rest.internal.RestAnnotationProcessor; - -import javax.inject.Inject; -import java.lang.reflect.Method; -import java.security.InvalidKeyException; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Predicates.instanceOf; -import static com.google.common.collect.Iterables.filter; -import static org.jclouds.blobstore.util.BlobStoreUtils.cleanRequest; - -public class CloudFilesBlobRequestSigner implements BlobRequestSigner { - - private final RestAnnotationProcessor processor; - private final Crypto crypto; - - private final Provider unixEpochTimestampProvider; - private final Provider temporaryUrlKeyProvider; - - private final BlobToObject blobToObject; - private final BlobToHttpGetOptions blob2HttpGetOptions; - - private final Method getMethod; - private final Method deleteMethod; - private final Method createMethod; - - @Inject - public CloudFilesBlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject, - BlobToHttpGetOptions blob2HttpGetOptions, Crypto crypto, - @TimeStamp Provider unixEpochTimestampProvider, - @TemporaryUrlKey Provider temporaryUrlKeyProvider) throws SecurityException, NoSuchMethodException { - this.processor = checkNotNull(processor, "processor"); - this.crypto = checkNotNull(crypto, "crypto"); - - this.unixEpochTimestampProvider = checkNotNull(unixEpochTimestampProvider, "unixEpochTimestampProvider"); - this.temporaryUrlKeyProvider = checkNotNull(temporaryUrlKeyProvider, "temporaryUrlKeyProvider"); - - this.blobToObject = checkNotNull(blobToObject, "blobToObject"); - this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions"); - - this.getMethod = CloudFilesAsyncClient.class.getMethod("getObject", String.class, String.class, GetOptions[].class); - this.deleteMethod = CloudFilesAsyncClient.class.getMethod("removeObject", String.class, String.class); - this.createMethod = CloudFilesAsyncClient.class.getMethod("putObject", String.class, SwiftObject.class); - } - - @Override - public HttpRequest signGetBlob(String container, String name) { - return cleanRequest(processor.createRequest(getMethod, container, name)); - } - - @Override - public HttpRequest signGetBlob(String container, String name, org.jclouds.blobstore.options.GetOptions options) { - return cleanRequest(processor.createRequest(getMethod, container, name, blob2HttpGetOptions.apply(options))); - } - - @Override - public HttpRequest signGetBlob(String container, String name, long timeInSeconds) { - HttpRequest request = processor.createRequest(getMethod, container, name); - return cleanRequest(signForTemporaryAccess(request, timeInSeconds)); - } - - @Override - public HttpRequest signPutBlob(String container, Blob blob) { - return cleanRequest(processor.createRequest(createMethod, container, blobToObject.apply(blob))); - } - - @Override - public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) { - HttpRequest request = processor.createRequest(createMethod, container, blobToObject.apply(blob)); - return cleanRequest(signForTemporaryAccess(request, timeInSeconds)); - } - - @Override - public HttpRequest signRemoveBlob(String container, String name) { - return cleanRequest(processor.createRequest(deleteMethod, container, name)); - } - - private HttpRequest signForTemporaryAccess(HttpRequest request, long timeInSeconds) { - HttpRequest.Builder builder = request.toBuilder(); - builder.filters(filter(request.getFilters(), instanceOf(AuthenticateRequest.class))); - - String key = temporaryUrlKeyProvider.get(); - long expiresInSeconds = unixEpochTimestampProvider.get() + timeInSeconds; - - builder.addQueryParam("temp_url_sig", createSignature(key, createStringToSign( - request.getMethod().toUpperCase(), request, expiresInSeconds))); - builder.addQueryParam("temp_url_expires", "" + expiresInSeconds); - - return builder.build(); - } - - private String createStringToSign(String method, HttpRequest request, long expiresInSeconds) { - checkArgument(method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("PUT")); - return String.format("%s\n%d\n%s", method.toUpperCase(), expiresInSeconds, - request.getEndpoint().getPath()); - } - - private String createSignature(String key, String stringToSign) { - try { - return CryptoStreams.hex(crypto.hmacSHA1(key.getBytes()).doFinal(stringToSign.getBytes())); - - } catch (InvalidKeyException e) { - throw Throwables.propagate(e); - } - } -} diff --git a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/config/CloudFilesBlobStoreContextModule.java b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/config/CloudFilesBlobStoreContextModule.java index 011a3054cf..edacb1d321 100644 --- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/config/CloudFilesBlobStoreContextModule.java +++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/config/CloudFilesBlobStoreContextModule.java @@ -25,14 +25,14 @@ import javax.inject.Singleton; import org.jclouds.blobstore.BlobRequestSigner; import org.jclouds.cloudfiles.CloudFilesClient; -import org.jclouds.cloudfiles.TemporaryUrlKey; +import org.jclouds.openstack.swift.TemporaryUrlKey; import org.jclouds.cloudfiles.blobstore.CloudFilesAsyncBlobStore; -import org.jclouds.cloudfiles.blobstore.CloudFilesBlobRequestSigner; import org.jclouds.cloudfiles.blobstore.CloudFilesBlobStore; import org.jclouds.cloudfiles.blobstore.functions.CloudFilesObjectToBlobMetadata; import org.jclouds.cloudfiles.domain.ContainerCDNMetadata; import org.jclouds.date.TimeStamp; import org.jclouds.openstack.swift.blobstore.SwiftAsyncBlobStore; +import org.jclouds.openstack.swift.blobstore.SwiftBlobRequestSigner; import org.jclouds.openstack.swift.blobstore.SwiftBlobStore; import org.jclouds.openstack.swift.blobstore.config.SwiftBlobStoreContextModule; import org.jclouds.openstack.swift.blobstore.functions.ObjectToBlobMetadata; @@ -64,28 +64,12 @@ public class CloudFilesBlobStoreContextModule extends SwiftBlobStoreContextModul }); } - @Provides - @TimeStamp - protected Long unixEpochTimestampProvider() { - return System.currentTimeMillis() / 1000; /* convert to seconds */ - } - - @Provides - @TemporaryUrlKey - protected String temporaryUrlKeyProvider(CloudFilesClient client) { - return client.getTemporaryUrlKey(); - } - @Override protected void configure() { super.configure(); bind(SwiftBlobStore.class).to(CloudFilesBlobStore.class); bind(SwiftAsyncBlobStore.class).to(CloudFilesAsyncBlobStore.class); bind(ObjectToBlobMetadata.class).to(CloudFilesObjectToBlobMetadata.class); - } - - @Override - protected void configureRequestSigner() { - bind(BlobRequestSigner.class).to(CloudFilesBlobRequestSigner.class); + bind(BlobRequestSigner.class).to(SwiftBlobRequestSigner.class); } } diff --git a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/reference/CloudFilesHeaders.java b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/reference/CloudFilesHeaders.java index 10547a5f06..e3fbf27ff9 100644 --- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/reference/CloudFilesHeaders.java +++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/reference/CloudFilesHeaders.java @@ -30,8 +30,6 @@ import org.jclouds.openstack.swift.reference.SwiftHeaders; */ public interface CloudFilesHeaders extends SwiftHeaders { - public static final String ACCOUNT_TEMPORARY_URL_KEY = "X-Account-Meta-Temp-Url-Key"; - public static final String CDN_ENABLED = "X-CDN-Enabled"; public static final String CDN_LOG_RETENTION = "X-Log-Retention"; public static final String CDN_TTL = "X-TTL"; diff --git a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/CloudFilesClientTest.java b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/CloudFilesClientTest.java deleted file mode 100644 index f09b74dc51..0000000000 --- a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/CloudFilesClientTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Licensed to jclouds, Inc. (jclouds) under one or more - * contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. jclouds 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.cloudfiles; - -import com.google.common.collect.ImmutableSet; -import com.google.inject.Module; -import com.google.inject.TypeLiteral; -import org.jclouds.apis.ApiMetadata; -import org.jclouds.cloudfiles.blobstore.config.CloudFilesBlobStoreContextModule; -import org.jclouds.cloudfiles.config.CloudFilesRestClientModule; -import org.jclouds.openstack.internal.TestOpenStackAuthenticationModule; -import org.jclouds.openstack.swift.CommonSwiftClientTest; -import org.jclouds.openstack.swift.SwiftApiMetadata; -import org.jclouds.openstack.swift.SwiftAsyncClient; -import org.jclouds.openstack.swift.config.SwiftRestClientModule; -import org.jclouds.rest.HttpClient; -import org.jclouds.rest.internal.RestAnnotationProcessor; -import org.testng.annotations.Test; - -import java.util.Properties; - -import static org.jclouds.Constants.PROPERTY_API_VERSION; -import static org.jclouds.Constants.PROPERTY_ENDPOINT; -import static org.jclouds.location.reference.LocationConstants.PROPERTY_REGIONS; - -/** - * @author Andrei Savu - */ -// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire -@Test(groups = "unit", testName = "CloudFilesClientTest") -public class CloudFilesClientTest extends CommonSwiftClientTest { - - public static final long UNIX_EPOCH_TIMESTAMP = 123456789L; - public static final String TEMPORARY_URL_KEY = "get-or-set-X-Account-Meta-Temp-Url-Key"; - - protected String provider = "cloudfiles"; - - public static class FixedCloudFilesBlobStoreContextModule extends CloudFilesBlobStoreContextModule { - @Override - protected Long unixEpochTimestampProvider() { - return UNIX_EPOCH_TIMESTAMP; - } - - @Override - protected String temporaryUrlKeyProvider(CloudFilesClient _) { - return TEMPORARY_URL_KEY; - } - } - - @Override - protected ApiMetadata createApiMetadata() { - return new CloudFilesApiMetadata().toBuilder().defaultModules( - ImmutableSet.>of(StorageEndpointModule.class, - CloudFilesRestClientModule.class, FixedCloudFilesBlobStoreContextModule.class)).build(); - } -} diff --git a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobSignerTest.java b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobSignerTest.java deleted file mode 100644 index 7bdcdaefc4..0000000000 --- a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobSignerTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Licensed to jclouds, Inc. (jclouds) under one or more - * contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. jclouds 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.cloudfiles.blobstore; - -import org.jclouds.blobstore.BlobRequestSigner; -import org.jclouds.blobstore.domain.Blob; -import org.jclouds.cloudfiles.CloudFilesClientTest; -import org.jclouds.http.HttpRequest; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.Date; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; - -/** - * @author Andrei Savu - */ -// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire -@Test(groups = "unit", testName = "CloudFilesBlobSignerTest") -public class CloudFilesBlobSignerTest extends CloudFilesClientTest { - - private BlobRequestSigner signer; - private Blob.Factory blobFactory; - - public void testSignGetBlobWithTime() { - HttpRequest request = signer.signGetBlob("container", "name", 120); - - assertRequestLineEquals(request, "GET http://storage/container/name?" + - "temp_url_sig=4759d99d13c826bba0af2c9f0c526ca53c95abaf&temp_url_expires=123456909 HTTP/1.1"); - assertFalse(request.getHeaders().containsKey("X-Auth-Token")); - assertPayloadEquals(request, null, null, false); - - assertEquals(request.getFilters().size(), 0); - } - - public void testSignPutBlobWithTime() throws Exception { - Blob blob = blobFactory.create(null); - - blob.getMetadata().setName("name"); - blob.setPayload(""); - blob.getPayload().getContentMetadata().setContentLength(2l); - blob.getPayload().getContentMetadata().setContentMD5(new byte[]{0, 2, 4, 8}); - blob.getPayload().getContentMetadata().setContentType("text/plain"); - blob.getPayload().getContentMetadata().setExpires(new Date(1000)); - - HttpRequest request = signer.signPutBlob("container", blob, 120 /* seconds */); - - assertRequestLineEquals(request, "PUT http://storage/container/name?" + - "temp_url_sig=490690286130adac9e7144d85b320a00b1bf9e2b&temp_url_expires=123456909 HTTP/1.1"); - - assertFalse(request.getHeaders().containsKey("X-Auth-Token")); - assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[]{0, 2, 4, 8}, new Date(1000)); - - assertEquals(request.getFilters().size(), 0); - } - - @BeforeClass - protected void setupFactory() throws IOException { - super.setupFactory(); - this.blobFactory = injector.getInstance(Blob.Factory.class); - this.signer = injector.getInstance(BlobRequestSigner.class); - } -} diff --git a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobLiveTest.java b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobLiveTest.java index 14de7f6200..360c307c4e 100644 --- a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobLiveTest.java +++ b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobLiveTest.java @@ -18,9 +18,8 @@ */ package org.jclouds.cloudfiles.blobstore.integration; -import org.jclouds.cloudfiles.CloudFilesApiMetadata; -import org.jclouds.cloudfiles.CloudFilesClient; import org.jclouds.openstack.swift.blobstore.integration.SwiftBlobLiveTest; +import org.jclouds.openstack.swift.extensions.TemporaryUrlKeyApi; import org.testng.annotations.Test; import java.util.UUID; @@ -33,12 +32,13 @@ import static org.testng.Assert.assertNotNull; */ @Test(groups = {"live"}) public class CloudFilesBlobLiveTest extends SwiftBlobLiveTest { + public CloudFilesBlobLiveTest() { provider = "cloudfiles"; } public void testGetAndSetTemporaryUrlKey() { - CloudFilesClient client = view.unwrap(CloudFilesApiMetadata.CONTEXT_TOKEN).getApi(); + TemporaryUrlKeyApi client = view.utils().injector().getInstance(TemporaryUrlKeyApi.class); String currentSecretKey = client.getTemporaryUrlKey(); assertNotNull(currentSecretKey); diff --git a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobSignerLiveTest.java b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobSignerLiveTest.java index 9ced14fa93..d67142e575 100644 --- a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobSignerLiveTest.java +++ b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobSignerLiveTest.java @@ -18,19 +18,9 @@ */ package org.jclouds.cloudfiles.blobstore.integration; -import org.jclouds.blobstore.domain.Blob; -import org.jclouds.http.HttpRequest; import org.jclouds.openstack.swift.blobstore.integration.SwiftBlobSignerLiveTest; -import org.jclouds.rest.AuthorizationException; -import org.jclouds.util.Strings2; import org.testng.annotations.Test; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; - /** * @author Adrian Cole */ @@ -39,58 +29,4 @@ public class CloudFilesBlobSignerLiveTest extends SwiftBlobSignerLiveTest { public CloudFilesBlobSignerLiveTest() { provider = "cloudfiles"; } - - @Test - public void testSignGetUrlWithTime() throws InterruptedException, IOException { - String name = "hello"; - String text = "fooooooooooooooooooooooo"; - - Blob blob = view.getBlobStore().blobBuilder(name).payload(text).contentType("text/plain").build(); - String container = getContainerName(); - try { - view.getBlobStore().putBlob(container, blob); - assertConsistencyAwareContainerSize(container, 1); - HttpRequest request = view.getSigner().signGetBlob(container, name, 3 /* seconds */); - - assertEquals(request.getFilters().size(), 0); - assertEquals(Strings2.toString(view.utils().http().invoke(request).getPayload()), text); - - TimeUnit.SECONDS.sleep(4); - try { - Strings2.toString(view.utils().http().invoke(request).getPayload()); - fail("Temporary URL did not expire as expected"); - } catch (AuthorizationException expected) { - } - } finally { - returnContainer(container); - } - } - - @Test - public void testSignPutUrlWithTime() throws Exception { - String name = "hello"; - String text = "fooooooooooooooooooooooo"; - - Blob blob = view.getBlobStore().blobBuilder(name).payload(text).contentType("text/plain").build(); - String container = getContainerName(); - try { - HttpRequest request = view.getSigner().signPutBlob(container, blob, 3 /* seconds */); - assertEquals(request.getFilters().size(), 0); - - Strings2.toString(view.utils().http().invoke(request).getPayload()); - assertConsistencyAwareContainerSize(container, 1); - - view.getBlobStore().removeBlob(container, name); - assertConsistencyAwareContainerSize(container, 0); - - TimeUnit.SECONDS.sleep(4); - try { - Strings2.toString(view.utils().http().invoke(request).getPayload()); - fail("Temporary URL did not expire as expected"); - } catch (AuthorizationException expected) { - } - } finally { - returnContainer(container); - } - } } diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/CommonSwiftAsyncClient.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/CommonSwiftAsyncClient.java index b7a9b9fa8c..c7f7ef8ea1 100644 --- a/apis/swift/src/main/java/org/jclouds/openstack/swift/CommonSwiftAsyncClient.java +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/CommonSwiftAsyncClient.java @@ -25,6 +25,7 @@ import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HEAD; +import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -54,6 +55,7 @@ import org.jclouds.openstack.swift.functions.ParseContainerMetadataFromHeaders; import org.jclouds.openstack.swift.functions.ParseObjectFromHeadersAndHttpContent; import org.jclouds.openstack.swift.functions.ParseObjectInfoFromHeaders; import org.jclouds.openstack.swift.functions.ParseObjectInfoListFromJsonResponse; +import org.jclouds.openstack.swift.functions.ParseTemporaryUrlKeyFromHeaders; import org.jclouds.openstack.swift.functions.ReturnTrueOn404FalseOn409; import org.jclouds.openstack.swift.options.CreateContainerOptions; import org.jclouds.openstack.swift.options.ListContainerOptions; @@ -248,5 +250,4 @@ public interface CommonSwiftAsyncClient { @Headers(keys = "X-Object-Manifest", values="{container}/{name}") ListenableFuture putObjectManifest(@PathParam("container") String container, @PathParam("name") String name); - } diff --git a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/TemporaryUrlKey.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/TemporaryUrlKey.java similarity index 97% rename from apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/TemporaryUrlKey.java rename to apis/swift/src/main/java/org/jclouds/openstack/swift/TemporaryUrlKey.java index 15b7d43e1c..7cb011bd74 100644 --- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/TemporaryUrlKey.java +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/TemporaryUrlKey.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.cloudfiles; +package org.jclouds.openstack.swift; import javax.inject.Qualifier; import java.lang.annotation.ElementType; diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobRequestSigner.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobRequestSigner.java index d3986f8e7e..4da0517a2a 100644 --- a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobRequestSigner.java +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobRequestSigner.java @@ -18,32 +18,46 @@ */ package org.jclouds.openstack.swift.blobstore; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.instanceOf; +import static com.google.common.collect.Iterables.filter; import static org.jclouds.blobstore.util.BlobStoreUtils.cleanRequest; import java.lang.reflect.Method; +import java.security.InvalidKeyException; import javax.inject.Inject; import javax.inject.Singleton; +import com.google.common.base.Throwables; +import com.google.inject.Provider; import org.jclouds.blobstore.BlobRequestSigner; import org.jclouds.blobstore.domain.Blob; import org.jclouds.blobstore.functions.BlobToHttpGetOptions; +import org.jclouds.crypto.Crypto; +import org.jclouds.crypto.CryptoStreams; +import org.jclouds.date.TimeStamp; import org.jclouds.http.HttpRequest; import org.jclouds.http.options.GetOptions; +import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; import org.jclouds.openstack.swift.CommonSwiftAsyncClient; +import org.jclouds.openstack.swift.TemporaryUrlKey; import org.jclouds.openstack.swift.blobstore.functions.BlobToObject; import org.jclouds.openstack.swift.domain.SwiftObject; import org.jclouds.rest.internal.RestAnnotationProcessor; /** - * * @author Adrian Cole */ @Singleton public class SwiftBlobRequestSigner implements BlobRequestSigner { private final RestAnnotationProcessor processor; + private final Crypto crypto; + + private final Provider unixEpochTimestampProvider; + private final Provider temporaryUrlKeyProvider; private final BlobToObject blobToObject; private final BlobToHttpGetOptions blob2HttpGetOptions; @@ -54,14 +68,19 @@ public class SwiftBlobRequestSigner implements BlobRequestSigner { @Inject public SwiftBlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject, - BlobToHttpGetOptions blob2HttpGetOptions) throws SecurityException, NoSuchMethodException { + BlobToHttpGetOptions blob2HttpGetOptions, Crypto crypto, @TimeStamp Provider unixEpochTimestampProvider, + @TemporaryUrlKey Provider temporaryUrlKeyProvider) throws SecurityException, NoSuchMethodException { this.processor = checkNotNull(processor, "processor"); + this.crypto = checkNotNull(crypto, "crypto"); + + this.unixEpochTimestampProvider = checkNotNull(unixEpochTimestampProvider, "unixEpochTimestampProvider"); + this.temporaryUrlKeyProvider = checkNotNull(temporaryUrlKeyProvider, "temporaryUrlKeyProvider"); this.blobToObject = checkNotNull(blobToObject, "blobToObject"); this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions"); this.getMethod = CommonSwiftAsyncClient.class.getMethod("getObject", String.class, String.class, - GetOptions[].class); + GetOptions[].class); this.deleteMethod = CommonSwiftAsyncClient.class.getMethod("removeObject", String.class, String.class); this.createMethod = CommonSwiftAsyncClient.class.getMethod("putObject", String.class, SwiftObject.class); } @@ -73,7 +92,13 @@ public class SwiftBlobRequestSigner implements BlobRequestSigner { @Override public HttpRequest signGetBlob(String container, String name, long timeInSeconds) { - throw new UnsupportedOperationException(); + HttpRequest request = processor.createRequest(getMethod, container, name); + return cleanRequest(signForTemporaryAccess(request, timeInSeconds)); + } + + @Override + public HttpRequest signGetBlob(String container, String name, org.jclouds.blobstore.options.GetOptions options) { + return cleanRequest(processor.createRequest(getMethod, container, name, blob2HttpGetOptions.apply(options))); } @Override @@ -83,7 +108,8 @@ public class SwiftBlobRequestSigner implements BlobRequestSigner { @Override public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) { - throw new UnsupportedOperationException(); + HttpRequest request = processor.createRequest(createMethod, container, blobToObject.apply(blob)); + return cleanRequest(signForTemporaryAccess(request, timeInSeconds)); } @Override @@ -91,8 +117,35 @@ public class SwiftBlobRequestSigner implements BlobRequestSigner { return cleanRequest(processor.createRequest(deleteMethod, container, name)); } - @Override - public HttpRequest signGetBlob(String container, String name, org.jclouds.blobstore.options.GetOptions options) { - return cleanRequest(processor.createRequest(getMethod, container, name, blob2HttpGetOptions.apply(options))); + private HttpRequest signForTemporaryAccess(HttpRequest request, long timeInSeconds) { + HttpRequest.Builder builder = request.toBuilder(); + builder.filters(filter(request.getFilters(), instanceOf(AuthenticateRequest.class))); + + String key = temporaryUrlKeyProvider.get(); + if (key == null) { + throw new UnsupportedOperationException(); + } + long expiresInSeconds = unixEpochTimestampProvider.get() + timeInSeconds; + + builder.addQueryParam("temp_url_sig", createSignature(key, createStringToSign( + request.getMethod().toUpperCase(), request, expiresInSeconds))); + builder.addQueryParam("temp_url_expires", "" + expiresInSeconds); + + return builder.build(); + } + + private String createStringToSign(String method, HttpRequest request, long expiresInSeconds) { + checkArgument(method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("PUT")); + return String.format("%s\n%d\n%s", method.toUpperCase(), expiresInSeconds, + request.getEndpoint().getPath()); + } + + private String createSignature(String key, String stringToSign) { + try { + return CryptoStreams.hex(crypto.hmacSHA1(key.getBytes()).doFinal(stringToSign.getBytes())); + + } catch (InvalidKeyException e) { + throw Throwables.propagate(e); + } } } diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/config/SwiftBlobStoreContextModule.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/config/SwiftBlobStoreContextModule.java index 6b60d81071..ae9ce38aa8 100644 --- a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/config/SwiftBlobStoreContextModule.java +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/config/SwiftBlobStoreContextModule.java @@ -18,17 +18,26 @@ */ package org.jclouds.openstack.swift.blobstore.config; +import com.google.inject.Provides; import org.jclouds.blobstore.AsyncBlobStore; import org.jclouds.blobstore.BlobRequestSigner; import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.attr.ConsistencyModel; import org.jclouds.blobstore.config.BlobStoreMapModule; +import org.jclouds.date.TimeStamp; +import org.jclouds.openstack.swift.TemporaryUrlKey; import org.jclouds.openstack.swift.blobstore.SwiftAsyncBlobStore; import org.jclouds.openstack.swift.blobstore.SwiftBlobRequestSigner; import org.jclouds.openstack.swift.blobstore.SwiftBlobStore; import com.google.inject.AbstractModule; import com.google.inject.Scopes; +import org.jclouds.openstack.swift.extensions.TemporaryUrlKeyApi; +import org.jclouds.openstack.swift.extensions.TemporaryUrlKeyAsyncApi; + +import java.util.UUID; + +import static org.jclouds.rest.config.BinderUtils.bindClientAndAsyncClient; /** * Configures the {@link CloudFilesBlobStoreContext}; requires {@link SwiftAsyncBlobStore} @@ -38,16 +47,31 @@ import com.google.inject.Scopes; */ public class SwiftBlobStoreContextModule extends AbstractModule { + @Provides + @TimeStamp + protected Long unixEpochTimestampProvider() { + return System.currentTimeMillis() / 1000; /* convert to seconds */ + } + + @Provides + @TemporaryUrlKey + protected String temporaryUrlKeyProvider(TemporaryUrlKeyApi client) { + String key = client.getTemporaryUrlKey(); + if (key == null) { + client.setTemporaryUrlKey(UUID.randomUUID().toString()); + return client.getTemporaryUrlKey(); + } + return key; + } + + @Override protected void configure() { install(new BlobStoreMapModule()); bind(ConsistencyModel.class).toInstance(ConsistencyModel.STRICT); bind(AsyncBlobStore.class).to(SwiftAsyncBlobStore.class).in(Scopes.SINGLETON); bind(BlobStore.class).to(SwiftBlobStore.class).in(Scopes.SINGLETON); - configureRequestSigner(); - } - - protected void configureRequestSigner() { bind(BlobRequestSigner.class).to(SwiftBlobRequestSigner.class); + bindClientAndAsyncClient(binder(), TemporaryUrlKeyApi.class, TemporaryUrlKeyAsyncApi.class); } } diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/extensions/TemporaryUrlKeyApi.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/extensions/TemporaryUrlKeyApi.java new file mode 100644 index 0000000000..b8a9f48b65 --- /dev/null +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/extensions/TemporaryUrlKeyApi.java @@ -0,0 +1,51 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.openstack.swift.extensions; + +import org.jclouds.concurrent.Timeout; + +import java.util.concurrent.TimeUnit; + +/** + * @author Andrei Savu + * @see + */ +@Timeout(duration = 120, timeUnit = TimeUnit.SECONDS) +public interface TemporaryUrlKeyApi { + /** + * Retrieve the key used to generate Temporary object access URLs + * + * @return shared secret key or null + * @see + */ + String getTemporaryUrlKey(); + + /** + * To create a Temporary URL you must first set a key as account metadata. + *

+ * Once the key is set, you should not change it while you still want others to be + * able to access your temporary URL. If you change it, the TempURL becomes invalid + * (within 60 seconds, which is the cache time for a key) and others will not be allowed + * to access it. + * + * @param temporaryUrlKey + * @see + */ + void setTemporaryUrlKey(String temporaryUrlKey); +} diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/extensions/TemporaryUrlKeyAsyncApi.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/extensions/TemporaryUrlKeyAsyncApi.java new file mode 100644 index 0000000000..352c7197ce --- /dev/null +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/extensions/TemporaryUrlKeyAsyncApi.java @@ -0,0 +1,60 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.openstack.swift.extensions; + +import com.google.common.util.concurrent.ListenableFuture; +import org.jclouds.openstack.filters.AuthenticateRequest; +import org.jclouds.openstack.swift.Storage; +import org.jclouds.openstack.swift.functions.ParseTemporaryUrlKeyFromHeaders; +import org.jclouds.openstack.swift.reference.SwiftHeaders; +import org.jclouds.rest.annotations.Endpoint; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; +import org.jclouds.rest.annotations.SkipEncoding; + +import javax.ws.rs.HEAD; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +/** + * @author Andrei Savu + * @see TemporaryUrlKeyApi + */ +@SkipEncoding('/') +@RequestFilters(AuthenticateRequest.class) +@Endpoint(Storage.class) +public interface TemporaryUrlKeyAsyncApi { + + /** + * @see TemporaryUrlKeyApi#getTemporaryUrlKey + */ + @HEAD + @Path("/") + @ResponseParser(ParseTemporaryUrlKeyFromHeaders.class) + ListenableFuture getTemporaryUrlKey(); + + /** + * @see TemporaryUrlKeyApi#setTemporaryUrlKey + */ + @POST + @Path("/") + ListenableFuture setTemporaryUrlKey(@HeaderParam(SwiftHeaders.ACCOUNT_TEMPORARY_URL_KEY) String key); + +} diff --git a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/functions/ParseTemporaryUrlKeyFromHeaders.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseTemporaryUrlKeyFromHeaders.java similarity index 71% rename from apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/functions/ParseTemporaryUrlKeyFromHeaders.java rename to apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseTemporaryUrlKeyFromHeaders.java index fc360aaccf..c126fca7ee 100644 --- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/functions/ParseTemporaryUrlKeyFromHeaders.java +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseTemporaryUrlKeyFromHeaders.java @@ -16,13 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.cloudfiles.functions; +package org.jclouds.openstack.swift.functions; import com.google.common.base.Function; +import com.google.common.collect.Multimap; import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.swift.reference.SwiftHeaders; import static com.google.common.collect.Iterables.getOnlyElement; -import static org.jclouds.cloudfiles.reference.CloudFilesHeaders.ACCOUNT_TEMPORARY_URL_KEY; +import static org.jclouds.openstack.swift.reference.SwiftHeaders.ACCOUNT_TEMPORARY_URL_KEY; /** * @author Andrei Savu @@ -31,6 +33,11 @@ public class ParseTemporaryUrlKeyFromHeaders implements Function headers = httpResponse.getHeaders(); + if (headers.containsKey(ACCOUNT_TEMPORARY_URL_KEY)) { + return getOnlyElement(headers.get(ACCOUNT_TEMPORARY_URL_KEY)); + } else { + return null; + } } } diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/reference/SwiftHeaders.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/reference/SwiftHeaders.java index 3e8d37d2fb..73e104257f 100644 --- a/apis/swift/src/main/java/org/jclouds/openstack/swift/reference/SwiftHeaders.java +++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/reference/SwiftHeaders.java @@ -24,12 +24,15 @@ package org.jclouds.openstack.swift.reference; */ public interface SwiftHeaders { + public static final String ACCOUNT_TEMPORARY_URL_KEY = "X-Account-Meta-Temp-Url-Key"; public static final String ACCOUNT_BYTES_USED = "X-Account-Bytes-Used"; public static final String ACCOUNT_CONTAINER_COUNT = "X-Account-Container-Count"; + public static final String CONTAINER_BYTES_USED = "X-Container-Bytes-Used"; public static final String CONTAINER_OBJECT_COUNT = "X-Container-Object-Count"; public static final String CONTAINER_METADATA_PREFIX = "X-Container-Meta-"; public static final String CONTAINER_DELETE_METADATA_PREFIX = "X-Remove-Container-Meta-"; + public static final String USER_METADATA_PREFIX = "X-Object-Meta-"; public static final String OBJECT_COPY_FROM = "X-Copy-From"; diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/CommonSwiftClientTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/CommonSwiftClientTest.java index a8d3815445..6e7308eab8 100644 --- a/apis/swift/src/test/java/org/jclouds/openstack/swift/CommonSwiftClientTest.java +++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/CommonSwiftClientTest.java @@ -34,6 +34,7 @@ import org.jclouds.openstack.internal.TestOpenStackAuthenticationModule; import org.jclouds.openstack.reference.AuthHeaders; import org.jclouds.openstack.swift.blobstore.config.SwiftBlobStoreContextModule; import org.jclouds.openstack.swift.config.SwiftRestClientModule; +import org.jclouds.openstack.swift.extensions.TemporaryUrlKeyApi; import org.jclouds.rest.internal.BaseAsyncClientTest; import org.jclouds.rest.internal.RestAnnotationProcessor; import org.testng.annotations.Test; @@ -46,13 +47,17 @@ import com.google.inject.TypeLiteral; /** * Tests behavior of {@code BindSwiftObjectMetadataToRequest} - * + * * @author Adrian Cole */ // NOTE:without testName, this will not call @Before* and fail w/NPE during surefire -@Test(groups = "unit", testName = "SwiftClientTest") +@Test(groups = "unit", testName = "CommonSwiftClientTest") public abstract class CommonSwiftClientTest extends BaseAsyncClientTest { + public static final long UNIX_EPOCH_TIMESTAMP = 123456789L; + + public static final String TEMPORARY_URL_KEY = "get-or-set-X-Account-Meta-Temp-Url-Key"; + @Override protected TypeLiteral> createTypeLiteral() { return new TypeLiteral>() { @@ -74,11 +79,23 @@ public abstract class CommonSwiftClientTest extends BaseAsyncClientTest> of(StorageEndpointModule.class, SwiftRestClientModule.class, - SwiftBlobStoreContextModule.class)).build(); + ImmutableSet.>of(StorageEndpointModule.class, SwiftRestClientModule.class, + FixedSwiftBlobStoreContextModule.class)).build(); } @Override diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/SwiftBlobRequestSignerTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/SwiftBlobRequestSignerTest.java index 7c3c82ce4a..f16199ec10 100644 --- a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/SwiftBlobRequestSignerTest.java +++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/SwiftBlobRequestSignerTest.java @@ -19,6 +19,7 @@ package org.jclouds.openstack.swift.blobstore; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import java.io.IOException; import java.util.Date; @@ -43,8 +44,7 @@ public class SwiftBlobRequestSignerTest extends CommonSwiftClientTest { private BlobRequestSigner signer; private Factory blobFactory; - public void testSignGetBlob() throws ArrayIndexOutOfBoundsException, SecurityException, IllegalArgumentException, - NoSuchMethodException, IOException { + public void testSignGetBlob() throws Exception { HttpRequest request = signer.signGetBlob("container", "name"); assertRequestLineEquals(request, "GET http://storage/container/name HTTP/1.1"); @@ -54,19 +54,18 @@ public class SwiftBlobRequestSignerTest extends CommonSwiftClientTest { assertEquals(request.getFilters().size(), 0); } - public void testSignRemoveBlob() throws ArrayIndexOutOfBoundsException, SecurityException, IllegalArgumentException, - NoSuchMethodException, IOException { - HttpRequest request = signer.signRemoveBlob("container", "name"); + public void testSignGetBlobWithTime() { + HttpRequest request = signer.signGetBlob("container", "name", 120); - assertRequestLineEquals(request, "DELETE http://storage/container/name HTTP/1.1"); - assertNonPayloadHeadersEqual(request, "X-Auth-Token: testtoken\n"); + assertRequestLineEquals(request, "GET http://storage/container/name?" + + "temp_url_sig=4759d99d13c826bba0af2c9f0c526ca53c95abaf&temp_url_expires=123456909 HTTP/1.1"); + assertFalse(request.getHeaders().containsKey("X-Auth-Token")); assertPayloadEquals(request, null, null, false); assertEquals(request.getFilters().size(), 0); } - public void testSignPutBlob() throws ArrayIndexOutOfBoundsException, SecurityException, IllegalArgumentException, - NoSuchMethodException, IOException { + public void testSignPutBlob() throws Exception { Blob blob = blobFactory.create(null); blob.getMetadata().setName("name"); blob.setPayload(""); @@ -84,6 +83,37 @@ public class SwiftBlobRequestSignerTest extends CommonSwiftClientTest { assertEquals(request.getFilters().size(), 0); } + public void testSignPutBlobWithTime() throws Exception { + Blob blob = blobFactory.create(null); + + blob.getMetadata().setName("name"); + blob.setPayload(""); + blob.getPayload().getContentMetadata().setContentLength(2l); + blob.getPayload().getContentMetadata().setContentMD5(new byte[]{0, 2, 4, 8}); + blob.getPayload().getContentMetadata().setContentType("text/plain"); + blob.getPayload().getContentMetadata().setExpires(new Date(1000)); + + HttpRequest request = signer.signPutBlob("container", blob, 120 /* seconds */); + + assertRequestLineEquals(request, "PUT http://storage/container/name?" + + "temp_url_sig=490690286130adac9e7144d85b320a00b1bf9e2b&temp_url_expires=123456909 HTTP/1.1"); + + assertFalse(request.getHeaders().containsKey("X-Auth-Token")); + assertContentHeadersEqual(request, "text/plain", null, null, null, (long) 2l, new byte[]{0, 2, 4, 8}, new Date(1000)); + + assertEquals(request.getFilters().size(), 0); + } + + public void testSignRemoveBlob() throws Exception { + HttpRequest request = signer.signRemoveBlob("container", "name"); + + assertRequestLineEquals(request, "DELETE http://storage/container/name HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "X-Auth-Token: testtoken\n"); + assertPayloadEquals(request, null, null, false); + + assertEquals(request.getFilters().size(), 0); + } + @BeforeClass protected void setupFactory() throws IOException { super.setupFactory(); diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobLiveTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobLiveTest.java index 5aa1b4931c..c1b0ff7a67 100644 --- a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobLiveTest.java +++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobLiveTest.java @@ -19,11 +19,16 @@ package org.jclouds.openstack.swift.blobstore.integration; import java.util.Properties; +import java.util.UUID; import org.jclouds.blobstore.integration.internal.BaseBlobLiveTest; import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties; +import org.jclouds.openstack.swift.extensions.TemporaryUrlKeyApi; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + /** * * @author James Murty diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobSignerLiveTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobSignerLiveTest.java index 392a38024e..9c6ed48d16 100644 --- a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobSignerLiveTest.java +++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobSignerLiveTest.java @@ -18,17 +18,16 @@ */ package org.jclouds.openstack.swift.blobstore.integration; -import java.util.Properties; - import org.jclouds.blobstore.integration.internal.BaseBlobSignerLiveTest; import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties; import org.testng.annotations.Test; +import java.util.Properties; + /** - * * @author Adrian Cole */ -@Test(groups = { "live" }) +@Test(groups = {"live"}) public class SwiftBlobSignerLiveTest extends BaseBlobSignerLiveTest { @Override diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/StubSwiftAsyncClient.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/StubSwiftAsyncClient.java index 1341de2b0c..2d0fb1584a 100644 --- a/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/StubSwiftAsyncClient.java +++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/internal/StubSwiftAsyncClient.java @@ -31,6 +31,7 @@ import java.util.concurrent.ExecutorService; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import javax.ws.rs.HeaderParam; import org.jclouds.Constants; import org.jclouds.blobstore.LocalAsyncBlobStore; @@ -60,6 +61,7 @@ import com.google.common.base.Throwables; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; +import org.jclouds.openstack.swift.reference.SwiftHeaders; /** * Implementation of {@link SwiftAsyncClient} which keeps all data in a local Map object. @@ -201,7 +203,7 @@ public class StubSwiftAsyncClient implements CommonSwiftAsyncClient { return null; } - public ListenableFuture setObjectInfo(String container, String key, Map userMetadata) { + public ListenableFuture setObjectInfo(String container, String key, Map userMetadata) { throw new UnsupportedOperationException(); } diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobSignerLiveTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobSignerLiveTest.java index 200f4399b6..bee7e726f1 100644 --- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobSignerLiveTest.java +++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobSignerLiveTest.java @@ -20,41 +20,28 @@ package org.jclouds.blobstore.integration.internal; import static org.jclouds.blobstore.options.GetOptions.Builder.range; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; import org.jclouds.blobstore.domain.Blob; import org.jclouds.http.HttpRequest; +import org.jclouds.rest.AuthorizationException; import org.jclouds.util.Strings2; +import org.testng.SkipException; import org.testng.annotations.Test; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + /** * Tests integrated functionality of all signature commands. *

* Each test uses a different container name, so it should be perfectly fine to run in parallel. - * + * * @author Adrian Cole */ -@Test(groups = { "live" }) +@Test(groups = {"live"}) public class BaseBlobSignerLiveTest extends BaseBlobStoreIntegrationTest { - @Test - public void testSignRemoveUrl() throws Exception { - String name = "hello"; - String text = "fooooooooooooooooooooooo"; - - Blob blob = view.getBlobStore().blobBuilder(name).payload(text).contentType("text/plain").build(); - String container = getContainerName(); - try { - view.getBlobStore().putBlob(container, blob); - assertConsistencyAwareContainerSize(container, 1); - HttpRequest request = view.getSigner().signRemoveBlob(container, name); - assertEquals(request.getFilters().size(), 0); - view.utils().http().invoke(request); - assert !view.getBlobStore().blobExists(container, name); - } finally { - returnContainer(container); - } - } - @Test public void testSignGetUrl() throws Exception { String name = "hello"; @@ -91,6 +78,34 @@ public class BaseBlobSignerLiveTest extends BaseBlobStoreIntegrationTest { } } + @Test + public void testSignGetUrlWithTime() throws InterruptedException, IOException { + String name = "hello"; + String text = "fooooooooooooooooooooooo"; + + Blob blob = view.getBlobStore().blobBuilder(name).payload(text).contentType("text/plain").build(); + String container = getContainerName(); + try { + view.getBlobStore().putBlob(container, blob); + assertConsistencyAwareContainerSize(container, 1); + HttpRequest request = view.getSigner().signGetBlob(container, name, 3 /* seconds */); + + assertEquals(request.getFilters().size(), 0); + assertEquals(Strings2.toString(view.utils().http().invoke(request).getPayload()), text); + + TimeUnit.SECONDS.sleep(4); + try { + Strings2.toString(view.utils().http().invoke(request).getPayload()); + fail("Temporary URL did not expire as expected"); + } catch (AuthorizationException expected) { + } + } catch (UnsupportedOperationException ignore) { + throw new SkipException("signGetUrl with a time limit is not supported on " + provider); + } finally { + returnContainer(container); + } + } + @Test public void testSignPutUrl() throws Exception { String name = "hello"; @@ -108,4 +123,52 @@ public class BaseBlobSignerLiveTest extends BaseBlobStoreIntegrationTest { } } + @Test + public void testSignPutUrlWithTime() throws Exception { + String name = "hello"; + String text = "fooooooooooooooooooooooo"; + + Blob blob = view.getBlobStore().blobBuilder(name).payload(text).contentType("text/plain").build(); + String container = getContainerName(); + try { + HttpRequest request = view.getSigner().signPutBlob(container, blob, 3 /* seconds */); + assertEquals(request.getFilters().size(), 0); + + Strings2.toString(view.utils().http().invoke(request).getPayload()); + assertConsistencyAwareContainerSize(container, 1); + + view.getBlobStore().removeBlob(container, name); + assertConsistencyAwareContainerSize(container, 0); + + TimeUnit.SECONDS.sleep(4); + try { + Strings2.toString(view.utils().http().invoke(request).getPayload()); + fail("Temporary URL did not expire as expected"); + } catch (AuthorizationException expected) { + } + } catch (UnsupportedOperationException ignore) { + throw new SkipException("signPutUrl with a time limit is not supported on " + provider); + } finally { + returnContainer(container); + } + } + + @Test + public void testSignRemoveUrl() throws Exception { + String name = "hello"; + String text = "fooooooooooooooooooooooo"; + + Blob blob = view.getBlobStore().blobBuilder(name).payload(text).contentType("text/plain").build(); + String container = getContainerName(); + try { + view.getBlobStore().putBlob(container, blob); + assertConsistencyAwareContainerSize(container, 1); + HttpRequest request = view.getSigner().signRemoveBlob(container, name); + assertEquals(request.getFilters().size(), 0); + view.utils().http().invoke(request); + assert !view.getBlobStore().blobExists(container, name); + } finally { + returnContainer(container); + } + } } \ No newline at end of file