From 3bdc25c3738381b13aacf48454590c3b31073112 Mon Sep 17 00:00:00 2001 From: Ignasi Barrera Date: Fri, 24 Nov 2017 17:08:22 +0100 Subject: [PATCH] OAuth filter customization per request --- .../oauth/v2/config/OAuthConfigFactory.java | 79 +++++++++++++++++++ .../ClientCredentialsJWTBearerTokenFlow.java | 29 +++---- .../filters/ClientCredentialsSecretFlow.java | 47 +++++------ .../oauth/v2/filters/JWTBearerTokenFlow.java | 17 ++-- .../v2/filters/TestJWTBearerTokenFlow.java | 7 +- 5 files changed, 122 insertions(+), 57 deletions(-) create mode 100644 apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthConfigFactory.java diff --git a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthConfigFactory.java b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthConfigFactory.java new file mode 100644 index 0000000000..63124d0dbd --- /dev/null +++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthConfigFactory.java @@ -0,0 +1,79 @@ +/* + * 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.oauth.v2.config; + +import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE; +import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE; + +import java.util.List; + +import javax.inject.Named; + +import org.jclouds.http.HttpRequest; +import org.jclouds.javax.annotation.Nullable; + +import com.google.auto.value.AutoValue; +import com.google.common.annotations.Beta; +import com.google.inject.ImplementedBy; +import com.google.inject.Inject; + +/** + * Provides all OAuth configuration. Implementations are api-specific. + */ +@Beta +@ImplementedBy(OAuthConfigFactory.OAuthConfigFromProperties.class) +public interface OAuthConfigFactory { + + @AutoValue + public abstract static class OAuthConfig { + public abstract List scopes(); + @Nullable public abstract String audience(); + @Nullable public abstract String resource(); + + public static OAuthConfig create(List scopes, String audience, String resource) { + return new AutoValue_OAuthConfigFactory_OAuthConfig(scopes, audience, resource); + } + } + + /** + * Returns the OAuth configuration to be used to authenticate the gicen + * request. + */ + OAuthConfig forRequest(HttpRequest input); + + public static class OAuthConfigFromProperties implements OAuthConfigFactory { + private final OAuthScopes scopes; + + @Inject(optional = true) + @Named(AUDIENCE) + private String audience; + + @Inject(optional = true) + @Named(RESOURCE) + private String resource; + + @Inject + OAuthConfigFromProperties(OAuthScopes scopes) { + this.scopes = scopes; + } + + @Override + public OAuthConfig forRequest(HttpRequest input) { + return OAuthConfig.create(scopes.forRequest(input), audience, resource); + } + } +} diff --git a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsJWTBearerTokenFlow.java b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsJWTBearerTokenFlow.java index dccb7f5c62..9adb848066 100644 --- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsJWTBearerTokenFlow.java +++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsJWTBearerTokenFlow.java @@ -18,20 +18,19 @@ package org.jclouds.oauth.v2.filters; import static java.util.concurrent.TimeUnit.SECONDS; 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.RESOURCE; + +import java.util.UUID; import javax.inject.Inject; import javax.inject.Named; -import java.util.List; -import java.util.UUID; import org.jclouds.domain.Credentials; import org.jclouds.http.HttpException; import org.jclouds.http.HttpRequest; import org.jclouds.location.Provider; import org.jclouds.oauth.v2.AuthorizationApi; -import org.jclouds.oauth.v2.config.OAuthScopes; +import org.jclouds.oauth.v2.config.OAuthConfigFactory; +import org.jclouds.oauth.v2.config.OAuthConfigFactory.OAuthConfig; import org.jclouds.oauth.v2.domain.ClientCredentialsAuthArgs; import org.jclouds.oauth.v2.domain.ClientCredentialsClaims; import org.jclouds.oauth.v2.domain.Token; @@ -54,22 +53,16 @@ import com.google.common.cache.LoadingCache; public class ClientCredentialsJWTBearerTokenFlow implements OAuthFilter { private static final Joiner ON_SPACE = Joiner.on(" "); - private final String resource; - private final String audience; private final Supplier credentialsSupplier; - private final OAuthScopes scopes; + private final OAuthConfigFactory oauthConfigFactory; private final LoadingCache tokenCache; @Inject ClientCredentialsJWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration, @Provider Supplier credentialsSupplier, - OAuthScopes scopes, - @Named(AUDIENCE) String audience, - @Named(RESOURCE) String resource) { + OAuthConfigFactory oauthConfigFactory) { this.credentialsSupplier = credentialsSupplier; - this.scopes = scopes; - this.audience = audience; - this.resource = resource; + this.oauthConfigFactory = oauthConfigFactory; // since the session interval is also the token expiration time requested to the server make the token expire a // bit before the deadline to make sure there aren't session expiration exceptions long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration; @@ -104,11 +97,11 @@ public class ClientCredentialsJWTBearerTokenFlow implements OAuthFilter { } @Override public HttpRequest filter(HttpRequest request) throws HttpException { - List configuredScopes = scopes.forRequest(request); + OAuthConfig oauthConfig = oauthConfigFactory.forRequest(request); ClientCredentialsClaims claims = ClientCredentialsClaims.create( // credentialsSupplier.get().identity, // iss credentialsSupplier.get().identity, // sub - audience, // aud + oauthConfig.audience(), // aud -1, // placeholder exp for the cache -1, // placeholder nbf for the cache null // placeholder jti for the cache @@ -116,8 +109,8 @@ public class ClientCredentialsJWTBearerTokenFlow implements OAuthFilter { ClientCredentialsAuthArgs authArgs = ClientCredentialsAuthArgs.create( credentialsSupplier.get().identity, claims, - resource == null ? "" : resource, - configuredScopes.isEmpty() ? null : ON_SPACE.join(configuredScopes) + oauthConfig.resource(), + oauthConfig.scopes().isEmpty() ? null : ON_SPACE.join(oauthConfig.scopes()) ); Token token = tokenCache.getUnchecked(authArgs); diff --git a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsSecretFlow.java b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsSecretFlow.java index d721865e27..85f43099fc 100644 --- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsSecretFlow.java +++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsSecretFlow.java @@ -16,31 +16,28 @@ */ package org.jclouds.oauth.v2.filters; -import java.util.List; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; + +import javax.inject.Named; + +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.location.Provider; +import org.jclouds.oauth.v2.AuthorizationApi; +import org.jclouds.oauth.v2.config.OAuthConfigFactory; +import org.jclouds.oauth.v2.config.OAuthConfigFactory.OAuthConfig; +import org.jclouds.oauth.v2.domain.ClientSecret; +import org.jclouds.oauth.v2.domain.Token; import com.google.common.base.Joiner; import com.google.common.base.Supplier; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; - -import org.jclouds.oauth.v2.AuthorizationApi; -import org.jclouds.oauth.v2.domain.ClientSecret; -import org.jclouds.oauth.v2.config.OAuthScopes; -import org.jclouds.oauth.v2.domain.Token; -import org.jclouds.domain.Credentials; -import org.jclouds.http.HttpException; -import org.jclouds.http.HttpRequest; -import org.jclouds.location.Provider; - -import javax.inject.Named; - import com.google.inject.Inject; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; -import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE; - /** * Authorizes new Bearer Tokens at runtime by sending up for the http request. * @@ -56,16 +53,14 @@ public class ClientCredentialsSecretFlow implements OAuthFilter { private final Supplier credentialsSupplier; private final LoadingCache tokenCache; - private final String resource; - private final OAuthScopes scopes; + private final OAuthConfigFactory oauthConfigFactory; @Inject ClientCredentialsSecretFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration, - @Provider Supplier credentialsSupplier, OAuthScopes scopes, - @Named(RESOURCE) String resource) { + @Provider Supplier credentialsSupplier, + OAuthConfigFactory oauthConfigFactory) { this.credentialsSupplier = credentialsSupplier; - this.scopes = scopes; - this.resource = resource; + this.oauthConfigFactory = oauthConfigFactory; // since the session interval is also the token expiration time requested to the server make the token expire a // bit before the deadline to make sure there aren't session expiration exceptions long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration; @@ -85,12 +80,12 @@ public class ClientCredentialsSecretFlow implements OAuthFilter { } @Override public HttpRequest filter(HttpRequest request) throws HttpException { - List configuredScopes = scopes.forRequest(request); + OAuthConfig oauthConfig = oauthConfigFactory.forRequest(request); ClientSecret client = ClientSecret.create( credentialsSupplier.get().identity, credentialsSupplier.get().credential, - resource == null ? "" : resource, - configuredScopes.isEmpty() ? null : ON_SPACE.join(configuredScopes) + oauthConfig.resource(), + oauthConfig.scopes().isEmpty() ? null : ON_SPACE.join(oauthConfig.scopes()) ); Token token = tokenCache.getUnchecked(client); String authorization = String.format("%s %s", token.tokenType(), token.accessToken()); diff --git a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java index 43ec5492d5..39bcacef78 100644 --- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java +++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java @@ -18,7 +18,6 @@ package org.jclouds.oauth.v2.filters; import static java.util.concurrent.TimeUnit.SECONDS; import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; -import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE; import javax.inject.Inject; import javax.inject.Named; @@ -28,7 +27,8 @@ import org.jclouds.http.HttpException; import org.jclouds.http.HttpRequest; import org.jclouds.location.Provider; import org.jclouds.oauth.v2.AuthorizationApi; -import org.jclouds.oauth.v2.config.OAuthScopes; +import org.jclouds.oauth.v2.config.OAuthConfigFactory; +import org.jclouds.oauth.v2.config.OAuthConfigFactory.OAuthConfig; import org.jclouds.oauth.v2.domain.Claims; import org.jclouds.oauth.v2.domain.Token; @@ -51,16 +51,14 @@ import com.google.common.cache.LoadingCache; public class JWTBearerTokenFlow implements OAuthFilter { private static final Joiner ON_COMMA = Joiner.on(","); - private final String audience; private final Supplier credentialsSupplier; - private final OAuthScopes scopes; + private final OAuthConfigFactory oauthConfigFactory; private final LoadingCache tokenCache; @Inject JWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration, - @Provider Supplier credentialsSupplier, OAuthScopes scopes, @Named(AUDIENCE) String audience) { + @Provider Supplier credentialsSupplier, OAuthConfigFactory oauthConfigFactory) { this.credentialsSupplier = credentialsSupplier; - this.scopes = scopes; - this.audience = audience; + this.oauthConfigFactory = oauthConfigFactory; // since the session interval is also the token expiration time requested to the server make the token expire a // bit before the deadline to make sure there aren't session expiration exceptions long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration; @@ -89,10 +87,11 @@ public class JWTBearerTokenFlow implements OAuthFilter { @Override public HttpRequest filter(HttpRequest request) throws HttpException { long now = currentTimeSeconds(); + OAuthConfig oauthConfig = oauthConfigFactory.forRequest(request); Claims claims = Claims.create( // credentialsSupplier.get().identity, // iss - ON_COMMA.join(scopes.forRequest(request)), // scope - audience, // aud + ON_COMMA.join(oauthConfig.scopes()), // scope + oauthConfig.audience(), // aud -1, // placeholder exp for the cache -1 // placeholder iat for the cache ); diff --git a/apis/oauth/src/test/java/org/jclouds/oauth/v2/filters/TestJWTBearerTokenFlow.java b/apis/oauth/src/test/java/org/jclouds/oauth/v2/filters/TestJWTBearerTokenFlow.java index f28e9808f2..23ac7df052 100644 --- a/apis/oauth/src/test/java/org/jclouds/oauth/v2/filters/TestJWTBearerTokenFlow.java +++ b/apis/oauth/src/test/java/org/jclouds/oauth/v2/filters/TestJWTBearerTokenFlow.java @@ -17,22 +17,21 @@ package org.jclouds.oauth.v2.filters; import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; -import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE; import javax.inject.Inject; import javax.inject.Named; import org.jclouds.domain.Credentials; import org.jclouds.location.Provider; -import org.jclouds.oauth.v2.config.OAuthScopes; +import org.jclouds.oauth.v2.config.OAuthConfigFactory; import com.google.common.base.Supplier; public class TestJWTBearerTokenFlow extends JWTBearerTokenFlow { @Inject TestJWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration, - @Provider Supplier credentialsSupplier, OAuthScopes scopes, @Named(AUDIENCE) String audience) { - super(loader, tokenDuration, credentialsSupplier, scopes, audience); + @Provider Supplier credentialsSupplier, OAuthConfigFactory oauthConfigFactory) { + super(loader, tokenDuration, credentialsSupplier, oauthConfigFactory); } /** Constant time for testing. */