From 6fa4fb732b720a469938efb0d620a0c0b68c67d1 Mon Sep 17 00:00:00 2001 From: "antoine.vinot" Date: Mon, 29 Jan 2024 16:58:48 +0100 Subject: [PATCH] SONAR-21116 Fix api/authentication/validate doesnt validate user tokens --- .../authentication/ws/ValidateAction.java | 25 ++-- .../authentication/ws/ValidateActionTest.java | 107 +++++++++++------- 2 files changed, 79 insertions(+), 53 deletions(-) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/authentication/ws/ValidateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/authentication/ws/ValidateAction.java index 412ff665ff4..51d9c0b7ea5 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/authentication/ws/ValidateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/authentication/ws/ValidateAction.java @@ -32,8 +32,11 @@ import org.sonar.api.web.HttpFilter; import org.sonar.api.web.UrlPattern; import org.sonar.db.user.UserDto; import org.sonar.server.authentication.BasicAuthentication; +import org.sonar.server.authentication.HttpHeadersAuthentication; import org.sonar.server.authentication.JwtHttpHandler; +import org.sonar.server.authentication.UserAuthResult; import org.sonar.server.authentication.event.AuthenticationException; +import org.sonar.server.usertoken.UserTokenAuthentication; import org.sonar.server.ws.ServletFilterHandler; import org.sonarqube.ws.MediaTypes; @@ -49,11 +52,17 @@ public class ValidateAction extends HttpFilter implements AuthenticationWsAction private final Configuration config; private final JwtHttpHandler jwtHttpHandler; private final BasicAuthentication basicAuthentication; + private final HttpHeadersAuthentication httpHeadersAuthentication; + private final UserTokenAuthentication userTokenAuthentication; + + public ValidateAction(Configuration config, BasicAuthentication basicAuthentication, JwtHttpHandler jwtHttpHandler, HttpHeadersAuthentication httpHeadersAuthentication, + UserTokenAuthentication userTokenAuthentication) { - public ValidateAction(Configuration config, BasicAuthentication basicAuthentication, JwtHttpHandler jwtHttpHandler) { this.config = config; this.basicAuthentication = basicAuthentication; this.jwtHttpHandler = jwtHttpHandler; + this.httpHeadersAuthentication = httpHeadersAuthentication; + this.userTokenAuthentication = userTokenAuthentication; } @Override @@ -84,15 +93,11 @@ public class ValidateAction extends HttpFilter implements AuthenticationWsAction private boolean authenticate(HttpRequest request, HttpResponse response) { try { - Optional user = jwtHttpHandler.validateToken(request, response); - if (user.isPresent()) { - return true; - } - user = basicAuthentication.authenticate(request); - if (user.isPresent()) { - return true; - } - return !config.getBoolean(CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE); + Optional user = httpHeadersAuthentication.authenticate(request, response) + .or(() -> jwtHttpHandler.validateToken(request, response)) + .or(() -> basicAuthentication.authenticate(request)) + .or(() -> userTokenAuthentication.authenticate(request).map(UserAuthResult::getUserDto)); + return user.isPresent() || !config.getBoolean(CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE); } catch (AuthenticationException e) { return false; } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/authentication/ws/ValidateActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/authentication/ws/ValidateActionTest.java index 861fefd3055..f3e59d8e9f9 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/authentication/ws/ValidateActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/authentication/ws/ValidateActionTest.java @@ -19,6 +19,7 @@ */ package org.sonar.server.authentication.ws; +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Optional; @@ -30,8 +31,11 @@ import org.sonar.api.server.http.HttpResponse; import org.sonar.api.server.ws.WebService; import org.sonar.api.web.FilterChain; import org.sonar.server.authentication.BasicAuthentication; +import org.sonar.server.authentication.HttpHeadersAuthentication; import org.sonar.server.authentication.JwtHttpHandler; +import org.sonar.server.authentication.UserAuthResult; import org.sonar.server.authentication.event.AuthenticationException; +import org.sonar.server.usertoken.UserTokenAuthentication; import org.sonar.server.ws.ServletFilterHandler; import org.sonar.test.JsonAssert; import org.sonarqube.ws.MediaTypes; @@ -42,6 +46,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.server.authentication.UserAuthResult.AuthType.TOKEN; public class ValidateActionTest { @@ -53,19 +58,25 @@ public class ValidateActionTest { private final BasicAuthentication basicAuthentication = mock(BasicAuthentication.class); private final JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); + private final HttpHeadersAuthentication httpHeadersAuthentication = mock(HttpHeadersAuthentication.class); + private final UserTokenAuthentication userTokenAuthentication = mock(UserTokenAuthentication.class); private final MapSettings settings = new MapSettings(); - private final ValidateAction underTest = new ValidateAction(settings.asConfig(), basicAuthentication, jwtHttpHandler); + private final ValidateAction underTest = new ValidateAction(settings.asConfig(), basicAuthentication, jwtHttpHandler, httpHeadersAuthentication, userTokenAuthentication); @Before public void setUp() throws Exception { PrintWriter writer = new PrintWriter(stringWriter); when(response.getWriter()).thenReturn(writer); + when(basicAuthentication.authenticate(request)).thenReturn(Optional.empty()); + when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); + when(httpHeadersAuthentication.authenticate(request, response)).thenReturn(Optional.empty()); + when(userTokenAuthentication.authenticate(request)).thenReturn(Optional.empty()); } @Test - public void verify_definition() { + public void define_shouldDefineWS() { String controllerKey = "foo"; WebService.Context context = new WebService.Context(); WebService.NewController newController = context.createController(controllerKey); @@ -80,81 +91,91 @@ public class ValidateActionTest { } @Test - public void return_true_when_jwt_token_is_set() throws Exception { + public void doFilter_whenJwtToken_shouldReturnTrue() throws Exception { when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.of(newUserDto())); - when(basicAuthentication.authenticate(request)).thenReturn(Optional.empty()); - underTest.doFilter(request, response, chain); - - verify(response).setContentType(MediaTypes.JSON); - JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":true}"); + verifyResponseIsTrue(); } @Test - public void return_true_when_basic_auth() throws Exception { - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); + public void doFilter_whenBasicAuth_shouldReturnTrue() throws Exception { when(basicAuthentication.authenticate(request)).thenReturn(Optional.of(newUserDto())); - underTest.doFilter(request, response, chain); - - verify(response).setContentType(MediaTypes.JSON); - JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":true}"); + verifyResponseIsTrue(); } @Test - public void return_true_when_no_jwt_nor_basic_auth_and_no_force_authentication() throws Exception { + public void doFilter_whenNoForceAuthentication_shoudlReturnTrue() throws Exception { settings.setProperty("sonar.forceAuthentication", "false"); - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); - when(basicAuthentication.authenticate(request)).thenReturn(Optional.empty()); - underTest.doFilter(request, response, chain); - - verify(response).setContentType(MediaTypes.JSON); - JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":true}"); + verifyResponseIsTrue(); } @Test - public void return_false_when_no_jwt_nor_basic_auth_and_force_authentication_is_true() throws Exception { + public void doFilter_whenForceAuthentication_shouldReturnFalse() throws Exception { settings.setProperty("sonar.forceAuthentication", "true"); - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); - when(basicAuthentication.authenticate(request)).thenReturn(Optional.empty()); - underTest.doFilter(request, response, chain); - - verify(response).setContentType(MediaTypes.JSON); - JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":false}"); + verifyResponseIsFalse(); } @Test - public void return_false_when_no_jwt_nor_basic_auth_and_force_authentication_fallback_to_default() throws Exception { - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); - when(basicAuthentication.authenticate(request)).thenReturn(Optional.empty()); - + public void doFilter_whenDefaultForceAuthentication_shouldReturnFalse() throws Exception { underTest.doFilter(request, response, chain); - - verify(response).setContentType(MediaTypes.JSON); - JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":false}"); + verifyResponseIsFalse(); } @Test - public void return_false_when_jwt_throws_unauthorized_exception() throws Exception { + public void doFilter_whenJwtThrowsUnauthorizedException_shouldReturnFalse() throws Exception { doThrow(AuthenticationException.class).when(jwtHttpHandler).validateToken(request, response); - when(basicAuthentication.authenticate(request)).thenReturn(Optional.empty()); + underTest.doFilter(request, response, chain); + verifyResponseIsFalse(); + } + @Test + public void doFilter_whenBasicAuthenticatorThrowsUnauthorizedException_shouldReturnFalse() throws Exception { + doThrow(AuthenticationException.class).when(basicAuthentication).authenticate(request); underTest.doFilter(request, response, chain); + verifyResponseIsFalse(); + } - verify(response).setContentType(MediaTypes.JSON); - JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":false}"); + @Test + public void doFiler_whenHttpHeaderAuthentication_shouldReturnTrue() throws IOException { + when(httpHeadersAuthentication.authenticate(request, response)).thenReturn(Optional.of(newUserDto())); + underTest.doFilter(request, response, chain); + verifyResponseIsTrue(); } @Test - public void return_false_when_basic_authenticator_throws_unauthorized_exception() throws Exception { - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); - doThrow(AuthenticationException.class).when(basicAuthentication).authenticate(request); + public void doFiler_whenHttpHeaderAuthenticationThrowsUnauthorizedException_shouldReturnFalse() throws IOException { + doThrow(AuthenticationException.class).when(httpHeadersAuthentication).authenticate(request, response); + underTest.doFilter(request, response, chain); + verifyResponseIsFalse(); + } + + @Test + public void doFilter_whenUserTokenAuthentication_shouldReturnTrue() throws IOException { + when(userTokenAuthentication.authenticate(request)).thenReturn(Optional.of(new UserAuthResult(newUserDto(), TOKEN))); + underTest.doFilter(request, response, chain); + verifyResponseIsTrue(); + } + @Test + public void doFiler_whenUserTokenAuthenticationThrowsUnauthorizedException_shouldReturnFalse() throws IOException { + doThrow(AuthenticationException.class).when(httpHeadersAuthentication).authenticate(request, response); underTest.doFilter(request, response, chain); + verifyResponseIsFalse(); + } + + private void verifyResponseIsFalse() { + verifyResponse("{\"valid\":false}"); + } + + private void verifyResponseIsTrue() { + verifyResponse("{\"valid\":true}"); + } + private void verifyResponse(String expectedJson) { verify(response).setContentType(MediaTypes.JSON); - JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":false}"); + JsonAssert.assertJson(stringWriter.toString()).isSimilarTo(expectedJson); } } -- 2.39.5