Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ALS-7575] Add cache inspection and eviction services #222

Merged
merged 6 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,7 +1,6 @@
package edu.harvard.hms.dbmi.avillach.auth.config;

import edu.harvard.hms.dbmi.avillach.auth.service.impl.AccessRuleService;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.SessionService;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.CacheEvictionService;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService;
import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil;
import io.jsonwebtoken.Claims;
Expand All @@ -18,15 +17,13 @@
public class CustomLogoutHandler implements LogoutHandler {

private final Logger logger = LoggerFactory.getLogger(CustomLogoutHandler.class);
private final SessionService sessionService;
private final UserService userService;
private final AccessRuleService accessRuleService;
private final CacheEvictionService cacheEvictionService;
private final JWTUtil jwtUtil;

public CustomLogoutHandler(SessionService sessionService, UserService userService, AccessRuleService accessRuleService, edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil jwtUtil) {
this.sessionService = sessionService;
public CustomLogoutHandler(UserService userService, CacheEvictionService cacheEvictionService, edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil jwtUtil) {
this.userService = userService;
this.accessRuleService = accessRuleService;
this.cacheEvictionService = cacheEvictionService;
this.jwtUtil = jwtUtil;
}

Expand All @@ -47,9 +44,7 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut

if (StringUtils.isNotBlank(subject)) {
logger.info("logout() Logging out User: {}", subject);
this.sessionService.endSession(subject);
this.userService.evictFromCache(subject);
this.accessRuleService.evictFromCache(subject);
this.cacheEvictionService.evictCache(subject);
this.userService.removeUserPassport(subject);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import edu.harvard.hms.dbmi.avillach.auth.filter.JWTFilter;
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.service.impl.SessionService;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService;
import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil;
Expand All @@ -24,25 +25,23 @@
public class SecurityConfig {

private final JWTFilter jwtFilter;
private final SessionService sessionService;
private final CacheEvictionService cacheEvictionService;
private final AuthenticationProvider authenticationProvider;
private final UserService userService;
private final AccessRuleService accessRuleService;
private final JWTUtil jwtUtil;

@Autowired
public SecurityConfig(JWTFilter jwtFilter, SessionService sessionService, AuthenticationProvider authenticationProvider, UserService userService, AccessRuleService accessRuleService, JWTUtil jwtUtil) {
public SecurityConfig(JWTFilter jwtFilter, AuthenticationProvider authenticationProvider, UserService userService, CacheEvictionService cacheEvictionService, JWTUtil jwtUtil) {
this.jwtFilter = jwtFilter;
this.sessionService = sessionService;
this.authenticationProvider = authenticationProvider;
this.userService = userService;
this.accessRuleService = accessRuleService;
this.jwtUtil = jwtUtil;
this.cacheEvictionService = cacheEvictionService;
}

@Bean
public CustomLogoutHandler customLogoutHandler() {
return new CustomLogoutHandler(sessionService, userService, accessRuleService, jwtUtil);
return new CustomLogoutHandler(userService, cacheEvictionService, jwtUtil);
}

@Bean
Expand All @@ -61,7 +60,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,33 @@
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);
}

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 @@ -127,6 +129,7 @@ private boolean handlePassportValidationResponse(String response, User user) {
private boolean handleFailedValidationResponse(String validateResponse, User user) {
this.userService.save(user);
this.userService.logoutUser(user);
this.cacheEvictionService.evictCache(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 @@ -55,7 +55,6 @@ public class UserService {
private final RoleService roleService;
private final long tokenExpirationTime;
private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour
private final SessionService sessionService;
private final boolean openAccessIsEnabled;

public long longTermTokenExpirationTime;
Expand All @@ -73,7 +72,7 @@ public UserService(BasicMailService basicMailService, TOSService tosService,
@Value("${application.token.expiration.time}") long tokenExpirationTime,
@Value("${application.default.uuid}") String applicationUUID,
@Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime,
JWTUtil jwtUtil, SessionService sessionService,
JWTUtil jwtUtil,
@Value("${open.idp.provider.is.enabled}") boolean openIdpProviderIsEnabled) {
this.basicMailService = basicMailService;
this.tosService = tosService;
Expand All @@ -88,7 +87,6 @@ public UserService(BasicMailService basicMailService, TOSService tosService,

long defaultLongTermTokenExpirationTime = 1000L * 60 * 60 * 24 * 30;
this.longTermTokenExpirationTime = longTermTokenExpirationTime > 0 ? longTermTokenExpirationTime : defaultLongTermTokenExpirationTime;
this.sessionService = sessionService;
this.openAccessIsEnabled = openIdpProviderIsEnabled;
}

Expand Down Expand Up @@ -665,9 +663,7 @@ 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
Loading