You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ad...@apache.org on 2013/09/30 01:30:04 UTC

git commit: JCLOUDS-308. Add Temporary Url Support to openstack-swift

Updated Branches:
  refs/heads/master cc1e2ae4d -> fd3f8cd01


JCLOUDS-308. Add Temporary Url Support to openstack-swift


Project: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/commit/fd3f8cd0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/tree/fd3f8cd0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/diff/fd3f8cd0

Branch: refs/heads/master
Commit: fd3f8cd01991363db91f90bb7f22133af1988061
Parents: cc1e2ae
Author: Adrian Cole <ad...@gmail.com>
Authored: Sun Sep 29 15:42:46 2013 -0700
Committer: Adrian Cole <ad...@gmail.com>
Committed: Sun Sep 29 16:27:41 2013 -0700

----------------------------------------------------------------------
 .../openstack/swift/v1/TemporaryUrlSigner.java  | 92 ++++++++++++++++++++
 .../openstack/swift/v1/domain/Account.java      |  5 ++
 .../openstack/swift/v1/features/AccountApi.java | 20 +++++
 .../swift/v1/TemporaryUrlSignerLiveTest.java    | 91 +++++++++++++++++++
 .../swift/v1/TemporaryUrlSignerMockTest.java    | 75 ++++++++++++++++
 .../swift/v1/features/AccountApiMockTest.java   | 20 +++++
 6 files changed, 303 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/fd3f8cd0/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java
new file mode 100644
index 0000000..35f2b12
--- /dev/null
+++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2013 Netflix, Inc.
+ *
+ * Licensed 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.v1;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Suppliers.memoizeWithExpiration;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.io.BaseEncoding.base16;
+import static java.lang.String.format;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.jclouds.openstack.swift.v1.features.AccountApi;
+
+import com.google.common.base.Supplier;
+
+/**
+ * Use this utility to create temporary urls.
+ * 
+ * @see <a
+ *      href="http://docs.openstack.org/trunk/config-reference/content/object-storage-tempurl.html">Temporary
+ *      URL Documentation</a>
+ */
+public class TemporaryUrlSigner {
+
+   public static TemporaryUrlSigner checkApiEvery(final AccountApi api, long seconds) {
+      Supplier<String> keySupplier = memoizeWithExpiration(new TemporaryUrlKeyFromAccount(api), seconds, SECONDS);
+      return new TemporaryUrlSigner(keySupplier);
+   }
+
+   private final Supplier<String> keySupplier;
+
+   TemporaryUrlSigner(Supplier<String> keySupplier) {
+      this.keySupplier = keySupplier;
+   }
+
+   public String sign(String method, String path, long expirationTimestampSeconds) {
+      checkNotNull(method, "method");
+      checkNotNull(path, "path");
+      checkArgument(expirationTimestampSeconds > 0, "expirationTimestamp must be a unix epoch timestamp");
+      String hmacBody = format("%s\n%s\n%s", method, expirationTimestampSeconds, path);
+      return base16().lowerCase().encode(hmacSHA1(hmacBody));
+   }
+
+   byte[] hmacSHA1(String data) {
+      try {
+         String key = keySupplier.get();
+         checkState(key != null, "%s returned a null temporaryUrlKey!", keySupplier);
+         Mac mac = Mac.getInstance("HmacSHA1");
+         mac.init(new SecretKeySpec(key.getBytes(UTF_8), "HmacSHA1"));
+         return mac.doFinal(data.getBytes(UTF_8));
+      } catch (Exception e) {
+         throw propagate(e);
+      }
+   }
+
+   static class TemporaryUrlKeyFromAccount implements Supplier<String> {
+      private final AccountApi api;
+
+      private TemporaryUrlKeyFromAccount(AccountApi api) {
+         this.api = checkNotNull(api, "accountApi");
+      }
+
+      @Override
+      public String get() {
+         return api.get().temporaryUrlKey().orNull();
+      }
+
+      @Override
+      public String toString() {
+         return format("get().temporaryUrlKey() using %s", api);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/fd3f8cd0/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java
index 304f329..a3857f0 100644
--- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java
+++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java
@@ -25,6 +25,7 @@ import java.util.Map.Entry;
 
 import com.google.common.base.Objects;
 import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableMap;
 
 /**
@@ -59,6 +60,10 @@ public class Account {
       return bytesUsed;
    }
 
+   public Optional<String> temporaryUrlKey() {
+      return Optional.fromNullable(metadata.get("temp-url-key"));
+   }
+
    /**
     * In current swift implementations, headers keys are lower-cased. This means
     * characters such as turkish will probably not work out well.

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/fd3f8cd0/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java
index 5e0bc6d..9736112 100644
--- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java
+++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java
@@ -23,6 +23,7 @@ import java.util.Map;
 import javax.inject.Named;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.HEAD;
+import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 
@@ -88,6 +89,25 @@ public interface AccountApi {
    boolean updateMetadata(@BinderParam(BindAccountMetadataToHeaders.class) Map<String, String> metadata);
 
    /**
+    * Replaces the {@link Account#temporaryUrlKey()}.
+    *
+    * @param metadata
+    *           the Account metadata to create or update.
+    *
+    * @see <a
+    *      href="http://docs.openstack.org/trunk/config-reference/content/object-storage-tempurl.html">
+    *      Temporary URL Documentation</a>
+    *
+    * @return <code>true</code> if the Temporary URL Key was successfully created
+    *         or updated, false if not.
+    */
+   @Named("UpdateAccountMetadata")
+   @POST
+   @Fallback(FalseOnNotFoundOr404.class)
+   @Path("/")
+   boolean updateTemporaryUrlKey(@HeaderParam("X-Account-Meta-Temp-URL-Key") String temporaryUrlKey);
+
+   /**
     * Deletes Account metadata.
     * 
     * @param metadata

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/fd3f8cd0/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java
new file mode 100644
index 0000000..f2ba78f
--- /dev/null
+++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.swift.v1;
+
+import static java.lang.String.format;
+import static org.jclouds.io.Payloads.newStringPayload;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.UUID;
+
+import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest;
+import org.jclouds.openstack.swift.v1.options.CreateContainerOptions;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+@Test(groups = "live", testName = "TemporaryUrlSignerLiveTest")
+public class TemporaryUrlSignerLiveTest extends BaseSwiftApiLiveTest {
+
+   private String name = getClass().getSimpleName();
+   private String containerName = getClass().getSimpleName() + "Container";
+
+   public void signForPublicAccess() throws Exception {
+      for (String regionId : api.configuredRegions()) {
+         SwiftObject object = api.objectApiInRegionForContainer(regionId, containerName).head(name);
+
+         long expires = System.currentTimeMillis() / 1000 + 5;
+         String signature = TemporaryUrlSigner.checkApiEvery(api.accountApiInRegion(regionId), 5) //
+               .sign("GET", object.uri().getPath(), expires);
+
+         URI signed = URI.create(format("%s?temp_url_sig=%s&temp_url_expires=%s", object.uri(), signature, expires));
+
+         InputStream publicStream = signed.toURL().openStream();
+         assertEquals(Strings2.toStringAndClose(publicStream), "swifty");
+
+         // let it expire
+         Thread.sleep(5000);
+         try {
+            signed.toURL().openStream();
+            fail("should have expired!");
+         } catch (IOException e) {
+         }
+      }
+   }
+
+   @Override
+   @BeforeClass(groups = "live")
+   public void setup() {
+      super.setup();
+      String key = UUID.randomUUID().toString();
+      for (String regionId : api.configuredRegions()) {
+         api.accountApiInRegion(regionId).updateTemporaryUrlKey(key);
+         api.containerApiInRegion(regionId).createIfAbsent(containerName, new CreateContainerOptions());
+         api.objectApiInRegionForContainer(regionId, containerName) //
+               .replace(name, newStringPayload("swifty"), ImmutableMap.<String, String> of());
+      }
+   }
+
+   @AfterMethod
+   @AfterClass(groups = "live")
+   public void tearDown() {
+      for (String regionId : api.configuredRegions()) {
+         api.objectApiInRegionForContainer(regionId, containerName).delete(name);
+         api.containerApiInRegion(regionId).deleteIfEmpty(containerName);
+      }
+      super.tearDown();
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/fd3f8cd0/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java
new file mode 100644
index 0000000..c3711a8
--- /dev/null
+++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.swift.v1;
+
+import static org.jclouds.openstack.swift.v1.features.AccountApiMockTest.accountResponse;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.openstack.swift.v1.internal.BaseSwiftMockTest;
+import org.testng.annotations.Test;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+
+@Test
+public class TemporaryUrlSignerMockTest extends BaseSwiftMockTest {
+
+   @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "accountApi")
+   public void whenAccountApiIsNull() {
+      TemporaryUrlSigner.checkApiEvery(null, 10000);
+   }
+
+   public void whenAccountApiHasKey() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(accountResponse().addHeader("X-Account-Meta-Temp-URL-Key", "mykey"));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         String signature = TemporaryUrlSigner.checkApiEvery(api.accountApiInRegion("DFW"), 10000)
+               .sign("GET", "/v1/AUTH_account/container/object", 1323479485l);
+
+         assertEquals(signature, "d9fc2067e52b06598421664cf6610bfc8fc431f6");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         assertEquals(server.takeRequest().getRequestLine(),
+               "HEAD /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/ HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = ".*returned a null temporaryUrlKey!")
+   public void whenAccountApiDoesntHaveKey() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(accountResponse());
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         TemporaryUrlSigner.checkApiEvery(api.accountApiInRegion("DFW"), 10000)
+            .sign("GET","/v1/AUTH_account/container/object", 1323479485l);
+      } finally {
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         assertEquals(server.takeRequest().getRequestLine(),
+               "HEAD /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/ HTTP/1.1");
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/fd3f8cd0/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java
----------------------------------------------------------------------
diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java
index c2a37c3..d4f7eda 100644
--- a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java
+++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java
@@ -90,6 +90,26 @@ public class AccountApiMockTest extends BaseSwiftMockTest {
       }
    }
 
+   public void updateTemporaryUrlKey() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(accountResponse());
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         assertTrue(api.accountApiInRegion("DFW").updateTemporaryUrlKey("foobar"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest replaceRequest = server.takeRequest();
+         assertEquals(replaceRequest.getRequestLine(),
+               "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/ HTTP/1.1");
+         assertEquals(replaceRequest.getHeader("X-Account-Meta-Temp-URL-Key"), "foobar");
+      } finally {
+         server.shutdown();
+      }
+   }
+
    public void deleteMetadata() throws Exception {
       MockWebServer server = mockSwiftServer();
       server.enqueue(new MockResponse().setBody(access));