JCLOUDS-523 add tempAuthCredentials to openstack-swift

This commit is contained in:
Adrian Cole 2014-11-26 10:13:57 -08:00 committed by Adrian Cole
parent 68f3bdc264
commit afa68a73d1
3 changed files with 261 additions and 8 deletions

View File

@ -23,13 +23,12 @@ import static org.jclouds.reflect.Reflection2.typeToken;
import java.net.URI; import java.net.URI;
import java.util.Properties; 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.CredentialTypes;
import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule;
import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule.RegionModule; 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.RegionScopedBlobStoreContext;
import org.jclouds.openstack.swift.v1.blobstore.config.SignUsingTemporaryUrls; import org.jclouds.openstack.swift.v1.blobstore.config.SignUsingTemporaryUrls;
import org.jclouds.openstack.swift.v1.blobstore.config.SwiftBlobStoreContextModule; 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.SwiftHttpApiModule;
import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters; import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters;
import org.jclouds.openstack.v2_0.ServiceType; 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.common.collect.ImmutableSet;
import com.google.inject.Module; import com.google.inject.Module;
/**
* Implementation of {@link ApiMetadata} for the Swift API.
*/
public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> { public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> {
@Override @Override
@ -59,6 +55,7 @@ public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> {
public static Properties defaultProperties() { public static Properties defaultProperties() {
Properties properties = BaseHttpApiMetadata.defaultProperties(); Properties properties = BaseHttpApiMetadata.defaultProperties();
properties.setProperty(SERVICE_TYPE, ServiceType.OBJECT_STORE); properties.setProperty(SERVICE_TYPE, ServiceType.OBJECT_STORE);
// Can alternatively be set to "tempAuthCredentials"
properties.setProperty(CREDENTIAL_TYPE, CredentialTypes.PASSWORD_CREDENTIALS); properties.setProperty(CREDENTIAL_TYPE, CredentialTypes.PASSWORD_CREDENTIALS);
return properties; return properties;
} }
@ -72,13 +69,12 @@ public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> {
.credentialName("${password}") .credentialName("${password}")
.documentation(URI.create("http://docs.openstack.org/api/openstack-object-storage/1.0/content/ch_object-storage-dev-overview.html")) .documentation(URI.create("http://docs.openstack.org/api/openstack-object-storage/1.0/content/ch_object-storage-dev-overview.html"))
.version("1") .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/") .defaultEndpoint("http://localhost:5000/v2.0/")
.defaultProperties(SwiftApiMetadata.defaultProperties()) .defaultProperties(SwiftApiMetadata.defaultProperties())
.view(typeToken(RegionScopedBlobStoreContext.class)) .view(typeToken(RegionScopedBlobStoreContext.class))
.defaultModules(ImmutableSet.<Class<? extends Module>>builder() .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
.add(AuthenticationApiModule.class) .add(SwiftAuthenticationModule.class)
.add(KeystoneAuthenticationModule.class)
.add(RegionModule.class) .add(RegionModule.class)
.add(SwiftTypeAdapters.class) .add(SwiftTypeAdapters.class)
.add(SwiftHttpApiModule.class) .add(SwiftHttpApiModule.class)

View File

@ -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;
}
}
}

View File

@ -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();
}
}