/** * ============================================================================= * * 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.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Resource; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import org.apache.commons.lang.StringUtils; import org.orcid.core.constants.OrcidOauth2Constants; import org.orcid.core.exception.OrcidInvalidScopeException; import org.orcid.core.locale.LocaleManager; import org.orcid.core.oauth.OrcidClientCredentialEndPointDelegator; import org.orcid.jaxb.model.message.ScopePathType; import org.orcid.persistence.dao.OrcidOauth2AuthoriziationCodeDetailDao; import org.orcid.persistence.jpa.entities.OrcidOauth2AuthoriziationCodeDetail; import org.orcid.pojo.ajaxForm.PojoUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.endpoint.AbstractEndpoint; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; /** * @author Declan Newman (declan) Date: 18/04/2012 */ @Component("orcidClientCredentialEndPointDelegator") public class OrcidClientCredentialEndPointDelegatorImpl extends AbstractEndpoint implements OrcidClientCredentialEndPointDelegator { private static final Logger LOGGER = LoggerFactory.getLogger(OrcidClientCredentialEndPointDelegatorImpl.class); @Resource private OrcidOauth2AuthoriziationCodeDetailDao orcidOauth2AuthoriziationCodeDetailDao; @Resource protected LocaleManager localeManager; @Transactional public Response obtainOauth2Token(String authorization, MultivaluedMap<String, String> formParams) { String code = formParams.getFirst("code"); String clientId = formParams.getFirst(OrcidOauth2Constants.CLIENT_ID_PARAM); String state = formParams.getFirst(OrcidOauth2Constants.STATE_PARAM); String redirectUri = formParams.getFirst(OrcidOauth2Constants.REDIRECT_URI_PARAM); String refreshToken = formParams.getFirst(OrcidOauth2Constants.REFRESH_TOKEN); String scopeList = formParams.getFirst(OrcidOauth2Constants.SCOPE_PARAM); String grantType = formParams.getFirst(OrcidOauth2Constants.GRANT_TYPE); Boolean revokeOld = formParams.containsKey(OrcidOauth2Constants.REVOKE_OLD) ? Boolean.valueOf(formParams.getFirst(OrcidOauth2Constants.REVOKE_OLD)) : true; Long expiresIn = calculateExpiresIn(formParams); String bearerToken = null; Set<String> scopes = new HashSet<String>(); if (StringUtils.isNotEmpty(scopeList)) { scopes = OAuth2Utils.parseParameterList(scopeList); } if(OrcidOauth2Constants.REFRESH_TOKEN.equals(grantType)) { if(!PojoUtil.isEmpty(authorization)) { if ((authorization.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) { String authHeaderValue = authorization.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim(); int commaIndex = authHeaderValue.indexOf(','); if (commaIndex > 0) { authHeaderValue = authHeaderValue.substring(0, commaIndex); } bearerToken = authHeaderValue; if(PojoUtil.isEmpty(bearerToken)) { throw new IllegalArgumentException("Refresh token request doesnt include the authorization"); } } } } LOGGER.info("OAuth2 authorization requested: clientId={}, grantType={}, refreshToken={}, code={}, scopes={}, state={}, redirectUri={}", new Object[] { clientId, grantType, refreshToken, code, scopes, state, redirectUri }); Authentication client = getClientAuthentication(); if (!client.isAuthenticated()) { LOGGER.info("Not authenticated for OAuth2: clientId={}, grantType={}, refreshToken={}, code={}, scopes={}, state={}, redirectUri={}", new Object[] { clientId, grantType, refreshToken, code, scopes, state, redirectUri }); throw new InsufficientAuthenticationException(localeManager.resolveMessage("apiError.client_not_authenticated.exception")); } /** * Patch, update any orcid-grants scope to funding scope * */ for (String scope : scopes) { if (scope.contains("orcid-grants")) { String newScope = scope.replace("orcid-grants", "funding"); LOGGER.info("Client {} provided a grants scope {} which will be updated to {}", new Object[] { clientId, scope, newScope }); scopes.remove(scope); scopes.add(newScope); } } try { if (scopes != null) { List<String> toRemove = new ArrayList<String>(); for (String scope : scopes) { ScopePathType scopeType = ScopePathType.fromValue(scope); if(scopeType.isInternalScope()) { // You should not allow any internal scope here! go away! String message = localeManager.resolveMessage("apiError.9015.developerMessage", new Object[]{}); throw new OrcidInvalidScopeException(message); } else if(OrcidOauth2Constants.GRANT_TYPE_CLIENT_CREDENTIALS.equals(grantType)) { if(!scopeType.isClientCreditalScope()) toRemove.add(scope); } else { if(scopeType.isClientCreditalScope()) toRemove.add(scope); } } for (String remove : toRemove) { scopes.remove(remove); } } } catch (IllegalArgumentException iae) { String message = localeManager.resolveMessage("apiError.9015.developerMessage", new Object[]{}); throw new OrcidInvalidScopeException(message); } OAuth2AccessToken token = generateToken(client, scopes, code, redirectUri, grantType, refreshToken, state, bearerToken, revokeOld, expiresIn); return getResponse(token); } /** * Calculates the real value of the "expires_in" param based on the current time * * @param formParams * The params container * * @return the expiration time in milliseconds based on the param OrcidOauth2Constants.EXPIRES_IN. * @throws IllegalArgumentException in case the parameter is not a number * */ private Long calculateExpiresIn(MultivaluedMap<String, String> formParams) { if(!formParams.containsKey(OrcidOauth2Constants.EXPIRES_IN)){ return 0L; } String expiresInParam = formParams.getFirst(OrcidOauth2Constants.EXPIRES_IN); Long result = 0L; try { result = Long.valueOf(expiresInParam); } catch(Exception e) { throw new IllegalArgumentException(expiresInParam + " is not a number"); } return result == 0 ? result : (System.currentTimeMillis() + (result * 1000)); } protected OAuth2AccessToken generateToken(Authentication client, Set<String> scopes, String code, String redirectUri, String grantType, String refreshToken, String state, String authorization, boolean revokeOld, Long expiresIn) { String clientId = client.getName(); Map<String, String> authorizationParameters = new HashMap<String, String>(); if(scopes != null) { String scopesString = StringUtils.join(scopes, ' '); authorizationParameters.put(OAuth2Utils.SCOPE, scopesString); } authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId); if (code != null) { authorizationParameters.put("code", code); OrcidOauth2AuthoriziationCodeDetail authorizationCodeEntity = orcidOauth2AuthoriziationCodeDetailDao.find(code); if(authorizationCodeEntity != null) { if(orcidOauth2AuthoriziationCodeDetailDao.isPersistentToken(code)) { authorizationParameters.put(OrcidOauth2Constants.IS_PERSISTENT, "true"); } else { authorizationParameters.put(OrcidOauth2Constants.IS_PERSISTENT, "false"); } if(!authorizationParameters.containsKey(OAuth2Utils.SCOPE) || PojoUtil.isEmpty(authorizationParameters.get(OAuth2Utils.SCOPE))) { String scopesString = StringUtils.join(authorizationCodeEntity.getScopes(), ' '); authorizationParameters.put(OAuth2Utils.SCOPE, scopesString); } } else { authorizationParameters.put(OrcidOauth2Constants.IS_PERSISTENT, "false"); } } //If it is a refresh token request, set the needed authorization parameters if(OrcidOauth2Constants.REFRESH_TOKEN.equals(grantType)) { authorizationParameters.put(OrcidOauth2Constants.AUTHORIZATION, authorization); authorizationParameters.put(OrcidOauth2Constants.REVOKE_OLD, String.valueOf(revokeOld)); authorizationParameters.put(OrcidOauth2Constants.EXPIRES_IN, String.valueOf(expiresIn)); authorizationParameters.put(OrcidOauth2Constants.REFRESH_TOKEN, String.valueOf(refreshToken)); } if (redirectUri != null) { authorizationParameters.put(OAuth2Utils.REDIRECT_URI, redirectUri); } AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(authorizationParameters); TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(authorizationRequest, grantType); OAuth2AccessToken token = getTokenGranter().grant(grantType, tokenRequest); Object params[] = {grantType}; if (token == null) { LOGGER.info("Unsupported grant type for OAuth2: clientId={}, grantType={}, code={}, scopes={}, state={}, redirectUri={}", new Object[] { clientId, grantType, code, scopes, state, redirectUri }); throw new UnsupportedGrantTypeException(localeManager.resolveMessage("apiError.unsupported_client_type.exception", params)); } LOGGER.info("OAuth2 access token granted: clientId={}, grantType={}, code={}, scopes={}, state={}, redirectUri={}, token={}", new Object[] { clientId, grantType, code, scopes, state, redirectUri, token }); return token; } protected Response getResponse(OAuth2AccessToken accessToken) { if(accessToken != null && accessToken.getAdditionalInformation() != null) { if(accessToken.getAdditionalInformation().containsKey(OrcidOauth2Constants.TOKEN_VERSION)) accessToken.getAdditionalInformation().remove(OrcidOauth2Constants.TOKEN_VERSION); if(accessToken.getAdditionalInformation().containsKey(OrcidOauth2Constants.PERSISTENT)) accessToken.getAdditionalInformation().remove(OrcidOauth2Constants.PERSISTENT); if(accessToken.getAdditionalInformation().containsKey(OrcidOauth2Constants.DATE_CREATED)) accessToken.getAdditionalInformation().remove(OrcidOauth2Constants.DATE_CREATED); } return Response.ok(accessToken).header("Cache-Control", "no-store").header("Pragma", "no-cache").build(); } protected Authentication getClientAuthentication() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { return authentication; } else { throw new InsufficientAuthenticationException(localeManager.resolveMessage("apiError.client_authentication_notfound.exception")); } } }