-
Notifications
You must be signed in to change notification settings - Fork 10
[ACL-263] Caching improvements #337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
fd9a36c
e3b591d
c358105
59ff7d0
eeb16cb
20637fb
b613473
8a751c3
f5600f7
3356360
861293e
3985cbd
7018fc1
a6aee89
d525dbf
a22049b
78ba523
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package com.truelayer.java.http.auth.cache; | ||
|
|
||
| import static java.nio.charset.StandardCharsets.UTF_8; | ||
| import static org.apache.commons.lang3.ObjectUtils.isEmpty; | ||
|
|
||
| import com.truelayer.java.TrueLayerException; | ||
| import com.truelayer.java.entities.RequestScopes; | ||
| import java.security.MessageDigest; | ||
| import java.security.NoSuchAlgorithmException; | ||
| import java.text.MessageFormat; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| public class CredentialsCacheHelper { | ||
| private static final String CACHE_KEY_PREFIX = "tl-auth-token"; | ||
| private static final String SCOPES_DELIMITER = ","; | ||
|
|
||
| public static String buildKey(String clientId, RequestScopes requestScopes) { | ||
| if (isEmpty(clientId) || isEmpty(requestScopes) || isEmpty(requestScopes.getScopes())) { | ||
| throw new TrueLayerException("Invalid client id or request scopes provided"); | ||
| } | ||
|
|
||
| List<String> scopes = new ArrayList<>(requestScopes.getScopes()); | ||
|
|
||
| // Use natural ordering to make ordering not significant | ||
| Collections.sort(scopes); | ||
|
|
||
| byte[] md5InBytes = digest(String.join(SCOPES_DELIMITER, scopes).getBytes(UTF_8)); | ||
| return MessageFormat.format("{0}:{1}:{2}", CACHE_KEY_PREFIX, clientId, bytesToHex(md5InBytes)); | ||
| } | ||
|
|
||
| private static byte[] digest(byte[] input) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using a numeric hashcode I'm using MD5. We don't really need this to be bullet-proof, so I think it's fine. |
||
| MessageDigest md; | ||
| try { | ||
| md = MessageDigest.getInstance("MD5"); | ||
| } catch (NoSuchAlgorithmException e) { | ||
| throw new TrueLayerException("Hashing algorithm is not available", e); | ||
| } | ||
| return md.digest(input); | ||
| } | ||
|
|
||
| private static String bytesToHex(byte[] bytes) { | ||
| StringBuilder sb = new StringBuilder(); | ||
| for (byte b : bytes) { | ||
| sb.append(String.format("%02x", b)); | ||
| } | ||
| return sb.toString(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,13 @@ | ||
| package com.truelayer.java.http.auth.cache; | ||
|
|
||
| import com.truelayer.java.auth.entities.AccessToken; | ||
| import com.truelayer.java.entities.RequestScopes; | ||
| import java.util.Optional; | ||
|
|
||
| public interface ICredentialsCache { | ||
|
|
||
| /** | ||
| * Gets the cached access token for the given request scopes. | ||
| * @param scopes the requested scopes | ||
| * @return an optional access token. If the token is expired an empty optional is returned | ||
| */ | ||
| Optional<AccessToken> getToken(RequestScopes scopes); | ||
| Optional<AccessToken> getToken(String key); | ||
|
Comment on lines
-14
to
+8
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the main change: users of a custom cache won't have to bother computing hash keys. They will receive what the client builds, and they have the option to customise/override those if needed |
||
|
|
||
| /** | ||
| * Stores an access token in cache for the given request scopes. | ||
| * @param token the new token to store | ||
| * @param scopes the requested scopes | ||
| */ | ||
| void storeToken(RequestScopes scopes, AccessToken token); | ||
| void storeToken(String key, AccessToken token); | ||
|
|
||
| /** | ||
| * Remove the entry in the cache for the given request scopes. | ||
| * @param scopes the requested scopes | ||
| */ | ||
| void clearToken(RequestScopes scopes); | ||
| void clearToken(String key); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.truelayer.java.http.auth.cache; | ||
|
|
||
| import com.truelayer.java.auth.entities.AccessToken; | ||
| import java.time.Clock; | ||
| import java.time.LocalDateTime; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @RequiredArgsConstructor | ||
| public class InMemoryCredentialsCache implements ICredentialsCache { | ||
|
|
||
| private final Clock clock; | ||
|
|
||
| /** | ||
| * internal state | ||
| */ | ||
| private final Map<String, AccessTokenRecord> tokenRecords = new ConcurrentHashMap<>(); | ||
|
|
||
| @Override | ||
| public Optional<AccessToken> getToken(String key) { | ||
| AccessTokenRecord tokenRecord = tokenRecords.get(key); | ||
| if (tokenRecord == null || !LocalDateTime.now(clock).isBefore(tokenRecord.expiresAt)) { | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| return Optional.of(tokenRecord.token); | ||
| } | ||
|
|
||
| @Override | ||
| public void storeToken(String key, AccessToken token) { | ||
| AccessTokenRecord tokenRecord = | ||
| new AccessTokenRecord(token, LocalDateTime.now(clock).plusSeconds(token.getExpiresIn())); | ||
|
|
||
| tokenRecords.put(key, tokenRecord); | ||
| } | ||
|
|
||
| @Override | ||
| public void clearToken(String key) { | ||
| tokenRecords.remove(key); | ||
| } | ||
|
|
||
| @RequiredArgsConstructor | ||
| public static class AccessTokenRecord { | ||
| private final AccessToken token; | ||
| private final LocalDateTime expiresAt; | ||
| } | ||
| } |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we anyway need a breaking change for the new cache interface (that aligns to what PHP and .NET libs do), I took the chance to rename this to be more explicit