Skip to content

Commit f3aeafa

Browse files
authored
feat: Evict non relevant tokens and rules (#2554)
* Create controller to evict non relevant tokens and rules Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Remove expired token Signed-off-by: at670475 <andrea.tabone@broadcom.com> * refactoring Signed-off-by: at670475 <andrea.tabone@broadcom.com> * remove irrelevant items from rules Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add test Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add more tests Signed-off-by: at670475 <andrea.tabone@broadcom.com> * fix license Signed-off-by: at670475 <andrea.tabone@broadcom.com> * fix code smell Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix code smells pt.2 Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Remove print from test Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add additional checks in the test Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Address PR request changes Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add integration test Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix IT Signed-off-by: at670475 <andrea.tabone@broadcom.com> * remove body from request Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix code smell Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add auth for to execute request Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix IT and address reviews Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Refactoring Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add test Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix test Signed-off-by: at670475 <andrea.tabone@broadcom.com> Signed-off-by: at670475 <andrea.tabone@broadcom.com>
1 parent f32ffa2 commit f3aeafa

File tree

26 files changed

+474
-44
lines changed

26 files changed

+474
-44
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public void invalidateAllTokensForUser(String userId, long timestamp) {
5656
public void invalidateAllTokensForService(String serviceId, long timestamp) {
5757
throw new NotImplementedException(NOT_IMPLEMENTED);
5858
}
59+
60+
@Override
61+
public void evictNonRelevantTokensAndRules() {
62+
throw new NotImplementedException(NOT_IMPLEMENTED);
63+
}
5964
};
6065
}
6166
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public class AuthConfigurationProperties {
4747

4848
private String revokeMultipleAccessTokens = "/gateway/auth/access-token/revoke/tokens";
4949

50+
private String evictAccessTokensAndRules = "/gateway/auth/access-token/evict";
51+
5052
private String gatewayRefreshEndpointOldFormat = "/api/v1/gateway/auth/refresh";
5153
private String gatewayRefreshEndpoint = "/gateway/api/v1/auth/refresh";
5254

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ public interface AccessTokenProvider {
2121
boolean isValidForScopes(String token, String serviceId);
2222
void invalidateAllTokensForUser(String userId, long timeStamp);
2323
void invalidateAllTokensForService(String serviceId, long timeStamp);
24+
void evictNonRelevantTokensAndRules();
2425
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,42 @@ public ResponseEntity<Object> getAllMaps(HttpServletRequest request) {
151151
).orElseGet(this::getUnauthorizedResponse);
152152
}
153153

154+
@DeleteMapping(value = "/cache-list/evict/rules/{mapKey}", produces = MediaType.APPLICATION_JSON_VALUE)
155+
@Operation(summary = "Delete a record from a rules map in the cache",
156+
description = "Will delete a key-value pair from a specific rules map")
157+
@ResponseBody
158+
@HystrixCommand
159+
public ResponseEntity<Object> evictRules(@PathVariable String mapKey, HttpServletRequest request) {
160+
return getServiceId(request).map(
161+
s -> {
162+
try {
163+
storage.removeNonRelevantRules(s, mapKey);
164+
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
165+
} catch (Exception exception) {
166+
return handleInternalError(exception, request.getRequestURL());
167+
}
168+
}
169+
).orElseGet(this::getUnauthorizedResponse);
170+
}
171+
172+
@DeleteMapping(value = "/cache-list/evict/tokens/{mapKey}", produces = MediaType.APPLICATION_JSON_VALUE)
173+
@Operation(summary = "Delete a record from an invalid tokens map in the cache",
174+
description = "Will delete a key-value pair from a specific tokens map")
175+
@ResponseBody
176+
@HystrixCommand
177+
public ResponseEntity<Object> evictTokens(@PathVariable String mapKey, HttpServletRequest request) {
178+
return getServiceId(request).map(
179+
s -> {
180+
try {
181+
storage.removeNonRelevantTokens(s, mapKey);
182+
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
183+
} catch (Exception exception) {
184+
return handleInternalError(exception, request.getRequestURL());
185+
}
186+
}
187+
).orElseGet(this::getUnauthorizedResponse);
188+
}
189+
154190
@PutMapping(value = "/cache", produces = MediaType.APPLICATION_JSON_VALUE)
155191
@Operation(summary = "Update key in the cache",
156192
description = "Value at the key in the provided key-value pair will be updated to the provided value")

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,18 @@ public interface Storage {
9595
* @param serviceId Id of the service to delete all key/value pairs for.
9696
*/
9797
void deleteForService(String serviceId);
98+
99+
/**
100+
* Delete a key/value pair from the rules map
101+
* @param serviceId the id of the service to identify the correct map
102+
* @param mapKey the map key
103+
*/
104+
void removeNonRelevantRules(String serviceId, String mapKey);
105+
106+
/**
107+
* Delete a key/value pair from the invalid tokens map
108+
* @param serviceId the id of the service to identify the correct map
109+
* @param mapKey the map key
110+
*/
111+
void removeNonRelevantTokens(String serviceId, String mapKey);
98112
}

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

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,21 @@
1010

1111
package org.zowe.apiml.caching.service.infinispan.storage;
1212

13+
import com.fasterxml.jackson.core.JsonProcessingException;
14+
import com.fasterxml.jackson.databind.ObjectMapper;
15+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
1316
import lombok.extern.slf4j.Slf4j;
1417
import org.infinispan.lock.api.ClusteredLock;
1518
import org.zowe.apiml.caching.model.KeyValue;
1619
import org.zowe.apiml.caching.service.Messages;
1720
import org.zowe.apiml.caching.service.Storage;
1821
import org.zowe.apiml.caching.service.StorageException;
22+
import org.zowe.apiml.models.AccessTokenContainer;
1923

24+
import java.time.LocalDateTime;
2025
import java.util.HashMap;
2126
import java.util.Map;
22-
import java.util.concurrent.CompletableFuture;
23-
import java.util.concurrent.CompletionException;
24-
import java.util.concurrent.ConcurrentMap;
25-
import java.util.concurrent.TimeUnit;
27+
import java.util.concurrent.*;
2628
import java.util.stream.Collectors;
2729

2830
@Slf4j
@@ -32,13 +34,18 @@ public class InfinispanStorage implements Storage {
3234
private final ConcurrentMap<String, KeyValue> cache;
3335
private final ConcurrentMap<String, Map<String, String>> tokenCache;
3436
private final ClusteredLock lock;
37+
private static final ObjectMapper objectMapper = new ObjectMapper();
3538

3639
public InfinispanStorage(ConcurrentMap<String, KeyValue> cache, ConcurrentMap<String, Map<String, String>> tokenCache, ClusteredLock lock) {
3740
this.cache = cache;
3841
this.tokenCache = tokenCache;
3942
this.lock = lock;
4043
}
4144

45+
static {
46+
objectMapper.registerModule(new JavaTimeModule());
47+
}
48+
4249
@Override
4350
public KeyValue create(String serviceId, KeyValue toCreate) {
4451
toCreate.setServiceId(serviceId);
@@ -70,16 +77,7 @@ public KeyValue storeMapItem(String serviceId, String mapKey, KeyValue toCreate)
7077
}
7178
}
7279
});
73-
try {
74-
complete.join();
75-
} catch (CompletionException e) {
76-
if (e.getCause() instanceof StorageException) {
77-
throw (StorageException) e.getCause();
78-
} else {
79-
log.error("Unexpected error while acquiring the lock ", e);
80-
throw e;
81-
}
82-
}
80+
completeJoin(complete);
8381
return null;
8482
}
8583

@@ -153,4 +151,70 @@ public void deleteForService(String serviceId) {
153151
}
154152
});
155153
}
154+
155+
@Override
156+
public void removeNonRelevantTokens(String serviceId, String mapKey) {
157+
CompletableFuture<Boolean> complete = lock.tryLock(4, TimeUnit.SECONDS).whenComplete((r, ex) -> {
158+
if (Boolean.TRUE.equals(r)) {
159+
try {
160+
removeToken(serviceId, mapKey);
161+
} finally {
162+
lock.unlock();
163+
}
164+
}
165+
});
166+
completeJoin(complete);
167+
}
168+
169+
private void removeToken(String serviceId, String mapKey) {
170+
Map<String, String> map = tokenCache.get(serviceId + mapKey);
171+
if (map != null && !map.isEmpty()) {
172+
Map<String,String> result = map.entrySet().stream().filter(entry -> {
173+
try {
174+
AccessTokenContainer c = objectMapper.readValue(entry.getValue(), AccessTokenContainer.class);
175+
return !c.getExpiresAt().isBefore(LocalDateTime.now());
176+
} catch (JsonProcessingException e) {
177+
log.error("Not able to parse invalidToken json value.", e);
178+
return true;
179+
}
180+
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
181+
tokenCache.put(serviceId + mapKey, result);
182+
}
183+
}
184+
185+
@Override
186+
public void removeNonRelevantRules(String serviceId, String mapKey) {
187+
CompletableFuture<Boolean> complete = lock.tryLock(4, TimeUnit.SECONDS).whenComplete((r, ex) -> {
188+
if (Boolean.TRUE.equals(r)) {
189+
try {
190+
long timestamp = System.currentTimeMillis();
191+
Map<String, String> map = tokenCache.get(serviceId + mapKey);
192+
if (map != null && !map.isEmpty()) {
193+
Map<String,String> result = map.entrySet().stream().filter(entry -> {
194+
long delta = timestamp - Long.parseLong(entry.getValue());
195+
long deltaToDays = TimeUnit.MILLISECONDS.toDays(delta);
196+
return deltaToDays <= 90;
197+
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
198+
tokenCache.put(serviceId + mapKey, result);
199+
}
200+
} finally {
201+
lock.unlock();
202+
}
203+
}
204+
});
205+
completeJoin(complete);
206+
}
207+
208+
private void completeJoin(CompletableFuture<Boolean> complete) {
209+
try {
210+
complete.join();
211+
} catch (CompletionException e) {
212+
if (e.getCause() instanceof StorageException) {
213+
throw (StorageException) e.getCause();
214+
} else {
215+
log.error("Unexpected error while acquiring the lock ", e);
216+
throw e;
217+
}
218+
}
219+
}
156220
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ public void deleteForService(String serviceId) {
126126
storage.remove(serviceId);
127127
}
128128

129+
@Override
130+
public void removeNonRelevantTokens(String serviceId, String mapKey) {
131+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
132+
}
133+
134+
@Override
135+
public void removeNonRelevantRules(String serviceId, String mapKey) {
136+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
137+
}
138+
129139
private boolean isKeyNotInCache(String serviceId, String keyToTest) {
130140
Map<String, KeyValue> serviceSpecificStorage = storage.get(serviceId);
131141
return serviceSpecificStorage == null || serviceSpecificStorage.get(keyToTest) == null;

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,14 @@ public void deleteForService(String serviceId) {
142142
log.info("No entries were deleted for {}", serviceId);
143143
}
144144
}
145+
146+
@Override
147+
public void removeNonRelevantTokens(String serviceId, String mapKey) {
148+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
149+
}
150+
151+
@Override
152+
public void removeNonRelevantRules(String serviceId, String mapKey) {
153+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
154+
}
145155
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,14 @@ public void deleteForService(String serviceId) {
208208
file.deleteForService(serviceId);
209209
}
210210
}
211+
212+
@Override
213+
public void removeNonRelevantTokens(String serviceId, String mapKey) {
214+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
215+
}
216+
217+
@Override
218+
public void removeNonRelevantRules(String serviceId, String mapKey) {
219+
throw new StorageException(Messages.INCOMPATIBLE_STORAGE_METHOD.getKey(), Messages.INCOMPATIBLE_STORAGE_METHOD.getStatus());
220+
}
211221
}

caching-service/src/test/java/org/zowe/apiml/caching/api/CachingControllerTest.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,34 @@ void givenNoCertificateInformation_thenReturnUnauthorized() throws StorageExcept
387387
}
388388

389389
@Test
390-
void givenErrorReadingStorage_thenResponseInternalError() throws StorageException {
390+
void givenErrorReadingStorage_thenResponseBadRequest() throws StorageException {
391391
when(mockStorage.getAllMapItems(any(), any())).thenThrow(new RuntimeException("error"));
392392

393393
ResponseEntity<?> response = underTest.getAllMapItems(any(), mockRequest);
394394
assertThat(response.getStatusCode(), is(HttpStatus.BAD_REQUEST));
395395
}
396396
}
397+
398+
@Nested
399+
class WhenEvictRecord {
400+
@Test
401+
void givenCorrectRequest_thenRemoveTokensAndRules() throws StorageException {
402+
ResponseEntity<?> responseTokenEviction = underTest.evictTokens(MAP_KEY, mockRequest);
403+
ResponseEntity<?> responseScopesEviction = underTest.evictRules(MAP_KEY, mockRequest);
404+
verify(mockStorage).removeNonRelevantTokens(SERVICE_ID, MAP_KEY);
405+
verify(mockStorage).removeNonRelevantRules(SERVICE_ID, MAP_KEY);
406+
assertThat(responseTokenEviction.getStatusCode(), is(HttpStatus.NO_CONTENT));
407+
assertThat(responseScopesEviction.getStatusCode(), is(HttpStatus.NO_CONTENT));
408+
}
409+
410+
@Test
411+
void givenInCorrectRequest_thenReturn500() throws StorageException {
412+
doThrow(new RuntimeException()).when(mockStorage).removeNonRelevantTokens(SERVICE_ID, MAP_KEY);
413+
doThrow(new RuntimeException()).when(mockStorage).removeNonRelevantRules(SERVICE_ID, MAP_KEY);
414+
ResponseEntity<?> responseScopesEviction = underTest.evictRules(MAP_KEY, mockRequest);
415+
ResponseEntity<?> responseTokenEviction = underTest.evictTokens(MAP_KEY, mockRequest);
416+
assertThat(responseTokenEviction.getStatusCode(), is(HttpStatus.INTERNAL_SERVER_ERROR));
417+
assertThat(responseScopesEviction.getStatusCode(), is(HttpStatus.INTERNAL_SERVER_ERROR));
418+
}
419+
}
397420
}

0 commit comments

Comments
 (0)