Skip to content

Commit 055aac9

Browse files
Petr WeinfurtPetr Weinfurttaban03
authored
feat: Caching Service can store invalidated token rules (#2460)
* feat: Caching Service can store invalidated tokens and revocation rules for users and services Signed-off-by: Petr Weinfurt <weipe03@ca.com> * fix integration tests Signed-off-by: Petr Weinfurt <weipe03@ca.com> * Add revokation of tokens using rules Signed-off-by: at670475 <andrea.tabone@broadcom.com> * add more integration tests Signed-off-by: Petr Weinfurt <weipe03@ca.com> * Add validation based on rule Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Remove condition for duplicated rules Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Remove unused method Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Increase code coverage Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix code smells Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix check for rules matching either userId or serviceId during validation Signed-off-by: at670475 <andrea.tabone@broadcom.com> * The isInvalidated() function also check for timestamp value in rules. Signed-off-by: Petr Weinfurt <weipe03@ca.com> Co-authored-by: Petr Weinfurt <weipe03@ca.com> Co-authored-by: Andrea Tabone <andrea.tabone@broadcom.com> Co-authored-by: Andrea Tabone <39694626+taban03@users.noreply.github.com>
1 parent 461c02b commit 055aac9

File tree

20 files changed

+476
-121
lines changed

20 files changed

+476
-121
lines changed

apiml-security-common/src/main/java/org/zowe/apiml/security/common/config/AccessTokenProviderConfig.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public void invalidateToken(String token) {
3030
}
3131

3232
@Override
33-
public boolean isInvalidated(String token) {
33+
public boolean isInvalidated(String token, String serviceId) {
3434
throw new NotImplementedException();
3535
}
3636

@@ -43,6 +43,11 @@ public String getToken(String username, int expirationTime, Set<String> scopes)
4343
public boolean isValidForScopes(String token, String serviceId) {
4444
throw new NotImplementedException();
4545
}
46+
47+
@Override
48+
public void invalidateTokensUsingRules(String ruleId, long timeStamp) {
49+
throw new NotImplementedException();
50+
}
4651
};
4752
}
4853
}

apiml-security-common/src/main/java/org/zowe/apiml/security/common/token/AccessTokenProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
public interface AccessTokenProvider {
1515

1616
void invalidateToken(String token) throws Exception;
17-
boolean isInvalidated(String token) throws Exception;
17+
boolean isInvalidated(String token, String serviceId) throws Exception;
1818
String getToken(String username, int expirationTime, Set<String> scopes);
1919
boolean isValidForScopes(String token, String serviceId);
20+
void invalidateTokensUsingRules(String ruleId, long timeStamp) throws Exception;
2021
}

caching-service/src/main/java/org/zowe/apiml/caching/api/CachingController.java

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,26 +106,43 @@ public ResponseEntity<Object> createKey(@RequestBody KeyValue keyValue, HttpServ
106106
keyValue, request, HttpStatus.CREATED);
107107
}
108108

109-
@PostMapping(value = "/cache-list", produces = MediaType.APPLICATION_JSON_VALUE)
110-
@Operation(summary = "Add a new list item in the cache",
111-
description = "A new key-value pair will be added to the cache")
109+
@PostMapping(value = "/cache-list/{mapKey}", produces = MediaType.APPLICATION_JSON_VALUE)
110+
@Operation(summary = "Add a new item in the cache map",
111+
description = "A new key-value pair will be added to the specific cache map with given map key.")
112112
@ResponseBody
113113
@HystrixCommand
114-
public ResponseEntity<Object> storeListItem(@RequestBody KeyValue keyValue, HttpServletRequest request) {
115-
return keyValueRequest(storage::storeListItem,
116-
keyValue, request, HttpStatus.CREATED);
114+
public ResponseEntity<Object> storeMapItem(@PathVariable String mapKey, @RequestBody KeyValue keyValue, HttpServletRequest request) {
115+
return mapKeyValueRequest(storage::storeMapItem,
116+
mapKey, keyValue, request, HttpStatus.CREATED);
117117
}
118118

119-
@GetMapping(value = "/cache-list/{key}", produces = MediaType.APPLICATION_JSON_VALUE)
120-
@Operation(summary = "Retrieves all the list items in the cache",
119+
@GetMapping(value = "/cache-list/{mapKey}", produces = MediaType.APPLICATION_JSON_VALUE)
120+
@Operation(summary = "Retrieves all the items in the cache map",
121+
description = "Values returned for the calling service and specific cache map.")
122+
@ResponseBody
123+
@HystrixCommand
124+
public ResponseEntity<Object> getAllMapItems(@PathVariable String mapKey, HttpServletRequest request) {
125+
return getServiceId(request).<ResponseEntity<Object>>map(
126+
s -> {
127+
try {
128+
return new ResponseEntity<>(storage.getAllMapItems(s, mapKey), HttpStatus.OK);
129+
} catch (Exception exception) {
130+
return handleIncompatibleStorageMethod(exception, request.getRequestURL());
131+
}
132+
}
133+
).orElseGet(this::getUnauthorizedResponse);
134+
}
135+
136+
@GetMapping(value = "/cache-list", produces = MediaType.APPLICATION_JSON_VALUE)
137+
@Operation(summary = "Retrieves all the maps in the cache",
121138
description = "Values returned for the calling service")
122139
@ResponseBody
123140
@HystrixCommand
124-
public ResponseEntity<Object> getAllListItems(@PathVariable String key, HttpServletRequest request) {
141+
public ResponseEntity<Object> getAllMaps(HttpServletRequest request) {
125142
return getServiceId(request).<ResponseEntity<Object>>map(
126143
s -> {
127144
try {
128-
return new ResponseEntity<>(storage.getAllMapItems(s, key), HttpStatus.OK);
145+
return new ResponseEntity<>(storage.getAllMaps(s), HttpStatus.OK);
129146
} catch (Exception exception) {
130147
return handleIncompatibleStorageMethod(exception, request.getRequestURL());
131148
}
@@ -201,6 +218,26 @@ private ResponseEntity<Object> keyValueRequest(KeyValueOperation keyValueOperati
201218
}
202219
}
203220

221+
private ResponseEntity<Object> mapKeyValueRequest(MapKeyValueOperation operation, String mapKey, KeyValue keyValue,
222+
HttpServletRequest request, HttpStatus successStatus) {
223+
Optional<String> serviceId = getServiceId(request);
224+
if (!serviceId.isPresent()) {
225+
return getUnauthorizedResponse();
226+
}
227+
228+
try {
229+
checkForInvalidPayload(keyValue);
230+
231+
operation.storageRequest(serviceId.get(), mapKey, keyValue);
232+
233+
return new ResponseEntity<>(successStatus);
234+
} catch (StorageException exception) {
235+
return exceptionToResponse(exception);
236+
} catch (Exception exception) {
237+
return handleInternalError(exception, request.getRequestURL());
238+
}
239+
}
240+
204241
private Optional<String> getServiceId(HttpServletRequest request) {
205242
Optional<String> certificateServiceId = getHeader(request, "X-Certificate-DistinguishedName");
206243
Optional<String> specificServiceId = getHeader(request, "X-CS-Service-ID");
@@ -268,4 +305,9 @@ interface KeyOperation {
268305
interface KeyValueOperation {
269306
KeyValue storageRequest(String serviceId, KeyValue keyValue) throws StorageException;
270307
}
308+
309+
@FunctionalInterface
310+
interface MapKeyValueOperation {
311+
KeyValue storageRequest(String serviceId, String mapKey, KeyValue keyValue);
312+
}
271313
}

caching-service/src/main/java/org/zowe/apiml/caching/service/Storage.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,30 @@ public interface Storage {
2727
KeyValue create(String serviceId, KeyValue toCreate);
2828

2929
/**
30-
* Store new KeyValue pair in the storage. The entry will be stored in a list for a specific key.
30+
* Store new KeyValue pair in the storage. The entry will be stored in a map under a specific map key.
3131
*
3232
* @param serviceId Id of the service to store the value for
33+
* @param mapKey key of the specific map underneath the key-value pair should be stored
3334
* @param toCreate KeyValue pair to be created.
3435
*/
35-
KeyValue storeListItem(String serviceId, KeyValue toCreate) throws StorageException;
36+
KeyValue storeMapItem(String serviceId, String mapKey, KeyValue toCreate) throws StorageException;
3637

3738
/**
38-
* Return all the items in the list for a specific key.
39+
* Return all the items in the specific map for a specific service.
3940
*
4041
* @param serviceId Id of the service to load all key/value pairs
41-
* @param key key to lookup
42+
* @param mapKey key of the specific map to return
4243
* @return Map with the key/value pairs or null if there is none existing.
4344
*/
44-
Map<String, String> getAllMapItems(String serviceId, String key) throws StorageException;
45+
Map<String, String> getAllMapItems(String serviceId, String mapKey) throws StorageException;
46+
47+
/**
48+
* Return all the items in all the maps for specific service
49+
*
50+
* @param serviceId Id of the service to load all key/value pairs
51+
* @return Map of all lists with the key/value pairs or null if there is none existing.
52+
*/
53+
Map<String, Map<String, String>> getAllMaps(String serviceId) throws StorageException;
4554

4655
/**
4756
* Returns the keys associated with the provided keys.

caching-service/src/main/java/org/zowe/apiml/caching/service/infinispan/storage/InfinispanStorage.java

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.concurrent.CompletionException;
2323
import java.util.concurrent.ConcurrentMap;
2424
import java.util.concurrent.TimeUnit;
25+
import java.util.stream.Collectors;
2526

2627
@Slf4j
2728
public class InfinispanStorage implements Storage {
@@ -51,22 +52,18 @@ public KeyValue create(String serviceId, KeyValue toCreate) {
5152
}
5253

5354
@Override
54-
public KeyValue storeListItem(String serviceId, KeyValue toCreate) {
55+
public KeyValue storeMapItem(String serviceId, String mapKey, KeyValue toCreate) {
5556
CompletableFuture<Boolean> complete = lock.tryLock(4, TimeUnit.SECONDS).whenComplete((r, ex) -> {
5657
if (Boolean.TRUE.equals(r)) {
5758
try {
58-
String cacheKey = serviceId + "invalidTokens";
59-
if (tokenCache.get(cacheKey) != null &&
60-
tokenCache.get(cacheKey).containsKey(toCreate.getKey())) {
61-
throw new StorageException(Messages.DUPLICATE_VALUE.getKey(), Messages.DUPLICATE_VALUE.getStatus(), toCreate.getValue());
59+
String cacheKey = serviceId + mapKey;
60+
log.info("Storing the item into token cache: {} -> {}|{}", cacheKey, toCreate.getKey(), toCreate.getValue());
61+
Map<String, String> tokenCacheItem = tokenCache.get(cacheKey);
62+
if (tokenCacheItem == null) {
63+
tokenCacheItem = new HashMap<>();
6264
}
63-
log.info("Storing the invalidated token: {}|{}|{}", serviceId, toCreate.getKey(), toCreate.getValue());
64-
Map<String, String> tokensList = tokenCache.get(cacheKey);
65-
if (tokensList == null) {
66-
tokensList = new HashMap<>();
67-
}
68-
tokensList.put(toCreate.getKey(), toCreate.getValue());
69-
tokenCache.put(cacheKey, tokensList);
65+
tokenCacheItem.put(toCreate.getKey(), toCreate.getValue());
66+
tokenCache.put(cacheKey, tokenCacheItem);
7067
} finally {
7168
lock.unlock();
7269
}
@@ -86,9 +83,18 @@ public KeyValue storeListItem(String serviceId, KeyValue toCreate) {
8683
}
8784

8885
@Override
89-
public Map<String, String> getAllMapItems(String serviceId, String key) {
90-
log.info("Reading all revoked tokens for service {} ", serviceId);
91-
return tokenCache.get(serviceId + key);
86+
public Map<String, String> getAllMapItems(String serviceId, String mapKey) {
87+
log.info("Reading all records from token cache for service {} under the {} key.", serviceId, mapKey);
88+
return tokenCache.get(serviceId + mapKey);
89+
}
90+
91+
@Override
92+
public Map<String, Map<String, String>> getAllMaps(String serviceId) {
93+
log.info("Reading all records from token cache for service {} ", serviceId);
94+
// filter all maps which belong given service and remove the service name from key names.
95+
return tokenCache.entrySet().stream().filter(
96+
entry -> entry.getKey().startsWith(serviceId))
97+
.collect(Collectors.toMap(e -> e.getKey().substring(serviceId.length()), Map.Entry::getValue));
9298
}
9399

94100
@Override

caching-service/src/main/java/org/zowe/apiml/caching/service/inmemory/InMemoryStorage.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,17 @@ public KeyValue create(String serviceId, KeyValue toCreate) {
6262
}
6363

6464
@Override
65-
public KeyValue storeListItem(String serviceId, KeyValue toCreate) throws StorageException {
65+
public KeyValue storeMapItem(String serviceId, String mapKey, KeyValue toCreate) throws StorageException {
6666
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
6767
}
6868

6969
@Override
70-
public Map<String, String> getAllMapItems(String serviceId, String key) throws StorageException {
70+
public Map<String, String> getAllMapItems(String serviceId, String mapKey) throws StorageException {
71+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
72+
}
73+
74+
@Override
75+
public Map<String, Map<String, String>> getAllMaps(String serviceId) throws StorageException {
7176
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
7277
}
7378

caching-service/src/main/java/org/zowe/apiml/caching/service/redis/RedisStorage.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,17 @@ public KeyValue create(String serviceId, KeyValue toCreate) {
5959
}
6060

6161
@Override
62-
public KeyValue storeListItem(String serviceId, KeyValue toCreate) throws StorageException {
62+
public KeyValue storeMapItem(String serviceId, String mapKey, KeyValue toCreate) throws StorageException {
6363
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
6464
}
6565

6666
@Override
67-
public Map<String, String> getAllMapItems(String serviceId, String key) throws StorageException {
67+
public Map<String, String> getAllMapItems(String serviceId, String mapKey) throws StorageException {
68+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
69+
}
70+
71+
@Override
72+
public Map<String, Map<String, String>> getAllMaps(String serviceId) throws StorageException {
6873
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
6974
}
7075

caching-service/src/main/java/org/zowe/apiml/caching/service/vsam/VsamStorage.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,17 @@ public KeyValue create(String serviceId, KeyValue toCreate) {
9595
}
9696

9797
@Override
98-
public KeyValue storeListItem(String serviceId, KeyValue toCreate) throws StorageException {
98+
public KeyValue storeMapItem(String serviceId, String mapKey, KeyValue toCreate) throws StorageException {
9999
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
100100
}
101101

102102
@Override
103-
public Map<String, String> getAllMapItems(String serviceId, String key) throws StorageException {
103+
public Map<String, String> getAllMapItems(String serviceId, String mapKey) throws StorageException {
104+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
105+
}
106+
107+
@Override
108+
public Map<String, Map<String, String>> getAllMaps(String serviceId) throws StorageException {
104109
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
105110
}
106111

0 commit comments

Comments
 (0)