Skip to content

Commit 09bbaa7

Browse files
committed
Use user guid for token cache
JIRA:LMCROSSITXSADEPLOY-3207
1 parent 19398a9 commit 09bbaa7

File tree

57 files changed

+812
-713
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+812
-713
lines changed

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Constants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public class Constants {
3030
public static final String B3_TRACE_ID_HEADER = "X-B3-TraceId";
3131
public static final String B3_SPAN_ID_HEADER = "X-B3-SpanId";
3232

33+
public static final int TOKEN_SERVICE_DELETION_CORE_POOL_SIZE = 1;
34+
public static final int TOKEN_SERVICE_DELETION_MAXIMUM_POOL_SIZE = 3;
35+
public static final int TOKEN_SERVICE_DELETION_KEEP_ALIVE_THREAD_IN_SECONDS = 30;
36+
3337
protected Constants() {
3438
}
3539
}

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
public final class Messages {
77

88
// Exception messages
9-
public static final String NO_VALID_TOKEN_FOUND = "No valid access token was found for user \"{0}\"";
9+
public static final String NO_VALID_TOKEN_FOUND = "No valid access token was found for user guid \"{0}\"";
1010
public static final String CANT_CREATE_CLIENT = "Could not create client";
1111
public static final String CANT_CREATE_CLIENT_FOR_SPACE_ID = "Could not create client in space with guid \"{0}\"";
1212
public static final String UNAUTHORISED_OPERATION_ORG_SPACE = "Not authorized to perform operation \"{0}\" in organization \"{1}\" and space \"{2}\"";

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientProvider.java

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22

33
import java.time.Duration;
44

5+
import com.sap.cloudfoundry.client.facade.CloudControllerClient;
6+
import com.sap.cloudfoundry.client.facade.CloudOperationException;
57
import jakarta.inject.Inject;
68
import jakarta.inject.Named;
7-
89
import org.cloudfoundry.multiapps.common.SLException;
910
import org.cloudfoundry.multiapps.controller.core.Messages;
1011
import org.cloudfoundry.multiapps.controller.core.model.CachedMap;
1112
import org.cloudfoundry.multiapps.controller.core.security.token.TokenService;
1213
import org.springframework.beans.factory.DisposableBean;
1314

14-
import com.sap.cloudfoundry.client.facade.CloudControllerClient;
15-
import com.sap.cloudfoundry.client.facade.CloudOperationException;
16-
1715
@Named
1816
public class CloudControllerClientProvider implements DisposableBean {
1917

@@ -25,59 +23,64 @@ public class CloudControllerClientProvider implements DisposableBean {
2523
private final CachedMap<String, CloudControllerClient> clients = new CachedMap<>(Duration.ofMinutes(30));
2624

2725
/**
28-
* Returns a client for the specified user name and space id by either getting it from the clients cache or creating a new one.
26+
* Returns a client for the specified user guid and space id by either getting it from the clients cache or creating a new one.
2927
*
30-
* @param userName the user name associated with the client
28+
* @param userName the username associated with the client
29+
* @param userGuid the userGuid associated with the client
3130
* @param spaceGuid the space guid associated with the client
3231
* @param correlationId of the process which is used to tag HTTP requests
3332
* @return a CF client for the specified access token, organization, and space
3433
*/
35-
public CloudControllerClient getControllerClient(String userName, String spaceGuid, String correlationId) {
34+
public CloudControllerClient getControllerClient(String userName, String userGuid, String spaceGuid, String correlationId) {
3635
try {
37-
return getClientFromCache(userName, spaceGuid, correlationId);
36+
return getClientFromCache(userName, userGuid, spaceGuid, correlationId);
3837
} catch (CloudOperationException e) {
3938
throw new SLException(e, Messages.CANT_CREATE_CLIENT_FOR_SPACE_ID, spaceGuid);
4039
}
4140
}
4241

4342
/**
44-
* Returns a client for the specified user name and space id by either getting it from the clients cache or creating a new one.
43+
* Returns a client for the specified username and space id by either getting it from the clients cache or creating a new one.
4544
*
46-
* @param userName the user name associated with the client
45+
* @param userName the username associated with the client
46+
* @param userGuid the userGuid associated with the client
4747
* @param spaceGuid the space guid associated with the client
4848
* @return a CF client for the specified access token, organization, and space
4949
*/
50-
public CloudControllerClient getControllerClientWithNoCorrelation(String userName, String spaceGuid) {
50+
public CloudControllerClient getControllerClientWithNoCorrelation(String userName, String userGuid, String spaceGuid) {
5151
try {
52-
return getClientFromCacheWithNoCorrelation(userName, spaceGuid);
52+
return getClientFromCacheWithNoCorrelation(userName, userGuid, spaceGuid);
5353
} catch (CloudOperationException e) {
5454
throw new SLException(e, Messages.CANT_CREATE_CLIENT_FOR_SPACE_ID, spaceGuid);
5555
}
5656
}
5757

5858
/**
59-
* Releases the client for the specified user name and space id by removing it from the clients cache.
59+
* Releases the client for the specified username and space id by removing it from the clients cache.
6060
*
61-
* @param userName the user name associated with the client
61+
* @param userGuid the userGuid associated with the client
6262
* @param spaceGuid the space id associated with the client
6363
*/
64-
public void releaseClient(String userName, String spaceGuid) {
65-
clients.remove(getKey(spaceGuid, userName));
64+
public void releaseClient(String userGuid, String spaceGuid) {
65+
clients.remove(getKey(spaceGuid, userGuid, null));
6666
}
6767

68-
private CloudControllerClient getClientFromCacheWithNoCorrelation(String userName, String spaceId) {
69-
String key = getKey(spaceId, userName);
70-
return clients.computeIfAbsent(key,
71-
() -> clientFactory.createClient(tokenService.getToken(userName), spaceId, null));
68+
private CloudControllerClient getClientFromCacheWithNoCorrelation(String userName, String userGuid, String spaceId) {
69+
String key = getKey(spaceId, userGuid, userName);
70+
return clients.computeIfAbsent(key, () -> clientFactory.createClient(tokenService.getToken(userName, userGuid), spaceId, null));
7271
}
7372

74-
private CloudControllerClient getClientFromCache(String userName, String spaceId, String correlationId) {
75-
String key = getKey(spaceId, userName);
73+
private CloudControllerClient getClientFromCache(String userName, String userGuid, String spaceId, String correlationId) {
74+
String key = getKey(spaceId, userGuid, userName);
7675
return clients.computeIfAbsent(key,
77-
() -> clientFactory.createClient(tokenService.getToken(userName), spaceId, correlationId));
76+
() -> clientFactory.createClient(tokenService.getToken(userName, userGuid), spaceId, correlationId));
7877
}
7978

80-
private String getKey(String spaceGuid, String username) {
79+
private String getKey(String spaceGuid, String userGuid, String username) {
80+
if (userGuid != null) {
81+
return spaceGuid + "|" + userGuid;
82+
}
83+
// TODO: Remove this branch when userGuid is guaranteed to be non-null(In the next release after introduction of userGuid)
8184
return spaceGuid + "|" + username;
8285
}
8386

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/OAuthClientExtended.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@
55
import java.time.Instant;
66
import java.time.temporal.ChronoUnit;
77

8+
import com.sap.cloudfoundry.client.facade.oauth2.OAuth2AccessTokenWithAdditionalInfo;
9+
import com.sap.cloudfoundry.client.facade.oauth2.OAuthClient;
810
import org.cloudfoundry.multiapps.controller.client.util.TokenProperties;
911
import org.cloudfoundry.multiapps.controller.core.Messages;
1012
import org.cloudfoundry.multiapps.controller.core.security.token.TokenService;
1113
import org.slf4j.Logger;
1214
import org.slf4j.LoggerFactory;
1315
import org.springframework.web.reactive.function.client.WebClient;
1416

15-
import com.sap.cloudfoundry.client.facade.oauth2.OAuth2AccessTokenWithAdditionalInfo;
16-
import com.sap.cloudfoundry.client.facade.oauth2.OAuthClient;
17-
1817
public class OAuthClientExtended extends OAuthClient {
1918

2019
private static final Logger LOGGER = LoggerFactory.getLogger(OAuthClientExtended.class);
@@ -35,10 +34,11 @@ public OAuth2AccessTokenWithAdditionalInfo getToken() {
3534
.isBefore(Instant.now()
3635
.plus(120, ChronoUnit.SECONDS))) {
3736
TokenProperties tokenProperties = TokenProperties.fromToken(token);
38-
token = tokenService.getToken(tokenProperties.getUserName());
39-
LOGGER.info(MessageFormat.format(Messages.RETRIEVED_TOKEN_FOR_USER_WITH_GUID_0_WITH_EXPIRATION_TIME_1, tokenProperties.getUserId(),
40-
token.getOAuth2AccessToken()
41-
.getExpiresAt()));
37+
token = tokenService.getToken(tokenProperties.getUserName(), tokenProperties.getUserId());
38+
LOGGER.info(
39+
MessageFormat.format(Messages.RETRIEVED_TOKEN_FOR_USER_WITH_GUID_0_WITH_EXPIRATION_TIME_1, tokenProperties.getUserId(),
40+
token.getOAuth2AccessToken()
41+
.getExpiresAt()));
4242
}
4343
return token;
4444
}

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/token/TokenService.java

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
import java.util.concurrent.TimeUnit;
1313
import java.util.stream.Collectors;
1414

15+
import com.sap.cloudfoundry.client.facade.oauth2.OAuth2AccessTokenWithAdditionalInfo;
16+
import jakarta.inject.Inject;
17+
import jakarta.inject.Named;
18+
import org.cloudfoundry.multiapps.controller.client.util.TokenProperties;
19+
import org.cloudfoundry.multiapps.controller.core.Constants;
1520
import org.cloudfoundry.multiapps.controller.core.Messages;
1621
import org.cloudfoundry.multiapps.controller.core.model.CachedMap;
1722
import org.cloudfoundry.multiapps.controller.core.security.token.parsers.TokenParserChain;
@@ -20,11 +25,6 @@
2025
import org.cloudfoundry.multiapps.controller.persistence.services.AccessTokenService;
2126
import org.springframework.beans.factory.DisposableBean;
2227

23-
import com.sap.cloudfoundry.client.facade.oauth2.OAuth2AccessTokenWithAdditionalInfo;
24-
25-
import jakarta.inject.Inject;
26-
import jakarta.inject.Named;
27-
2828
/**
2929
* Provides functionality for persisting, updating and removing tokens from a token store
3030
*/
@@ -35,11 +35,10 @@ public class TokenService implements DisposableBean {
3535
private final TokenParserChain tokenParserChain;
3636
private final Duration tokenExpirationTime = Duration.ofMinutes(10);
3737
private final CachedMap<String, OAuth2AccessTokenWithAdditionalInfo> cachedTokens = new CachedMap<>(tokenExpirationTime);
38-
private final ExecutorService threadPoolForTokensDeletion = new ThreadPoolExecutor(1,
39-
3,
40-
30,
41-
TimeUnit.SECONDS,
42-
new LinkedBlockingQueue<>());
38+
private final ExecutorService threadPoolForTokensDeletion = new ThreadPoolExecutor(Constants.TOKEN_SERVICE_DELETION_CORE_POOL_SIZE,
39+
Constants.TOKEN_SERVICE_DELETION_MAXIMUM_POOL_SIZE,
40+
Constants.TOKEN_SERVICE_DELETION_KEEP_ALIVE_THREAD_IN_SECONDS,
41+
TimeUnit.SECONDS, new LinkedBlockingQueue<>());
4342

4443
@Inject
4544
public TokenService(AccessTokenService accessTokenService, TokenParserChain tokenParserChain) {
@@ -51,21 +50,19 @@ public TokenService(AccessTokenService accessTokenService, TokenParserChain toke
5150
* Chooses a token among all tokens for this user in the access token table.
5251
*
5352
* @param username the username
53+
* @param userGuid the userGuid
5454
* @return the latest token, or throw an exception if token is not found
5555
*/
56-
public OAuth2AccessTokenWithAdditionalInfo getToken(String username) {
57-
OAuth2AccessTokenWithAdditionalInfo cachedAccessToken = cachedTokens.get(username);
58-
if (shouldUseCachedToken(cachedAccessToken)) {
59-
return cachedAccessToken;
60-
}
61-
List<AccessToken> accessTokens = getSortedAccessTokensByUsername(username);
62-
if (accessTokens.isEmpty()) {
63-
throw new IllegalStateException(MessageFormat.format(Messages.NO_VALID_TOKEN_FOUND, username));
56+
public OAuth2AccessTokenWithAdditionalInfo getToken(String username, String userGuid) {
57+
if (userGuid != null) {
58+
OAuth2AccessTokenWithAdditionalInfo cachedAccessToken = cachedTokens.get(userGuid);
59+
if (shouldUseCachedToken(cachedAccessToken)) {
60+
return cachedAccessToken;
61+
}
62+
return getLatestAccessTokenByUserGuid(userGuid);
6463
}
65-
OAuth2AccessTokenWithAdditionalInfo tokenByUser = getLatestToken(accessTokens);
66-
cachedTokens.put(username, tokenByUser);
67-
deleteTokens(accessTokens.subList(1, accessTokens.size()));
68-
return tokenByUser;
64+
// TODO: If no tokens are found for the userGuid, try to find tokens by username. This is temporary and should be removed in the next release.
65+
return getLatestAccessTokenByUsername(username);
6966
}
7067

7168
private boolean shouldUseCachedToken(OAuth2AccessTokenWithAdditionalInfo cachedAccessToken) {
@@ -75,9 +72,42 @@ private boolean shouldUseCachedToken(OAuth2AccessTokenWithAdditionalInfo cachedA
7572
.plus(120, ChronoUnit.SECONDS));
7673
}
7774

78-
private List<AccessToken> getSortedAccessTokensByUsername(String userName) {
75+
private OAuth2AccessTokenWithAdditionalInfo getLatestAccessTokenByUserGuid(String userGuid) {
76+
List<AccessToken> tokensByGuid = getSortedAccessTokensByUserGuid(userGuid);
77+
if (tokensByGuid.isEmpty()) {
78+
throw new IllegalStateException(MessageFormat.format(Messages.NO_VALID_TOKEN_FOUND, userGuid));
79+
}
80+
OAuth2AccessTokenWithAdditionalInfo latestToken = getLatestToken(tokensByGuid);
81+
addTokenToCache(latestToken, tokensByGuid);
82+
return latestToken;
83+
}
84+
85+
private List<AccessToken> getSortedAccessTokensByUserGuid(String userGuid) {
86+
return accessTokenService.createQuery()
87+
.userGuid(userGuid)
88+
.orderByExpiresAt(OrderDirection.DESCENDING)
89+
.list();
90+
}
91+
92+
private void addTokenToCache(OAuth2AccessTokenWithAdditionalInfo token, List<AccessToken> accessTokens) {
93+
cachedTokens.put((String) token.getAdditionalInfo()
94+
.get(TokenProperties.USER_ID_KEY), token);
95+
if (accessTokens.size() > 1) {
96+
deleteTokens(accessTokens.subList(1, accessTokens.size()));
97+
}
98+
}
99+
100+
private OAuth2AccessTokenWithAdditionalInfo getLatestAccessTokenByUsername(String username) {
101+
List<AccessToken> tokensByUsername = getSortedAccessTokensByUsername(username);
102+
if (tokensByUsername.isEmpty()) {
103+
throw new IllegalStateException(MessageFormat.format(Messages.NO_VALID_TOKEN_FOUND, username));
104+
}
105+
return getLatestToken(tokensByUsername);
106+
}
107+
108+
private List<AccessToken> getSortedAccessTokensByUsername(String username) {
79109
return accessTokenService.createQuery()
80-
.username(userName)
110+
.username(username)
81111
.orderByExpiresAt(OrderDirection.DESCENDING)
82112
.list();
83113
}

0 commit comments

Comments
 (0)