mirror of https://github.com/apache/jclouds.git
JCLOUDS-523 add tempAuthCredentials to openstack-swift
This commit is contained in:
parent
68f3bdc264
commit
afa68a73d1
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue