package com.janrain.oauth2;
import com.janrain.backplane.common.BackplaneServerException;
import com.janrain.backplane.dao.DaoException;
import com.janrain.backplane.server2.dao.BP2DAOs;
import com.janrain.backplane.server2.oauth2.model.*;
import com.janrain.backplane2.server.GrantLogic;
import com.janrain.backplane2.server.GrantType;
import com.janrain.backplane2.server.Scope;
import com.janrain.backplane2.server.TokenBuilder;
import com.janrain.commons.util.Pair;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import scala.Option;
import scala.collection.JavaConversions;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import static com.janrain.oauth2.OAuth2.OAUTH2_TOKEN_INVALID_CLIENT;
import static com.janrain.oauth2.OAuth2.OAUTH2_TOKEN_UNSUPPORTED_GRANT;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
/**
* @author Johnny Bufu
*/
public class AuthenticatedTokenRequest implements TokenRequest {
// - PUBLIC
public AuthenticatedTokenRequest(String grantType, Client authenticatedClient, String code,
String redirectUri, String scope,
HttpServletRequest request) throws TokenException, DaoException {
this.authenticatedClientId = authenticatedClient.id();
this.authenticatedClientSourceUrl = authenticatedClient.get(ClientFields.SOURCE_URL()).getOrElse(null);
try {
// this must be done as early as possible in order to invalidate misused codes/grants, before any other failures
if (StringUtils.isNotEmpty(code)) {
// throw whenever a code is parameter is supplied, regardless of the grant type declared in the request
this.codeGrant = GrantLogic.getAndActivateCodeGrant(code, this.authenticatedClientId);
}
} catch (BackplaneServerException e) {
logger.error("error processing token request: " + e.getMessage(), e);
throw new TokenException(OAuth2.OAUTH2_TOKEN_SERVER_ERROR, "error processing token request", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
if (StringUtils.isEmpty(authenticatedClientId)) {
throw new TokenException(OAUTH2_TOKEN_INVALID_CLIENT, "Client authentication failed", SC_UNAUTHORIZED);
}
this.grantType = GrantType.fromOauthType(grantType);
if (this.grantType == null) {
throw new TokenException(OAUTH2_TOKEN_UNSUPPORTED_GRANT, "Invalid grant type: " + grantType);
}
if ( this.grantType == GrantType.AUTHORIZATION_CODE ^ StringUtils.isNotEmpty(code) ) {
throw new TokenException("code is required if and only if grant_type is authorization_code");
}
if ( this.grantType == GrantType.AUTHORIZATION_CODE ^ codeGrant != null ) {
throw new TokenException("Invalid code: " + code);
}
Option<Token> token = Token.fromRequest(request);
if ( this.grantType == GrantType.REFRESH_PRIVILEGED ^ (token.isDefined()) ) {
throw new TokenException("refresh_token is required if and only if grant_type is refresh_token");
}
if ( this.grantType == GrantType.AUTHORIZATION_CODE ^ StringUtils.isNotEmpty(redirectUri) ) {
throw new TokenException("redirect_uri is required if and only if grant_type is authorization_code");
}
try {
OAuth2.validateRedirectUri(redirectUri, (String) authenticatedClient.get(ClientFields.REDIRECT_URI()).getOrElse(null));
} catch (ValidationException e) {
throw new TokenException(e.getCode(), "Invalid redirect_uri: " + redirectUri);
}
if (token.isDefined()) {
this.refreshToken = token.get();
if ( ! this.refreshToken.grantType().isRefresh()) {
logger.warn("access token presented where refresh token is expected: " + refreshToken);
throw new TokenException(OAuth2.OAUTH2_TOKEN_INVALID_REQUEST, "invalid token: " + refreshToken);
}
}
this.requestScope = new Scope(scope);
}
@Override
public Map<String,Object> tokenResponse() throws TokenException, DaoException {
logger.info("Responding to authenticated token request...");
final Token accessToken;
final Integer expiresIn = grantType.getAccessType().getTokenExpiresSecondsDefault();
Date expires = new Date(System.currentTimeMillis() + expiresIn.longValue() * 1000);
Pair<Scope,List<String>> scopeGrants = processScope();
try {
accessToken = new TokenBuilder(grantType.getAccessType(), scopeGrants.getLeft().toString())
.expires(expires)
.issuedToClient(authenticatedClientId)
.clientSourceUrl(authenticatedClientSourceUrl)
.grants(scopeGrants.getRight())
.buildToken();
BP2DAOs.tokenDao().store(accessToken);
return accessToken.response(generateRefreshToken(grantType.getRefreshType(), accessToken));
} catch (DaoException e) {
logger.error("error processing anonymous access token request: " + e.getMessage(), e);
throw new TokenException(OAuth2.OAUTH2_TOKEN_SERVER_ERROR, "error processing anonymous token request", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (BackplaneServerException bpe) {
logger.error("error processing anonymous access token request: " + bpe.getMessage(), bpe);
throw new TokenException(OAuth2.OAUTH2_TOKEN_SERVER_ERROR, "error processing anonymous token request", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
try {
if (this.refreshToken != null) {
BP2DAOs.tokenDao().delete(this.refreshToken.id());
}
} catch (DaoException e) {
logger.error("error deleting used refresh token: " + refreshToken.id(), e);
}
}
}
// - PRIVATE
private static final Logger logger = Logger.getLogger(AuthenticatedTokenRequest.class);
private final GrantType grantType;
private final String authenticatedClientId;
private final String authenticatedClientSourceUrl;
private final Scope requestScope;
private Grant2 codeGrant;
private Token refreshToken;
private Pair<Scope,List<String>> processScope() throws TokenException, DaoException {
if (refreshToken != null) {
return new Pair<Scope, List<String>>(Scope.checkCombine(refreshToken.scope(), requestScope), JavaConversions.seqAsJavaList(refreshToken.backingGrants()));
}
if (codeGrant != null) {
return new Pair<Scope, List<String>>(Scope.checkCombine(codeGrant.getAuthorizedScope(), requestScope), new ArrayList<String>() {{add(codeGrant.id());}});
}
// client credentials scope
try {
Map<Scope,Set<Grant2>> scopeGrantsMap = GrantLogic.retrieveClientGrants(authenticatedClientId, requestScope);
List<String> allGrants = new ArrayList<String>();
for(Set<Grant2> grants : scopeGrantsMap.values()) {
for(Grant2 grant : grants) {
allGrants.add(grant.id());
}
}
if (requestScope.isAuthorizationRequired()) {
if (!scopeGrantsMap.keySet().containsAll(requestScope.getAuthReqScopes())) {
throw new TokenException(OAuth2.OAUTH2_TOKEN_INVALID_SCOPE, "unauthorized scope: " + requestScope);
}
return new Pair<Scope, List<String>>(requestScope, allGrants);
} else {
return new Pair<Scope, List<String>>(Scope.checkCombine(scopeGrantsMap.keySet().iterator().next(), requestScope), allGrants);
}
} catch (BackplaneServerException e) {
logger.error("error processing token request: " + e.getMessage(), e);
throw new TokenException(OAuth2.OAUTH2_TOKEN_SERVER_ERROR, "error processing token request", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
private String generateRefreshToken(GrantType refreshType, Token accessToken) throws BackplaneServerException, DaoException {
if (! refreshType.isRefresh()) return null;
Token refreshToken = new TokenBuilder(refreshType, accessToken.get(TokenFields.SCOPE()).get())
.issuedToClient(authenticatedClientId)
.clientSourceUrl(authenticatedClientSourceUrl)
.grants(JavaConversions.seqAsJavaList(accessToken.backingGrants()))
.buildToken();
BP2DAOs.tokenDao().store(refreshToken);
return refreshToken.id();
}
}