From f09636de59d830d85e8cab1739283e834c7950cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Denis?= Date: Mon, 3 Sep 2018 23:44:40 +0200 Subject: [PATCH 1/2] Enrich and expose some methods related to auth: - add some missing TokenInfo fields, and expose GoogleAuth.getTokenInfoRemote - Store validated TokenInfo or GoogleIdToken in request, for custom authenticators usage - Add Javadoc on Attribute constants --- .../com/google/api/server/spi/PeerAuth.java | 7 ++- .../auth/GoogleAppEngineAuthenticator.java | 2 +- .../api/server/spi/auth/GoogleAuth.java | 23 +++++++--- .../spi/auth/GoogleJwtAuthenticator.java | 4 +- .../spi/auth/GoogleOAuth2Authenticator.java | 2 + .../api/server/spi/request/Attribute.java | 43 ++++++++++++++++++- .../google/api/server/spi/request/Auth.java | 8 +++- .../spi/auth/GoogleJwtAuthenticatorTest.java | 13 +++++- .../auth/GoogleOAuth2AuthenticatorTest.java | 11 +++++ 9 files changed, 99 insertions(+), 14 deletions(-) diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/PeerAuth.java b/endpoints-framework/src/main/java/com/google/api/server/spi/PeerAuth.java index f5e0dee9..86cd2817 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/PeerAuth.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/PeerAuth.java @@ -70,6 +70,11 @@ public PeerAuthenticator apply(Class clazz) { } }; + public static PeerAuthenticator instantiatePeerAuthenticator(Class clazz) { + return INSTANTIATE_PEER_AUTHENTICATOR.apply(clazz); + } + + private final HttpServletRequest request; private final Attribute attr; private final ApiMethodConfig config; @@ -78,7 +83,7 @@ public PeerAuthenticator apply(Class clazz) { PeerAuth(HttpServletRequest request) { this.request = request; attr = Attribute.from(request); - config = (ApiMethodConfig) attr.get(Attribute.API_METHOD_CONFIG); + config = attr.get(Attribute.API_METHOD_CONFIG); } static PeerAuth from(HttpServletRequest request) { diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleAppEngineAuthenticator.java b/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleAppEngineAuthenticator.java index 57053d3e..18dbe769 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleAppEngineAuthenticator.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleAppEngineAuthenticator.java @@ -122,7 +122,7 @@ public User authenticate(HttpServletRequest request) throws ServiceUnavailableEx } com.google.appengine.api.users.User appEngineUser = null; - ApiMethodConfig config = (ApiMethodConfig) attr.get(Attribute.API_METHOD_CONFIG); + ApiMethodConfig config = attr.get(Attribute.API_METHOD_CONFIG); if (!attr.isEnabled(Attribute.SKIP_TOKEN_AUTH)) { appEngineUser = getOAuth2User(request, config); } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleAuth.java b/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleAuth.java index 9db726b0..94938fb8 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleAuth.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleAuth.java @@ -49,9 +49,9 @@ public class GoogleAuth { private static final Pattern JWT_PATTERN = Pattern.compile(String.format("%s\\.%s\\.%s", BASE64_REGEX, BASE64_REGEX, BASE64_REGEX)); - // Remote API for validating OAuth2 access token. + // Remote API for validating access or id token. private static final String TOKEN_INFO_ENDPOINT = - "https://www.googleapis.com/oauth2/v2/tokeninfo?access_token="; + "https://www.googleapis.com/oauth2/v2/tokeninfo"; @VisibleForTesting static final String AUTHORIZATION_HEADER = "Authorization"; @@ -115,14 +115,14 @@ private static String matchAuthScheme(String authHeader) { return null; } - static boolean isJwt(String token) { + public static boolean isJwt(String token) { if (token == null) { return false; } return JWT_PATTERN.matcher(token).matches(); } - static boolean isOAuth2Token(String token) { + public static boolean isOAuth2Token(String token) { if (token == null) { return false; } @@ -176,6 +176,9 @@ public static class TokenInfo { @Key("issued_to") public String clientId; @Key("scope") public String scopes; @Key("user_id") public String userId; + @Key("audience") public String audience; + @Key("expires_in") public Integer expiresIn; + @Key("verified_email") public Boolean verifiedEmail; @Key("error_description") public String errorDescription; } @@ -183,10 +186,18 @@ public static class TokenInfo { * Get OAuth2 token info from remote token validation API. * Retries IOExceptions and 5xx responses once. */ - static TokenInfo getTokenInfoRemote(String token) throws ServiceUnavailableException { + public static TokenInfo getTokenInfoRemote(String token) throws ServiceUnavailableException { try { + String tokenParam; + if (isOAuth2Token(token)) { + tokenParam = "?access_token="; + } else if(isJwt(token)) { + tokenParam = "?id_token="; + } else { + return null; + } HttpRequest request = Client.getInstance().getJsonHttpRequestFactory() - .buildGetRequest(new GenericUrl(TOKEN_INFO_ENDPOINT + token)); + .buildGetRequest(new GenericUrl(TOKEN_INFO_ENDPOINT + tokenParam + token)); configureErrorHandling(request); return parseTokenInfo(request); } catch (IOException e) { diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleJwtAuthenticator.java b/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleJwtAuthenticator.java index 1936c36a..15ca77fb 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleJwtAuthenticator.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleJwtAuthenticator.java @@ -79,10 +79,12 @@ public User authenticate(HttpServletRequest request) { return null; } + attr.set(Attribute.ID_TOKEN, idToken); + String clientId = idToken.getPayload().getAuthorizedParty(); String audience = (String) idToken.getPayload().getAudience(); - ApiMethodConfig config = (ApiMethodConfig) attr.get(Attribute.API_METHOD_CONFIG); + ApiMethodConfig config = attr.get(Attribute.API_METHOD_CONFIG); // Check client id. if ((attr.isEnabled(Attribute.ENABLE_CLIENT_ID_WHITELIST) diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleOAuth2Authenticator.java b/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleOAuth2Authenticator.java index 3ce915e9..349e986f 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleOAuth2Authenticator.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/auth/GoogleOAuth2Authenticator.java @@ -56,6 +56,8 @@ public User authenticate(HttpServletRequest request) throws ServiceUnavailableEx return null; } + attr.set(Attribute.TOKEN_INFO, tokenInfo); + ApiMethodConfig config = (ApiMethodConfig) request.getAttribute(Attribute.API_METHOD_CONFIG); // Check scopes. diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/request/Attribute.java b/endpoints-framework/src/main/java/com/google/api/server/spi/request/Attribute.java index c70f0f64..e56b4965 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/request/Attribute.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/request/Attribute.java @@ -26,15 +26,54 @@ * Defines attribute constants passed in Request. */ public class Attribute { + /** + * A {@link com.google.appengine.api.users.User} with the currently authenticated App Engine user. + * + */ public static final String AUTHENTICATED_APPENGINE_USER = "endpoints:Authenticated-AppEngine-User"; + /** + * A {@link com.google.api.server.spi.config.model.ApiMethodConfig} with the current API method's + * configuration. + */ public static final String API_METHOD_CONFIG = "endpoints:Api-Method-Config"; + /** + * A {@link Boolean} indicating if client id whitelist should be checked. + */ public static final String ENABLE_CLIENT_ID_WHITELIST = "endpoints:Enable-Client-Id-Whitelist"; + /** + * @deprecated + */ public static final String RESTRICT_SERVLET = "endpoints:Restrict-Servlet"; + /** + * A {@link Boolean} indicating if the App Engine user should be populated. + */ public static final String REQUIRE_APPENGINE_USER = "endpoints:Require-AppEngine-User"; + /** + * A {@link Boolean} indicating if token-based authentications (OAuth2 and JWT) should be skipped. + */ public static final String SKIP_TOKEN_AUTH = "endpoints:Skip-Token-Auth"; + /** + * A {@link String} with the current request's auth token. + */ public static final String AUTH_TOKEN = "endpoints:Auth-Token"; + /** + * If set, contains a cached OAuth2 {@link com.google.api.server.spi.auth.GoogleAuth.TokenInfo} + * corresponding to the String token in the {@link Attribute#AUTH_TOKEN} {@value AUTH_TOKEN} + * attribute. + * The authentication from {@link com.google.api.server.spi.auth.GoogleOAuth2Authenticator} might + * have failed anyway because of unauthorized client id or scopes. + */ + public static final String TOKEN_INFO = "endpoints:Token-Info"; + /** + * If set, contains a cached instance of a parsed and valid JWT + * {@link com.google.api.client.googleapis.auth.oauth2.GoogleIdToken} corresponding to the String + * token in the {@link Attribute#AUTH_TOKEN} {@value AUTH_TOKEN} attribute. + * The authentication from {@link com.google.api.server.spi.auth.GoogleJwtAuthenticator} might + * have failed anyway because of unauthorized client id or audience. + */ + public static final String ID_TOKEN = "endpoints:Id-Token"; private final HttpServletRequest request; @@ -47,8 +86,8 @@ public static Attribute from(HttpServletRequest request) { return new Attribute(request); } - public Object get(String attr) { - return request.getAttribute(attr); + public T get(String attr) { + return (T) request.getAttribute(attr); } public void set(String attr, Object value) { diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/request/Auth.java b/endpoints-framework/src/main/java/com/google/api/server/spi/request/Auth.java index ef5ee75a..5dcca8a3 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/request/Auth.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/request/Auth.java @@ -67,6 +67,10 @@ public Authenticator apply(Class clazz) { } }; + public static Authenticator instantiateAuthenticator(Class clazz) { + return INSTANTIATE_AUTHENTICATOR.apply(clazz); + } + private final HttpServletRequest request; private final Attribute attr; private final ApiMethodConfig config; @@ -75,7 +79,7 @@ public Authenticator apply(Class clazz) { Auth(HttpServletRequest request) { this.request = request; attr = Attribute.from(request); - config = (ApiMethodConfig) attr.get(Attribute.API_METHOD_CONFIG); + config = attr.get(Attribute.API_METHOD_CONFIG); } static Auth from(HttpServletRequest request) { @@ -122,7 +126,7 @@ com.google.appengine.api.users.User authenticateAppEngineUser() throws ServiceEx return null; } com.google.appengine.api.users.User appEngineUser = - (com.google.appengine.api.users.User) attr.get(Attribute.AUTHENTICATED_APPENGINE_USER); + attr.get(Attribute.AUTHENTICATED_APPENGINE_USER); if (appEngineUser != null) { return appEngineUser; } else { diff --git a/endpoints-framework/src/test/java/com/google/api/server/spi/auth/GoogleJwtAuthenticatorTest.java b/endpoints-framework/src/test/java/com/google/api/server/spi/auth/GoogleJwtAuthenticatorTest.java index a59310b4..e67140d7 100644 --- a/endpoints-framework/src/test/java/com/google/api/server/spi/auth/GoogleJwtAuthenticatorTest.java +++ b/endpoints-framework/src/test/java/com/google/api/server/spi/auth/GoogleJwtAuthenticatorTest.java @@ -16,6 +16,7 @@ package com.google.api.server.spi.auth; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; @@ -90,18 +91,21 @@ public void testVerifyToken() throws Exception { public void testAuthenticate_skipTokenAuth() { attr.set(Attribute.SKIP_TOKEN_AUTH, true); assertNull(authenticator.authenticate(request)); + assertNull(attr.get(Attribute.ID_TOKEN)); } @Test public void testAuthenticate_notJwt() { request.addHeader(GoogleAuth.AUTHORIZATION_HEADER, "Bearer abc.abc"); assertNull(authenticator.authenticate(request)); + assertNull(attr.get(Attribute.ID_TOKEN)); } @Test public void testAuthenticate_invalidToken() throws Exception { when(verifier.verify(TOKEN)).thenReturn(null); assertNull(authenticator.authenticate(request)); + assertNull(attr.get(Attribute.ID_TOKEN)); } @Test @@ -109,6 +113,7 @@ public void testAuthenticate_clientIdNotAllowed() throws Exception { when(verifier.verify(TOKEN)).thenReturn(token); when(config.getClientIds()).thenReturn(ImmutableList.of("clientId2")); assertNull(authenticator.authenticate(request)); + assertNotNull(attr.get(Attribute.ID_TOKEN)); } @Test @@ -117,6 +122,7 @@ public void testAuthenticate_audienceNotAllowed() throws Exception { when(config.getClientIds()).thenReturn(ImmutableList.of(CLIENT_ID)); when(config.getAudiences()).thenReturn(ImmutableList.of("audience2")); assertNull(authenticator.authenticate(request)); + assertNotNull(attr.get(Attribute.ID_TOKEN)); } @Test @@ -128,6 +134,7 @@ public void testAuthenticate_skipClientIdCheck() throws Exception { User user = authenticator.authenticate(request); assertEquals(EMAIL, user.getEmail()); assertEquals(USER_ID, user.getId()); + assertNotNull(attr.get(Attribute.ID_TOKEN)); } @Test @@ -138,6 +145,10 @@ public void testAuthenticate() throws Exception { User user = authenticator.authenticate(request); assertEquals(EMAIL, user.getEmail()); assertEquals(USER_ID, user.getId()); + GoogleIdToken idToken = attr.get(Attribute.ID_TOKEN); + assertNotNull(idToken); + assertEquals(EMAIL, idToken.getPayload().getEmail()); + assertEquals(USER_ID, idToken.getPayload().getSubject()); } @Test @@ -150,7 +161,7 @@ public void testAuthenticate_appEngineUser() throws GeneralSecurityException, IO assertEquals(EMAIL, user.getEmail()); assertEquals(USER_ID, user.getId()); com.google.appengine.api.users.User appEngineuser = - (com.google.appengine.api.users.User) attr.get(Attribute.AUTHENTICATED_APPENGINE_USER); + attr.get(Attribute.AUTHENTICATED_APPENGINE_USER); assertEquals(EMAIL, appEngineuser.getEmail()); assertNull(appEngineuser.getUserId()); } diff --git a/endpoints-framework/src/test/java/com/google/api/server/spi/auth/GoogleOAuth2AuthenticatorTest.java b/endpoints-framework/src/test/java/com/google/api/server/spi/auth/GoogleOAuth2AuthenticatorTest.java index 77320a5f..b05839fa 100644 --- a/endpoints-framework/src/test/java/com/google/api/server/spi/auth/GoogleOAuth2AuthenticatorTest.java +++ b/endpoints-framework/src/test/java/com/google/api/server/spi/auth/GoogleOAuth2AuthenticatorTest.java @@ -16,6 +16,7 @@ package com.google.api.server.spi.auth; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; @@ -69,24 +70,28 @@ private void initializeRequest(String bearerString) { public void testAuthenticate_skipTokenAuth() throws ServiceUnavailableException { attr.set(Attribute.SKIP_TOKEN_AUTH, true); assertNull(authenticator.authenticate(request)); + assertNull(attr.get(Attribute.TOKEN_INFO)); } @Test public void testAuthenticate_notOAuth2() throws ServiceUnavailableException { initializeRequest("Bearer badToken"); assertNull(authenticator.authenticate(request)); + assertNull(attr.get(Attribute.TOKEN_INFO)); } @Test public void testAuthenticate_nullTokenInfo() throws ServiceUnavailableException { authenticator = createAuthenticator(null, null, null, null); assertNull(authenticator.authenticate(request)); + assertNull(attr.get(Attribute.TOKEN_INFO)); } @Test public void testAuthenticate_scopeNotAllowed() throws ServiceUnavailableException { when(config.getScopeExpression()).thenReturn(AuthScopeExpressions.interpret("scope3")); assertNull(authenticator.authenticate(request)); + assertNotNull(attr.get(Attribute.TOKEN_INFO)); } @Test @@ -94,6 +99,7 @@ public void testAuthenticate_clientIdNotAllowed() throws ServiceUnavailableExcep when(config.getScopeExpression()).thenReturn(AuthScopeExpressions.interpret("scope1")); when(config.getClientIds()).thenReturn(ImmutableList.of("clientId2")); assertNull(authenticator.authenticate(request)); + assertNotNull(attr.get(Attribute.TOKEN_INFO)); } @Test @@ -104,6 +110,7 @@ public void testAuthenticate_skipClientIdCheck() throws ServiceUnavailableExcept User user = authenticator.authenticate(request); assertEquals(EMAIL, user.getEmail()); assertEquals(USER_ID, user.getId()); + assertNotNull(attr.get(Attribute.TOKEN_INFO)); } @Test @@ -113,6 +120,10 @@ public void testAuthenticate() throws ServiceUnavailableException { User user = authenticator.authenticate(request); assertEquals(EMAIL, user.getEmail()); assertEquals(USER_ID, user.getId()); + final TokenInfo tokenInfo = attr.get(Attribute.TOKEN_INFO); + assertNotNull(tokenInfo); + assertEquals(EMAIL, tokenInfo.email); + assertEquals(USER_ID, tokenInfo.userId); } @Test From c567c669871d122aee139b0ea39e2b4945458750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Denis?= Date: Wed, 12 Sep 2018 00:08:53 +0200 Subject: [PATCH 2/2] Remove duplicate code in Auth and PeerAuth --- .../com/google/api/server/spi/PeerAuth.java | 58 ++++-------------- .../api/server/spi/config/Singleton.java | 59 +++++++++++++++++++ .../google/api/server/spi/request/Auth.java | 52 ++++------------ 3 files changed, 81 insertions(+), 88 deletions(-) diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/PeerAuth.java b/endpoints-framework/src/main/java/com/google/api/server/spi/PeerAuth.java index 86cd2817..0cfb9cef 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/PeerAuth.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/PeerAuth.java @@ -21,16 +21,6 @@ import com.google.api.server.spi.config.model.ApiMethodConfig; import com.google.api.server.spi.request.Attribute; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; @@ -38,43 +28,20 @@ * Utilities used to do peer authorization. */ public class PeerAuth { - private static final Logger logger = Logger.getLogger(PeerAuth.class.getName()); - - private static volatile - Map, PeerAuthenticator> peerAuthenticatorInstances = - new HashMap, PeerAuthenticator>(); - - private static final PeerAuthenticator DEFAULT_PEER_AUTHENTICATOR = - new EndpointsPeerAuthenticator(); - - private static final - Function, PeerAuthenticator> - INSTANTIATE_PEER_AUTHENTICATOR = - new Function, PeerAuthenticator>() { - @Override - public PeerAuthenticator apply(Class clazz) { - try { - if (clazz.getAnnotation(Singleton.class) != null) { - if (!peerAuthenticatorInstances.containsKey(clazz)) { - peerAuthenticatorInstances.put(clazz, clazz.newInstance()); - } - return peerAuthenticatorInstances.get(clazz); - } else { - return clazz.newInstance(); - } - } catch (IllegalAccessException | InstantiationException e) { - logger.log(Level.WARNING, - "Could not instantiate peer authenticator: " + clazz.getName()); - return null; - } - } - }; + private static final Singleton.Instantiator INSTANTIATOR + = new Singleton.Instantiator(new EndpointsPeerAuthenticator()); + /** + * Must be used to instantiate new {@link PeerAuthenticator}s to honor + * {@link com.google.api.server.spi.config.Singleton} contract. + * + * @return a new instance of clazz, or an existing one if clazz is annotated with @{@link + * com.google.api.server.spi.config.Singleton} + */ public static PeerAuthenticator instantiatePeerAuthenticator(Class clazz) { - return INSTANTIATE_PEER_AUTHENTICATOR.apply(clazz); + return INSTANTIATOR.getInstanceOrDefault(clazz); } - private final HttpServletRequest request; private final Attribute attr; private final ApiMethodConfig config; @@ -92,10 +59,7 @@ static PeerAuth from(HttpServletRequest request) { @VisibleForTesting Iterable getPeerAuthenticatorInstances() { - List> classes = config.getPeerAuthenticators(); - return classes == null ? ImmutableList.of(DEFAULT_PEER_AUTHENTICATOR) - : Iterables.filter(Iterables.transform(classes, INSTANTIATE_PEER_AUTHENTICATOR), - Predicates.notNull()); + return INSTANTIATOR.getInstancesOrDefault(config.getPeerAuthenticators()); } boolean authorizePeer() { diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/Singleton.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/Singleton.java index f3004c01..10d3fdda 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/Singleton.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/Singleton.java @@ -15,10 +15,20 @@ */ package com.google.api.server.spi.config; +import com.google.common.base.Function; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Annotation used with Authenticator and PeerAuthenticator to denote only one instance will be @@ -28,4 +38,53 @@ @Target(value = ElementType.TYPE) @Retention(value = RetentionPolicy.RUNTIME) public @interface Singleton { + + /** + * Instantiates instances of A, honoring the @{@link Singleton} contract. + * Return a default instance when passed null values. + */ + class Instantiator { + + private static final Logger logger = Logger.getLogger(Instantiator.class.getName()); + + private volatile Map, A> instances = new HashMap<>(); + + private final A defaultValue; + + private final Function, A> instantiator + = new Function, A>() { + @Override + public A apply(Class clazz) { + try { + if (clazz.getAnnotation(Singleton.class) != null) { + if (!instances.containsKey(clazz)) { + instances.put(clazz, clazz.newInstance()); + } + return instances.get(clazz); + } else { + return clazz.newInstance(); + } + } catch (IllegalAccessException | InstantiationException e) { + logger.log(Level.WARNING, "Could not instantiate: " + clazz.getName()); + return null; + } + } + }; + + public Instantiator(A defaultValue) { + this.defaultValue = defaultValue; + } + + public A getInstanceOrDefault(Class clazz) { + return clazz == null ? defaultValue : instantiator.apply(clazz); + } + + public Iterable getInstancesOrDefault(List> classes) { + return classes == null ? ImmutableList.of(defaultValue) + : Iterables.filter(Iterables.transform(classes, instantiator), + Predicates.notNull()); + } + + } + } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/request/Auth.java b/endpoints-framework/src/main/java/com/google/api/server/spi/request/Auth.java index 5dcca8a3..a98121e6 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/request/Auth.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/request/Auth.java @@ -23,16 +23,6 @@ import com.google.api.server.spi.config.Singleton; import com.google.api.server.spi.config.model.ApiMethodConfig; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; @@ -40,35 +30,18 @@ * Utilities for end user authentication. */ public class Auth { - private static final Logger logger = Logger.getLogger(Auth.class.getName()); - - private static volatile Map, Authenticator> - authenticatorInstances = new HashMap, Authenticator>(); - - private static final Authenticator DEFAULT_AUTHENTICATOR = new EndpointsAuthenticator(); - - private static final Function, Authenticator> - INSTANTIATE_AUTHENTICATOR = new Function, Authenticator>() { - @Override - public Authenticator apply(Class clazz) { - try { - if (clazz.getAnnotation(Singleton.class) != null) { - if (!authenticatorInstances.containsKey(clazz)) { - authenticatorInstances.put(clazz, clazz.newInstance()); - } - return authenticatorInstances.get(clazz); - } else { - return clazz.newInstance(); - } - } catch (IllegalAccessException | InstantiationException e) { - logger.log(Level.WARNING, "Could not instantiate authenticator: " + clazz.getName()); - return null; - } - } - }; + private static final Singleton.Instantiator INSTANTIATOR + = new Singleton.Instantiator(new EndpointsAuthenticator()); + /** + * Must be used to instantiate new {@link Authenticator}s to honor + * {@link com.google.api.server.spi.config.Singleton} contract. + * + * @return a new instance of clazz, or an existing one if clazz is annotated with @{@link + * com.google.api.server.spi.config.Singleton} + */ public static Authenticator instantiateAuthenticator(Class clazz) { - return INSTANTIATE_AUTHENTICATOR.apply(clazz); + return INSTANTIATOR.getInstanceOrDefault(clazz); } private final HttpServletRequest request; @@ -88,10 +61,7 @@ static Auth from(HttpServletRequest request) { @VisibleForTesting Iterable getAuthenticatorInstances() { - List> classes = config.getAuthenticators(); - return classes == null ? ImmutableList.of(DEFAULT_AUTHENTICATOR) - : Iterables.filter(Iterables.transform(classes, INSTANTIATE_AUTHENTICATOR), - Predicates.notNull()); + return INSTANTIATOR.getInstancesOrDefault(config.getAuthenticators()); } /**