/**
* =============================================================================
*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.annotation.Resource;
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.core.manager.ClientDetailsManager;
import org.orcid.core.oauth.OrcidOauth2ClientAuthentication;
import org.orcid.core.oauth.OrcidOauth2TokenDetailService;
import org.orcid.core.oauth.OrcidRandomValueTokenServices;
import org.orcid.persistence.jpa.entities.ClientDetailsEntity;
import org.orcid.persistence.jpa.entities.OrcidOauth2TokenDetail;
import org.orcid.persistence.jpa.entities.ProfileEntity;
import org.orcid.test.DBUnitTest;
import org.orcid.test.OrcidJUnit4ClassRunner;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.test.context.ContextConfiguration;
/**
*
* @author Will Simpson
*/
@RunWith(OrcidJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:orcid-core-context.xml" })
public class OrcidRandomValueTokenServicesTest extends DBUnitTest {
@Resource
private OrcidRandomValueTokenServices tokenServices;
@Resource
private OrcidOauth2TokenDetailService orcidOauthTokenDetailService;
@Resource
private ClientDetailsManager clientDetailsManager;
@BeforeClass
public static void initDBUnitData() throws Exception {
initDBUnitData(Arrays.asList("/data/SecurityQuestionEntityData.xml", "/data/SubjectEntityData.xml", "/data/SourceClientDetailsEntityData.xml",
"/data/ProfileEntityData.xml", "/data/ClientDetailsEntityData.xml", "/data/OrcidOauth2AuthorisationDetailsData.xml"));
}
@AfterClass
public static void removeDBUnitData() throws Exception {
removeDBUnitData(Arrays.asList("/data/OrcidOauth2AuthorisationDetailsData.xml", "/data/ClientDetailsEntityData.xml", "/data/ProfileEntityData.xml",
"/data/SubjectEntityData.xml", "/data/SecurityQuestionEntityData.xml"));
}
@Test
public void testCreateReadLimitedAccessToken() {
Date earliestExpiry = oneHoursTime();
Map<String, String> authorizationParameters = new HashMap<>();
String clientId = "4444-4444-4444-4441";
authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId);
authorizationParameters.put(OAuth2Utils.SCOPE, "/orcid-profile/read-limited");
OAuth2Request request = new OAuth2Request(Collections.<String, String> emptyMap(), clientId, Collections.<GrantedAuthority> emptyList(), true, new HashSet<String>(Arrays.asList("/orcid-profile/read-limited")), Collections.<String> emptySet(), null, Collections.<String> emptySet(), Collections.<String, Serializable> emptyMap());
ClientDetailsEntity clientDetails = clientDetailsManager.findByClientId(clientId);
Authentication userAuthentication = new OrcidOauth2ClientAuthentication(clientDetails);
OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication);
OAuth2AccessToken oauth2AccessToken = tokenServices.createAccessToken(authentication);
Date latestExpiry = oneHoursTime();
assertNotNull(oauth2AccessToken);
assertFalse(oauth2AccessToken.getExpiration().before(earliestExpiry));
assertFalse(oauth2AccessToken.getExpiration().after(latestExpiry));
}
@Test
public void testCreateAddWorkAccessToken() {
Date earliestExpiry = oneHoursTime();
Map<String, String> authorizationParameters = new HashMap<>();
String clientId = "4444-4444-4444-4441";
authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId);
authorizationParameters.put(OAuth2Utils.SCOPE, "/orcid-works/create");
OAuth2Request request = new OAuth2Request(Collections.<String, String> emptyMap(), clientId, Collections.<GrantedAuthority> emptyList(), true, new HashSet<String>(Arrays.asList("/orcid-profile/read-limited")), Collections.<String> emptySet(), null, Collections.<String> emptySet(), Collections.<String, Serializable> emptyMap());
ClientDetailsEntity clientDetails = clientDetailsManager.findByClientId(clientId);
Authentication userAuthentication = new OrcidOauth2ClientAuthentication(clientDetails);
OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication);
OAuth2AccessToken oauth2AccessToken = tokenServices.createAccessToken(authentication);
Date latestExpiry = oneHoursTime();
assertNotNull(oauth2AccessToken);
assertFalse(oauth2AccessToken.getExpiration().before(earliestExpiry));
assertFalse(oauth2AccessToken.getExpiration().after(latestExpiry));
}
@Test
public void testReissuedAccessTokenHasUpdatedExpiration() throws InterruptedException {
Date earliestExpiry = oneHoursTime();
Map<String, String> authorizationParameters = new HashMap<>();
String clientId = "4444-4444-4444-4441";
authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId);
authorizationParameters.put(OAuth2Utils.SCOPE, "/orcid-works/create");
OAuth2Request request = new OAuth2Request(Collections.<String, String> emptyMap(), clientId, Collections.<GrantedAuthority> emptyList(), true, new HashSet<String>(Arrays.asList("/orcid-profile/read-limited")), Collections.<String> emptySet(), null, Collections.<String> emptySet(), Collections.<String, Serializable> emptyMap());
ClientDetailsEntity clientDetails = clientDetailsManager.findByClientId(clientId);
Authentication userAuthentication = new OrcidOauth2ClientAuthentication(clientDetails);
OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication);
OAuth2AccessToken oauth2AccessToken = tokenServices.createAccessToken(authentication);
Date latestExpiry = oneHoursTime();
assertNotNull(oauth2AccessToken);
assertFalse(oauth2AccessToken.getExpiration().before(earliestExpiry));
assertFalse(oauth2AccessToken.getExpiration().after(latestExpiry));
Thread.sleep(1000);
earliestExpiry = oneHoursTime();
OAuth2AccessToken reissuedOauth2AccessToken = tokenServices.createAccessToken(authentication);
latestExpiry = oneHoursTime();
assertNotNull(reissuedOauth2AccessToken);
assertFalse(reissuedOauth2AccessToken.getExpiration().before(earliestExpiry));
assertFalse(reissuedOauth2AccessToken.getExpiration().after(latestExpiry));
}
private Date twentyYearsTime() {
Calendar earliestExpiry = new GregorianCalendar();
// This is roughly 2 years in seconds - used in the implementation, but
// not sure how was calculated now.
earliestExpiry.add(Calendar.SECOND, 631138519);
return earliestExpiry.getTime();
}
private Date oneHoursTime() {
Calendar earliestExpiry = new GregorianCalendar();
earliestExpiry.add(Calendar.HOUR, 1);
return earliestExpiry.getTime();
}
/**
* Check that the token created with a non persistent code will expire within an hour
* */
@Test
public void tokenExpireInAnHourTest() throws InterruptedException {
Map<String, String> authorizationParameters = new HashMap<>();
String clientId = "4444-4444-4444-4441";
authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId);
authorizationParameters.put(OAuth2Utils.SCOPE, "/orcid-works/create");
authorizationParameters.put("code", "code2");
OAuth2Request request = new OAuth2Request(Collections.<String, String> emptyMap(), clientId, Collections.<GrantedAuthority> emptyList(), true, new HashSet<String>(Arrays.asList("/orcid-profile/read-limited")), Collections.<String> emptySet(), null, Collections.<String> emptySet(), Collections.<String, Serializable> emptyMap());
ClientDetailsEntity clientDetails = clientDetailsManager.findByClientId(clientId);
Authentication userAuthentication = new OrcidOauth2ClientAuthentication(clientDetails);
OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication);
OAuth2AccessToken oauth2AccessToken = tokenServices.createAccessToken(authentication);
Date tokenExpiration = oauth2AccessToken.getExpiration();
Thread.sleep(2000);
//The token expires in less than one hour
assertFalse(tokenExpiration.after(oneHoursTime()));
}
/**
* Check that the token created with a persistent code will expire within 20 years
* */
@Test
public void tokenExpireIn20YearsTest() throws InterruptedException {
Date in20years = twentyYearsTime();
Thread.sleep(2000);
Map<String, String> requestParameters = new HashMap<>();
String clientId = "4444-4444-4444-4441";
requestParameters.put(OAuth2Utils.CLIENT_ID, clientId);
requestParameters.put(OAuth2Utils.SCOPE, "/orcid-works/create");
requestParameters.put("code", "code1");
requestParameters.put(OrcidOauth2Constants.IS_PERSISTENT, "true");
OAuth2Request request = new OAuth2Request(requestParameters, clientId, Collections.<GrantedAuthority> emptyList(), true, new HashSet<String>(Arrays.asList("/orcid-profile/read-limited")), Collections.<String> emptySet(), null, Collections.<String> emptySet(), Collections.<String, Serializable> emptyMap());
ClientDetailsEntity clientDetails = clientDetailsManager.findByClientId(clientId);
Authentication userAuthentication = new OrcidOauth2ClientAuthentication(clientDetails);
OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication);
OAuth2AccessToken oauth2AccessToken = tokenServices.createAccessToken(authentication);
Date tokenExpiration = oauth2AccessToken.getExpiration();
//The token expires in 20 years
assertFalse(in20years.after(tokenExpiration));
in20years = twentyYearsTime();
//Confirm the token expires in 20 years
assertFalse(tokenExpiration.after(in20years));
}
@Test
public void tokenExpiredDoesntWorkTest() {
OrcidOauth2TokenDetail expiredToken = new OrcidOauth2TokenDetail();
expiredToken.setApproved(true);
expiredToken.setAuthenticationKey("authentication-key");
expiredToken.setClientDetailsId("4444-4444-4444-4441");
expiredToken.setProfile(new ProfileEntity("4444-4444-4444-4442"));
expiredToken.setResourceId("orcid");
expiredToken.setScope("/read-limited");
expiredToken.setTokenExpiration(new Date(System.currentTimeMillis() - 1000));
expiredToken.setTokenValue("token-value");
orcidOauthTokenDetailService.removeConflictsAndCreateNew(expiredToken);
// The first time we try to use it, we get a InvalidTokenException with message Access token expired: token-value
try {
tokenServices.loadAuthentication("token-value");
fail();
} catch(InvalidTokenException e) {
assertEquals("Access token expired: token-value", e.getMessage());
}
// Second time we try to use it, we get a InvalidTokenException with message Invalid access token: token-value
try {
tokenServices.loadAuthentication("token-value");
fail();
} catch(InvalidTokenException e) {
assertEquals("Invalid access token: token-value", e.getMessage());
}
}
/**
* Load authentication using a persistent token
* */
@Test
public void loadAuthenticationWithPersistentTokenTest() {
try {
OAuth2Authentication result = tokenServices.loadAuthentication("persistent-token-2");
assertNotNull(result);
} catch(Exception e) {
fail();
}
}
}