Skip to content

Commit

Permalink
Add cache inspection and eviction services
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Gcolon021 committed Oct 11, 2024
1 parent 60cdb6c commit 46acad1
Show file tree
Hide file tree
Showing 21 changed files with 176 additions and 68 deletions.
12 changes: 12 additions & 0 deletions cache.http
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,6 @@ public Set<AccessRule> 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)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,6 @@ public Set<User> getAllUsersWithAPassport() {
* @param user
*/
public void logoutUser(User user) {
evictFromCache(user.getSubject());
this.removeUserPassport(user.getSubject());
this.sessionService.endSession(user.getSubject());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -128,7 +127,7 @@ private User initializeUser(JsonNode introspectResponse) {
return null;
}

clearCache(user);
cacheEvictionService.evictCache(user);
return user;
}

Expand All @@ -146,11 +145,6 @@ private HashMap<String, String> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -75,6 +77,7 @@ public Auth0AuthenticationService(OauthUserMatchingService matchingService,
this.connectionRepository = connectionRepository;
this.restClientUtil = restClientUtil;
this.isAuth0Enabled = isAuth0Enabled;
this.cacheEvictionService = cacheEvictionService;
}

@Override
Expand Down Expand Up @@ -121,6 +124,7 @@ public HashMap<String, String> authenticate(Map<String, String> authRequest, Str
}
}

cacheEvictionService.evictCache(user);
HashMap<String, Object> claims = new HashMap<>();
claims.put("sub", userId);
claims.put("name", user.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -119,12 +127,8 @@ public HashMap<String, String> authenticate(Map<String, String> 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.");
Expand Down Expand Up @@ -167,7 +171,6 @@ public HashMap<String, String> authenticate(Map<String, String> authRequest, Str
}



@Override
public String getProvider() {
return "fence";
Expand Down
Loading

0 comments on commit 46acad1

Please sign in to comment.