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));