chore: Create helper method for user session service (#23243)
## Description For security concious customers we want to enable the tenant level setting to enable single session per user. Which means if user tries to login with different browser/machine we should invalidate the existing session for the user. This PR adds the tenant level config boolean variable `enableSingleSessionPerUser` which by default will opt out of this functionality but admin user can enable this from the admin settings page. > TL;DR: Enable functionality to have a single active session per user. Fixes https://github.com/appsmithorg/appsmith/issues/22727 Corresponding EE PR: https://github.com/appsmithorg/appsmith-ee/pull/1409 ## Type of change - New feature (non-breaking change which adds functionality) - This change requires a documentation update ## How Has This Been Tested? - Manual ## Checklist: ### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag ### QA activity: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
parent
afb763204a
commit
dd2ae3d5cf
|
|
@ -4,6 +4,8 @@ import com.appsmith.server.domains.User;
|
|||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SessionUserServiceCE {
|
||||
|
||||
Mono<User> getCurrentUser();
|
||||
|
|
@ -11,4 +13,8 @@ public interface SessionUserServiceCE {
|
|||
Mono<User> refreshCurrentUser(ServerWebExchange exchange);
|
||||
|
||||
Mono<Void> logoutAllSessions(String email);
|
||||
|
||||
Mono<List<String>> getSessionKeysByUserEmail(String email);
|
||||
|
||||
Mono<Long> deleteSessionsByKeys(List<String> keys);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import org.springframework.web.server.WebSession;
|
|||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME;
|
||||
|
||||
@Slf4j
|
||||
|
|
@ -28,6 +30,9 @@ public class SessionUserServiceCEImpl implements SessionUserServiceCE {
|
|||
private final UserRepository userRepository;
|
||||
private final ReactiveRedisOperations<String, Object> redisOperations;
|
||||
|
||||
public final static String SPRING_SESSION_PATTERN = "spring:session:sessions:*";
|
||||
private final static String SESSION_ATTRIBUTE = "sessionAttr:";
|
||||
|
||||
@Override
|
||||
public Mono<User> getCurrentUser() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
|
|
@ -67,15 +72,22 @@ public class SessionUserServiceCEImpl implements SessionUserServiceCE {
|
|||
|
||||
@Override
|
||||
public Mono<Void> logoutAllSessions(String email) {
|
||||
return getSessionKeysByUserEmail(email)
|
||||
.flatMap(this::deleteSessionsByKeys)
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<List<String>> getSessionKeysByUserEmail(String email) {
|
||||
// This pattern string comes from calling `ReactiveRedisSessionRepository.getSessionKey("*")` private method.
|
||||
return redisOperations.keys("spring:session:sessions:*")
|
||||
return redisOperations.keys(SPRING_SESSION_PATTERN)
|
||||
.flatMap(key -> Mono.zip(
|
||||
Mono.just(key),
|
||||
// The values are maps, containing various pieces of session related information.
|
||||
// One of them, holds the serialized User object. We want just that.
|
||||
redisOperations.opsForHash().entries(key)
|
||||
.filter(e -> e.getValue() != null &&
|
||||
("sessionAttr:" + DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME).equals(e.getKey())
|
||||
(SESSION_ATTRIBUTE + DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME).equals(e.getKey())
|
||||
)
|
||||
.map(e -> (User) ((SecurityContext) e.getValue()).getAuthentication().getPrincipal())
|
||||
.next()
|
||||
|
|
@ -84,14 +96,17 @@ public class SessionUserServiceCEImpl implements SessionUserServiceCE {
|
|||
// Filter the ones we need to clear out.
|
||||
.filter(tuple -> StringUtils.equalsIgnoreCase(email, tuple.getT2().getEmail()))
|
||||
.map(Tuple2::getT1)
|
||||
.collectList()
|
||||
.flatMap(keys ->
|
||||
CollectionUtils.isNullOrEmpty(keys)
|
||||
? Mono.just(0L)
|
||||
: redisOperations.delete(keys.toArray(String[]::new))
|
||||
.collectList();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> deleteSessionsByKeys(List<String> keys) {
|
||||
return CollectionUtils.isNullOrEmpty(keys)
|
||||
? Mono.just(0L)
|
||||
: redisOperations.delete(keys.toArray(String[]::new)
|
||||
)
|
||||
.doOnError(error -> log.error("Error clearing user sessions", error))
|
||||
.then();
|
||||
.doOnError(error -> log.error("Error clearing user sessions", error));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user