Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,54 +21,26 @@
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;

/**
* Utilities used to do peer authorization.
*/
public class PeerAuth {
private static final Logger logger = Logger.getLogger(PeerAuth.class.getName());

private static volatile
Map<Class<? extends PeerAuthenticator>, PeerAuthenticator> peerAuthenticatorInstances =
new HashMap<Class<? extends PeerAuthenticator>, PeerAuthenticator>();

private static final PeerAuthenticator DEFAULT_PEER_AUTHENTICATOR =
new EndpointsPeerAuthenticator();
private static final Singleton.Instantiator<PeerAuthenticator> INSTANTIATOR
= new Singleton.Instantiator<PeerAuthenticator>(new EndpointsPeerAuthenticator());

private static final
Function<Class<? extends PeerAuthenticator>, PeerAuthenticator>
INSTANTIATE_PEER_AUTHENTICATOR =
new Function<Class<? extends PeerAuthenticator>, PeerAuthenticator>() {
@Override
public PeerAuthenticator apply(Class<? extends PeerAuthenticator> 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;
}
}
};
/**
* 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<? extends PeerAuthenticator> clazz) {
return INSTANTIATOR.getInstanceOrDefault(clazz);
}

private final HttpServletRequest request;
private final Attribute attr;
Expand All @@ -78,7 +50,7 @@ public PeerAuthenticator apply(Class<? extends PeerAuthenticator> 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) {
Expand All @@ -87,10 +59,7 @@ static PeerAuth from(HttpServletRequest request) {

@VisibleForTesting
Iterable<PeerAuthenticator> getPeerAuthenticatorInstances() {
List<Class<? extends PeerAuthenticator>> 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -176,17 +176,28 @@ 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;
}

/**
* 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<A> {

private static final Logger logger = Logger.getLogger(Instantiator.class.getName());

private volatile Map<Class<? extends A>, A> instances = new HashMap<>();

private final A defaultValue;

private final Function<Class<? extends A>, A> instantiator
= new Function<Class<? extends A>, A>() {
@Override
public A apply(Class<? extends A> 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<? extends A> clazz) {
return clazz == null ? defaultValue : instantiator.apply(clazz);
}

public Iterable<A> getInstancesOrDefault(List<Class<? extends A>> classes) {
return classes == null ? ImmutableList.of(defaultValue)
: Iterables.filter(Iterables.transform(classes, instantiator),
Predicates.notNull());
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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> T get(String attr) {
return (T) request.getAttribute(attr);
}

public void set(String attr, Object value) {
Expand Down
Loading