/**
* =============================================================================
*
* ORCID (R) Open Source
* http://orcid.org
*
* Copyright (c) 2012-2014 ORCID, Inc.
* Licensed under an MIT-Style License (MIT)
* http://orcid.org/open-source-license
*
* This copyright and license information (including a link to the full license)
* shall be included in its entirety in all copies or substantial portion of
* the software.
*
* =============================================================================
*/
package org.orcid.core.oauth;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import org.orcid.core.manager.ClientDetailsEntityCacheManager;
import org.orcid.core.oauth.service.OrcidOAuth2RequestValidator;
import org.orcid.persistence.dao.OrcidOauth2AuthoriziationCodeDetailDao;
import org.orcid.persistence.jpa.entities.ClientDetailsEntity;
import org.orcid.persistence.jpa.entities.OrcidOauth2AuthoriziationCodeDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
public class OrcidAuthorizationCodeTokenGranter extends AbstractTokenGranter {
private static final Logger LOGGER = LoggerFactory.getLogger(OrcidAuthorizationCodeTokenGranter.class);
private static final String GRANT_TYPE = "authorization_code";
private final AuthorizationCodeServices authorizationCodeServices;
@Value("${org.orcid.core.oauth.auth_code.expiration_minutes:1440}")
private int authorizationCodeExpiration;
@Resource(name = "orcidOauth2AuthoriziationCodeDetailDao")
private OrcidOauth2AuthoriziationCodeDetailDao orcidOauth2AuthoriziationCodeDetailDao;
@Resource
private ClientDetailsEntityCacheManager clientDetailsEntityCacheManager;
@Resource
private OrcidOAuth2RequestValidator orcidOAuth2RequestValidator;
public OrcidAuthorizationCodeTokenGranter(AuthorizationServerTokenServices tokenServices, AuthorizationCodeServices authorizationCodeServices,
ClientDetailsService clientDetailsService, OAuth2RequestFactory oAuth2RequestFactory) {
super(tokenServices, clientDetailsService, oAuth2RequestFactory, GRANT_TYPE);
this.authorizationCodeServices = authorizationCodeServices;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = tokenRequest.getRequestParameters();
String authorizationCode = parameters.get("code");
String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI);
LOGGER.info("Getting OAuth2 authentication: code={}, redirectUri={}, clientId={}, scope={}", new Object[] { authorizationCode, redirectUri,
tokenRequest.getClientId(), tokenRequest.getScope() });
if (authorizationCode == null) {
throw new OAuth2Exception("An authorization code must be supplied.");
}
//Validate the client is active
ClientDetailsEntity clientDetails = clientDetailsEntityCacheManager.retrieve(tokenRequest.getClientId());
orcidOAuth2RequestValidator.validateClientIsEnabled(clientDetails);
//Validate scopes
OrcidOauth2AuthoriziationCodeDetail codeDetails = orcidOauth2AuthoriziationCodeDetailDao.find(authorizationCode);
if(codeDetails == null) {
throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
} else {
// Check auth code expiration
Date tokenCreationDate = codeDetails.getDateCreated();
Calendar calendar = Calendar.getInstance();
calendar.setTime(tokenCreationDate);
calendar.add(Calendar.MINUTE, authorizationCodeExpiration);
Date tokenExpirationDate = calendar.getTime();
if(tokenExpirationDate.before(new Date())) {
throw new IllegalArgumentException("Authorization code has expired");
}
// Check granted scopes
Set<String> grantedScopes = codeDetails.getScopes();
Set<String> requestScopes = tokenRequest.getScope();
for(String requestScope : requestScopes) {
if(!grantedScopes.contains(requestScope)) {
throw new InvalidScopeException("Invalid scopes: " + requestScope + " available scopes for this code are: " + grantedScopes);
}
}
}
//Consume code
OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
if (storedAuth == null) {
throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
}
OAuth2Request pendingAuthorizationRequest = storedAuth.getOAuth2Request();
//Regenerate the authorization request but now with the request parameters
pendingAuthorizationRequest = pendingAuthorizationRequest.createOAuth2Request(parameters);
LOGGER.info("Found pending authorization request: redirectUri={}, clientId={}, scope={}, is_approved={}", new Object[] { pendingAuthorizationRequest.getRedirectUri(),
pendingAuthorizationRequest.getClientId(), pendingAuthorizationRequest.getScope(), pendingAuthorizationRequest.isApproved() });
// https://jira.springsource.org/browse/SECOAUTH-333
// This might be null, if the authorization was done without the
// redirect_uri parameter
String redirectUriApprovalParameter = pendingAuthorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
if ((redirectUri != null || redirectUriApprovalParameter != null) && !pendingAuthorizationRequest.getRedirectUri().equals(redirectUri)) {
throw new RedirectMismatchException("Redirect URI mismatch.");
}
String pendingClientId = pendingAuthorizationRequest.getClientId();
String clientId = client.getClientId();
LOGGER.info("Comparing client ids: pendingClientId={}, authorizationRequest.clientId={}", pendingClientId, clientId);
if (clientId != null && !clientId.equals(pendingClientId)) {
// just a sanity check.
throw new InvalidClientException("Client ID mismatch");
}
Authentication userAuth = storedAuth.getUserAuthentication();
return new OAuth2Authentication(pendingAuthorizationRequest, userAuth);
}
}