mirror of https://github.com/apache/jclouds.git
* Change OAuthScopes into an interface as opposed to boilerplating annotations.
* Fixed errors because of boilerplating annotations.
This commit is contained in:
parent
7a644f8b6e
commit
19e2cdd5d2
|
@ -13,4 +13,4 @@ mvn clean install -Plive\
|
|||
-Dtest.oauth.credential=<accout pk in pem format>\
|
||||
-Dtest.oauth.endpoint=https://accounts.google.com/o/oauth2/token\
|
||||
-Dtest.jclouds.oauth.audience=https://accounts.google.com/o/oauth2/token\
|
||||
-Dtest.jclouds.oauth.scopes=https://www.googleapis.com/auth/prediction
|
||||
-Dtest.jclouds.oauth.scope=https://www.googleapis.com/auth/prediction
|
|
@ -37,7 +37,7 @@
|
|||
<test.oauth.endpoint>FIX_ME</test.oauth.endpoint>
|
||||
<test.jclouds.oauth.jws-alg>RS256</test.jclouds.oauth.jws-alg>
|
||||
<test.jclouds.oauth.audience>FIX_ME</test.jclouds.oauth.audience>
|
||||
<test.jclouds.oauth.scopes>FIX_ME</test.jclouds.oauth.scopes>
|
||||
<test.jclouds.oauth.scope>FIX_ME</test.jclouds.oauth.scope>
|
||||
<test.oauth.api-version>2</test.oauth.api-version>
|
||||
<test.oauth.build-version />
|
||||
</properties>
|
||||
|
@ -123,7 +123,7 @@
|
|||
<test.oauth.build-version>${test.oauth.build-version}</test.oauth.build-version>
|
||||
<test.jclouds.oauth.jws-alg>${test.jclouds.oauth.jws-alg}</test.jclouds.oauth.jws-alg>
|
||||
<test.jclouds.oauth.audience>${test.jclouds.oauth.audience}</test.jclouds.oauth.audience>
|
||||
<test.jclouds.oauth.scopes>${test.jclouds.oauth.scopes}</test.jclouds.oauth.scopes>
|
||||
<test.jclouds.oauth.scope>${test.jclouds.oauth.scope}</test.jclouds.oauth.scope>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
|
|
@ -29,9 +29,7 @@ import com.google.common.base.Supplier;
|
|||
import com.google.common.base.Suppliers;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
/**
|
||||
* OAuth module to when accessing OAuth stand-alone.
|
||||
*/
|
||||
/** Api module to when accessing OAuth stand-alone. */
|
||||
@ConfiguresHttpApi
|
||||
public class OAuthHttpApiModule extends HttpApiModule<OAuthApi> {
|
||||
|
||||
|
@ -41,5 +39,4 @@ public class OAuthHttpApiModule extends HttpApiModule<OAuthApi> {
|
|||
protected Supplier<URI> provideAuthenticationEndpoint(ProviderMetadata providerMetadata) {
|
||||
return Suppliers.ofInstance(URI.create(providerMetadata.getEndpoint()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.jclouds.oauth.v2.functions.PrivateKeySupplier;
|
|||
import org.jclouds.oauth.v2.functions.SignOrProduceMacForToken;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
@ -81,11 +82,7 @@ public class OAuthModule extends AbstractModule {
|
|||
@Provides @Singleton Supplier<Function<byte[], byte[]>> signOrProduceMacForToken(@Named(JWS_ALG) String jwsAlg,
|
||||
Provider<SignOrProduceMacForToken> in) {
|
||||
if (jwsAlg.equals(NONE)) { // Current implementation requires we return null on none.
|
||||
return Suppliers.<Function<byte[], byte[]>>ofInstance(new Function<byte[], byte[]>() {
|
||||
@Override public byte[] apply(byte[] input) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return (Supplier) Suppliers.ofInstance(Functions.constant(null));
|
||||
}
|
||||
return Suppliers.memoize(in.get());
|
||||
}
|
||||
|
|
|
@ -16,26 +16,57 @@
|
|||
*/
|
||||
package org.jclouds.oauth.v2.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Used to annotate REST methods/ifaces that use OAuthAuthentication.
|
||||
* <p/>
|
||||
* Sets the scopes for the token request for that particular method.
|
||||
* Implementations are api-specific, typically routing GET or HEAD requests to a read-only role, and others to a
|
||||
* read-write one.
|
||||
*/
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Target(value = {ElementType.TYPE, ElementType.METHOD})
|
||||
@Qualifier
|
||||
public @interface OAuthScopes {
|
||||
public interface OAuthScopes {
|
||||
|
||||
/**
|
||||
* @return the OAuth scopes required to access the resource.
|
||||
*/
|
||||
String[] value();
|
||||
/** Returns a list of scopes needed to perform the request. */
|
||||
List<String> forRequest(HttpRequest input);
|
||||
|
||||
@AutoValue public abstract static class SingleScope implements OAuthScopes {
|
||||
abstract List<String> scopes();
|
||||
|
||||
public static SingleScope create(String scope) {
|
||||
return new AutoValue_OAuthScopes_SingleScope(ImmutableList.of(scope));
|
||||
}
|
||||
|
||||
@Override public List<String> forRequest(HttpRequest input) {
|
||||
return scopes();
|
||||
}
|
||||
|
||||
SingleScope() {
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue public abstract static class ReadOrWriteScopes implements OAuthScopes {
|
||||
abstract List<String> readScopes();
|
||||
|
||||
abstract List<String> writeScopes();
|
||||
|
||||
public static ReadOrWriteScopes create(String readScope, String writeScope) {
|
||||
return new AutoValue_OAuthScopes_ReadOrWriteScopes( //
|
||||
ImmutableList.of(readScope), //
|
||||
ImmutableList.of(writeScope) //
|
||||
);
|
||||
}
|
||||
|
||||
@Override public List<String> forRequest(HttpRequest input) {
|
||||
if (input.getMethod().equals("GET") || input.getMethod().equals("HEAD")) {
|
||||
return readScopes();
|
||||
}
|
||||
return writeScopes();
|
||||
}
|
||||
|
||||
ReadOrWriteScopes() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.jclouds.oauth.v2.functions;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
|
||||
|
@ -35,12 +34,10 @@ import org.jclouds.location.Provider;
|
|||
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||
import org.jclouds.oauth.v2.domain.Header;
|
||||
import org.jclouds.oauth.v2.domain.TokenRequest;
|
||||
import org.jclouds.rest.internal.GeneratedHttpRequest;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.reflect.Invokable;
|
||||
|
||||
/** Builds the default token request with the following claims: {@code iss,scope,aud,iat,exp}. */
|
||||
public class BuildTokenRequest implements Function<HttpRequest, TokenRequest> {
|
||||
|
@ -49,13 +46,14 @@ public class BuildTokenRequest implements Function<HttpRequest, TokenRequest> {
|
|||
private final String assertionTargetDescription;
|
||||
private final String signatureAlgorithm;
|
||||
private final Supplier<Credentials> credentialsSupplier;
|
||||
private final OAuthScopes scopes;
|
||||
private final long tokenDuration;
|
||||
|
||||
public static class TestBuildTokenRequest extends BuildTokenRequest {
|
||||
@Inject TestBuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription,
|
||||
@Named(JWS_ALG) String signatureAlgorithm, @Provider Supplier<Credentials> credentialsSupplier,
|
||||
@Named(PROPERTY_SESSION_INTERVAL) long tokenDuration) {
|
||||
super(assertionTargetDescription, signatureAlgorithm, credentialsSupplier, tokenDuration);
|
||||
OAuthScopes scopes, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration) {
|
||||
super(assertionTargetDescription, signatureAlgorithm, credentialsSupplier, scopes, tokenDuration);
|
||||
}
|
||||
|
||||
public long currentTimeSeconds() {
|
||||
|
@ -65,10 +63,11 @@ public class BuildTokenRequest implements Function<HttpRequest, TokenRequest> {
|
|||
|
||||
@Inject BuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription,
|
||||
@Named(JWS_ALG) String signatureAlgorithm, @Provider Supplier<Credentials> credentialsSupplier,
|
||||
@Named(PROPERTY_SESSION_INTERVAL) long tokenDuration) {
|
||||
OAuthScopes scopes, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration) {
|
||||
this.assertionTargetDescription = assertionTargetDescription;
|
||||
this.signatureAlgorithm = signatureAlgorithm;
|
||||
this.credentialsSupplier = credentialsSupplier;
|
||||
this.scopes = scopes;
|
||||
this.tokenDuration = tokenDuration;
|
||||
}
|
||||
|
||||
|
@ -77,7 +76,7 @@ public class BuildTokenRequest implements Function<HttpRequest, TokenRequest> {
|
|||
|
||||
Map<String, Object> claims = new LinkedHashMap<String, Object>();
|
||||
claims.put("iss", credentialsSupplier.get().identity);
|
||||
claims.put("scope", getOAuthScopes((GeneratedHttpRequest) request));
|
||||
claims.put("scope", ON_COMMA.join(scopes.forRequest(request)));
|
||||
claims.put("aud", assertionTargetDescription);
|
||||
|
||||
long now = currentTimeSeconds();
|
||||
|
@ -87,18 +86,6 @@ public class BuildTokenRequest implements Function<HttpRequest, TokenRequest> {
|
|||
return TokenRequest.create(header, claims);
|
||||
}
|
||||
|
||||
//TODO: Remove and switch to a request function.
|
||||
private String getOAuthScopes(GeneratedHttpRequest request) {
|
||||
Invokable<?, ?> invokable = request.getInvocation().getInvokable();
|
||||
OAuthScopes classScopes = invokable.getOwnerType().getRawType().getAnnotation(OAuthScopes.class);
|
||||
OAuthScopes methodScopes = invokable.getAnnotation(OAuthScopes.class);
|
||||
checkState(classScopes != null || methodScopes != null, "Api interface or method should be annotated " //
|
||||
+ "with OAuthScopes specifying required permissions. Api interface: %s, Method: %s", //
|
||||
invokable.getOwnerType(), invokable.getName());
|
||||
OAuthScopes scopes = methodScopes != null ? methodScopes : classScopes;
|
||||
return ON_COMMA.join(scopes.value());
|
||||
}
|
||||
|
||||
long currentTimeSeconds() {
|
||||
return System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
|
|
@ -25,29 +25,31 @@ import static org.testng.Assert.assertTrue;
|
|||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jclouds.ContextBuilder;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.oauth.v2.OAuthApiMetadata;
|
||||
import org.jclouds.oauth.v2.OAuthTestUtils;
|
||||
import org.jclouds.json.config.GsonModule;
|
||||
import org.jclouds.oauth.v2.domain.Header;
|
||||
import org.jclouds.oauth.v2.domain.TokenRequest;
|
||||
import org.jclouds.util.Strings2;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
@Test(groups = "unit", testName = "OAuthTokenBinderTest")
|
||||
public class TokenBinderTest {
|
||||
public static final String STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING = "§1234567890'+±!\"#$%&/()" +
|
||||
"=?*qwertyuiopº´WERTYUIOPªàsdfghjklç~ASDFGHJKLÇ^<zxcvbnm," +
|
||||
".->ZXCVBNM;:_@€";
|
||||
"=?*qwertyuiopº´WERTYUIOPªàsdfghjklç~ASDFGHJKLÇ^<zxcvbnm,.->ZXCVBNM;:_@€";
|
||||
|
||||
public void testPayloadIsUrlSafe() throws IOException {
|
||||
TokenBinder tokenRequestFormat = ContextBuilder.newBuilder(new OAuthApiMetadata()).overrides
|
||||
(OAuthTestUtils.defaultProperties(null)).build().utils()
|
||||
.injector().getInstance(TokenBinder.class);
|
||||
Header header = Header.create("a", "b");
|
||||
|
||||
Map<String, Object> claims = ImmutableMap.<String, Object>builder()
|
||||
|
@ -56,7 +58,7 @@ public class TokenBinderTest {
|
|||
.put("ist", STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING).build();
|
||||
|
||||
TokenRequest tokenRequest = TokenRequest.create(header, claims);
|
||||
HttpRequest request = tokenRequestFormat.bindToRequest(
|
||||
HttpRequest request = tokenBinder.bindToRequest(
|
||||
HttpRequest.builder().method("GET").endpoint("http://localhost").build(), tokenRequest);
|
||||
|
||||
assertNotNull(request.getPayload());
|
||||
|
@ -71,4 +73,13 @@ public class TokenBinderTest {
|
|||
assertTrue(!payload.contains("+"));
|
||||
assertTrue(!payload.contains("/"));
|
||||
}
|
||||
|
||||
private final TokenBinder tokenBinder = Guice.createInjector(new GsonModule(), new Module() {
|
||||
@Override public void configure(Binder binder) {
|
||||
}
|
||||
|
||||
@Provides Supplier<Function<byte[], byte[]>> signer() {
|
||||
return (Supplier) Suppliers.ofInstance(Functions.constant(null));
|
||||
}
|
||||
}).getInstance(TokenBinder.class);
|
||||
}
|
||||
|
|
|
@ -65,14 +65,13 @@ public class OAuthApiLiveTest extends BaseOAuthApiLiveTest {
|
|||
|
||||
Header header = Header.create(jwsAlg, "JWT");
|
||||
|
||||
String scopes = getMandatoryProperty(properties, "jclouds.oauth.scopes");
|
||||
String audience = getMandatoryProperty(properties, AUDIENCE);
|
||||
|
||||
long now = nowInSeconds();
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
|
||||
Map<String, Object> claims = ImmutableMap.<String, Object>builder()
|
||||
.put("iss", identity)
|
||||
.put("scope", scopes)
|
||||
.put("scope", scope)
|
||||
.put("aud", audience)
|
||||
.put(EXPIRATION_TIME, now + 3600)
|
||||
.put(ISSUED_AT, now).build();
|
||||
|
|
|
@ -37,6 +37,8 @@ import org.jclouds.concurrent.config.ExecutorServiceModule;
|
|||
import org.jclouds.oauth.v2.OAuthApi;
|
||||
import org.jclouds.oauth.v2.OAuthApiMetadata;
|
||||
import org.jclouds.oauth.v2.OAuthTestUtils;
|
||||
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||
import org.jclouds.oauth.v2.config.OAuthScopes.SingleScope;
|
||||
import org.jclouds.oauth.v2.domain.Header;
|
||||
import org.jclouds.oauth.v2.domain.Token;
|
||||
import org.jclouds.oauth.v2.domain.TokenRequest;
|
||||
|
@ -46,6 +48,7 @@ import com.google.common.base.Joiner;
|
|||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Module;
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||
|
@ -53,19 +56,19 @@ import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
|||
|
||||
@Test(groups = "unit", testName = "OAuthApiMockTest")
|
||||
public class OAuthApiMockTest {
|
||||
private static final String SCOPE = "https://www.googleapis.com/auth/prediction";
|
||||
|
||||
private static final String header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
|
||||
|
||||
private static final String claims = "{\"iss\":\"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer" +
|
||||
".gserviceaccount.com\"," +
|
||||
"\"scope\":\"https://www.googleapis.com/auth/prediction\",\"aud\":\"https://accounts.google" +
|
||||
".gserviceaccount.com\",\"scope\":\"" + SCOPE + "\",\"aud\":\"https://accounts.google" +
|
||||
".com/o/oauth2/token\",\"exp\":1328573381,\"iat\":1328569781}";
|
||||
|
||||
private static final Token TOKEN = Token.create("1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M", "Bearer", 3600);
|
||||
|
||||
private static final Map<String, Object> CLAIMS = ImmutableMap.<String, Object>builder()
|
||||
.put("iss", "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com")
|
||||
.put("scope", "https://www.googleapis.com/auth/prediction")
|
||||
.put("scope", SCOPE)
|
||||
.put("aud", "https://accounts.google.com/o/oauth2/token")
|
||||
.put(EXPIRATION_TIME, 1328573381)
|
||||
.put(ISSUED_AT, 1328569781).build();
|
||||
|
@ -113,7 +116,11 @@ public class OAuthApiMockTest {
|
|||
.credentials("foo", toStringAndClose(OAuthTestUtils.class.getResourceAsStream("/testpk.pem")))
|
||||
.endpoint(url.toString())
|
||||
.overrides(overrides)
|
||||
.modules(ImmutableSet.<Module>of(new ExecutorServiceModule(sameThreadExecutor())))
|
||||
.modules(ImmutableSet.of(new ExecutorServiceModule(sameThreadExecutor()), new Module() {
|
||||
@Override public void configure(Binder binder) {
|
||||
binder.bind(OAuthScopes.class).toInstance(SingleScope.create(SCOPE));
|
||||
}
|
||||
}))
|
||||
.buildApi(OAuthApi.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,36 +20,44 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
import static org.jclouds.oauth.v2.OAuthTestUtils.setCredential;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
|
||||
import static org.jclouds.oauth.v2.config.OAuthScopes.SingleScope;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jclouds.apis.BaseApiLiveTest;
|
||||
import org.jclouds.oauth.v2.OAuthApi;
|
||||
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Module;
|
||||
|
||||
@Test(groups = "live")
|
||||
public class BaseOAuthApiLiveTest extends BaseApiLiveTest<OAuthApi> {
|
||||
|
||||
protected String scope;
|
||||
|
||||
public BaseOAuthApiLiveTest() {
|
||||
provider = "oauth";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties setupProperties() {
|
||||
@Override protected Properties setupProperties() {
|
||||
Properties props = super.setupProperties();
|
||||
setCredential(props, "oauth.credential");
|
||||
checkNotNull(setIfTestSystemPropertyPresent(props, "oauth.endpoint"), "test.oauth.endpoint must be set");
|
||||
checkNotNull(setIfTestSystemPropertyPresent(props, AUDIENCE), "test.jclouds.oauth.audience must be set");
|
||||
setIfTestSystemPropertyPresent(props, "jclouds.oauth.scopes");
|
||||
scope = setIfTestSystemPropertyPresent(props, "jclouds.oauth.scope");
|
||||
setIfTestSystemPropertyPresent(props, JWS_ALG);
|
||||
return props;
|
||||
}
|
||||
|
||||
protected long nowInSeconds() {
|
||||
return TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||
@Override protected Iterable<Module> setupModules() {
|
||||
return ImmutableList.<Module>builder().add(new Module() {
|
||||
@Override public void configure(Binder binder) {
|
||||
binder.bind(OAuthScopes.class).toInstance(SingleScope.create(scope));
|
||||
}
|
||||
}).addAll(super.setupModules()).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue