diff --git a/celements-spring-security/pom.xml b/celements-spring-security/pom.xml index ec5d68e..03baf07 100644 --- a/celements-spring-security/pom.xml +++ b/celements-spring-security/pom.xml @@ -36,6 +36,11 @@ celements-servlet provided + + com.celements + celements-user-manager + provided + com.celements celements-reference @@ -144,6 +149,11 @@ celements-shared-tests test + + com.celements + celements-core + test + com.celements celements-subsystem-migration-manager diff --git a/celements-spring-security/src/main/java/com/celements/spring/security/CelSecurityFilterChainConfig.java b/celements-spring-security/src/main/java/com/celements/spring/security/CelSecurityFilterChainConfig.java index 7f4ef34..917d521 100644 --- a/celements-spring-security/src/main/java/com/celements/spring/security/CelSecurityFilterChainConfig.java +++ b/celements-spring-security/src/main/java/com/celements/spring/security/CelSecurityFilterChainConfig.java @@ -30,6 +30,7 @@ import org.springframework.security.web.util.matcher.NegatedRequestMatcher; import org.xwiki.context.Execution; +import com.celements.auth.MainAdminConfig; import com.celements.auth.user.UserService; import com.celements.spring.security.oauth2.IdentityService; import com.celements.spring.security.oauth2.cookietoken.CompositeBearerTokenResolver; @@ -53,6 +54,7 @@ public class CelSecurityFilterChainConfig { private final AuthenticationManagerResolver authManagerResolver; private final UserService userService; private final CookieTokenService tokenService; + private final MainAdminConfig mainAdminConfig; private final Execution execution; @Inject @@ -61,11 +63,13 @@ public CelSecurityFilterChainConfig( AuthenticationManagerResolver authManagerResolver, UserService userService, CookieTokenService tokenService, + MainAdminConfig mainAdminConfig, Execution execution) { this.identityService = identityService; this.authManagerResolver = authManagerResolver; this.userService = userService; this.tokenService = tokenService; + this.mainAdminConfig = mainAdminConfig; this.execution = execution; } @@ -102,7 +106,7 @@ public SecurityFilterChain loginFilterChain( new TokenRefreshFilter(tokenService), BearerTokenAuthenticationFilter.class) .addFilterAfter( - new ExecutionContextAuthenticationFilter(userService, execution), + new ExecutionContextAuthenticationFilter(userService, mainAdminConfig, execution), BearerTokenAuthenticationFilter.class) .addFilterAfter( new HeaderToCookieAccessTokenFilter(tokenService), @@ -144,7 +148,7 @@ public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { new TokenRefreshFilter(tokenService), BearerTokenAuthenticationFilter.class) .addFilterAfter( - new ExecutionContextAuthenticationFilter(userService, execution), + new ExecutionContextAuthenticationFilter(userService, mainAdminConfig, execution), BearerTokenAuthenticationFilter.class) .addFilterAfter( new HeaderToCookieAccessTokenFilter(tokenService), diff --git a/celements-spring-security/src/main/java/com/celements/spring/security/oauth2/filter/ExecutionContextAuthenticationFilter.java b/celements-spring-security/src/main/java/com/celements/spring/security/oauth2/filter/ExecutionContextAuthenticationFilter.java index 0826c04..d4a4ef7 100644 --- a/celements-spring-security/src/main/java/com/celements/spring/security/oauth2/filter/ExecutionContextAuthenticationFilter.java +++ b/celements-spring-security/src/main/java/com/celements/spring/security/oauth2/filter/ExecutionContextAuthenticationFilter.java @@ -1,6 +1,7 @@ package com.celements.spring.security.oauth2.filter; import java.io.IOException; +import java.util.List; import java.util.Optional; import java.util.Set; @@ -11,7 +12,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -20,32 +23,47 @@ import org.springframework.web.filter.OncePerRequestFilter; import org.xwiki.context.Execution; +import com.celements.auth.MainAdminConfig; import com.celements.auth.user.User; +import com.celements.auth.user.UserInstantiationException; import com.celements.auth.user.UserService; import com.celements.execution.XWikiExecutionProp; +import com.xpn.xwiki.user.api.XWikiUser; public class ExecutionContextAuthenticationFilter extends OncePerRequestFilter { private static final Logger LOGGER = LoggerFactory .getLogger(ExecutionContextAuthenticationFilter.class); - private final Execution execution; private final UserService userService; + private final MainAdminConfig mainAdminConfig; + private final Execution execution; - public ExecutionContextAuthenticationFilter(UserService userService, Execution execution) { + public ExecutionContextAuthenticationFilter( + UserService userService, + MainAdminConfig mainAdminConfig, + Execution execution) { this.userService = userService; + this.mainAdminConfig = mainAdminConfig; this.execution = execution; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + var securityContext = SecurityContextHolder.getContext(); + autoLogin(securityContext) + .or(() -> processAuthentication(securityContext)) + .ifPresent(user -> execution.getContext().set(XWikiExecutionProp.XWIKI_USER, user)); + filterChain.doFilter(request, response); + } + + private Optional processAuthentication(SecurityContext securityContext) { + Authentication auth = securityContext.getAuthentication(); if ((auth != null) && auth.isAuthenticated()) { String userId = auth.getName(); // the Keycloak GUID (sub) String username; String email; - if (auth instanceof JwtAuthenticationToken) { JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) auth; Jwt jwt = jwtAuth.getToken(); @@ -70,7 +88,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse User user = userOpt.get(); LOGGER.debug("setting xwiki isGlobal='{}', username='{}', email='{}'", user.isGlobal(), user.asXWikiUser().getUser(), user.email()); - execution.getContext().set(XWikiExecutionProp.XWIKI_USER, user.asXWikiUser()); + return Optional.of(user.asXWikiUser()); } else { LOGGER.info("no celements user found for email='{}'", email); } @@ -81,7 +99,17 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } else { LOGGER.info("is NOT authenticated with user '{}'", "null"); } - filterChain.doFilter(request, response); + return Optional.empty(); + } + + private Optional autoLogin(SecurityContext securityContext) { + if (!mainAdminConfig.isAutoLoginEnabled()) { + return Optional.empty(); + } + var xwikiUser = mainAdminConfig.getXWikiUser(); + var auth = new UsernamePasswordAuthenticationToken(xwikiUser.getUser(), null, List.of()); + securityContext.setAuthentication(auth); + return Optional.of(xwikiUser); } } diff --git a/celements-spring-security/src/test/java/com/celements/spring/security/oauth2/filter/ExecutionContextAuthenticationFilterTest.java b/celements-spring-security/src/test/java/com/celements/spring/security/oauth2/filter/ExecutionContextAuthenticationFilterTest.java new file mode 100644 index 0000000..0acafa8 --- /dev/null +++ b/celements-spring-security/src/test/java/com/celements/spring/security/oauth2/filter/ExecutionContextAuthenticationFilterTest.java @@ -0,0 +1,77 @@ +package com.celements.spring.security.oauth2.filter; + +import static com.celements.execution.XWikiExecutionProp.*; +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.core.context.SecurityContextHolder; +import org.xwiki.context.Execution; + +import com.celements.auth.MainAdminConfig; +import com.celements.auth.user.UserService; +import com.celements.common.test.AbstractComponentTest; +import com.xpn.xwiki.user.api.XWikiUser; + +public class ExecutionContextAuthenticationFilterTest extends AbstractComponentTest { + + private ExecutionContextAuthenticationFilter filter; + private UserService userService; + private MainAdminConfig mainAdminConfig; + + @Before + public void prepareTest() throws Exception { + userService = createDefaultMock(UserService.class); + mainAdminConfig = createDefaultMock(MainAdminConfig.class); + filter = new ExecutionContextAuthenticationFilter(userService, mainAdminConfig, + getBeanFactory().getBean(Execution.class)); + } + + @After + public void clearSecurityContext() { + SecurityContextHolder.clearContext(); + } + + @Test + public void test_doFilterInternal_autoLoginAdmin() throws Exception { + HttpServletRequest request = createDefaultMock(HttpServletRequest.class); + HttpServletResponse response = createDefaultMock(HttpServletResponse.class); + FilterChain chain = createDefaultMock(FilterChain.class); + XWikiUser xwikiUser = new XWikiUser("xwiki:XWiki.Admin", true); + expect(mainAdminConfig.isAutoLoginEnabled()).andReturn(true); + expect(mainAdminConfig.getXWikiUser()).andReturn(xwikiUser); + chain.doFilter(request, response); + replayDefault(); + + filter.doFilterInternal(request, response, chain); + + verifyDefault(); + assertEquals("xwiki:XWiki.Admin", + SecurityContextHolder.getContext().getAuthentication().getName()); + assertTrue(SecurityContextHolder.getContext().getAuthentication().isAuthenticated()); + assertSame(xwikiUser, getBeanFactory().getBean(Execution.class).getContext() + .get(XWIKI_USER).orElseThrow()); + } + + @Test + public void test_doFilterInternal_autoLoginDisabled() throws Exception { + HttpServletRequest request = createDefaultMock(HttpServletRequest.class); + HttpServletResponse response = createDefaultMock(HttpServletResponse.class); + FilterChain chain = createDefaultMock(FilterChain.class); + expect(mainAdminConfig.isAutoLoginEnabled()).andReturn(false); + chain.doFilter(request, response); + replayDefault(); + + filter.doFilterInternal(request, response, chain); + + verifyDefault(); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + +}