diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobRequestSigner.java b/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobRequestSigner.java
index 0fe231f79b..1d211bc4da 100644
--- a/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobRequestSigner.java
+++ b/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosBlobRequestSigner.java
@@ -67,11 +67,21 @@ public class AtmosBlobRequestSigner implements BlobRequestSigner {
return cleanRequest(processor.createRequest(getMethod, getPath(container, name)));
}
+ @Override
+ public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
@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) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public HttpRequest signRemoveBlob(String container, String name) {
return cleanRequest(processor.createRequest(deleteMethod, getPath(container, name)));
diff --git a/apis/cloudfiles/pom.xml b/apis/cloudfiles/pom.xml
index 708148679c..a067095c2e 100644
--- a/apis/cloudfiles/pom.xml
+++ b/apis/cloudfiles/pom.xml
@@ -53,6 +53,11 @@
swift
${project.version}
+
+ org.jclouds.driver
+ jclouds-joda
+ ${project.version}
+
org.jclouds
jclouds-core
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 14e3b28f5f..9b73ad6971 100644
--- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesAsyncClient.java
+++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesAsyncClient.java
@@ -36,6 +36,7 @@ import org.jclouds.blobstore.functions.ReturnNullOnContainerNotFound;
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;
@@ -57,7 +58,7 @@ import com.google.common.util.concurrent.ListenableFuture;
* All commands return a ListenableFuture of the result from Cloud Files. Any exceptions incurred
* during processing will be backend in an {@link ExecutionException} as documented in
* {@link ListenableFuture#get()}.
- *
+ *
* @see CloudFilesClient
* @see
* @author Adrian Cole
@@ -130,4 +131,19 @@ public interface CloudFilesAsyncClient extends CommonSwiftAsyncClient {
@Endpoint(CDNManagement.class)
ListenableFuture disableCDN(@PathParam("container") String container);
+
+ /**
+ * @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 e74ca1ec54..f52d7b8ad8 100644
--- a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesClient.java
+++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/CloudFilesClient.java
@@ -34,9 +34,9 @@ import org.jclouds.openstack.swift.CommonSwiftClient;
*
* All commands return a Future of the result from Cloud Files. Any exceptions incurred during
* processing will be backend in an {@link ExecutionException} as documented in {@link Future#get()}.
- *
- * @see
+ *
* @author Adrian Cole
+ * @see
*/
@Timeout(duration = 120, timeUnit = TimeUnit.SECONDS)
public interface CloudFilesClient extends CommonSwiftClient {
@@ -52,4 +52,24 @@ public interface CloudFilesClient extends CommonSwiftClient {
boolean disableCDN(String container);
+ /**
+ * 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/TemporaryUrlKey.java b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/TemporaryUrlKey.java
new file mode 100644
index 0000000000..15b7d43e1c
--- /dev/null
+++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/TemporaryUrlKey.java
@@ -0,0 +1,37 @@
+/**
+ * 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 javax.inject.Qualifier;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Represents the key used for signing URLs that have temporary access to objects
+ *
+ * @see
+ * @author Andrei Savu
+ */
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = { ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+@Qualifier
+public @interface 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
new file mode 100644
index 0000000000..b6b9d7136a
--- /dev/null
+++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobRequestSigner.java
@@ -0,0 +1,124 @@
+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 c9b64d196a..2c4b8f1544 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
@@ -23,11 +23,15 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Singleton;
+import org.jclouds.blobstore.BlobRequestSigner;
import org.jclouds.cloudfiles.CloudFilesClient;
+import org.jclouds.cloudfiles.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.SwiftBlobStore;
import org.jclouds.openstack.swift.blobstore.config.SwiftBlobStoreContextModule;
@@ -37,9 +41,11 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.inject.Provides;
+import org.jclouds.rest.HttpClient;
+import org.jclouds.rest.RequestSigner;
+import org.joda.time.Instant;
/**
- *
* @author Adrian Cole
*/
public class CloudFilesBlobStoreContextModule extends SwiftBlobStoreContextModule {
@@ -61,6 +67,18 @@ public class CloudFilesBlobStoreContextModule extends SwiftBlobStoreContextModul
});
}
+ @Provides
+ @TimeStamp
+ protected Long unixEpochTimestampProvider() {
+ return Instant.now().getMillis() / 1000; /* in seconds */
+ }
+
+ @Provides
+ @TemporaryUrlKey
+ protected String temporaryUrlKeyProvider(CloudFilesClient client) {
+ return client.getTemporaryUrlKey();
+ }
+
@Override
protected void configure() {
super.configure();
@@ -68,4 +86,9 @@ public class CloudFilesBlobStoreContextModule extends SwiftBlobStoreContextModul
bind(SwiftAsyncBlobStore.class).to(CloudFilesAsyncBlobStore.class);
bind(ObjectToBlobMetadata.class).to(CloudFilesObjectToBlobMetadata.class);
}
+
+ @Override
+ protected void configureRequestSigner() {
+ bind(BlobRequestSigner.class).to(CloudFilesBlobRequestSigner.class);
+ }
}
diff --git a/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/functions/ParseTemporaryUrlKeyFromHeaders.java b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/functions/ParseTemporaryUrlKeyFromHeaders.java
new file mode 100644
index 0000000000..fc360aaccf
--- /dev/null
+++ b/apis/cloudfiles/src/main/java/org/jclouds/cloudfiles/functions/ParseTemporaryUrlKeyFromHeaders.java
@@ -0,0 +1,36 @@
+/**
+ * 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.functions;
+
+import com.google.common.base.Function;
+import org.jclouds.http.HttpResponse;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.jclouds.cloudfiles.reference.CloudFilesHeaders.ACCOUNT_TEMPORARY_URL_KEY;
+
+/**
+ * @author Andrei Savu
+ */
+public class ParseTemporaryUrlKeyFromHeaders implements Function {
+
+ @Override
+ public String apply(HttpResponse httpResponse) {
+ return getOnlyElement(httpResponse.getHeaders().get(ACCOUNT_TEMPORARY_URL_KEY));
+ }
+}
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 696398f395..377a79f4ea 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,6 +30,8 @@ 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_REFERRER_ACL = "X-Referrer-ACL ";
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
new file mode 100644
index 0000000000..f09b74dc51
--- /dev/null
+++ b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/CloudFilesClientTest.java
@@ -0,0 +1,72 @@
+/**
+ * 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
new file mode 100644
index 0000000000..7bdcdaefc4
--- /dev/null
+++ b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/CloudFilesBlobSignerTest.java
@@ -0,0 +1,83 @@
+/**
+ * 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 edae3c1abf..14de7f6200 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,16 +18,37 @@
*/
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.testng.annotations.Test;
+import java.util.UUID;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
/**
- *
* @author Adrian Cole
*/
-@Test(groups = { "live" })
+@Test(groups = {"live"})
public class CloudFilesBlobLiveTest extends SwiftBlobLiveTest {
- public CloudFilesBlobLiveTest(){
+ public CloudFilesBlobLiveTest() {
provider = "cloudfiles";
}
+
+ public void testGetAndSetTemporaryUrlKey() {
+ CloudFilesClient client = view.unwrap(CloudFilesApiMetadata.CONTEXT_TOKEN).getApi();
+
+ String currentSecretKey = client.getTemporaryUrlKey();
+ assertNotNull(currentSecretKey);
+ try {
+ String testKey = UUID.randomUUID().toString();
+ client.setTemporaryUrlKey(testKey);
+ assertEquals(client.getTemporaryUrlKey(), testKey);
+
+ } finally {
+ client.setTemporaryUrlKey(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 7add9a211d..980505c59e 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,16 +18,78 @@
*/
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
*/
-@Test(groups = { "live" })
+@Test(groups = {"live"})
public class CloudFilesBlobSignerLiveTest extends SwiftBlobSignerLiveTest {
- public CloudFilesBlobSignerLiveTest(){
+ public CloudFilesBlobSignerLiveTest() {
provider = "cloudfiles";
}
+
+ 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/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobRequestSigner.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/S3BlobRequestSigner.java
index 45aa6ba3c0..71718ede7e 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
@@ -69,11 +69,21 @@ public class S3BlobRequestSigner implements BlobRequestSigner {
return cleanRequest(processor.createRequest(getMethod, container, name));
}
+ @Override
+ public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
@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) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public HttpRequest signRemoveBlob(String container, String name) {
return cleanRequest(processor.createRequest(deleteMethod, container, name));
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 7ba1fc8a10..d3986f8e7e 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
@@ -37,12 +37,14 @@ 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 BlobToObject blobToObject;
private final BlobToHttpGetOptions blob2HttpGetOptions;
@@ -54,13 +56,14 @@ public class SwiftBlobRequestSigner implements BlobRequestSigner {
public SwiftBlobRequestSigner(RestAnnotationProcessor processor, BlobToObject blobToObject,
BlobToHttpGetOptions blob2HttpGetOptions) throws SecurityException, NoSuchMethodException {
this.processor = checkNotNull(processor, "processor");
+
this.blobToObject = checkNotNull(blobToObject, "blobToObject");
this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions");
+
this.getMethod = CommonSwiftAsyncClient.class.getMethod("getObject", String.class, String.class,
GetOptions[].class);
this.deleteMethod = CommonSwiftAsyncClient.class.getMethod("removeObject", String.class, String.class);
this.createMethod = CommonSwiftAsyncClient.class.getMethod("putObject", String.class, SwiftObject.class);
-
}
@Override
@@ -68,11 +71,21 @@ public class SwiftBlobRequestSigner implements BlobRequestSigner {
return cleanRequest(processor.createRequest(getMethod, container, name));
}
+ @Override
+ public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
@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) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public HttpRequest signRemoveBlob(String container, String name) {
return cleanRequest(processor.createRequest(deleteMethod, container, name));
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 01d39c7d2d..6b60d81071 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
@@ -33,7 +33,7 @@ import com.google.inject.Scopes;
/**
* Configures the {@link CloudFilesBlobStoreContext}; requires {@link SwiftAsyncBlobStore}
* bound.
- *
+ *
* @author Adrian Cole
*/
public class SwiftBlobStoreContextModule extends AbstractModule {
@@ -44,8 +44,10 @@ public class SwiftBlobStoreContextModule extends AbstractModule {
bind(ConsistencyModel.class).toInstance(ConsistencyModel.STRICT);
bind(AsyncBlobStore.class).to(SwiftAsyncBlobStore.class).in(Scopes.SINGLETON);
bind(BlobStore.class).to(SwiftBlobStore.class).in(Scopes.SINGLETON);
- bind(BlobRequestSigner.class).to(SwiftBlobRequestSigner.class);
+ configureRequestSigner();
}
-
+ protected void configureRequestSigner() {
+ bind(BlobRequestSigner.class).to(SwiftBlobRequestSigner.class);
+ }
}
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 d677fcb151..a8d3815445 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
@@ -74,6 +74,7 @@ public abstract class CommonSwiftClientTest extends BaseAsyncClientTest> of(StorageEndpointModule.class, SwiftRestClientModule.class,
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..fd6882b718 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;
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 019e66bcfc..392a38024e 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
@@ -25,18 +25,19 @@ import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties;
import org.testng.annotations.Test;
/**
- *
+ *
* @author Adrian Cole
*/
@Test(groups = { "live" })
public class SwiftBlobSignerLiveTest extends BaseBlobSignerLiveTest {
+
@Override
protected Properties setupProperties() {
Properties props = super.setupProperties();
setIfTestSystemPropertyPresent(props, KeystoneProperties.CREDENTIAL_TYPE);
return props;
}
-
+
public SwiftBlobSignerLiveTest() {
provider = System.getProperty("test.swift.provider", "swift");
}
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/BlobRequestSigner.java b/blobstore/src/main/java/org/jclouds/blobstore/BlobRequestSigner.java
index 7720910f4e..6510ec0248 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/BlobRequestSigner.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/BlobRequestSigner.java
@@ -47,6 +47,16 @@ public interface BlobRequestSigner {
*/
HttpRequest signGetBlob(String container, String name);
+ /**
+ * gets a signed request, including headers as necessary, to allow access to a blob
+ * from an external client for a limited period of time
+ *
+ * @param timeInSeconds
+ * validity time in seconds for the generated request
+ * @see #signGetBlob(String, String)
+ */
+ HttpRequest signGetBlob(String container, String name, long timeInSeconds);
+
/**
* @param options
* @see #signGetBlob(String, String)
@@ -84,4 +94,15 @@ public interface BlobRequestSigner {
* @see BlobBuilder#forSigning
*/
HttpRequest signPutBlob(String container, Blob blob);
+
+ /**
+ * gets a signed request, including headers as necessary, to upload a blob from an
+ * external client for a limited period of time
+ *
+ * @param timeInSeconds
+ * validity time in seconds for the generated request
+ * @see BlobBuilder#forSigning
+ * @see BlobRequestSigner#signPutBlob
+ */
+ HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds);
}
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/TransientBlobRequestSigner.java b/blobstore/src/main/java/org/jclouds/blobstore/TransientBlobRequestSigner.java
index e005ca3df3..9cd59a79be 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/TransientBlobRequestSigner.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/TransientBlobRequestSigner.java
@@ -62,6 +62,11 @@ public class TransientBlobRequestSigner implements BlobRequestSigner {
return basicAuth.filter(request);
}
+ @Override
+ public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public HttpRequest signPutBlob(String container, Blob blob) {
HttpRequest request = HttpRequest.builder().method("PUT").endpoint(
@@ -71,6 +76,11 @@ public class TransientBlobRequestSigner implements BlobRequestSigner {
return basicAuth.filter(request);
}
+ @Override
+ public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public HttpRequest signRemoveBlob(String container, String name) {
HttpRequest request = HttpRequest.builder().method("DELETE").endpoint(String.format("%s/%s/%s", endpoint.get(), container,
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/internal/RequestSigningUnsupported.java b/blobstore/src/main/java/org/jclouds/blobstore/internal/RequestSigningUnsupported.java
index f86ed67310..cd17ebee9b 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/internal/RequestSigningUnsupported.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/internal/RequestSigningUnsupported.java
@@ -38,7 +38,12 @@ public class RequestSigningUnsupported implements BlobRequestSigner {
}
@Override
- public HttpRequest signPutBlob(String container, Blob blob) {
+ public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpRequest signGetBlob(String container, String name, GetOptions options) {
throw new UnsupportedOperationException();
}
@@ -48,7 +53,12 @@ public class RequestSigningUnsupported implements BlobRequestSigner {
}
@Override
- public HttpRequest signGetBlob(String container, String name, GetOptions options) {
+ public HttpRequest signPutBlob(String container, Blob blob) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) {
throw new UnsupportedOperationException();
}
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java
index 535d7201aa..72e881a484 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java
@@ -66,11 +66,21 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
return cleanRequest(processor.createRequest(getMethod, container, name));
}
+ @Override
+ public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public HttpRequest signPutBlob(String container, Blob blob) {
return cleanRequest(processor.createRequest(createMethod, container, blobToBlob.apply(blob)));
}
+ @Override
+ public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public HttpRequest signRemoveBlob(String container, String name) {
return cleanRequest(processor.createRequest(deleteMethod, container, name));
diff --git a/providers/hpcloud-objectstorage/src/main/java/org/jclouds/hpcloud/objectstorage/blobstore/HPCloudObjectStorageBlobRequestSigner.java b/providers/hpcloud-objectstorage/src/main/java/org/jclouds/hpcloud/objectstorage/blobstore/HPCloudObjectStorageBlobRequestSigner.java
index 90829b5418..556ff74416 100644
--- a/providers/hpcloud-objectstorage/src/main/java/org/jclouds/hpcloud/objectstorage/blobstore/HPCloudObjectStorageBlobRequestSigner.java
+++ b/providers/hpcloud-objectstorage/src/main/java/org/jclouds/hpcloud/objectstorage/blobstore/HPCloudObjectStorageBlobRequestSigner.java
@@ -19,7 +19,6 @@ import org.jclouds.openstack.swift.domain.SwiftObject;
import org.jclouds.rest.internal.RestAnnotationProcessor;
/**
- *
* @author Adrian Cole
*/
@Singleton
@@ -42,7 +41,6 @@ public class HPCloudObjectStorageBlobRequestSigner implements BlobRequestSigner
GetOptions[].class);
this.deleteMethod = HPCloudObjectStorageAsyncClient.class.getMethod("removeObject", String.class, String.class);
this.createMethod = HPCloudObjectStorageAsyncClient.class.getMethod("putObject", String.class, SwiftObject.class);
-
}
@Override
@@ -50,11 +48,21 @@ public class HPCloudObjectStorageBlobRequestSigner implements BlobRequestSigner
return cleanRequest(processor.createRequest(getMethod, container, name));
}
+ @Override
+ public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
+ throw new UnsupportedOperationException();
+ }
+
@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) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public HttpRequest signRemoveBlob(String container, String name) {
return cleanRequest(processor.createRequest(deleteMethod, container, name));