Skip to content

Commit

Permalink
#22 Avoid logging out active user
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitrys committed Jul 18, 2024
1 parent 15b5db8 commit 05597d3
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,11 @@

import com.odysseusinc.arachne.datanode.security.AuthenticationTokenFilter;
import com.odysseusinc.arachne.datanode.security.EntryPointUnauthorizedHandler;
import com.odysseusinc.arachne.datanode.service.AuthenticationService;
import com.odysseusinc.arachne.datanode.service.UserRegistrationStrategy;
import com.odysseusinc.arachne.datanode.service.impl.AuthenticationServiceImpl;
import org.ohdsi.authenticator.service.authentication.AccessTokenResolver;
import org.ohdsi.authenticator.service.authentication.AuthenticationMode;
import org.ohdsi.authenticator.service.authentication.Authenticator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
Expand Down Expand Up @@ -71,12 +66,6 @@ public AccessTokenResolver accessTokenResolver() {
return new AccessTokenResolver(tokenHeader, authenticationMode);
}

@Bean
public AuthenticationService authenticationService(ApplicationContext context, Authenticator authenticator, UserRegistrationStrategy userRegisterStrategy) {

return new AuthenticationServiceImpl(context, authenticator, userRegisterStrategy, authenticationMode);
}

@Override
protected void configure(HttpSecurity http) throws Exception {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.ohdsi.authenticator.model.UserInfo;
import org.ohdsi.authenticator.service.authentication.AccessTokenResolver;
import org.ohdsi.authenticator.service.authentication.AuthenticationMode;
import org.ohdsi.authenticator.service.authentication.Authenticator;
import org.pac4j.core.credentials.UsernamePasswordCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -199,6 +198,10 @@ private ResponseEntity<JsonResult<?>> unauthorized(String errorMessage) {
}

private String authCookie(String token, int expiry) {
return authCookie(token, expiry, accessTokenResolver);
}

public static String authCookie(String token, int expiry, AccessTokenResolver accessTokenResolver) {
return ResponseCookie.from(accessTokenResolver.getTokenHeaderName(), token)
.path("/")
.httpOnly(true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.odysseusinc.arachne.datanode.controller;

import lombok.Getter;
import org.ohdsi.authenticator.converter.TokenInfoToTokenConverter;
import org.ohdsi.authenticator.converter.TokenInfoToUserInfoConverter;
import org.ohdsi.authenticator.exception.MethodNotSupportedAuthenticationException;
import org.ohdsi.authenticator.model.TokenInfo;
import org.ohdsi.authenticator.model.UserInfo;
import org.ohdsi.authenticator.service.AuthService;
import org.ohdsi.authenticator.service.authentication.AuthServiceProvider;
import org.ohdsi.authenticator.service.authentication.TokenProvider;
import org.pac4j.core.credentials.Credentials;
import org.springframework.stereotype.Service;

@Getter
@Service
public class Authenticator {
private final TokenProvider tokenProvider;
private final AuthServiceProvider authServiceProvider;
private final TokenInfoToTokenConverter tokenInfoToTokenConverter;
private final TokenInfoToUserInfoConverter tokenInfoToUserInfoConverter;

public Authenticator(TokenProvider tokenProvider, AuthServiceProvider authServiceProvider) {
this.tokenProvider = tokenProvider;
this.authServiceProvider = authServiceProvider;
this.tokenInfoToTokenConverter = new TokenInfoToTokenConverter(tokenProvider);
this.tokenInfoToUserInfoConverter = new TokenInfoToUserInfoConverter();
}

public UserInfo authenticate(String method, Credentials request) {
AuthService authService = authServiceProvider.getByMethod(method).orElseThrow(MethodNotSupportedAuthenticationException::new);
TokenInfo authentication = authService.authenticate(request);
String token = tokenInfoToTokenConverter.toToken(authentication);
return tokenInfoToUserInfoConverter.toUserInfo(authentication, token);
}

public String resolveUsername(String token) {
return tokenProvider.resolveValue(token, "sub", String.class);
}

public UserInfo refreshToken(String token) {
TokenInfo tokenInfo = tokenInfoToTokenConverter.toTokenInfo(token);
AuthService authService = authServiceProvider.getByMethod(tokenInfo.getAuthMethod()).orElseThrow(MethodNotSupportedAuthenticationException::new);
TokenInfo newAuthentication = authService.refreshToken(tokenInfo);
String newToken = tokenInfoToTokenConverter.toToken(newAuthentication);
return tokenInfoToUserInfoConverter.toUserInfo(newAuthentication, newToken);
}

public void invalidateToken(String token) {
this.tokenProvider.invalidateToken(token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,31 @@

package com.odysseusinc.arachne.datanode.security;

import com.odysseusinc.arachne.datanode.controller.AuthController;
import com.odysseusinc.arachne.datanode.exception.AuthException;
import com.odysseusinc.arachne.datanode.service.AuthenticationService;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ohdsi.authenticator.service.authentication.AccessTokenResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.filter.GenericFilterBean;

public class AuthenticationTokenFilter extends GenericFilterBean {
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;

Logger log = LoggerFactory.getLogger(AuthenticationTokenFilter.class);
@Slf4j
public class AuthenticationTokenFilter extends GenericFilterBean {

@Autowired
private AccessTokenResolver accessTokenResolver;
Expand All @@ -64,9 +64,9 @@ public void doFilter(
httpRequest.getHeader(headerName)
);

if (StringUtils.isNotEmpty(accessToken)){
if (StringUtils.isNotEmpty(accessToken)) {
try {
authenticationService.authenticate(accessToken, httpRequest);
authenticationService.authenticate(authMethod, accessToken, httpRequest, (HttpServletResponse) response, token -> AuthController.authCookie(token, -1, accessTokenResolver));
} catch (AuthenticationException | AuthException | org.ohdsi.authenticator.exception.AuthenticationException ex) {
logAuthenticationException(httpRequest, ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@

package com.odysseusinc.arachne.datanode.service;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.core.Authentication;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.function.Function;

public interface AuthenticationService {

Authentication authenticate(String authToken, HttpServletRequest httpRequest);
Authentication authenticate(String authMethod, String authToken, HttpServletRequest httpRequest, HttpServletResponse response, Function<String, String> cookie);

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,45 @@

package com.odysseusinc.arachne.datanode.service.impl;

import com.odysseusinc.arachne.datanode.controller.Authenticator;
import com.odysseusinc.arachne.datanode.exception.AuthException;
import com.odysseusinc.arachne.datanode.model.user.User;
import com.odysseusinc.arachne.datanode.service.AuthenticationService;
import com.odysseusinc.arachne.datanode.service.UserRegistrationStrategy;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ohdsi.authenticator.converter.TokenInfoToTokenConverter;
import org.ohdsi.authenticator.service.authentication.AuthenticationMode;
import org.ohdsi.authenticator.service.authentication.Authenticator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Service;

public class AuthenticationServiceImpl implements AuthenticationService, InitializingBean {
private final Authenticator authenticator;

private final ApplicationContext applicationContext;

private UserDetailsService userDetailsService;

private UserRegistrationStrategy userRegisterStrategy;

private AuthenticationMode authenticationMode;

public AuthenticationServiceImpl(ApplicationContext applicationContext, Authenticator authenticator,
UserRegistrationStrategy userRegisterStrategy, AuthenticationMode authenticationMode) {
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.function.Function;

this.applicationContext = applicationContext;
this.authenticator = authenticator;
this.userRegisterStrategy = userRegisterStrategy;
this.authenticationMode = authenticationMode;
}
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthenticationServiceImpl implements AuthenticationService {
private final Authenticator authenticator;
private final UserDetailsService userDetailsService;
private final UserRegistrationStrategy userRegisterStrategy;
@Value("${security.authentication.mode:" + AuthenticationMode.Const.STANDARD + "}")
private AuthenticationMode authenticationMode = AuthenticationMode.STANDARD;

@Override
public Authentication authenticate(String accessToken, HttpServletRequest httpRequest) {
public Authentication authenticate(
String authMethod, String accessToken, HttpServletRequest httpRequest, HttpServletResponse response, Function<String, String> cookie
) {

if (StringUtils.isNotEmpty(accessToken)) {
String username = authenticator.resolveUsername(accessToken);
Expand All @@ -68,18 +67,22 @@ public Authentication authenticate(String accessToken, HttpServletRequest httpRe
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
}
SecurityContextHolder.getContext().setAuthentication(authentication);

String path = httpRequest.getServletPath();
if (!path.endsWith("submissions")) {
TokenInfoToTokenConverter tokenConverter = authenticator.getTokenInfoToTokenConverter();
// Parsing doesn't include expiration date, so serialization will set a new expiration date
String token = tokenConverter.toToken(tokenConverter.toTokenInfo(accessToken));
String apply = cookie.apply(token);
response.setHeader(HttpHeaders.SET_COOKIE, apply);
}

return authentication;
}
}
return null;
}

@Override
public void afterPropertiesSet() throws Exception {

userDetailsService = applicationContext.getBean(UserDetailsService.class);
}

private void createUserByTokenIfNecessary(String username) {

if (authenticationMode == AuthenticationMode.STANDARD) {
Expand Down

0 comments on commit 05597d3

Please sign in to comment.