/** * ============================================================================= * * 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.service; import java.util.ArrayList; import java.util.Collection; 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 org.apache.commons.lang.StringUtils; import org.orcid.core.constants.OrcidOauth2Constants; import org.orcid.core.manager.ClientDetailsEntityCacheManager; import org.orcid.core.manager.ProfileEntityCacheManager; import org.orcid.core.oauth.OrcidOAuth2Authentication; import org.orcid.core.oauth.OrcidOauth2TokenDetailService; import org.orcid.core.oauth.OrcidOauth2UserAuthentication; import org.orcid.persistence.jpa.entities.ClientDetailsEntity; import org.orcid.persistence.jpa.entities.OrcidOauth2TokenDetail; import org.orcid.persistence.jpa.entities.ProfileEntity; import org.orcid.pojo.ajaxForm.PojoUtil; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * @author Declan Newman (declan) Date: 15/04/2012 */ @Service("orcidTokenStore") public class OrcidTokenStoreServiceImpl implements TokenStore { @Resource private OrcidOauth2TokenDetailService orcidOauthTokenDetailService; @Resource(name = "profileEntityCacheManager") ProfileEntityCacheManager profileEntityCacheManager; @Resource ClientDetailsEntityCacheManager clientDetailsEntityCacheManager; @Resource private OrcidOAuth2RequestValidator orcidOAuth2RequestValidator; @Resource private AuthenticationKeyGenerator authenticationKeyGenerator; /** * Read the authentication stored under the specified token value. * * @param token * The token value under which the authentication is stored. * @return The authentication, or null if none. */ @Override @Transactional public OAuth2Authentication readAuthentication(String token) { OrcidOauth2TokenDetail detail = orcidOauthTokenDetailService.findNonDisabledByTokenValue(token); return getOAuth2AuthenticationFromDetails(detail); } @Override @Transactional public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { return readAuthentication(token.getValue()); } /** * Store an access token. * * @param token * The token to store. * @param authentication * The authentication associated with the token. */ @Override public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { OrcidOauth2TokenDetail detail = populatePropertiesFromTokenAndAuthentication(token, authentication, null); orcidOauthTokenDetailService.removeConflictsAndCreateNew(detail); } /** * Read an access token from the store. * * @param tokenValue * The token value. * @return The access token to read. */ @Override public OAuth2AccessToken readAccessToken(String tokenValue) { if (tokenValue == null) { return null; } OrcidOauth2TokenDetail detail = orcidOauthTokenDetailService.findNonDisabledByTokenValue(tokenValue); return getOauth2AccessTokenFromDetails(detail); } /** * Remove an access token from the database. * * @param tokenValue * The token to remove from the database. */ @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void removeAccessToken(OAuth2AccessToken accessToken) { orcidOauthTokenDetailService.disableAccessToken(accessToken.getValue()); } /** * Store the specified refresh token in the database. * * @param refreshToken * The refresh token to store. * @param authentication * The authentication associated with the refresh token. */ @Override public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { // The refresh token will be stored during the token creation. We don't // want to create it beforehand } /** * Read a refresh token from the store. * * @param refreshTokenValue * The value of the token to read. * @return The token. */ @Override public ExpiringOAuth2RefreshToken readRefreshToken(String refreshTokenValue) { OrcidOauth2TokenDetail detail = orcidOauthTokenDetailService.findByRefreshTokenValue(refreshTokenValue); if (detail != null && StringUtils.isNotBlank(detail.getTokenValue())) { return new DefaultExpiringOAuth2RefreshToken(detail.getRefreshTokenValue(), detail.getRefreshTokenExpiration()); } return null; } /** * @param refreshTokenValue * a refresh token value * @return the authentication originally used to grant the refresh token */ @Override public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken refreshToken) { OrcidOauth2TokenDetail detail = orcidOauthTokenDetailService.findByRefreshTokenValue(refreshToken.getValue()); return getOAuth2AuthenticationFromDetails(detail); } /** * Remove a refresh token from the database. * * @param refreshTokenValue * The value of the token to remove from the database. */ @Override @Transactional public void removeRefreshToken(OAuth2RefreshToken refreshToken) { orcidOauthTokenDetailService.removeByRefreshTokenValue(refreshToken.getValue()); } /** * Remove an access token using a refresh token. This functionality is * necessary so refresh tokens can't be used to create an unlimited number * of access tokens. * * @param refreshTokenValue * The refresh token. */ @Override @Transactional public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { orcidOauthTokenDetailService.disableAccessTokenByRefreshToken(refreshToken.getValue()); } /** * Retrieve an access token stored against the provided authentication key, * if it exists. * * @param authentication * the authentication key for the access token * @return the access token or null if there was none */ @Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { String authKey = authenticationKeyGenerator.extractKey(authentication); List<OrcidOauth2TokenDetail> details = orcidOauthTokenDetailService.findByAuthenticationKey(authKey); // Since we are now able to have more than one token for the combo // user-scopes, we will need to return the oldest token, the first token // created with these scopes if (details != null && !details.isEmpty()) { OrcidOauth2TokenDetail oldestToken = null; for (OrcidOauth2TokenDetail tokenDetails : details) { if (oldestToken == null) { oldestToken = tokenDetails; } else { if (tokenDetails.getDateCreated().before(oldestToken.getDateCreated())) { oldestToken = tokenDetails; } } } return getOauth2AccessTokenFromDetails(oldestToken); } return null; } /** * @param userName * the user name to search * @return a collection of access tokens */ public Collection<OAuth2AccessToken> findTokensByUserName(String userName) { List<OrcidOauth2TokenDetail> details = orcidOauthTokenDetailService.findByUserName(userName); List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(); if (details != null && !details.isEmpty()) { for (OrcidOauth2TokenDetail detail : details) { accessTokens.add(getOauth2AccessTokenFromDetails(detail)); } } return accessTokens; } /** * @param clientId * the client id * @return a collection of access tokens */ @Override public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) { List<OrcidOauth2TokenDetail> details = orcidOauthTokenDetailService.findByClientId(clientId); List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(); if (details != null && !details.isEmpty()) { for (OrcidOauth2TokenDetail detail : details) { accessTokens.add(getOauth2AccessTokenFromDetails(detail)); } } return accessTokens; } private OAuth2AccessToken getOauth2AccessTokenFromDetails(OrcidOauth2TokenDetail detail) { DefaultOAuth2AccessToken token = null; if (detail != null && StringUtils.isNotBlank(detail.getTokenValue())) { token = new DefaultOAuth2AccessToken(detail.getTokenValue()); token.setExpiration(detail.getTokenExpiration()); token.setScope(OAuth2Utils.parseParameterList(detail.getScope())); token.setTokenType(detail.getTokenType()); String refreshToken = detail.getRefreshTokenValue(); OAuth2RefreshToken rt; if (StringUtils.isNotBlank(refreshToken)) { if (detail.getRefreshTokenExpiration() != null) { rt = new DefaultExpiringOAuth2RefreshToken(detail.getRefreshTokenValue(), detail.getRefreshTokenExpiration()); } else { rt = new DefaultOAuth2RefreshToken(detail.getRefreshTokenValue()); } token.setRefreshToken(rt); } ProfileEntity profile = detail.getProfile(); if (profile != null) { Map<String, Object> additionalInfo = new HashMap<String, Object>(); additionalInfo.put(OrcidOauth2Constants.ORCID, profile.getId()); additionalInfo.put(OrcidOauth2Constants.PERSISTENT, detail.isPersistent()); additionalInfo.put(OrcidOauth2Constants.DATE_CREATED, detail.getDateCreated()); additionalInfo.put(OrcidOauth2Constants.TOKEN_VERSION, detail.getVersion()); token.setAdditionalInformation(additionalInfo); } String clientId = detail.getClientDetailsId(); if(!PojoUtil.isEmpty(clientId)) { Map<String, Object> additionalInfo = new HashMap<String, Object>(); Map<String, Object> additionalInfoInToken = token.getAdditionalInformation(); if(additionalInfoInToken != null && !additionalInfoInToken.isEmpty()) { additionalInfo.putAll(additionalInfoInToken); } // Copy to a new one to avoid unmodifiable additionalInfo.put(OrcidOauth2Constants.CLIENT_ID, clientId); token.setAdditionalInformation(additionalInfo); } } return token; } private OAuth2Authentication getOAuth2AuthenticationFromDetails(OrcidOauth2TokenDetail details) { if (details != null) { ClientDetailsEntity clientDetailsEntity = clientDetailsEntityCacheManager.retrieve(details.getClientDetailsId()); Authentication authentication = null; AuthorizationRequest request = null; if (clientDetailsEntity != null) { //Check member is not locked orcidOAuth2RequestValidator.validateClientIsEnabled(clientDetailsEntity); Set<String> scopes = OAuth2Utils.parseParameterList(details.getScope()); request = new AuthorizationRequest(clientDetailsEntity.getClientId(), scopes); request.setAuthorities(clientDetailsEntity.getAuthorities()); Set<String> resourceIds = new HashSet<>(); resourceIds.add(details.getResourceId()); request.setResourceIds(resourceIds); request.setApproved(details.isApproved()); ProfileEntity profile = details.getProfile(); if (profile != null) { authentication = new OrcidOauth2UserAuthentication(profile, details.isApproved()); } } return new OrcidOAuth2Authentication(request, authentication, details.getTokenValue()); } throw new InvalidTokenException("Token not found"); } private OrcidOauth2TokenDetail populatePropertiesFromTokenAndAuthentication(OAuth2AccessToken token, OAuth2Authentication authentication, OrcidOauth2TokenDetail detail) { OAuth2Request authorizationRequest = authentication.getOAuth2Request(); if (detail == null) { detail = new OrcidOauth2TokenDetail(); } String clientId = authorizationRequest.getClientId(); String authKey = authenticationKeyGenerator.extractKey(authentication); detail.setAuthenticationKey(authKey); detail.setClientDetailsId(clientId); OAuth2RefreshToken refreshToken = token.getRefreshToken(); if (refreshToken != null && StringUtils.isNotBlank(refreshToken.getValue())) { if (refreshToken instanceof ExpiringOAuth2RefreshToken) { // Override the refresh token expiration from the client // details, and make it the same as the token itself detail.setRefreshTokenExpiration(token.getExpiration()); } detail.setRefreshTokenValue(refreshToken.getValue()); } if (!authentication.isClientOnly()) { Object principal = authentication.getPrincipal(); if (principal instanceof ProfileEntity) { ProfileEntity profileEntity = (ProfileEntity) authentication.getPrincipal(); profileEntity = profileEntityCacheManager.retrieve(profileEntity.getId()); detail.setProfile(profileEntity); } } detail.setTokenValue(token.getValue()); detail.setTokenType(token.getTokenType()); detail.setTokenExpiration(token.getExpiration()); detail.setApproved(authorizationRequest.isApproved()); detail.setRedirectUri(authorizationRequest.getRedirectUri()); Set<String> resourceIds = authorizationRequest.getResourceIds(); if(resourceIds == null || resourceIds.isEmpty()) { ClientDetailsEntity clientDetails = clientDetailsEntityCacheManager.retrieve(clientId); resourceIds = clientDetails.getResourceIds(); } detail.setResourceId(OAuth2Utils.formatParameterList(resourceIds)); detail.setResponseType(OAuth2Utils.formatParameterList(authorizationRequest.getResponseTypes())); detail.setScope(OAuth2Utils.formatParameterList(authorizationRequest.getScope())); Map<String, Object> additionalInfo = token.getAdditionalInformation(); if (additionalInfo != null) { if (additionalInfo.containsKey(OrcidOauth2Constants.TOKEN_VERSION)) { String sVersion = String.valueOf(additionalInfo.get(OrcidOauth2Constants.TOKEN_VERSION)); detail.setVersion(Long.valueOf(sVersion)); } else { // TODO: As of Jan 2015 all tokens will be new tokens, so, we // will have to remove the token version code and // treat all tokens as new tokens detail.setVersion(Long.valueOf(OrcidOauth2Constants.PERSISTENT_TOKEN)); } if (additionalInfo.containsKey(OrcidOauth2Constants.PERSISTENT)) { boolean isPersistentKey = (Boolean) additionalInfo.get(OrcidOauth2Constants.PERSISTENT); detail.setPersistent(isPersistentKey); } else { detail.setPersistent(false); } } else { detail.setPersistent(false); } return detail; } @Override public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) { List<OrcidOauth2TokenDetail> details = orcidOauthTokenDetailService.findByClientIdAndUserName(clientId, userName); List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(); if (details != null && !details.isEmpty()) { for (OrcidOauth2TokenDetail detail : details) { if(detail.getTokenDisabled() == null || !detail.getTokenDisabled()) { accessTokens.add(getOauth2AccessTokenFromDetails(detail)); } } } return accessTokens; } }