From 46acad12be478035f9d0392c335e38c32756a88b Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 11 Oct 2024 12:43:33 -0400 Subject: [PATCH] Add cache inspection and eviction services Added new endpoints for cache inspection and introduced CacheEvictionService to handle cache eviction for users. Security configuration was updated to allow access to cache endpoints, and several services were modified to utilize the new cache eviction mechanisms. --- cache.http | 12 ++++++ .../auth/config/CustomKeyGenerator.java | 16 ++++++- .../auth/config/CustomLogoutHandler.java | 3 +- .../avillach/auth/config/SecurityConfig.java | 3 +- .../avillach/auth/rest/CacheController.java | 42 +++++++++++++++++++ .../auth/service/impl/AccessRuleService.java | 10 ----- .../service/impl/CacheEvictionService.java | 34 +++++++++++++++ .../auth/service/impl/RASPassPortService.java | 7 +++- .../auth/service/impl/UserService.java | 1 - .../AimAheadAuthenticationService.java | 18 +++----- .../Auth0AuthenticationService.java | 8 +++- .../FENCEAuthenticationService.java | 17 ++++---- .../RASAuthenticationService.java | 14 ++----- .../authorization/AuthorizationService.java | 19 ++++----- .../src/main/resources/application.properties | 7 ++++ .../impl/AuthenticationServiceTest.java | 4 +- .../impl/AuthorizationServiceTest.java | 1 + .../service/impl/RASPassPortServiceTest.java | 2 +- .../AuthenticationServiceTest.java | 11 +++-- .../RASAuthenticationServiceTest.java | 14 ++++--- .../AuthorizationServiceTest.java | 1 + 21 files changed, 176 insertions(+), 68 deletions(-) create mode 100644 cache.http create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/CacheController.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CacheEvictionService.java diff --git a/cache.http b/cache.http new file mode 100644 index 00000000..8b380736 --- /dev/null +++ b/cache.http @@ -0,0 +1,12 @@ +# This class can be used to interact with the CacheController in the PSAMA API +# The CacheController must be enable in the application.properties file +# You can enable it by setting the following property: +# app.cache.inspect.enabled=true +# If you are using a environment variable to set the property, you can set it like this: +# CACHE_INSPECT_ENABLED=true + +### GET /cache - Get a list of all caches in the application +GET https://dev.picsure.biodatacatalyst.nhlbi.nih.gov/psama/cache + +### GET /cache/{cacheName} - Get the contents of a specific cache +GET https://dev.picsure.biodatacatalyst.nhlbi.nih.gov/psama/cache/mergedRulesCache \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/CustomKeyGenerator.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/CustomKeyGenerator.java index 75485f4a..248bf391 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/CustomKeyGenerator.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/CustomKeyGenerator.java @@ -1,20 +1,34 @@ package edu.harvard.hms.dbmi.avillach.auth.config; import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.cache.interceptor.KeyGenerator; import java.lang.reflect.Method; public class CustomKeyGenerator implements KeyGenerator { + private final Logger logger = LoggerFactory.getLogger(CustomKeyGenerator.class); + @Override public Object generate(Object target, Method method, Object... params) { + String key = null; for (Object param : params) { if (param instanceof User user) { - return user.getSubject(); + key = user.getSubject(); + } + + if (param instanceof String userSubject) { + key = userSubject; } } + if (key != null) { + logger.info("Generated cache key: {}", key); + return key; + } + throw new IllegalArgumentException("No valid params found. Cannot generate cache key"); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/CustomLogoutHandler.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/CustomLogoutHandler.java index 4b82e74d..a92cb5dc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/CustomLogoutHandler.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/CustomLogoutHandler.java @@ -49,7 +49,8 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut logger.info("logout() Logging out User: {}", subject); this.sessionService.endSession(subject); this.userService.evictFromCache(subject); - this.accessRuleService.evictFromCache(subject); + this.accessRuleService.evictFromMergedAccessRuleCache(subject); + this.accessRuleService.evictFromPreProcessedAccessRules(subject); this.userService.removeUserPassport(subject); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java index 3b47eb4b..a5fce600 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java @@ -61,7 +61,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/user/me/queryTemplate", "/user/me/queryTemplate/**", "/open/validate", - "/logout" + "/logout", + "/cache/**" ).permitAll() .anyRequest().authenticated() ) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/CacheController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/CacheController.java new file mode 100644 index 00000000..36ebccb5 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/CacheController.java @@ -0,0 +1,42 @@ +package edu.harvard.hms.dbmi.avillach.auth.rest; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collection; + +@RestController +@ConditionalOnExpression("${app.cache.inspect.enabled:false}") +@RequestMapping("/cache") +public class CacheController { + + private final CacheManager cacheManager; + + @Autowired + public CacheController(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + @GetMapping + public Collection getCacheNames() { + return cacheManager.getCacheNames(); + } + + @GetMapping("/{cacheName}") + public Object getCache(@PathVariable("cacheName") String cacheName) { + Cache cache = cacheManager.getCache(cacheName); + if (cache == null) { + throw new IllegalArgumentException("Cache not found: " + cacheName); + } + + return cache.getNativeCache(); + } + + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java index e032821a..971bab1c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java @@ -158,16 +158,6 @@ public Set getAccessRulesForUserAndApp(User user, Application applic return new HashSet<>(); } - /** - * Evicts the user from all AccessRule caches - * @param userSubject the email to evict - */ - public void evictFromCache(String userSubject) { - logger.info("evictFromCache called for user.email: {}", userSubject); - evictFromMergedAccessRuleCache(userSubject); - evictFromPreProcessedAccessRules(userSubject); - } - @CacheEvict(value = "mergedRulesCache") public void evictFromMergedAccessRuleCache(String userSubject) { if (StringUtils.isBlank(userSubject)) { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CacheEvictionService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CacheEvictionService.java new file mode 100644 index 00000000..eb251145 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CacheEvictionService.java @@ -0,0 +1,34 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class CacheEvictionService { + + private final SessionService sessionService; + private final UserService userService; + private final AccessRuleService accessRuleService; + + @Autowired + public CacheEvictionService(SessionService sessionService, UserService userService, AccessRuleService accessRuleService) { + this.sessionService = sessionService; + this.userService = userService; + this.accessRuleService = accessRuleService; + } + + public void evictCache(String userSubject) { + this.sessionService.endSession(userSubject); + this.userService.evictFromCache(userSubject); + this.accessRuleService.evictFromMergedAccessRuleCache(userSubject); + this.accessRuleService.evictFromPreProcessedAccessRules(userSubject); + this.userService.removeUserPassport(userSubject); + } + + public void evictCache(User user) { + String userSubject = user.getSubject(); + evictCache(userSubject); + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/RASPassPortService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/RASPassPortService.java index f1c61643..9d3be7d5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/RASPassPortService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/RASPassPortService.java @@ -33,14 +33,16 @@ public class RASPassPortService { private final RestClientUtil restClientUtil; private final UserService userService; private final String rasURI; + private final CacheEvictionService cacheEvictionService; @Autowired public RASPassPortService(RestClientUtil restClientUtil, UserService userService, - @Value("${ras.idp.uri}") String rasURI) { + @Value("${ras.idp.uri}") String rasURI, CacheEvictionService cacheEvictionService) { this.restClientUtil = restClientUtil; this.userService = userService; this.rasURI = rasURI.replaceAll("/$", ""); + this.cacheEvictionService = cacheEvictionService; logger.info("RASPassPortService initialized with rasURI: {}", rasURI); } @@ -72,7 +74,7 @@ public void validateAllUserPassports() { logger.error("FAILED TO DECODE PASSPORT ___ USER: {}", user.getSubject()); user.setPassport(null); userService.save(user); - userService.logoutUser(user); + cacheEvictionService.evictCache(user); return; } @@ -126,6 +128,7 @@ private boolean handlePassportValidationResponse(String response, User user) { */ private boolean handleFailedValidationResponse(String validateResponse, User user) { this.userService.save(user); + this.userService.evictFromCache(user.getSubject()); this.userService.logoutUser(user); this.logger.info("handleFailedValidationResponse - {} - USER LOGGED OUT - {}", validateResponse, user.getSubject()); return false; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 6d59ba37..8666a2ae 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -665,7 +665,6 @@ public Set getAllUsersWithAPassport() { * @param user */ public void logoutUser(User user) { - evictFromCache(user.getSubject()); this.removeUserPassport(user.getSubject()); this.sessionService.endSession(user.getSubject()); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AimAheadAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AimAheadAuthenticationService.java index 736198f0..54e61b58 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AimAheadAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AimAheadAuthenticationService.java @@ -8,7 +8,7 @@ import edu.harvard.hms.dbmi.avillach.auth.service.AuthenticationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.RoleService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.AccessRuleService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.CacheEvictionService; import edu.harvard.hms.dbmi.avillach.auth.utils.RestClientUtil; import jakarta.persistence.NoResultException; import org.apache.commons.lang3.StringUtils; @@ -32,7 +32,8 @@ public class AimAheadAuthenticationService extends OktaAuthenticationService imp private final String connectionId; private final boolean isOktaEnabled; - private final AccessRuleService accessRuleService; + + private final CacheEvictionService cacheEvictionService; /** * Constructor for the OktaOAuthAuthenticationService @@ -46,26 +47,24 @@ public class AimAheadAuthenticationService extends OktaAuthenticationService imp @Autowired public AimAheadAuthenticationService(UserService userService, RoleService roleService, - AccessRuleService accessRuleService, RestClientUtil restClientUtil, @Value("${a4.okta.idp.provider.is.enabled}") boolean isOktaEnabled, @Value("${a4.okta.idp.provider.uri}") String idp_provider_uri, @Value("${a4.okta.connection.id}") String connectionId, @Value("${a4.okta.client.id}") String clientId, - @Value("${a4.okta.client.secret}") String spClientSecret) { + @Value("${a4.okta.client.secret}") String spClientSecret, CacheEvictionService cacheEvictionService) { super(idp_provider_uri, clientId, spClientSecret, restClientUtil); this.userService = userService; this.roleService = roleService; this.connectionId = connectionId; this.isOktaEnabled = isOktaEnabled; + this.cacheEvictionService = cacheEvictionService; logger.info("OktaOAuthAuthenticationService is enabled: {}", isOktaEnabled); logger.info("OktaOAuthAuthenticationService initialized"); logger.info("idp_provider_uri: {}", idp_provider_uri); logger.info("connectionId: {}", connectionId); - - this.accessRuleService = accessRuleService; } /** @@ -128,7 +127,7 @@ private User initializeUser(JsonNode introspectResponse) { return null; } - clearCache(user); + cacheEvictionService.evictCache(user); return user; } @@ -146,11 +145,6 @@ private HashMap createUserClaims(User user) { return userService.getUserProfileResponse(claims); } - private void clearCache(User user) { - userService.evictFromCache(user.getSubject()); - accessRuleService.evictFromCache(user.getSubject()); - } - /** * Using the introspection token response, load the user from the database. If the user does not exist, we * will reject their login attempt. diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/Auth0AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/Auth0AuthenticationService.java index 8c7f4acc..6a0f8a23 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/Auth0AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/Auth0AuthenticationService.java @@ -9,6 +9,7 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.service.AuthenticationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.BasicMailService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.CacheEvictionService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.OauthUserMatchingService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.RestClientUtil; @@ -46,6 +47,7 @@ public class Auth0AuthenticationService implements AuthenticationService { private static final int AUTH_RETRY_LIMIT = 3; private final boolean isAuth0Enabled; + private final CacheEvictionService cacheEvictionService; private boolean deniedEmailEnabled; @@ -64,8 +66,8 @@ public Auth0AuthenticationService(OauthUserMatchingService matchingService, RestClientUtil restClientUtil, @Value("${auth0.idp.provider.is.enabled}") boolean isAuth0Enabled, @Value("${auth0.denied.email.enabled}") boolean deniedEmailEnabled, - @Value("${auth0.host}") String auth0host - ) { + @Value("${auth0.host}") String auth0host, + CacheEvictionService cacheEvictionService) { this.matchingService = matchingService; this.userRepository = userRepository; this.basicMailService = basicMailService; @@ -75,6 +77,7 @@ public Auth0AuthenticationService(OauthUserMatchingService matchingService, this.connectionRepository = connectionRepository; this.restClientUtil = restClientUtil; this.isAuth0Enabled = isAuth0Enabled; + this.cacheEvictionService = cacheEvictionService; } @Override @@ -121,6 +124,7 @@ public HashMap authenticate(Map authRequest, Str } } + cacheEvictionService.evictCache(user); HashMap claims = new HashMap<>(); claims.put("sub", userId); claims.put("name", user.getName()); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/FENCEAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/FENCEAuthenticationService.java index 10b624e0..6ec96481 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/FENCEAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/FENCEAuthenticationService.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; @@ -38,6 +39,9 @@ public class FENCEAuthenticationService implements AuthenticationService { private final ConnectionWebService connectionService; // We will need to investigate if the ConnectionWebService will need to be versioned as well. private final AccessRuleService accessRuleService; private final FenceMappingUtility fenceMappingUtility; + private final String applicationUUID; + private final ApplicationService applicationService; + private final CacheEvictionService cacheEvictionService; private Connection fenceConnection; @@ -57,8 +61,9 @@ public FENCEAuthenticationService(UserService userService, @Value("${fence.idp.provider.uri}") String idpProviderUri, @Value("${fence.client.id}") String fenceClientId, @Value("${fence.client.secret}") String fenceClientSecret, + @Value("${application.default.uuid}") String applicationUUID, AccessRuleService accessRuleService, - FenceMappingUtility fenceMappingUtility) { + FenceMappingUtility fenceMappingUtility, ApplicationService applicationService, CacheEvictionService cacheEvictionService) { this.userService = userService; this.connectionService = connectionService; this.idp_provider_uri = idpProviderUri; @@ -68,6 +73,9 @@ public FENCEAuthenticationService(UserService userService, this.accessRuleService = accessRuleService; this.fenceMappingUtility = fenceMappingUtility; this.isFenceEnabled = isFenceEnabled; + this.applicationUUID = applicationUUID; + this.applicationService = applicationService; + this.cacheEvictionService = cacheEvictionService; } @PostConstruct @@ -119,12 +127,8 @@ public HashMap authenticate(Map authRequest, Str // in the Gen3/FENCE profile currentUser = createUserFromFENCEProfile(fence_user_profile); logger.info("getFENCEProfile() saved details for user with e-mail:{} and subject:{}", currentUser.getEmail(), currentUser.getSubject()); + cacheEvictionService.evictCache(currentUser); - if (!currentUser.getEmail().isEmpty()) { - String subject = currentUser.getSubject(); - accessRuleService.evictFromCache(subject); - userService.evictFromCache(subject); - } } catch (Exception ex) { logger.error("getFENCEToken() Could not persist the user information, because {}", ex.getMessage()); throw new NotAuthorizedException("The user details could not be persisted. Please contact the administrator."); @@ -167,7 +171,6 @@ public HashMap authenticate(Map authRequest, Str } - @Override public String getProvider() { return "fence"; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/RASAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/RASAuthenticationService.java index 95df31d1..254bace5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/RASAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/RASAuthenticationService.java @@ -28,9 +28,9 @@ public class RASAuthenticationService extends OktaAuthenticationService implemen private final UserService userService; private final boolean isEnabled; - private final AccessRuleService accessRuleService; private final RoleService roleService; private final RASPassPortService rasPassPortService; + private final CacheEvictionService cacheEvictionService; private Connection rasConnection; private final String rasPassportIssuer; @@ -45,7 +45,6 @@ public class RASAuthenticationService extends OktaAuthenticationService implemen */ @Autowired public RASAuthenticationService(UserService userService, - AccessRuleService accessRuleService, RestClientUtil restClientUtil, @Value("${ras.okta.idp.provider.is.enabled}") boolean isEnabled, @Value("${ras.okta.idp.provider.uri}") String idp_provider_uri, @@ -55,7 +54,7 @@ public RASAuthenticationService(UserService userService, @Value("${ras.passport.issuer}") String rasPassportIssuer, RoleService roleService, RASPassPortService rasPassPortService, - ConnectionWebService connectionService ) { + ConnectionWebService connectionService, CacheEvictionService cacheEvictionService) { super(idp_provider_uri, clientId, clientSecret, restClientUtil); this.userService = userService; @@ -69,8 +68,8 @@ public RASAuthenticationService(UserService userService, logger.info("idp_provider_uri: {}", idp_provider_uri); logger.info("connectionId: {}", connectionId); - this.accessRuleService = accessRuleService; this.rasConnection = connectionService.getConnectionByLabel("RAS"); + this.cacheEvictionService = cacheEvictionService; } /** @@ -161,7 +160,7 @@ private Optional initializeUser(JsonNode introspectResponse) { currentUser.setGeneralMetadata(generateRasUserMetadata(currentUser).toString()); logger.info("USER METADATA SUCCESSFULLY ADDED - USER DATA: {}", currentUser.getGeneralMetadata()); - clearCache(currentUser); + cacheEvictionService.evictCache(currentUser); return Optional.of(currentUser); } @@ -180,11 +179,6 @@ private HashMap createUserClaims(User user, String idToken) { return userService.getUserProfileResponse(claims); } - private void clearCache(User user) { - userService.evictFromCache(user.getSubject()); - accessRuleService.evictFromCache(user.getSubject()); - } - /** * Generate the user metadata that will be stored in the database. This metadata is used to determine the user's * role and other information. diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authorization/AuthorizationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authorization/AuthorizationService.java index 65f8cb4a..7ab39432 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authorization/AuthorizationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authorization/AuthorizationService.java @@ -150,7 +150,13 @@ public boolean isAuthorized(Application application, Object requestBody, User us label = user.getConnection().getLabel(); } - if (!this.strictConnections.contains(label)) { + if (this.strictConnections.contains(label)) { + accessRules = this.accessRuleService.getAccessRulesForUserAndApp(user, application); + if (accessRules.isEmpty()) { + logger.info("ACCESS_LOG ___ {},{},{} ___ has been denied access to execute query ___ {} ___ in application ___ {} ___ NO ACCESS RULES EVALUATED", user.getUuid().toString(), user.getEmail(), user.getName(), formattedQuery, applicationName); + return false; + } + } else { Set privileges = user.getPrivilegesByApplication(application); // List all privileges of the user logger.info("ACCESS_LOG ___ {},{},{} ___ has the following privileges: {}", user.getUuid().toString(), user.getEmail(), user.getName(), privileges.stream().map(Privilege::getName).collect(Collectors.joining(", "))); @@ -160,21 +166,14 @@ public boolean isAuthorized(Application application, Object requestBody, User us } accessRules = this.accessRuleService.cachedPreProcessAccessRules(user, privileges); - logger.info("ACCESS_LOG ___ {},{},{} ___ has the following access rules: {}", user.getUuid().toString(), user.getEmail(), user.getName(), accessRules.stream().map(AccessRule::getName).collect(Collectors.joining(", "))); if (accessRules.isEmpty()) { logger.info("ACCESS_LOG ___ {},{},{} ___ has been granted access to execute query ___ {} ___ in application ___ {} ___ NO ACCESS RULES EVALUATED", user.getUuid().toString(), user.getEmail(), user.getName(), formattedQuery, applicationName); return true; } - } else { - accessRules = this.accessRuleService.getAccessRulesForUserAndApp(user, application); - - logger.info("ACCESS_LOG ___ {},{},{} ___ has the following access rules: {}", user.getUuid().toString(), user.getEmail(), user.getName(), accessRules.stream().map(AccessRule::toString).collect(Collectors.joining(", "))); - if (accessRules.isEmpty()) { - logger.info("ACCESS_LOG ___ {},{},{} ___ has been denied access to execute query ___ {} ___ in application ___ {} ___ NO ACCESS RULES EVALUATED", user.getUuid().toString(), user.getEmail(), user.getName(), formattedQuery, applicationName); - return false; - } } + logger.info("ACCESS_LOG ___ {},{},{} ___ has the following access rules: {}", user.getUuid().toString(), user.getEmail(), user.getName(), accessRules.stream().map(AccessRule::toString).collect(Collectors.joining(", "))); + EvaluateAccessRuleResult evaluationResult = passesAccessRuleEvaluation(requestBody, accessRules); boolean result = evaluationResult.result(); String passRuleName = evaluationResult.passRuleName(); diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index e51b7d3f..e2c227c3 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -24,6 +24,13 @@ logging.level.org.springframework.security=${LOGGING_LEVEL_SECURITY:INFO} logging.level.root=${LOGGING_LEVEL_ROOT:INFO} logging.level.org.springframework.web=${LOGGING_LEVEL_SLF4J:INFO} logging.level.edu.harvard.hms.dbmi.avillach.auth.service.impl.authentication.RASAuthenticationService=${LOGGING_LEVEL_RAS_AUTHENTICATION:INFO} +logging.level.edu.harvard.hms.dbmi.avillach.auth.service.impl.authentication.FENCEAuthenticationService=${LOGGING_LEVEL_FENCE_AUTHENTICATION:INFO} +logging.level.edu.harvard.hms.dbmi.avillach.auth.service.impl.AccessRuleService=${LOGGING_LEVEL_ACCESS_RULE_SERVICE:INFO} +logging.level.org.springframework.cache=${LOGGING_LEVEL_CACHE:INFO} + +# Cache Controller Configuration. This is used to gain insight into the cache. +# This should never be enabled in production. +app.cache.inspect.enabled=${CACHE_INSPECT_ENABLED:false} # Mail session configuration (Assuming Gmail SMTP for example) spring.mail.host=smtp.gmail.com diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java index e0720425..621b50de 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java @@ -42,6 +42,8 @@ public class AuthenticationServiceTest { private ConnectionRepository connectionRepository; @Mock private RestClientUtil restClientUtil; + @Mock + private CacheEvictionService cacheEvictionService; private Auth0AuthenticationService authenticationService; @@ -58,7 +60,7 @@ public void setUp() { authRequest.put("access_token", accessToken); authRequest.put("redirectURI", redirectURI); - authenticationService = new Auth0AuthenticationService(matchingService, userRepository, basicMailService, userService, connectionRepository, restClientUtil, true, false, "localhost"); + authenticationService = new Auth0AuthenticationService(matchingService, userRepository, basicMailService, userService, connectionRepository, restClientUtil, true, false, "localhost", cacheEvictionService); } // Tests missing parameters in the authentication request diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java index ed95ccb9..19381278 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java @@ -52,6 +52,7 @@ public void testIsAuthorized_AccessRulePassed() { // create access_rule for privilege AccessRule accessRule = new AccessRule(); + accessRule.setUuid(UUID.randomUUID()); accessRule.setRule("$.test"); accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); accessRule.setValue("value"); diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/RASPassPortServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/RASPassPortServiceTest.java index 01e9eea5..39fdb666 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/RASPassPortServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/RASPassPortServiceTest.java @@ -33,7 +33,7 @@ public class RASPassPortServiceTest { @Before public void setUp() throws Exception { - this.rasPassPortService = new RASPassPortService(restClientUtil, null, "https://test.com/"); + this.rasPassPortService = new RASPassPortService(restClientUtil, null, "https://test.com/", null); // Parse the passport and get the visa so we can mock the validateVisa method Optional passport = JWTUtil.parsePassportJWTV11(exampleRasPassport); diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AuthenticationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AuthenticationServiceTest.java index df74304e..e9aa36f7 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AuthenticationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AuthenticationServiceTest.java @@ -7,6 +7,7 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.service.impl.BasicMailService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.CacheEvictionService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.OauthUserMatchingService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.RestClientUtil; @@ -44,13 +45,12 @@ public class AuthenticationServiceTest { private ConnectionRepository connectionRepository; @Mock private RestClientUtil restClientUtil; + @Mock + private CacheEvictionService cacheEvictionService; private Auth0AuthenticationService authenticationService; private final String accessToken = "dummyAccessToken"; - private final String redirectURI = "http://dummyRedirectUri.com"; - private final String userId = "user123"; - private final String connectionId = "conn123"; private Map authRequest; @Before @@ -58,9 +58,10 @@ public void setUp() { MockitoAnnotations.initMocks(this); authRequest = new HashMap<>(); authRequest.put("access_token", accessToken); + String redirectURI = "http://dummyRedirectUri.com"; authRequest.put("redirectURI", redirectURI); - authenticationService = new Auth0AuthenticationService(matchingService, userRepository, basicMailService, userService, connectionRepository, restClientUtil, true, false, "localhost"); + authenticationService = new Auth0AuthenticationService(matchingService, userRepository, basicMailService, userService, connectionRepository, restClientUtil, true, false, "localhost", cacheEvictionService); } // Tests missing parameters in the authentication request @@ -131,10 +132,12 @@ private void setupSuccessfulTokenRetrievalScenario() throws IOException { this.authenticationService.setDeniedEmailEnabled(false); JsonNode mockUserInfo = mock(JsonNode.class); when(mockUserInfo.get("user_id")).thenReturn(mock(JsonNode.class)); + String userId = "user123"; when(mockUserInfo.get("user_id").asText()).thenReturn(userId); when(mockUserInfo.get("identities")).thenReturn(mock(JsonNode.class)); when(mockUserInfo.get("identities").get(0)).thenReturn(mock(JsonNode.class)); when(mockUserInfo.get("identities").get(0).get("connection")).thenReturn(mock(JsonNode.class)); + String connectionId = "conn123"; when(mockUserInfo.get("identities").get(0).get("connection").asText()).thenReturn(connectionId); String validJson = "{" diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/RASAuthenticationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/RASAuthenticationServiceTest.java index dbc7ceec..431a12cc 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/RASAuthenticationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/RASAuthenticationServiceTest.java @@ -15,6 +15,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.OngoingStubbing; import org.springframework.http.ResponseEntity; import java.util.*; @@ -28,11 +29,12 @@ public class RASAuthenticationServiceTest { @Mock private UserService userService; @Mock - private AccessRuleService accessRuleService; - @Mock private RestClientUtil restClientUtil; @Mock private ConnectionWebService connectionService; + @Mock + private CacheEvictionService cacheEvictionService; + private RASPassPortService rasPassPortService; private RASAuthenticationService rasAuthenticationService; @@ -46,12 +48,11 @@ public class RASAuthenticationServiceTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); RoleService roleService = new RoleService(mock(RoleRepository.class), mock(PrivilegeService.class), mock(FenceMappingUtility.class)); - this.rasPassPortService = spy(new RASPassPortService(restClientUtil, userService, "")); + this.rasPassPortService = spy(new RASPassPortService(restClientUtil, userService, "", cacheEvictionService)); doReturn(false).when(rasPassPortService).isExpired(any()); rasAuthenticationService = new RASAuthenticationService( userService, - accessRuleService, restClientUtil, true, "test.com", @@ -61,7 +62,8 @@ public void setUp() throws Exception { "https://stsstg.nih.gov", roleService, rasPassPortService, - connectionService + connectionService, + cacheEvictionService ); Connection rasConnection = new Connection(); @@ -91,6 +93,8 @@ public void testAuthorizationCodeFlow_Successful() { // introspect when(restClientUtil.retrievePostResponse(anyString(), any(), eq(payload))).thenReturn(ResponseEntity.ok(introspectionResponse)); + doNothing().when(cacheEvictionService).evictCache(any(User.class)); + User user = createTestUser(); user.setSubject("okta-ras|adfadfaf"); when(userService.createRasUser(any(), any())).thenReturn(Optional.of(user)); diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authorization/AuthorizationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authorization/AuthorizationServiceTest.java index 7a779eaf..5a81ab52 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authorization/AuthorizationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authorization/AuthorizationServiceTest.java @@ -332,6 +332,7 @@ public void testIsAuthorized_AccessRulePassed() { // create access_rule for privilege AccessRule accessRule = new AccessRule(); + accessRule.setUuid(UUID.randomUUID()); accessRule.setRule("$.test"); accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); accessRule.setValue("value");