Skip to content

Commit ee483b2

Browse files
authored
Merge pull request #6837 from ORCID/RedisClient
Add Redis cache to store the tokens
2 parents 7f359d0 + 9855548 commit ee483b2

File tree

22 files changed

+452
-63
lines changed

22 files changed

+452
-63
lines changed

orcid-api-common/src/main/java/org/orcid/api/common/oauth/OrcidClientCredentialEndPointDelegator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@
99
public interface OrcidClientCredentialEndPointDelegator {
1010

1111
Response obtainOauth2Token(String authorization, MultivaluedMap<String, String> formParams);
12+
13+
void setTokenCacheEnabled(boolean enabled);
14+
15+
boolean isTokenCacheEnabled();
1216
}

orcid-api-common/src/main/java/org/orcid/api/common/oauth/OrcidClientCredentialEndPointDelegatorImpl.java

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
import org.orcid.core.constants.OrcidOauth2Constants;
1717
import org.orcid.core.exception.OrcidInvalidScopeException;
1818
import org.orcid.core.locale.LocaleManager;
19+
import org.orcid.core.manager.EncryptionManager;
1920
import org.orcid.core.oauth.OAuthError;
2021
import org.orcid.core.oauth.OAuthErrorUtils;
22+
import org.orcid.core.utils.JsonUtils;
23+
import org.orcid.core.utils.cache.redis.RedisClient;
2124
import org.orcid.jaxb.model.message.ScopePathType;
2225
import org.orcid.persistence.dao.OrcidOauth2AuthoriziationCodeDetailDao;
2326
import org.orcid.persistence.dao.ProfileLastModifiedDao;
@@ -26,6 +29,7 @@
2629
import org.orcid.pojo.ajaxForm.PojoUtil;
2730
import org.slf4j.Logger;
2831
import org.slf4j.LoggerFactory;
32+
import org.springframework.beans.factory.annotation.Value;
2933
import org.springframework.security.authentication.InsufficientAuthenticationException;
3034
import org.springframework.security.core.Authentication;
3135
import org.springframework.security.core.context.SecurityContextHolder;
@@ -57,6 +61,15 @@ public class OrcidClientCredentialEndPointDelegatorImpl extends AbstractEndpoint
5761
@Resource(name="profileLastModifiedDao")
5862
private ProfileLastModifiedDao profileLastModifiedDao;
5963

64+
@Resource
65+
private RedisClient redisClient;
66+
67+
@Resource
68+
private EncryptionManager encryptionManager;
69+
70+
@Value("${org.orcid.core.utils.cache.redis.enabled:true}")
71+
private boolean isTokenCacheEnabled;
72+
6073
@Transactional
6174
public Response obtainOauth2Token(String authorization, MultivaluedMap<String, String> formParams) {
6275
String code = formParams.getFirst("code");
@@ -152,12 +165,39 @@ public Response obtainOauth2Token(String authorization, MultivaluedMap<String, S
152165
}
153166
}
154167
}
168+
169+
removeMetadataFromToken(token);
170+
setToCache(client.getName(), token);
155171
return getResponse(token);
156172
} catch (InvalidGrantException e){ //this needs to be caught here so the transaction doesn't roll back
157173
OAuthError error = OAuthErrorUtils.getOAuthError(e);
158174
return Response.status(error.getResponseStatus().getStatusCode()).entity(error).build();
159175
}
160176
}
177+
178+
/**
179+
* Set the access token in the cache
180+
* */
181+
protected void setToCache(String clientId, OAuth2AccessToken accessToken) {
182+
if(isTokenCacheEnabled) {
183+
try {
184+
String tokenValue = accessToken.getValue();
185+
Map<String, String> tokenData = new HashMap<String, String>();
186+
tokenData.put(OrcidOauth2Constants.ACCESS_TOKEN, tokenValue);
187+
tokenData.put(OrcidOauth2Constants.TOKEN_EXPIRATION_TIME, String.valueOf(accessToken.getExpiration().getTime()));
188+
StringBuilder sb = new StringBuilder();
189+
accessToken.getScope().forEach(x -> {sb.append(x); sb.append(' ');});
190+
tokenData.put(OrcidOauth2Constants.SCOPE_PARAM, sb.toString());
191+
tokenData.put(OrcidOauth2Constants.ORCID, (String) accessToken.getAdditionalInformation().get(OrcidOauth2Constants.ORCID));
192+
tokenData.put(OrcidOauth2Constants.CLIENT_ID, clientId);
193+
tokenData.put(OrcidOauth2Constants.RESOURCE_IDS, OrcidOauth2Constants.ORCID);
194+
tokenData.put(OrcidOauth2Constants.APPROVED, Boolean.TRUE.toString());
195+
redisClient.set(tokenValue, JsonUtils.convertToJsonString(tokenData));
196+
} catch(Exception e) {
197+
LOGGER.info("Unable to set token in Redis cache", e);
198+
}
199+
}
200+
}
161201

162202
/**
163203
* Calculates the real value of the "expires_in" param based on the current time
@@ -261,7 +301,7 @@ protected OAuth2AccessToken generateToken(Authentication client, Set<String> sco
261301
return token;
262302
}
263303

264-
protected Response getResponse(OAuth2AccessToken accessToken) {
304+
protected void removeMetadataFromToken(OAuth2AccessToken accessToken) {
265305
if(accessToken != null && accessToken.getAdditionalInformation() != null) {
266306
if(accessToken.getAdditionalInformation().containsKey(OrcidOauth2Constants.TOKEN_VERSION))
267307
accessToken.getAdditionalInformation().remove(OrcidOauth2Constants.TOKEN_VERSION);
@@ -271,8 +311,10 @@ protected Response getResponse(OAuth2AccessToken accessToken) {
271311
accessToken.getAdditionalInformation().remove(OrcidOauth2Constants.DATE_CREATED);
272312
if(accessToken.getAdditionalInformation().containsKey(OrcidOauth2Constants.TOKEN_ID))
273313
accessToken.getAdditionalInformation().remove(OrcidOauth2Constants.TOKEN_ID);
274-
}
275-
314+
}
315+
}
316+
317+
protected Response getResponse(OAuth2AccessToken accessToken) {
276318
return Response.ok((DefaultOAuth2AccessToken)accessToken).header("Cache-Control", "no-store").header("Pragma", "no-cache").build();
277319
}
278320

@@ -286,4 +328,12 @@ protected Authentication getClientAuthentication() {
286328

287329
}
288330

331+
public boolean isTokenCacheEnabled() {
332+
return isTokenCacheEnabled;
333+
}
334+
335+
public void setTokenCacheEnabled(boolean isTokenCacheEnabled) {
336+
this.isTokenCacheEnabled = isTokenCacheEnabled;
337+
}
338+
289339
}

orcid-api-common/src/main/resources/orcid-oauth2-api-common-config.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,13 @@
123123
<ref bean="orcidImplicitTokenGranter" />
124124
</list>
125125
</constructor-arg>
126-
</bean>
126+
</bean>
127+
128+
<!-- Redis cache -->
129+
<bean id="redisClient" class="org.orcid.core.utils.cache.redis.RedisClient">
130+
<constructor-arg index="0" value="${org.orcid.core.utils.cache.redis.host}"/>
131+
<constructor-arg index="1" value="${org.orcid.core.utils.cache.redis.port}" />
132+
<constructor-arg index="2" value="${org.orcid.core.utils.cache.redis.password}" />
133+
<constructor-arg index="3" value="${org.orcid.core.utils.cache.redis.expiration_in_secs:600}" />
134+
</bean>
127135
</beans>

orcid-api-common/src/test/java/org/orcid/api/common/oauth/OrcidClientCredentialEndPointDelegatorTest.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
import static org.junit.Assert.assertNotNull;
55
import static org.junit.Assert.assertTrue;
66
import static org.junit.Assert.fail;
7+
import static org.mockito.Mockito.never;
78
import static org.mockito.Mockito.times;
89
import static org.mockito.Mockito.verify;
910

1011
import java.util.Arrays;
12+
import java.util.HashMap;
1113
import java.util.HashSet;
14+
import java.util.Map;
1215

1316
import javax.annotation.Resource;
1417
import javax.ws.rs.core.MultivaluedMap;
@@ -23,8 +26,11 @@
2326
import org.mockito.Mock;
2427
import org.mockito.Mockito;
2528
import org.mockito.MockitoAnnotations;
29+
import org.orcid.core.constants.OrcidOauth2Constants;
2630
import org.orcid.core.oauth.openid.OpenIDConnectKeyService;
31+
import org.orcid.core.utils.JsonUtils;
2732
import org.orcid.core.utils.SecurityContextTestUtils;
33+
import org.orcid.core.utils.cache.redis.RedisClient;
2834
import org.orcid.jaxb.model.message.ScopePathType;
2935
import org.orcid.persistence.dao.OrcidOauth2AuthoriziationCodeDetailDao;
3036
import org.orcid.persistence.dao.ProfileLastModifiedDao;
@@ -64,6 +70,9 @@ public class OrcidClientCredentialEndPointDelegatorTest extends DBUnitTest {
6470
@Mock
6571
private ProfileLastModifiedDao profileLastModifiedDaoMock;
6672

73+
@Mock
74+
private RedisClient redisClientMock;
75+
6776
@Resource
6877
private ProfileLastModifiedDao profileLastModifiedDao;
6978

@@ -77,6 +86,9 @@ public static void initDBUnitData() throws Exception {
7786
public void before() {
7887
MockitoAnnotations.initMocks(this);
7988
TargetProxyHelper.injectIntoProxy(orcidClientCredentialEndPointDelegator, "profileLastModifiedDao", profileLastModifiedDaoMock);
89+
TargetProxyHelper.injectIntoProxy(orcidClientCredentialEndPointDelegator, "redisClient", redisClientMock);
90+
// Keep the cache disabled
91+
orcidClientCredentialEndPointDelegator.setTokenCacheEnabled(false);
8092
}
8193

8294
@AfterClass
@@ -268,4 +280,66 @@ public void generateRefreshTokenThatExpireAfterParentTokenTest() {
268280
assertTrue(token.getExpiration().getTime() > refreshToken.getExpiration().getTime());
269281
}
270282

283+
@Test
284+
public void obtainOauth2TokenSetCacheTest() {
285+
// Enable cache
286+
orcidClientCredentialEndPointDelegator.setTokenCacheEnabled(true);
287+
SecurityContextTestUtils.setUpSecurityContextForClientOnly(CLIENT_ID_1, ScopePathType.ACTIVITIES_UPDATE, ScopePathType.READ_LIMITED);
288+
OrcidOauth2AuthoriziationCodeDetail authCode = createAuthorizationCode("code-1", CLIENT_ID_1, "http://www.APP-5555555555555555.com/redirect/oauth", true,
289+
"/activities/update");
290+
MultivaluedMap<String, String> formParams = new MultivaluedHashMap<String, String>();
291+
formParams.add("client_id", CLIENT_ID_1);
292+
formParams.add("client_secret", "DhkFj5EI0qp6GsUKi55Vja+h+bsaKpBx");
293+
formParams.add("grant_type", "authorization_code");
294+
formParams.add("redirect_uri", "http://www.APP-5555555555555555.com/redirect/oauth");
295+
formParams.add("code", authCode.getId());
296+
Response response = orcidClientCredentialEndPointDelegator.obtainOauth2Token(null, formParams);
297+
assertNotNull(response);
298+
assertNotNull(response.getEntity());
299+
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) response.getEntity();
300+
assertNotNull(token);
301+
assertTrue(!PojoUtil.isEmpty(token.getValue()));
302+
303+
String tokenValue = token.getValue();
304+
305+
306+
Map<String, String> tokenData = new HashMap<String, String>();
307+
tokenData.put(OrcidOauth2Constants.ACCESS_TOKEN, tokenValue);
308+
tokenData.put(OrcidOauth2Constants.TOKEN_EXPIRATION_TIME, String.valueOf(token.getExpiration().getTime()));
309+
StringBuilder sb = new StringBuilder();
310+
token.getScope().forEach(x -> {sb.append(x); sb.append(' ');});
311+
tokenData.put(OrcidOauth2Constants.SCOPE_PARAM, sb.toString());
312+
tokenData.put(OrcidOauth2Constants.ORCID, (String) token.getAdditionalInformation().get(OrcidOauth2Constants.ORCID));
313+
tokenData.put(OrcidOauth2Constants.CLIENT_ID, CLIENT_ID_1);
314+
tokenData.put(OrcidOauth2Constants.RESOURCE_IDS, OrcidOauth2Constants.ORCID);
315+
tokenData.put(OrcidOauth2Constants.APPROVED, Boolean.TRUE.toString());
316+
317+
String tokenDataString = JsonUtils.convertToJsonString(tokenData);
318+
319+
verify(redisClientMock, times(1)).set(Mockito.eq(tokenValue), Mockito.eq(tokenDataString));
320+
}
321+
322+
@Test
323+
public void obtainOauth2TokenSkipCacheTest() {
324+
// Ensure cache is disabled
325+
orcidClientCredentialEndPointDelegator.setTokenCacheEnabled(false);
326+
327+
SecurityContextTestUtils.setUpSecurityContextForClientOnly(CLIENT_ID_1, ScopePathType.ACTIVITIES_UPDATE, ScopePathType.READ_LIMITED);
328+
OrcidOauth2AuthoriziationCodeDetail authCode = createAuthorizationCode("code-1", CLIENT_ID_1, "http://www.APP-5555555555555555.com/redirect/oauth", true,
329+
"/activities/update");
330+
MultivaluedMap<String, String> formParams = new MultivaluedHashMap<String, String>();
331+
formParams.add("client_id", CLIENT_ID_1);
332+
formParams.add("client_secret", "DhkFj5EI0qp6GsUKi55Vja+h+bsaKpBx");
333+
formParams.add("grant_type", "authorization_code");
334+
formParams.add("redirect_uri", "http://www.APP-5555555555555555.com/redirect/oauth");
335+
formParams.add("code", authCode.getId());
336+
Response response = orcidClientCredentialEndPointDelegator.obtainOauth2Token(null, formParams);
337+
assertNotNull(response);
338+
assertNotNull(response.getEntity());
339+
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) response.getEntity();
340+
assertNotNull(token);
341+
assertTrue(!PojoUtil.isEmpty(token.getValue()));
342+
343+
verify(redisClientMock, never()).set(Mockito.any(), Mockito.any());
344+
}
271345
}

orcid-core/pom.xml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@
3939
<dependency>
4040
<groupId>org.orcid</groupId>
4141
<artifactId>orcid-model</artifactId>
42-
</dependency>
42+
</dependency>
43+
<dependency>
44+
<groupId>${project.parent.groupId}</groupId>
45+
<artifactId>orcid-utils</artifactId>
46+
<version>${project.parent.version}</version>
47+
</dependency>
4348

4449
<!-- Internal test only -->
4550
<dependency>
@@ -322,7 +327,12 @@
322327
<groupId>javax.ws.rs</groupId>
323328
<artifactId>javax.ws.rs-api</artifactId>
324329
</dependency>
325-
330+
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
331+
<dependency>
332+
<groupId>redis.clients</groupId>
333+
<artifactId>jedis</artifactId>
334+
<version>4.4.3</version>
335+
</dependency>
326336
</dependencies>
327337
<build>
328338
<plugins>

orcid-core/src/main/java/org/orcid/core/constants/OrcidOauth2Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class OrcidOauth2Constants {
2626
public static final String EXPIRES_IN = "expires_in";
2727
public static final String TOKEN_ID = "tokenId";
2828
public static final String ORIGINAL_AUTHORIZATION_REQUEST = "org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST";
29+
public static final String TOKEN_EXPIRATION_TIME = "token_expiration_time";
2930

3031
public static final String APPROVED = "approved";
3132
public static final String RESOURCE_IDS = "resourceIds";

orcid-core/src/main/java/org/orcid/core/manager/impl/EncryptionManagerImpl.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,14 @@ public String encryptForExternalUse(String stringToEncrypt) {
108108
return externalEncryptor.encrypt(stringToEncrypt);
109109
}
110110

111-
@Override
112-
public String decryptForExternalUse(String stringToDecrypt) {
113-
try {
114-
return externalEncryptor.decrypt(stringToDecrypt);
115-
} catch (EncryptionOperationNotPossibleException e) {
116-
return decryptForLegacyExternalUse(stringToDecrypt);
117-
}
118-
}
111+
@Override
112+
public String decryptForExternalUse(String stringToDecrypt) {
113+
try {
114+
return externalEncryptor.decrypt(stringToDecrypt);
115+
} catch (EncryptionOperationNotPossibleException e) {
116+
return decryptForLegacyExternalUse(stringToDecrypt);
117+
}
118+
}
119119

120120
@Override
121121
@Deprecated
@@ -225,14 +225,14 @@ public String getEmailHash(String email) {
225225
}
226226
}
227227

228-
@Override
229-
public boolean matches(CharSequence rawPass, String encPass) {
230-
LOGGER.debug("About to start password check");
231-
StopWatch stopWatch = new StopWatch();
232-
stopWatch.start();
233-
boolean result = hashMatches(rawPass.toString(), encPass);
234-
stopWatch.stop();
235-
LOGGER.debug("Password check took {} ms", stopWatch.getTime());
236-
return result;
237-
}
228+
@Override
229+
public boolean matches(CharSequence rawPass, String encPass) {
230+
LOGGER.debug("About to start password check");
231+
StopWatch stopWatch = new StopWatch();
232+
stopWatch.start();
233+
boolean result = hashMatches(rawPass.toString(), encPass);
234+
stopWatch.stop();
235+
LOGGER.debug("Password check took {} ms", stopWatch.getTime());
236+
return result;
237+
}
238238
}

0 commit comments

Comments
 (0)