diff --git a/datanode/src/main/java/com/odysseusinc/arachne/datanode/config/WebSecurityConfig.java b/datanode/src/main/java/com/odysseusinc/arachne/datanode/config/WebSecurityConfig.java index f3ca7d67..6e493e62 100644 --- a/datanode/src/main/java/com/odysseusinc/arachne/datanode/config/WebSecurityConfig.java +++ b/datanode/src/main/java/com/odysseusinc/arachne/datanode/config/WebSecurityConfig.java @@ -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; @@ -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 { diff --git a/datanode/src/main/java/com/odysseusinc/arachne/datanode/controller/AuthController.java b/datanode/src/main/java/com/odysseusinc/arachne/datanode/controller/AuthController.java index eff82340..24e6be61 100644 --- a/datanode/src/main/java/com/odysseusinc/arachne/datanode/controller/AuthController.java +++ b/datanode/src/main/java/com/odysseusinc/arachne/datanode/controller/AuthController.java @@ -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; @@ -199,6 +198,10 @@ private ResponseEntity> 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) diff --git a/datanode/src/main/java/com/odysseusinc/arachne/datanode/controller/Authenticator.java b/datanode/src/main/java/com/odysseusinc/arachne/datanode/controller/Authenticator.java new file mode 100644 index 00000000..f66feeaf --- /dev/null +++ b/datanode/src/main/java/com/odysseusinc/arachne/datanode/controller/Authenticator.java @@ -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); + } +} diff --git a/datanode/src/main/java/com/odysseusinc/arachne/datanode/security/AuthenticationTokenFilter.java b/datanode/src/main/java/com/odysseusinc/arachne/datanode/security/AuthenticationTokenFilter.java index f961656d..200fd04e 100644 --- a/datanode/src/main/java/com/odysseusinc/arachne/datanode/security/AuthenticationTokenFilter.java +++ b/datanode/src/main/java/com/odysseusinc/arachne/datanode/security/AuthenticationTokenFilter.java @@ -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; @@ -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); } diff --git a/datanode/src/main/java/com/odysseusinc/arachne/datanode/service/AuthenticationService.java b/datanode/src/main/java/com/odysseusinc/arachne/datanode/service/AuthenticationService.java index 5bdce08a..a2bbad15 100644 --- a/datanode/src/main/java/com/odysseusinc/arachne/datanode/service/AuthenticationService.java +++ b/datanode/src/main/java/com/odysseusinc/arachne/datanode/service/AuthenticationService.java @@ -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 cookie); } diff --git a/datanode/src/main/java/com/odysseusinc/arachne/datanode/service/impl/AuthenticationServiceImpl.java b/datanode/src/main/java/com/odysseusinc/arachne/datanode/service/impl/AuthenticationServiceImpl.java index 552395fc..f993b221 100644 --- a/datanode/src/main/java/com/odysseusinc/arachne/datanode/service/impl/AuthenticationServiceImpl.java +++ b/datanode/src/main/java/com/odysseusinc/arachne/datanode/service/impl/AuthenticationServiceImpl.java @@ -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 cookie + ) { if (StringUtils.isNotEmpty(accessToken)) { String username = authenticator.resolveUsername(accessToken); @@ -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) {