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 2014/11/27 02:19:56 UTC

jclouds git commit: JCLOUDS-523 add tempAuthCredentials to openstack-swift

Repository: jclouds
Updated Branches:
  refs/heads/master a449b24e7 -> df4323b4c


JCLOUDS-523 add tempAuthCredentials to openstack-swift


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

Branch: refs/heads/master
Commit: df4323b4c69e222abc3f537c3b4e6df52441eb0f
Parents: a449b24
Author: Adrian Cole <ac...@twitter.com>
Authored: Wed Nov 26 10:13:57 2014 -0800
Committer: Adrian Cole <ad...@apache.org>
Committed: Wed Nov 26 17:19:38 2014 -0800

----------------------------------------------------------------------
 .../openstack/swift/v1/SwiftApiMetadata.java    |  12 +-
 .../v1/config/SwiftAuthenticationModule.java    | 160 +++++++++++++++++++
 .../openstack/swift/v1/TempAuthMockTest.java    |  97 +++++++++++
 3 files changed, 261 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/df4323b4/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
index 8d77bb0..478bd81 100644
--- a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
@@ -23,13 +23,12 @@ import static org.jclouds.reflect.Reflection2.typeToken;
 import java.net.URI;
 import java.util.Properties;
 
-import org.jclouds.openstack.keystone.v2_0.config.AuthenticationApiModule;
 import org.jclouds.openstack.keystone.v2_0.config.CredentialTypes;
-import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule;
 import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule.RegionModule;
 import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext;
 import org.jclouds.openstack.swift.v1.blobstore.config.SignUsingTemporaryUrls;
 import org.jclouds.openstack.swift.v1.blobstore.config.SwiftBlobStoreContextModule;
+import org.jclouds.openstack.swift.v1.config.SwiftAuthenticationModule;
 import org.jclouds.openstack.swift.v1.config.SwiftHttpApiModule;
 import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters;
 import org.jclouds.openstack.v2_0.ServiceType;
@@ -38,9 +37,6 @@ import org.jclouds.rest.internal.BaseHttpApiMetadata;
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.Module;
 
-/**
- * Implementation of {@link ApiMetadata} for the Swift API.
- */
 public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> {
 
    @Override
@@ -59,6 +55,7 @@ public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> {
    public static Properties defaultProperties() {
       Properties properties = BaseHttpApiMetadata.defaultProperties();
       properties.setProperty(SERVICE_TYPE, ServiceType.OBJECT_STORE);
+      // Can alternatively be set to "tempAuthCredentials"
       properties.setProperty(CREDENTIAL_TYPE, CredentialTypes.PASSWORD_CREDENTIALS);
       return properties;
    }
@@ -72,13 +69,12 @@ public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> {
          .credentialName("${password}")
          .documentation(URI.create("http://docs.openstack.org/api/openstack-object-storage/1.0/content/ch_object-storage-dev-overview.html"))
          .version("1")
-         .endpointName("Keystone base url ending in /v2.0/")
+         .endpointName("Keystone base url ending in /v2.0/ or TempAuth url ending in auth/v1.0/")
          .defaultEndpoint("http://localhost:5000/v2.0/")
          .defaultProperties(SwiftApiMetadata.defaultProperties())
          .view(typeToken(RegionScopedBlobStoreContext.class))
          .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
-                                     .add(AuthenticationApiModule.class)
-                                     .add(KeystoneAuthenticationModule.class)
+                                     .add(SwiftAuthenticationModule.class)
                                      .add(RegionModule.class)
                                      .add(SwiftTypeAdapters.class)
                                      .add(SwiftHttpApiModule.class)

http://git-wip-us.apache.org/repos/asf/jclouds/blob/df4323b4/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftAuthenticationModule.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftAuthenticationModule.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftAuthenticationModule.java
new file mode 100644
index 0000000..9b6ae40
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftAuthenticationModule.java
@@ -0,0 +1,160 @@
+/*
+ * 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.config;
+
+import static org.jclouds.http.HttpUtils.releasePayload;
+import static org.jclouds.http.Uris.uriBuilder;
+import static org.jclouds.openstack.v2_0.ServiceType.OBJECT_STORE;
+import static org.jclouds.openstack.v2_0.reference.AuthHeaders.AUTH_TOKEN;
+import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
+
+import java.io.Closeable;
+import java.net.URI;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.openstack.keystone.v2_0.AuthenticationApi;
+import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule;
+import org.jclouds.openstack.keystone.v2_0.domain.Access;
+import org.jclouds.openstack.keystone.v2_0.domain.Endpoint;
+import org.jclouds.openstack.keystone.v2_0.domain.Service;
+import org.jclouds.openstack.keystone.v2_0.domain.Token;
+import org.jclouds.openstack.keystone.v2_0.domain.User;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.InvocationContext;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.VirtualHost;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Injector;
+import com.google.inject.name.Named;
+
+/**
+ * When {@link org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties#CREDENTIAL_TYPE} is set to {@code
+ * tempAuthCredentials}, do not use Keystone. Instead, bridge TempAuth to Keystone by faking a service catalog out of
+ * the storage url. The {@link ContextBuilder#endpoint(String) endpoint} must be set to the TempAuth url, usually ending
+ * in {@code auth/v1.0/}.
+ */
+public final class SwiftAuthenticationModule extends KeystoneAuthenticationModule {
+   private static final String STORAGE_USER = "X-Storage-User";
+   private static final String STORAGE_PASS = "X-Storage-Pass";
+   private static final String STORAGE_URL = "X-Storage-Url";
+
+   @Override
+   protected void configure() {
+      super.configure();
+      bindHttpApi(binder(), AuthenticationApi.class);
+      bindHttpApi(binder(), TempAuthApi.class);
+   }
+
+   @Override protected Map<String, Function<Credentials, Access>> authenticationMethods(Injector i) {
+      return ImmutableMap.<String, Function<Credentials, Access>>builder()
+                         .putAll(super.authenticationMethods(i))
+                         .put("tempAuthCredentials", i.getInstance(TempAuth.class)).build();
+   }
+
+   static final class TempAuth implements Function<Credentials, Access> {
+      private final TempAuthApi delegate;
+
+      @Inject TempAuth(TempAuthApi delegate) {
+         this.delegate = delegate;
+      }
+
+      @Override public Access apply(Credentials input) {
+         return delegate.auth(input.identity, input.credential);
+      }
+   }
+
+   @VirtualHost
+   interface TempAuthApi  extends Closeable {
+
+      @Named("TempAuth")
+      @GET
+      @Consumes
+      @ResponseParser(AdaptTempAuthResponseToAccess.class)
+      Access auth(@HeaderParam(STORAGE_USER) String user, @HeaderParam(STORAGE_PASS) String key);
+   }
+
+   static final class AdaptTempAuthResponseToAccess
+         implements Function<HttpResponse, Access>, InvocationContext<AdaptTempAuthResponseToAccess> {
+
+      private final String apiVersion;
+
+      private String host;
+      private String username;
+
+      @Inject AdaptTempAuthResponseToAccess(@ApiVersion String apiVersion) {
+         this.apiVersion = apiVersion;
+      }
+
+      @Override public Access apply(HttpResponse from) {
+         releasePayload(from);
+         URI storageUrl = null;
+         String authToken = null;
+         for (Map.Entry<String, String> entry : from.getHeaders().entries()) {
+            String header = entry.getKey();
+            if (header.equalsIgnoreCase(STORAGE_URL)) {
+               storageUrl = getURI(entry.getValue());
+            } else if (header.equalsIgnoreCase(AUTH_TOKEN)) {
+               authToken = entry.getKey();
+            }
+         }
+         if (storageUrl == null || authToken == null) {
+            throw new AuthorizationException("Invalid headers in TempAuth response " + from);
+         }
+         // For portability with keystone, based on common knowledge that these tokens tend to expire in 24 hours
+         // http://docs.openstack.org/api/openstack-object-storage/1.0/content/authentication-object-dev-guide.html
+         Date expires = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24));
+         return Access.builder()
+               .user(User.builder().id(username).name(username).build())
+               .token(Token.builder().id(authToken).expires(expires).build())
+               .service(Service.builder().name("Object Storage").type(OBJECT_STORE)
+               .endpoint(Endpoint.builder().publicURL(storageUrl).id(apiVersion).region(storageUrl.getHost()).build())
+               .build()).build();
+      }
+
+      // TODO: find the swift configuration or bug related to returning localhost
+      private URI getURI(String headerValue) {
+         if (headerValue == null)
+            return null;
+         URI toReturn = URI.create(headerValue);
+         if (!"127.0.0.1".equals(toReturn.getHost()))
+            return toReturn;
+         return uriBuilder(toReturn).host(host).build();
+      }
+
+      @Override
+      public AdaptTempAuthResponseToAccess setContext(HttpRequest request) {
+         String host = request.getEndpoint().getHost();
+         this.host = host;
+         this.username = request.getFirstHeaderOrNull(STORAGE_USER);
+         return this;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/df4323b4/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TempAuthMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TempAuthMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TempAuthMockTest.java
new file mode 100644
index 0000000..140db7d
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TempAuthMockTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+@Test(groups = "unit", testName = "TempAuthMockTest", singleThreaded = true)
+public class TempAuthMockTest {
+
+   private MockWebServer swiftServer;
+   private MockWebServer tempAuthServer;
+
+
+   public void testGenerateJWTRequest() throws Exception {
+      tempAuthServer.enqueue(new MockResponse().setResponseCode(204)
+            .addHeader("X-Auth-Token", "token")
+            .addHeader("X-Storage-Url", "http://127.0.0.1:" + swiftServer.getPort()));
+
+      swiftServer.enqueue(new MockResponse().setBody("[{\"name\":\"test_container_1\",\"count\":2,\"bytes\":78}]"));
+
+      SwiftApi api = api("http://127.0.0.1:" + tempAuthServer.getPort());
+
+      // Region name is derived from the swift server host.
+      assertEquals(api.getConfiguredRegions(), ImmutableSet.of("127.0.0.1"));
+
+      assertTrue(api.getContainerApi("127.0.0.1").list().iterator().hasNext());
+
+      RecordedRequest auth = tempAuthServer.takeRequest();
+      assertEquals(auth.getMethod(), "GET");
+      assertEquals(auth.getHeader("X-Storage-User"), "user");
+      assertEquals(auth.getHeader("X-Storage-Pass"), "password");
+
+      // list request went to the destination specified in X-Storage-Url.
+      RecordedRequest listContainers = swiftServer.takeRequest();
+      assertEquals(listContainers.getMethod(), "GET");
+      assertEquals(listContainers.getPath(), "/");
+      assertEquals(listContainers.getHeader("Accept"), APPLICATION_JSON);
+   }
+
+   private SwiftApi api(String authUrl) throws IOException {
+      Properties overrides = new Properties();
+      overrides.setProperty(CREDENTIAL_TYPE, "tempAuthCredentials");
+      return ContextBuilder.newBuilder(new SwiftApiMetadata())
+            .credentials("user", "password")
+            .endpoint(authUrl)
+            .overrides(overrides)
+            .modules(ImmutableSet.of(new ExecutorServiceModule(sameThreadExecutor())))
+            .buildApi(SwiftApi.class);
+   }
+
+   @BeforeMethod
+   public void start() throws IOException {
+      tempAuthServer = new MockWebServer();
+      tempAuthServer.play();
+
+      swiftServer = new MockWebServer();
+      swiftServer.play();
+   }
+
+   @AfterMethod(alwaysRun = true)
+   public void stop() throws IOException {
+      tempAuthServer.shutdown();
+      swiftServer.shutdown();
+   }
+}