From 19e2cdd5d2305eaa77b60f3561d5108ba7984053 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 11 Nov 2014 20:15:56 -0800 Subject: [PATCH] * Change OAuthScopes into an interface as opposed to boilerplating annotations. * Fixed errors because of boilerplating annotations. --- apis/oauth/README | 2 +- apis/oauth/pom.xml | 4 +- .../oauth/v2/config/OAuthHttpApiModule.java | 5 +- .../jclouds/oauth/v2/config/OAuthModule.java | 7 +-- .../jclouds/oauth/v2/config/OAuthScopes.java | 63 ++++++++++++++----- .../oauth/v2/functions/BuildTokenRequest.java | 25 ++------ .../oauth/v2/binders/TokenBinderTest.java | 29 ++++++--- .../oauth/v2/features/OAuthApiLiveTest.java | 5 +- .../oauth/v2/features/OAuthApiMockTest.java | 15 +++-- .../v2/internal/BaseOAuthApiLiveTest.java | 22 ++++--- 10 files changed, 107 insertions(+), 70 deletions(-) diff --git a/apis/oauth/README b/apis/oauth/README index 1c5f73b455..40b039d991 100644 --- a/apis/oauth/README +++ b/apis/oauth/README @@ -13,4 +13,4 @@ mvn clean install -Plive\ -Dtest.oauth.credential=\ -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 \ No newline at end of file + -Dtest.jclouds.oauth.scope=https://www.googleapis.com/auth/prediction \ No newline at end of file diff --git a/apis/oauth/pom.xml b/apis/oauth/pom.xml index 275ce4bda2..9f83ac8242 100644 --- a/apis/oauth/pom.xml +++ b/apis/oauth/pom.xml @@ -37,7 +37,7 @@ FIX_ME RS256 FIX_ME - FIX_ME + FIX_ME 2 @@ -123,7 +123,7 @@ ${test.oauth.build-version} ${test.jclouds.oauth.jws-alg} ${test.jclouds.oauth.audience} - ${test.jclouds.oauth.scopes} + ${test.jclouds.oauth.scope} diff --git a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthHttpApiModule.java b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthHttpApiModule.java index 3bb7c1aaa3..5a1d5d2d44 100644 --- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthHttpApiModule.java +++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthHttpApiModule.java @@ -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 { @@ -41,5 +39,4 @@ public class OAuthHttpApiModule extends HttpApiModule { protected Supplier provideAuthenticationEndpoint(ProviderMetadata providerMetadata) { return Suppliers.ofInstance(URI.create(providerMetadata.getEndpoint())); } - } diff --git a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java index e831c0f8f9..e6d5985295 100644 --- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java +++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java @@ -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> signOrProduceMacForToken(@Named(JWS_ALG) String jwsAlg, Provider in) { if (jwsAlg.equals(NONE)) { // Current implementation requires we return null on none. - return Suppliers.>ofInstance(new Function() { - @Override public byte[] apply(byte[] input) { - return null; - } - }); + return (Supplier) Suppliers.ofInstance(Functions.constant(null)); } return Suppliers.memoize(in.get()); } diff --git a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthScopes.java b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthScopes.java index d4fe7d4a92..d154839bf8 100644 --- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthScopes.java +++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthScopes.java @@ -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. - *

- * 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 forRequest(HttpRequest input); + @AutoValue public abstract static class SingleScope implements OAuthScopes { + abstract List scopes(); + + public static SingleScope create(String scope) { + return new AutoValue_OAuthScopes_SingleScope(ImmutableList.of(scope)); + } + + @Override public List forRequest(HttpRequest input) { + return scopes(); + } + + SingleScope() { + } + } + + @AutoValue public abstract static class ReadOrWriteScopes implements OAuthScopes { + abstract List readScopes(); + + abstract List writeScopes(); + + public static ReadOrWriteScopes create(String readScope, String writeScope) { + return new AutoValue_OAuthScopes_ReadOrWriteScopes( // + ImmutableList.of(readScope), // + ImmutableList.of(writeScope) // + ); + } + + @Override public List forRequest(HttpRequest input) { + if (input.getMethod().equals("GET") || input.getMethod().equals("HEAD")) { + return readScopes(); + } + return writeScopes(); + } + + ReadOrWriteScopes() { + } + } } diff --git a/apis/oauth/src/main/java/org/jclouds/oauth/v2/functions/BuildTokenRequest.java b/apis/oauth/src/main/java/org/jclouds/oauth/v2/functions/BuildTokenRequest.java index 4a51954758..51ab318428 100644 --- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/functions/BuildTokenRequest.java +++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/functions/BuildTokenRequest.java @@ -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 { @@ -49,13 +46,14 @@ public class BuildTokenRequest implements Function { private final String assertionTargetDescription; private final String signatureAlgorithm; private final Supplier 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 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 { @Inject BuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription, @Named(JWS_ALG) String signatureAlgorithm, @Provider Supplier 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 { Map claims = new LinkedHashMap(); 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 { 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; } diff --git a/apis/oauth/src/test/java/org/jclouds/oauth/v2/binders/TokenBinderTest.java b/apis/oauth/src/test/java/org/jclouds/oauth/v2/binders/TokenBinderTest.java index 2f56aafc00..93042f400a 100644 --- a/apis/oauth/src/test/java/org/jclouds/oauth/v2/binders/TokenBinderTest.java +++ b/apis/oauth/src/test/java/org/jclouds/oauth/v2/binders/TokenBinderTest.java @@ -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;:_@€"; + "=?*qwertyuiopº´WERTYUIOPªàsdfghjklç~ASDFGHJKLÇ^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 claims = ImmutableMap.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> signer() { + return (Supplier) Suppliers.ofInstance(Functions.constant(null)); + } + }).getInstance(TokenBinder.class); } diff --git a/apis/oauth/src/test/java/org/jclouds/oauth/v2/features/OAuthApiLiveTest.java b/apis/oauth/src/test/java/org/jclouds/oauth/v2/features/OAuthApiLiveTest.java index a48b725c7d..cc83f8ba63 100644 --- a/apis/oauth/src/test/java/org/jclouds/oauth/v2/features/OAuthApiLiveTest.java +++ b/apis/oauth/src/test/java/org/jclouds/oauth/v2/features/OAuthApiLiveTest.java @@ -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 claims = ImmutableMap.builder() .put("iss", identity) - .put("scope", scopes) + .put("scope", scope) .put("aud", audience) .put(EXPIRATION_TIME, now + 3600) .put(ISSUED_AT, now).build(); diff --git a/apis/oauth/src/test/java/org/jclouds/oauth/v2/features/OAuthApiMockTest.java b/apis/oauth/src/test/java/org/jclouds/oauth/v2/features/OAuthApiMockTest.java index f76a360946..febaee751a 100644 --- a/apis/oauth/src/test/java/org/jclouds/oauth/v2/features/OAuthApiMockTest.java +++ b/apis/oauth/src/test/java/org/jclouds/oauth/v2/features/OAuthApiMockTest.java @@ -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 CLAIMS = ImmutableMap.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.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); } } diff --git a/apis/oauth/src/test/java/org/jclouds/oauth/v2/internal/BaseOAuthApiLiveTest.java b/apis/oauth/src/test/java/org/jclouds/oauth/v2/internal/BaseOAuthApiLiveTest.java index 0a50dfe363..2421d1660d 100644 --- a/apis/oauth/src/test/java/org/jclouds/oauth/v2/internal/BaseOAuthApiLiveTest.java +++ b/apis/oauth/src/test/java/org/jclouds/oauth/v2/internal/BaseOAuthApiLiveTest.java @@ -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 { + 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 setupModules() { + return ImmutableList.builder().add(new Module() { + @Override public void configure(Binder binder) { + binder.bind(OAuthScopes.class).toInstance(SingleScope.create(scope)); + } + }).addAll(super.setupModules()).build(); } - }