/** * ============================================================================= * * 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Resource; import javax.persistence.NoResultException; import org.apache.commons.lang.StringUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.orcid.core.constants.OrcidOauth2Constants; import org.orcid.persistence.jpa.entities.OrcidOauth2TokenDetail; import org.orcid.persistence.jpa.entities.ProfileEntity; import org.orcid.pojo.ajaxForm.PojoUtil; import org.orcid.test.DBUnitTest; import org.orcid.test.OrcidJUnit4ClassRunner; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.test.context.ContextConfiguration; @RunWith(OrcidJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:orcid-core-context.xml", "classpath:orcid-oauth2-common-config.xml" }) public class OrcidRefreshTokenTokenGranterTest extends DBUnitTest { private static final String CLIENT_ID_1 = "APP-5555555555555555"; private static final String CLIENT_ID_2 = "APP-5555555555555556"; private static final String USER_ORCID = "0000-0000-0000-0001"; @Resource private OrcidOauth2TokenDetailService orcidOauth2TokenDetailService; @Resource private OrcidRefreshTokenTokenGranter refreshTokenTokenGranter; @BeforeClass public static void initDBUnitData() throws Exception { initDBUnitData(Arrays.asList("/data/SecurityQuestionEntityData.xml", "/data/SubjectEntityData.xml", "/data/SourceClientDetailsEntityData.xml", "/data/ProfileEntityData.xml")); } @AfterClass public static void removeDBUnitData() throws Exception { removeDBUnitData(Arrays.asList("/data/ProfileEntityData.xml", "/data/SourceClientDetailsEntityData.xml", "/data/SubjectEntityData.xml", "/data/SecurityQuestionEntityData.xml")); } private OrcidOauth2TokenDetail createToken(String clientId, String userOrcid, String tokenValue, String refreshTokenValue, Date expirationDate, String scopes) { OrcidOauth2TokenDetail token = new OrcidOauth2TokenDetail(); token.setApproved(true); token.setClientDetailsId(clientId); token.setDateCreated(new Date()); token.setLastModified(new Date()); token.setProfile(new ProfileEntity(userOrcid)); token.setScope(scopes); token.setTokenDisabled(false); token.setTokenExpiration(expirationDate); token.setTokenType("bearer"); token.setTokenValue(tokenValue); token.setRefreshTokenValue(refreshTokenValue); orcidOauth2TokenDetailService.saveOrUpdate(token); return token; } private OAuth2AccessToken generateRefreshToken(OrcidOauth2TokenDetail tokenDetails, String customClientId, Boolean revokeOld, Long expiresIn, String... scopesParam) { Set<String> scopes = null; if (scopesParam != null) { scopes = new HashSet<String>(Arrays.asList(scopesParam)); } Map<String, String> authorizationParameters = new HashMap<String, String>(); String scopesString = scopes == null ? null : StringUtils.join(scopes, ' '); String clientId = PojoUtil.isEmpty(customClientId) ? tokenDetails.getClientDetailsId() : customClientId; String refreshTokenValue = tokenDetails.getRefreshTokenValue(); authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId); authorizationParameters.put(OrcidOauth2Constants.IS_PERSISTENT, "true"); authorizationParameters.put(OrcidOauth2Constants.AUTHORIZATION, tokenDetails.getTokenValue()); authorizationParameters.put(OrcidOauth2Constants.REFRESH_TOKEN, refreshTokenValue); authorizationParameters.put(OAuth2Utils.REDIRECT_URI, tokenDetails.getRedirectUri()); if (!PojoUtil.isEmpty(scopesString)) { authorizationParameters.put(OAuth2Utils.SCOPE, scopesString); } if (revokeOld != null) { authorizationParameters.put(OrcidOauth2Constants.REVOKE_OLD, String.valueOf(revokeOld)); } if (expiresIn != null) { authorizationParameters.put(OrcidOauth2Constants.EXPIRES_IN, String.valueOf(expiresIn)); } TokenRequest tokenRequest = new TokenRequest(authorizationParameters, clientId, scopes, OrcidOauth2Constants.REFRESH_TOKEN); return refreshTokenTokenGranter.grant(OrcidOauth2Constants.REFRESH_TOKEN, tokenRequest); } @Test public void createRefreshTokenTest() { // Create token, create refresh, parent should be disabled, scopes // should be equal long time = System.currentTimeMillis(); String scope = "/activities/update"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = null; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, scope); OAuth2AccessToken refresh = generateRefreshToken(parent, null, revokeOld, expireIn, scope); assertNotNull(refresh); OrcidOauth2TokenDetail parentToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(parent.getTokenValue()); assertNotNull(parentToken); assertEquals(tokenValue, parentToken.getTokenValue()); assertTrue(parentToken.getTokenDisabled()); assertEquals(scope, parentToken.getScope()); assertNotNull(parentToken.getTokenExpiration()); OrcidOauth2TokenDetail refreshToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(refresh.getValue()); assertNotNull(refreshToken); assertNotNull(refreshToken.getTokenValue()); assertNotNull(refreshToken.getRefreshTokenValue()); assertFalse(refreshToken.getTokenDisabled()); assertEquals(scope, refreshToken.getScope()); assertNotNull(refreshToken.getTokenExpiration()); assertEquals(parentToken.getTokenExpiration().getTime(), refreshToken.getTokenExpiration().getTime()); } @Test public void createRefreshTokenWithNarrowerScopesTest() { // Create token, create refresh with narrower scopes, parent should be // disabled, scopes should be narrower long time = System.currentTimeMillis(); String parentScope = "/activities/update"; String refreshScope = "/orcid-works/create"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = true; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); OAuth2AccessToken refresh = generateRefreshToken(parent, null, revokeOld, expireIn, refreshScope); assertNotNull(refresh); OrcidOauth2TokenDetail parentToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(parent.getTokenValue()); assertNotNull(parentToken); assertEquals(tokenValue, parentToken.getTokenValue()); assertTrue(parentToken.getTokenDisabled()); assertEquals(parentScope, parentToken.getScope()); assertNotNull(parentToken.getTokenExpiration()); OrcidOauth2TokenDetail refreshToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(refresh.getValue()); assertNotNull(refreshToken); assertNotNull(refreshToken.getTokenValue()); assertNotNull(refreshToken.getRefreshTokenValue()); assertFalse(refreshToken.getTokenDisabled()); assertEquals(refreshScope, refreshToken.getScope()); assertNotNull(refreshToken.getTokenExpiration()); assertEquals(parentToken.getTokenExpiration().getTime(), refreshToken.getTokenExpiration().getTime()); } @Test public void createRefreshTokenWithoutRevokeParent() { // Create token, create refresh without disabling parent token, parent // should be enabled, refresh should be enabled long time = System.currentTimeMillis(); String parentScope = "/activities/update /read-limited"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = false; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); OAuth2AccessToken refresh = generateRefreshToken(parent, null, revokeOld, expireIn); assertNotNull(refresh); OrcidOauth2TokenDetail parentToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(parent.getTokenValue()); assertNotNull(parentToken); assertEquals(tokenValue, parentToken.getTokenValue()); assertFalse(parentToken.getTokenDisabled()); assertNotNull(parentToken.getTokenExpiration()); OrcidOauth2TokenDetail refreshToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(refresh.getValue()); assertNotNull(refreshToken); assertNotNull(refreshToken.getTokenValue()); assertNotNull(refreshToken.getRefreshTokenValue()); assertFalse(refreshToken.getTokenDisabled()); assertNotNull(refreshToken.getTokenExpiration()); assertEquals(parentToken.getTokenExpiration().getTime(), refreshToken.getTokenExpiration().getTime()); assertEquals(parentToken.getScope(), refreshToken.getScope()); Set<String> tokenScopes = OAuth2Utils.parseParameterList(parentToken.getScope()); Set<String> originalScopes = OAuth2Utils.parseParameterList(parentScope); assertEquals(originalScopes, tokenScopes); } @Test public void createRefreshTokenWithoutRevokeParentAndWithNarrowerScopes() { // Create token, create refresh with narrower scopes and without // disabling parent token, parent should work, refresh should have // narrower scopes long time = System.currentTimeMillis(); String parentScope = "/person/read-limited"; String refreshScope = "/orcid-bio/read-limited"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = false; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); OAuth2AccessToken refresh = generateRefreshToken(parent, null, revokeOld, expireIn, refreshScope); assertNotNull(refresh); OrcidOauth2TokenDetail parentToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(parent.getTokenValue()); assertNotNull(parentToken); assertEquals(tokenValue, parentToken.getTokenValue()); assertFalse(parentToken.getTokenDisabled()); assertEquals(parentScope, parentToken.getScope()); assertNotNull(parentToken.getTokenExpiration()); OrcidOauth2TokenDetail refreshToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(refresh.getValue()); assertNotNull(refreshToken); assertNotNull(refreshToken.getTokenValue()); assertNotNull(refreshToken.getRefreshTokenValue()); assertFalse(refreshToken.getTokenDisabled()); assertEquals(refreshScope, refreshToken.getScope()); assertNotNull(refreshToken.getTokenExpiration()); assertEquals(parentToken.getTokenExpiration().getTime(), refreshToken.getTokenExpiration().getTime()); } @Test public void createRefreshTokenWithExpirationOf10Secs() { // Create token, dont revoke parent and set expiration to 10 secs long time = System.currentTimeMillis(); String parentScope = "/person/read-limited"; String refreshScope = "/orcid-bio/read-limited"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = false; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = 5L; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); OAuth2AccessToken refresh = generateRefreshToken(parent, null, revokeOld, expireIn, refreshScope); assertNotNull(refresh); OrcidOauth2TokenDetail parentToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(parent.getTokenValue()); assertNotNull(parentToken); assertEquals(tokenValue, parentToken.getTokenValue()); assertFalse(parentToken.getTokenDisabled()); assertEquals(parentScope, parentToken.getScope()); assertNotNull(parentToken.getTokenExpiration()); OrcidOauth2TokenDetail refreshToken = orcidOauth2TokenDetailService.findIgnoringDisabledByTokenValue(refresh.getValue()); assertNotNull(refreshToken); assertNotNull(refreshToken.getTokenValue()); assertNotNull(refreshToken.getRefreshTokenValue()); assertFalse(refreshToken.getTokenDisabled()); assertEquals(refreshScope, refreshToken.getScope()); assertNotNull(refreshToken.getTokenExpiration()); assertTrue(parentToken.getTokenExpiration().getTime() > refreshToken.getTokenExpiration().getTime()); // Assert that current time plus 6 secs is greather than refresh token // expiration assertTrue((time + 6000) > refreshToken.getTokenExpiration().getTime()); } @Test public void tryToCreateRefreshTokenWithInvalidScopesTest() { // Create token, try to create refresh token with invalid scopes, fail long time = System.currentTimeMillis(); String parentScope = "/person/update"; String refreshScope = "/orcid-works/read-limited"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = true; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); try { generateRefreshToken(parent, null, revokeOld, expireIn, refreshScope); fail(); } catch(InvalidScopeException e) { assertTrue(e.getMessage().contains("is not allowed for the parent token")); } catch(Exception e) { fail(); } } @Test public void tryToCreateRefreshTokenWithThatExpireAfterParentTokenTest() { // Create token, try to create refresh token that expires after parent // token, fail long time = System.currentTimeMillis(); String parentScope = "/person/update"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = true; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = time + (15000); OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); try { generateRefreshToken(parent, null, revokeOld, expireIn, parentScope); fail(); } catch(IllegalArgumentException e) { assertTrue(e.getMessage().contains("Token expiration can't be after")); } catch(Exception e) { fail(); } } @Test public void tryToCreateRefreshTokenWithInvalidClientTest() { // Create token for client # 1, try to create a refresh token using // client # 2, fail long time = System.currentTimeMillis(); String parentScope = "/person/update"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = true; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); try { generateRefreshToken(parent, CLIENT_ID_2, revokeOld, expireIn, parentScope); fail(); } catch(IllegalArgumentException e) { assertTrue(e.getMessage().contains("This token doesnt belong to the given client")); } catch(Exception e) { fail(); } } @Test public void tryToRefreshAnExpiredTokenTest() { long time = System.currentTimeMillis(); String parentScope = "/person/update"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = true; Date parentTokenExpiration = new Date(time - 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); try { generateRefreshToken(parent, null, revokeOld, expireIn, parentScope); fail(); } catch(InvalidTokenException e) { assertTrue(e.getMessage().contains("Access token expired:")); } catch(Exception e) { fail(); } } @Test public void tryToCreateRefreshTokenWithInvalidRefreshTokenTest() { // Create token, try to create refresh token with invalid refresh value, // fail long time = System.currentTimeMillis(); String parentScope = "/person/update"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = true; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); try { //Change the value we are going to use for the refresh token parent.setRefreshTokenValue("invalid-value"); generateRefreshToken(parent, null, revokeOld, expireIn, parentScope); fail(); } catch(InvalidTokenException e) { assertTrue(e.getMessage().contains("Token and refresh token does not match")); } catch(Exception e) { fail(); } } @Test public void tryToCreateRefreshTokenWithInvalidParentTokenValueTest() { // Create token, try to create refresh token with invalid parent token // value, fail long time = System.currentTimeMillis(); String parentScope = "/person/update"; String tokenValue = "parent-token-" + time; String refreshTokenValue = "refresh-token-" + time; Boolean revokeOld = true; Date parentTokenExpiration = new Date(time + 10000); Long expireIn = null; OrcidOauth2TokenDetail parent = createToken(CLIENT_ID_1, USER_ORCID, tokenValue, refreshTokenValue, parentTokenExpiration, parentScope); try { //Change the value we are going to use for the refresh token parent.setTokenValue("invalid-value"); generateRefreshToken(parent, null, revokeOld, expireIn, parentScope); fail(); } catch(NoResultException e) { } catch(Exception e) { fail(); } } }