package com.github.hburgmeier.jerseyoauth2.authsrv.impl.services; import java.net.URI; import java.net.URISyntaxException; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.hburgmeier.jerseyoauth2.api.protocol.IAccessTokenRequest; import com.github.hburgmeier.jerseyoauth2.api.protocol.IAuthCodeAccessTokenRequest; import com.github.hburgmeier.jerseyoauth2.api.protocol.IRefreshTokenRequest; import com.github.hburgmeier.jerseyoauth2.api.protocol.OAuth2ErrorCode; import com.github.hburgmeier.jerseyoauth2.api.protocol.OAuth2ProtocolException; import com.github.hburgmeier.jerseyoauth2.api.protocol.ResponseBuilderException; import com.github.hburgmeier.jerseyoauth2.api.token.InvalidTokenException; import com.github.hburgmeier.jerseyoauth2.api.types.ResponseType; import com.github.hburgmeier.jerseyoauth2.api.user.IUser; import com.github.hburgmeier.jerseyoauth2.authsrv.api.IConfiguration; import com.github.hburgmeier.jerseyoauth2.authsrv.api.client.IAuthorizedClientApp; import com.github.hburgmeier.jerseyoauth2.authsrv.api.client.IClientService; import com.github.hburgmeier.jerseyoauth2.authsrv.api.client.IPendingClientToken; import com.github.hburgmeier.jerseyoauth2.authsrv.api.client.IRegisteredClientApp; import com.github.hburgmeier.jerseyoauth2.authsrv.api.protocol.IOAuth2Response; import com.github.hburgmeier.jerseyoauth2.authsrv.api.protocol.IResponseBuilder; import com.github.hburgmeier.jerseyoauth2.authsrv.api.token.IAccessTokenInfo; import com.github.hburgmeier.jerseyoauth2.authsrv.api.token.IAccessTokenStorageService; import com.github.hburgmeier.jerseyoauth2.authsrv.api.token.ITokenGenerator; import com.github.hburgmeier.jerseyoauth2.authsrv.api.token.ITokenService; import com.github.hburgmeier.jerseyoauth2.authsrv.api.token.TokenGenerationException; import com.github.hburgmeier.jerseyoauth2.authsrv.api.token.TokenStorageException; import com.github.hburgmeier.jerseyoauth2.authsrv.api.ui.AuthorizationFlowException; import com.github.hburgmeier.jerseyoauth2.authsrv.api.ui.IAuthorizationFlow; import com.github.hburgmeier.jerseyoauth2.authsrv.impl.protocol.ClientIdentityValidator; import com.github.hburgmeier.jerseyoauth2.authsrv.impl.protocol.InvalidScopeException; import com.github.hburgmeier.jerseyoauth2.authsrv.impl.protocol.ScopeValidator; public class TokenService implements ITokenService { private static final String SERVER_ERROR_DESCRIPTION = "Server error"; private static final Logger LOGGER = LoggerFactory.getLogger(TokenService.class); private final IAccessTokenStorageService accessTokenService; private final IClientService clientService; private final ITokenGenerator tokenGenerator; private final IResponseBuilder responseBuilder; private final IAuthorizationFlow authFlow; private final ClientIdentityValidator clientIdValidator = new ClientIdentityValidator(); protected final boolean issueRefreshToken; protected final boolean allowScopeEnhancement; protected final ScopeValidator scopeValidator; @Inject public TokenService(final IAccessTokenStorageService accessTokenService, final IClientService clientService, final ITokenGenerator tokenGenerator, final IAuthorizationFlow authFlow, final IConfiguration configuration, final IResponseBuilder responseBuilder) { this.accessTokenService = accessTokenService; this.clientService = clientService; this.tokenGenerator = tokenGenerator; this.authFlow = authFlow; this.responseBuilder = responseBuilder; this.issueRefreshToken = configuration.getEnableRefreshTokenGeneration(); this.allowScopeEnhancement = configuration.getAllowScopeEnhancementWithRefreshToken(); this.scopeValidator = new ScopeValidator(configuration); } @Override public IOAuth2Response handleRequest(HttpServletRequest request, IAccessTokenRequest oauthRequest) throws OAuth2ProtocolException, ResponseBuilderException, AuthorizationFlowException { LOGGER.debug("Token request received, grant type {}", oauthRequest.getGrantType()); switch (oauthRequest.getGrantType()) { case REFRESH_TOKEN: if (issueRefreshToken) { return refreshToken(request, (IRefreshTokenRequest)oauthRequest); } else { LOGGER.error("Refresh token generation is disabled"); throw new OAuth2ProtocolException(OAuth2ErrorCode.UNSUPPORTED_GRANT_TYPE, "Refresh token is disabled", null); } case AUTHORIZATION_REQUEST: return handleAuthorizationRequest(request, (IAuthCodeAccessTokenRequest)oauthRequest); default: throw new OAuth2ProtocolException(OAuth2ErrorCode.UNSUPPORTED_GRANT_TYPE, null, null); } } @Override public IOAuth2Response issueNewToken(HttpServletRequest request, IAuthorizedClientApp clientApp, ResponseType responseType, String state) throws OAuth2ProtocolException, ResponseBuilderException { try { String accessToken = tokenGenerator.createAccessToken(); String refreshToken = (issueRefreshToken && (responseType!=ResponseType.TOKEN))?tokenGenerator.createRefreshToken():null; IAccessTokenInfo accessTokenInfo = accessTokenService.issueToken(accessToken, refreshToken, clientApp); LOGGER.debug("token {} issued", accessToken); return sendTokenResponse(accessTokenInfo, responseType, state); } catch (TokenStorageException e) { LOGGER.error("error with token storage", e); throw new OAuth2ProtocolException(OAuth2ErrorCode.SERVER_ERROR, SERVER_ERROR_DESCRIPTION, null, e); } catch (TokenGenerationException e) { LOGGER.error("error with token generation", e); throw new OAuth2ProtocolException(OAuth2ErrorCode.SERVER_ERROR, SERVER_ERROR_DESCRIPTION, null, e); } } @Override public IOAuth2Response sendErrorResponse(IAccessTokenRequest oauthRequest, OAuth2ProtocolException ex) throws ResponseBuilderException { if (ex.getErrorCode() == OAuth2ErrorCode.INVALID_CLIENT && oauthRequest.hasUsedAuhorizationHeader()) { return responseBuilder.buildUnauthorizedResponse(ex); } else { return responseBuilder.buildRequestTokenErrorResponse(ex); } } @Override public IOAuth2Response sendTokenResponse(IAccessTokenInfo accessTokenInfo, ResponseType responseType, String state) throws ResponseBuilderException { LOGGER.debug("sending response for {}", responseType); if (responseType == ResponseType.TOKEN) { try { return responseBuilder.buildImplicitGrantAccessTokenResponse(accessTokenInfo, new URI(accessTokenInfo.getClientApp().getCallbackUrl()), state); } catch (URISyntaxException e) { LOGGER.debug("error with callback URL {}", accessTokenInfo.getClientApp().getCallbackUrl()); throw new ResponseBuilderException(e); } } else { return responseBuilder.buildAccessTokenResponse(accessTokenInfo, state); } } @Override public void removeTokensForUser(IUser user) { accessTokenService.invalidateTokensForUser(user); clientService.removePendingTokensForUser(user); } protected IOAuth2Response handleAuthorizationRequest(HttpServletRequest request, IAuthCodeAccessTokenRequest tokenRequest) throws OAuth2ProtocolException, ResponseBuilderException { IPendingClientToken pendingClientToken = clientService.findPendingClientToken(tokenRequest.getClientId(), tokenRequest.getClientSecret(), tokenRequest.getCode()); if (pendingClientToken == null) { LOGGER.error("no pending token found, client id {}", tokenRequest.getClientId()); throw new OAuth2ProtocolException(OAuth2ErrorCode.INVALID_GRANT, "client not authorized", null); } if (pendingClientToken.isExpired()) { LOGGER.debug("removing expired pending client token {}", tokenRequest.getClientId()); clientService.removePendingClientToken(pendingClientToken); throw new OAuth2ProtocolException(OAuth2ErrorCode.INVALID_GRANT, "client not authorized", null); } IRegisteredClientApp clientApp = clientService.getRegisteredClient(tokenRequest.getClientId()); clientIdValidator.validateAccessTokenRequest(tokenRequest, clientApp); IOAuth2Response oauthResponse = issueNewToken(request, pendingClientToken.getAuthorizedClient(), ResponseType.CODE, null); clientService.removePendingClientToken(pendingClientToken); return oauthResponse; } protected IOAuth2Response refreshToken(HttpServletRequest request, IRefreshTokenRequest refreshTokenRequest) throws OAuth2ProtocolException, ResponseBuilderException, AuthorizationFlowException { String refreshToken = refreshTokenRequest.getRefreshToken(); try { if (refreshTokenRequest.getScopes()!=null && !refreshTokenRequest.getScopes().isEmpty()) { scopeValidator.validateScopes(refreshTokenRequest.getScopes()); } IAccessTokenInfo oldTokenInfo = accessTokenService.getTokenInfoByRefreshToken(refreshToken); validateRefreshTokenRequest(oldTokenInfo, refreshTokenRequest); if (!isScopeEnhancement(refreshTokenRequest, oldTokenInfo)) { String newAccessToken = tokenGenerator.createAccessToken(); String newRefreshToken = issueRefreshToken?tokenGenerator.createRefreshToken():null; IAccessTokenInfo accessTokenInfo = accessTokenService.refreshToken( oldTokenInfo.getAccessToken(), newAccessToken, newRefreshToken); LOGGER.debug("token {} refreshed to {}", oldTokenInfo.getAccessToken(), newAccessToken); return sendTokenResponse(accessTokenInfo, ResponseType.CODE, null); } else { if (allowScopeEnhancement) { IRegisteredClientApp clientApp = clientService.getRegisteredClient(oldTokenInfo.getClientId()); return authFlow.startScopeEnhancementFlow(oldTokenInfo.getUser(), clientApp, refreshTokenRequest.getScopes(), refreshTokenRequest, request); } else { LOGGER.error("Client wants to extend scope with refresh token, but this is disabled"); throw new OAuth2ProtocolException(OAuth2ErrorCode.INVALID_SCOPE, null); } } } catch (InvalidTokenException e) { LOGGER.error("invalid token", e); throw new OAuth2ProtocolException(OAuth2ErrorCode.INVALID_GRANT, "token is invalid", null, e); } catch (TokenStorageException e) { LOGGER.error("error with token storage", e); throw new OAuth2ProtocolException(OAuth2ErrorCode.SERVER_ERROR, SERVER_ERROR_DESCRIPTION, null, e); } catch (TokenGenerationException e) { LOGGER.error("error with token generation", e); throw new OAuth2ProtocolException(OAuth2ErrorCode.SERVER_ERROR, SERVER_ERROR_DESCRIPTION, null, e); } catch (InvalidScopeException e) { LOGGER.error("Scope is invalid", e); throw new OAuth2ProtocolException(OAuth2ErrorCode.INVALID_SCOPE, "Scope is invalid", null, e); } } protected void validateRefreshTokenRequest(IAccessTokenInfo oldTokenInfo, IRefreshTokenRequest request) throws OAuth2ProtocolException { IRegisteredClientApp clientApp = clientService.getRegisteredClient(oldTokenInfo.getClientId()); clientIdValidator.validateRefreshTokenRequest(request, clientApp); } protected boolean isScopeEnhancement(IRefreshTokenRequest refreshRequest, IAccessTokenInfo oldTokenInfo) throws OAuth2ProtocolException, AuthorizationFlowException { if (refreshRequest.getScopes()!=null && !scopeValidator.isScopeEqual(refreshRequest.getScopes(), oldTokenInfo.getAuthorizedScopes())) { return true; } else { return false; } } }