diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2019-06-21 18:20:16 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-06-28 08:45:54 +0200 |
commit | 9669c6d690b5ced80d0b9b80feeb8b9813284ebe (patch) | |
tree | 33ab6cd647849bd68a03e864124db4ee4c87a8b4 /server | |
parent | f535399ec7ccf1807d3866cfb17cce3571ca38b5 (diff) | |
download | sonarqube-9669c6d690b5ced80d0b9b80feeb8b9813284ebe.tar.gz sonarqube-9669c6d690b5ced80d0b9b80feeb8b9813284ebe.zip |
SONAR-12148 Generate cookie in case of authentication error or email update
Diffstat (limited to 'server')
13 files changed, 183 insertions, 96 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java index 7e295b31680..b0a79c33687 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java @@ -19,49 +19,56 @@ */ package org.sonar.server.authentication; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.server.authentication.event.AuthenticationException; -import static java.lang.String.format; import static org.sonar.server.authentication.AuthenticationRedirection.encodeMessage; import static org.sonar.server.authentication.AuthenticationRedirection.redirectTo; +import static org.sonar.server.authentication.Cookies.newCookieBuilder; -final class AuthenticationError { +public final class AuthenticationError { private static final String UNAUTHORIZED_PATH = "/sessions/unauthorized"; - private static final String UNAUTHORIZED_PATH_WITH_MESSAGE = UNAUTHORIZED_PATH + "?message=%s"; + private static final Logger LOGGER = Loggers.get(AuthenticationError.class); + private static final String AUTHENTICATION_ERROR_COOKIE = "AUTHENTICATION-ERROR"; + private static final int FIVE_MINUTES_IN_SECONDS = 5 * 60; private AuthenticationError() { // Utility class } - static void handleError(Exception e, HttpServletResponse response, String message) { + static void handleError(Exception e, HttpServletRequest request, HttpServletResponse response, String message) { LOGGER.warn(message, e); - redirectToUnauthorized(response); + redirectToUnauthorized(request, response); } - static void handleError(HttpServletResponse response, String message) { + static void handleError(HttpServletRequest request, HttpServletResponse response, String message) { LOGGER.warn(message); - redirectToUnauthorized(response); - } - - static void handleAuthenticationError(AuthenticationException e, HttpServletResponse response, String contextPath) { - redirectTo(response, getPath(e, contextPath)); + redirectToUnauthorized(request, response); } - private static String getPath(AuthenticationException e, String contextPath) { + static void handleAuthenticationError(AuthenticationException e, HttpServletRequest request, HttpServletResponse response) { String publicMessage = e.getPublicMessage(); - if (publicMessage == null || publicMessage.isEmpty()) { - return UNAUTHORIZED_PATH; + if (publicMessage != null && !publicMessage.isEmpty()) { + addErrorCookie(request, response, publicMessage); } - return contextPath + format(UNAUTHORIZED_PATH_WITH_MESSAGE, encodeMessage(publicMessage)); + redirectToUnauthorized(request, response); } - public static void redirectToUnauthorized(HttpServletResponse response) { - redirectTo(response, UNAUTHORIZED_PATH); + public static void addErrorCookie(HttpServletRequest request, HttpServletResponse response, String value) { + response.addCookie(newCookieBuilder(request) + .setName(AUTHENTICATION_ERROR_COOKIE) + .setValue(encodeMessage(value)) + .setHttpOnly(false) + .setExpiry(FIVE_MINUTES_IN_SECONDS) + .build()); } + private static void redirectToUnauthorized(HttpServletRequest request, HttpServletResponse response) { + redirectTo(response, request.getContextPath() + UNAUTHORIZED_PATH); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationFilter.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationFilter.java index b77d5cc2d21..244ccfbc2a8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationFilter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationFilter.java @@ -22,7 +22,6 @@ package org.sonar.server.authentication; import javax.annotation.CheckForNull; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.sonar.api.platform.Server; import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.api.web.ServletFilter; @@ -31,12 +30,11 @@ import static java.lang.String.format; import static org.sonar.server.authentication.AuthenticationError.handleError; public abstract class AuthenticationFilter extends ServletFilter { + static final String CALLBACK_PATH = "/oauth2/callback/"; private final IdentityProviderRepository identityProviderRepository; - private final Server server; - public AuthenticationFilter(Server server, IdentityProviderRepository identityProviderRepository) { - this.server = server; + public AuthenticationFilter(IdentityProviderRepository identityProviderRepository) { this.identityProviderRepository = identityProviderRepository; } @@ -47,15 +45,15 @@ public abstract class AuthenticationFilter extends ServletFilter { @CheckForNull IdentityProvider resolveProviderOrHandleResponse(HttpServletRequest request, HttpServletResponse response, String path) { String requestUri = request.getRequestURI(); - String providerKey = extractKeyProvider(requestUri, server.getContextPath() + path); + String providerKey = extractKeyProvider(requestUri, request.getContextPath() + path); if (providerKey == null) { - handleError(response, "No provider key found in URI"); + handleError(request, response, "No provider key found in URI"); return null; } try { return identityProviderRepository.getEnabledByKey(providerKey); } catch (Exception e) { - handleError(e, response, format("Failed to retrieve IdentityProvider for key '%s'", providerKey)); + handleError(e, request, response, format("Failed to retrieve IdentityProvider for key '%s'", providerKey)); return null; } } @@ -70,8 +68,4 @@ public abstract class AuthenticationFilter extends ServletFilter { } return null; } - - String getContextPath() { - return server.getContextPath(); - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationRedirection.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationRedirection.java index 191e664691d..b3e91772dd1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationRedirection.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationRedirection.java @@ -35,7 +35,9 @@ public class AuthenticationRedirection { public static String encodeMessage(String message) { try { - return encode(message, UTF_8.name()); + return encode(message, UTF_8.name()) + // In order for Javascript to be able to decode this message, + must be replaced by %20 + .replace("+", "%20"); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(format("Fail to encode %s", message), e); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/Cookies.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/Cookies.java index 3ac7f29a349..1af5276cbcd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/Cookies.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/Cookies.java @@ -31,7 +31,7 @@ import static java.util.Objects.requireNonNull; /** * Helper class to create a {@link javax.servlet.http.Cookie}. * - * The {@link javax.servlet.http.Cookie#secure} will automatically be set. + * The {@link javax.servlet.http.Cookie#setSecure(boolean)} will automatically be set to true. */ public class Cookies { @@ -65,7 +65,7 @@ public class Cookies { private boolean httpOnly; private int expiry; - public CookieBuilder(HttpServletRequest request) { + CookieBuilder(HttpServletRequest request) { this.request = request; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java index 33d9ed3300d..180ccd29501 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java @@ -25,14 +25,14 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.sonar.api.platform.Server; import org.sonar.api.server.authentication.BaseIdentityProvider; import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.UnauthorizedException; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; -import org.sonar.server.authentication.exception.RedirectionException; +import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException; +import org.sonar.server.authentication.exception.UpdateLoginRedirectionException; import static java.lang.String.format; import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError; @@ -50,8 +50,8 @@ public class InitFilter extends AuthenticationFilter { private final OAuth2AuthenticationParameters oAuthOAuth2AuthenticationParameters; public InitFilter(IdentityProviderRepository identityProviderRepository, BaseContextFactory baseContextFactory, - OAuth2ContextFactory oAuth2ContextFactory, Server server, AuthenticationEvent authenticationEvent, OAuth2AuthenticationParameters oAuthOAuth2AuthenticationParameters) { - super(server, identityProviderRepository); + OAuth2ContextFactory oAuth2ContextFactory, AuthenticationEvent authenticationEvent, OAuth2AuthenticationParameters oAuthOAuth2AuthenticationParameters) { + super(identityProviderRepository); this.baseContextFactory = baseContextFactory; this.oAuth2ContextFactory = oAuth2ContextFactory; this.authenticationEvent = authenticationEvent; @@ -82,18 +82,22 @@ public class InitFilter extends AuthenticationFilter { oAuthOAuth2AuthenticationParameters.init(request, response); handleOAuth2IdentityProvider(request, response, (OAuth2IdentityProvider) provider); } else { - handleError(response, format("Unsupported IdentityProvider class: %s", provider.getClass())); + handleError(request, response, format("Unsupported IdentityProvider class: %s", provider.getClass())); } + } catch (EmailAlreadyExistsRedirectionException e) { + oAuthOAuth2AuthenticationParameters.delete(request, response); + e.addCookie(request, response); + redirectTo(response, e.getPath(request.getContextPath())); + } catch (UpdateLoginRedirectionException e) { + oAuthOAuth2AuthenticationParameters.delete(request, response); + redirectTo(response, e.getPath(request.getContextPath())); } catch (AuthenticationException e) { oAuthOAuth2AuthenticationParameters.delete(request, response); authenticationEvent.loginFailure(request, e); - handleAuthenticationError(e, response, getContextPath()); - } catch (RedirectionException e) { - oAuthOAuth2AuthenticationParameters.delete(request, response); - redirectTo(response, e.getPath(getContextPath())); + handleAuthenticationError(e, request, response); } catch (Exception e) { oAuthOAuth2AuthenticationParameters.delete(request, response); - handleError(e, response, format("Fail to initialize authentication with provider '%s'", provider.getKey())); + handleError(e, request, response, format("Fail to initialize authentication with provider '%s'", provider.getKey())); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImpl.java index 24f29b14533..848dfab0ee0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImpl.java @@ -33,10 +33,10 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import static java.net.URLDecoder.decode; -import static java.net.URLEncoder.encode; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Optional.empty; import static org.apache.commons.lang.StringUtils.isNotBlank; +import static org.sonar.server.authentication.AuthenticationRedirection.encodeMessage; import static org.sonar.server.authentication.Cookies.findCookie; import static org.sonar.server.authentication.Cookies.newCookieBuilder; @@ -131,11 +131,7 @@ public class OAuth2AuthenticationParametersImpl implements OAuth2AuthenticationP private static String toJson(Map<String, String> map) { Gson gson = new GsonBuilder().create(); - try { - return encode(gson.toJson(map), UTF_8.name()); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); - } + return encodeMessage(gson.toJson(map)); } private static Map<String, String> fromJson(String json) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java index a2376fad354..b1ab26072ab 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java @@ -25,13 +25,13 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.sonar.api.platform.Server; import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.UnauthorizedException; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; -import org.sonar.server.authentication.exception.RedirectionException; +import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException; +import org.sonar.server.authentication.exception.UpdateLoginRedirectionException; import org.sonar.server.user.ThreadLocalUserSession; import static java.lang.String.format; @@ -48,8 +48,8 @@ public class OAuth2CallbackFilter extends AuthenticationFilter { private final ThreadLocalUserSession threadLocalUserSession; public OAuth2CallbackFilter(IdentityProviderRepository identityProviderRepository, OAuth2ContextFactory oAuth2ContextFactory, - Server server, AuthenticationEvent authenticationEvent, OAuth2AuthenticationParameters oauth2Parameters, ThreadLocalUserSession threadLocalUserSession) { - super(server, identityProviderRepository); + AuthenticationEvent authenticationEvent, OAuth2AuthenticationParameters oauth2Parameters, ThreadLocalUserSession threadLocalUserSession) { + super(identityProviderRepository); this.oAuth2ContextFactory = oAuth2ContextFactory; this.authenticationEvent = authenticationEvent; this.oauth2Parameters = oauth2Parameters; @@ -77,18 +77,22 @@ public class OAuth2CallbackFilter extends AuthenticationFilter { if (provider instanceof OAuth2IdentityProvider) { handleOAuth2Provider(response, request, (OAuth2IdentityProvider) provider); } else { - handleError(response, format("Not an OAuth2IdentityProvider: %s", provider.getClass())); + handleError(request, response, format("Not an OAuth2IdentityProvider: %s", provider.getClass())); } + } catch (EmailAlreadyExistsRedirectionException e) { + oauth2Parameters.delete(request, response); + e.addCookie(request, response); + redirectTo(response, e.getPath(request.getContextPath())); + } catch (UpdateLoginRedirectionException e) { + oauth2Parameters.delete(request, response); + redirectTo(response, e.getPath(request.getContextPath())); } catch (AuthenticationException e) { oauth2Parameters.delete(request, response); authenticationEvent.loginFailure(request, e); - handleAuthenticationError(e, response, getContextPath()); - } catch (RedirectionException e) { - oauth2Parameters.delete(request, response); - redirectTo(response, e.getPath(getContextPath())); + handleAuthenticationError(e, request, response); } catch (Exception e) { oauth2Parameters.delete(request, response); - handleError(e, response, format("Fail to callback authentication with '%s'", provider.getKey())); + handleError(e, request, response, format("Fail to callback authentication with '%s'", provider.getKey())); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java index 6b6fa443ff9..7c6bafe1eb3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java @@ -103,7 +103,7 @@ public class UserSessionInitializer { } if (isNotLocalOrJwt(e.getSource())) { // redirect to Unauthorized error page - handleAuthenticationError(e, response, request.getContextPath()); + handleAuthenticationError(e, request, response); return false; } // Web pages should redirect to the index.html file diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/EmailAlreadyExistsRedirectionException.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/EmailAlreadyExistsRedirectionException.java index 9cc370c368c..c2d103709b4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/EmailAlreadyExistsRedirectionException.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/EmailAlreadyExistsRedirectionException.java @@ -19,12 +19,16 @@ */ package org.sonar.server.authentication.exception; +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.api.server.authentication.UserIdentity; import org.sonar.db.user.UserDto; -import static java.lang.String.format; -import static org.sonar.server.authentication.AuthenticationRedirection.encodeMessage; +import static org.sonar.server.authentication.AuthenticationError.addErrorCookie; /** * This exception is used to redirect the user to a page explaining him that his email is already used by another account, @@ -32,7 +36,12 @@ import static org.sonar.server.authentication.AuthenticationRedirection.encodeMe */ public class EmailAlreadyExistsRedirectionException extends RedirectionException { - private static final String PATH = "/sessions/email_already_exists?email=%s&login=%s&provider=%s&existingLogin=%s&existingProvider=%s"; + private static final String PATH = "/sessions/email_already_exists"; + private static final String EMAIL_FIELD = "email"; + private static final String LOGIN_FIELD = "login"; + private static final String PROVIDER_FIELD = "provider"; + private static final String EXISTING_LOGIN_FIELD = "existingLogin"; + private static final String EXISTING_PROVIDER_FIELD = "existingProvider"; private final String email; private final UserDto existingUser; @@ -46,13 +55,19 @@ public class EmailAlreadyExistsRedirectionException extends RedirectionException this.provider = provider; } + public void addCookie(HttpServletRequest request, HttpServletResponse response) { + Gson gson = new GsonBuilder().create(); + String message = gson.toJson(ImmutableMap.of( + EMAIL_FIELD, email, + LOGIN_FIELD, userIdentity.getProviderLogin(), + PROVIDER_FIELD, provider.getKey(), + EXISTING_LOGIN_FIELD, existingUser.getExternalLogin(), + EXISTING_PROVIDER_FIELD, existingUser.getExternalIdentityProvider())); + addErrorCookie(request, response, message); + } + @Override public String getPath(String contextPath) { - return contextPath + format(PATH, - encodeMessage(email), - encodeMessage(userIdentity.getProviderLogin()), - encodeMessage(provider.getKey()), - encodeMessage(existingUser.getExternalLogin()), - encodeMessage(existingUser.getExternalIdentityProvider())); + return contextPath + PATH; } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java index de18a14d194..c23bb1ed59a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java @@ -20,6 +20,7 @@ package org.sonar.server.authentication; import javax.servlet.FilterChain; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Before; @@ -27,7 +28,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; -import org.sonar.api.platform.Server; import org.sonar.api.server.authentication.BaseIdentityProvider; import org.sonar.api.server.authentication.Display; import org.sonar.api.server.authentication.IdentityProvider; @@ -65,7 +65,6 @@ public class InitFilterTest { private BaseContextFactory baseContextFactory = mock(BaseContextFactory.class); private OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class); - private Server server = mock(Server.class); private HttpServletRequest request = mock(HttpServletRequest.class); private HttpServletResponse response = mock(HttpServletResponse.class); @@ -80,14 +79,15 @@ public class InitFilterTest { private OAuth2AuthenticationParameters auth2AuthenticationParameters = mock(OAuth2AuthenticationParameters.class); private ArgumentCaptor<AuthenticationException> authenticationExceptionCaptor = ArgumentCaptor.forClass(AuthenticationException.class); + private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - private InitFilter underTest = new InitFilter(identityProviderRepository, baseContextFactory, oAuth2ContextFactory, server, authenticationEvent, auth2AuthenticationParameters); + private InitFilter underTest = new InitFilter(identityProviderRepository, baseContextFactory, oAuth2ContextFactory, authenticationEvent, auth2AuthenticationParameters); @Before public void setUp() throws Exception { when(oAuth2ContextFactory.newContext(request, response, oAuth2IdentityProvider)).thenReturn(oauth2Context); when(baseContextFactory.newContext(request, response, baseIdentityProvider)).thenReturn(baseContext); - when(server.getContextPath()).thenReturn(""); + when(request.getContextPath()).thenReturn(""); } @Test @@ -97,7 +97,7 @@ public class InitFilterTest { @Test public void do_filter_with_context() { - when(server.getContextPath()).thenReturn("/sonarqube"); + when(request.getContextPath()).thenReturn("/sonarqube"); when(request.getRequestURI()).thenReturn("/sonarqube/sessions/init/" + OAUTH2_PROVIDER_KEY); identityProviderRepository.addIdentityProvider(oAuth2IdentityProvider); @@ -131,7 +131,7 @@ public class InitFilterTest { @Test public void init_authentication_parameter_on_auth2_identity_provider() { - when(server.getContextPath()).thenReturn("/sonarqube"); + when(request.getContextPath()).thenReturn("/sonarqube"); when(request.getRequestURI()).thenReturn("/sonarqube/sessions/init/" + OAUTH2_PROVIDER_KEY); identityProviderRepository.addIdentityProvider(oAuth2IdentityProvider); @@ -187,14 +187,14 @@ public class InitFilterTest { } @Test - public void redirect_when_failing_because_of_UnauthorizedExceptionException() throws Exception { + public void redirect_contains_cookie_with_error_message_when_failing_because_of_UnauthorizedExceptionException() throws Exception { IdentityProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider("failing"); when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey()); identityProviderRepository.addIdentityProvider(identityProvider); underTest.doFilter(request, response, chain); - verify(response).sendRedirect("/sessions/unauthorized?message=Email+john%40email.com+is+already+used"); + verify(response).sendRedirect("/sessions/unauthorized"); verify(authenticationEvent).loginFailure(eq(request), authenticationExceptionCaptor.capture()); AuthenticationException authenticationException = authenticationExceptionCaptor.getValue(); assertThat(authenticationException).hasMessage("Email john@email.com is already used"); @@ -202,23 +202,31 @@ public class InitFilterTest { assertThat(authenticationException.getLogin()).isNull(); assertThat(authenticationException.getPublicMessage()).isEqualTo("Email john@email.com is already used"); verifyDeleteAuthCookie(); + + verify(response).addCookie(cookieArgumentCaptor.capture()); + Cookie cookie = cookieArgumentCaptor.getValue(); + assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR"); + assertThat(cookie.getValue()).isEqualTo("Email%20john%40email.com%20is%20already%20used"); + assertThat(cookie.getPath()).isEqualTo("/"); + assertThat(cookie.isHttpOnly()).isFalse(); + assertThat(cookie.getMaxAge()).isEqualTo(300); + assertThat(cookie.getSecure()).isFalse(); } @Test public void redirect_with_context_path_when_failing_because_of_UnauthorizedException() throws Exception { - when(server.getContextPath()).thenReturn("/sonarqube"); + when(request.getContextPath()).thenReturn("/sonarqube"); IdentityProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider("failing"); when(request.getRequestURI()).thenReturn("/sonarqube/sessions/init/" + identityProvider.getKey()); identityProviderRepository.addIdentityProvider(identityProvider); underTest.doFilter(request, response, chain); - verify(response).sendRedirect("/sonarqube/sessions/unauthorized?message=Email+john%40email.com+is+already+used"); - verifyDeleteAuthCookie(); + verify(response).sendRedirect("/sonarqube/sessions/unauthorized"); } @Test - public void redirect_when_failing_because_of_EmailAlreadyExistException() throws Exception { + public void redirect_contains_cookie_when_failing_because_of_EmailAlreadyExistException() throws Exception { UserDto existingUser = newUserDto().setEmail("john@email.com").setExternalLogin("john.bitbucket").setExternalIdentityProvider("bitbucket"); FailWithEmailAlreadyExistException identityProvider = new FailWithEmailAlreadyExistException("failing", existingUser); when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey()); @@ -226,8 +234,16 @@ public class InitFilterTest { underTest.doFilter(request, response, chain); - verify(response).sendRedirect("/sessions/email_already_exists?email=john%40email.com&login=john.github&provider=failing&existingLogin=john.bitbucket&existingProvider=bitbucket"); + verify(response).sendRedirect("/sessions/email_already_exists"); verify(auth2AuthenticationParameters).delete(eq(request), eq(response)); + verify(response).addCookie(cookieArgumentCaptor.capture()); + Cookie cookie = cookieArgumentCaptor.getValue(); + assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR"); + assertThat(cookie.getValue()).contains("john%40email.com"); + assertThat(cookie.getPath()).isEqualTo("/"); + assertThat(cookie.isHttpOnly()).isFalse(); + assertThat(cookie.getMaxAge()).isEqualTo(300); + assertThat(cookie.getSecure()).isFalse(); } @Test @@ -243,6 +259,18 @@ public class InitFilterTest { verifyDeleteAuthCookie(); } + @Test + public void redirect_with_context_when_failing_because_of_Exception() throws Exception { + when(request.getContextPath()).thenReturn("/sonarqube"); + IdentityProvider identityProvider = new FailWithIllegalStateException("failing"); + when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey()); + identityProviderRepository.addIdentityProvider(identityProvider); + + underTest.doFilter(request, response, chain); + + verify(response).sendRedirect("/sonarqube/sessions/unauthorized"); + } + private void assertOAuth2InitCalled() { assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty(); assertThat(oAuth2IdentityProvider.isInitCalled()).isTrue(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImplTest.java index 1633de1dcd4..f60db60416e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImplTest.java @@ -26,7 +26,6 @@ import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.sonar.api.platform.Server; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -40,7 +39,6 @@ public class OAuth2AuthenticationParametersImplTest { private static final String AUTHENTICATION_COOKIE_NAME = "AUTH-PARAMS"; private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - private Server server = mock(Server.class); private HttpServletResponse response = mock(HttpServletResponse.class); private HttpServletRequest request = mock(HttpServletRequest.class); @@ -48,7 +46,7 @@ public class OAuth2AuthenticationParametersImplTest { @Before public void setUp() throws Exception { - when(server.getContextPath()).thenReturn(""); + when(request.getContextPath()).thenReturn(""); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java index b4a16191590..a42f29cf059 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java @@ -20,6 +20,7 @@ package org.sonar.server.authentication; import javax.servlet.FilterChain; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Before; @@ -27,7 +28,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; -import org.sonar.api.platform.Server; import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.UnauthorizedException; import org.sonar.api.server.authentication.UserIdentity; @@ -64,7 +64,6 @@ public class OAuth2CallbackFilterTest { private HttpServletRequest request = mock(HttpServletRequest.class); private HttpServletResponse response = mock(HttpServletResponse.class); - private Server server = mock(Server.class); private FilterChain chain = mock(FilterChain.class); private FakeOAuth2IdentityProvider oAuth2IdentityProvider = new WellbehaveFakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true, LOGIN); @@ -73,14 +72,15 @@ public class OAuth2CallbackFilterTest { private ThreadLocalUserSession threadLocalUserSession = mock(ThreadLocalUserSession.class); private ArgumentCaptor<AuthenticationException> authenticationExceptionCaptor = ArgumentCaptor.forClass(AuthenticationException.class); + private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - private OAuth2CallbackFilter underTest = new OAuth2CallbackFilter(identityProviderRepository, oAuth2ContextFactory, server, authenticationEvent, oAuthRedirection, + private OAuth2CallbackFilter underTest = new OAuth2CallbackFilter(identityProviderRepository, oAuth2ContextFactory, authenticationEvent, oAuthRedirection, threadLocalUserSession); @Before public void setUp() throws Exception { when(oAuth2ContextFactory.newCallback(request, response, oAuth2IdentityProvider)).thenReturn(mock(OAuth2IdentityProvider.CallbackContext.class)); - when(server.getContextPath()).thenReturn(""); + when(request.getContextPath()).thenReturn(""); } @Test @@ -90,7 +90,7 @@ public class OAuth2CallbackFilterTest { @Test public void do_filter_with_context() { - when(server.getContextPath()).thenReturn("/sonarqube"); + when(request.getContextPath()).thenReturn("/sonarqube"); when(request.getRequestURI()).thenReturn("/sonarqube/oauth2/callback/" + OAUTH2_PROVIDER_KEY); identityProviderRepository.addIdentityProvider(oAuth2IdentityProvider); when(threadLocalUserSession.hasSession()).thenReturn(true); @@ -104,7 +104,7 @@ public class OAuth2CallbackFilterTest { @Test public void do_filter_with_context_no_log_if_provider_did_not_call_authenticate_on_context() { - when(server.getContextPath()).thenReturn("/sonarqube"); + when(request.getContextPath()).thenReturn("/sonarqube"); when(request.getRequestURI()).thenReturn("/sonarqube/oauth2/callback/" + OAUTH2_PROVIDER_KEY); FakeOAuth2IdentityProvider identityProvider = new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true); identityProviderRepository.addIdentityProvider(identityProvider); @@ -164,7 +164,7 @@ public class OAuth2CallbackFilterTest { underTest.doFilter(request, response, chain); - verify(response).sendRedirect("/sessions/unauthorized?message=Email+john%40email.com+is+already+used"); + verify(response).sendRedirect("/sessions/unauthorized"); verify(authenticationEvent).loginFailure(eq(request), authenticationExceptionCaptor.capture()); AuthenticationException authenticationException = authenticationExceptionCaptor.getValue(); assertThat(authenticationException).hasMessage("Email john@email.com is already used"); @@ -172,18 +172,27 @@ public class OAuth2CallbackFilterTest { assertThat(authenticationException.getLogin()).isNull(); assertThat(authenticationException.getPublicMessage()).isEqualTo("Email john@email.com is already used"); verify(oAuthRedirection).delete(eq(request), eq(response)); + + verify(response).addCookie(cookieArgumentCaptor.capture()); + Cookie cookie = cookieArgumentCaptor.getValue(); + assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR"); + assertThat(cookie.getValue()).isEqualTo("Email%20john%40email.com%20is%20already%20used"); + assertThat(cookie.getPath()).isEqualTo("/"); + assertThat(cookie.isHttpOnly()).isFalse(); + assertThat(cookie.getMaxAge()).isEqualTo(300); + assertThat(cookie.getSecure()).isFalse(); } @Test public void redirect_with_context_path_when_failing_because_of_UnauthorizedExceptionException() throws Exception { - when(server.getContextPath()).thenReturn("/sonarqube"); + when(request.getContextPath()).thenReturn("/sonarqube"); FailWithUnauthorizedExceptionIdProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider(); when(request.getRequestURI()).thenReturn("/sonarqube/oauth2/callback/" + identityProvider.getKey()); identityProviderRepository.addIdentityProvider(identityProvider); underTest.doFilter(request, response, chain); - verify(response).sendRedirect("/sonarqube/sessions/unauthorized?message=Email+john%40email.com+is+already+used"); + verify(response).sendRedirect("/sonarqube/sessions/unauthorized"); verify(oAuthRedirection).delete(eq(request), eq(response)); } @@ -201,6 +210,18 @@ public class OAuth2CallbackFilterTest { } @Test + public void redirect_with_context_when_failing_because_of_Exception() throws Exception { + when(request.getContextPath()).thenReturn("/sonarqube"); + FailWithIllegalStateException identityProvider = new FailWithIllegalStateException(); + when(request.getRequestURI()).thenReturn("/oauth2/callback/" + identityProvider.getKey()); + identityProviderRepository.addIdentityProvider(identityProvider); + + underTest.doFilter(request, response, chain); + + verify(response).sendRedirect("/sonarqube/sessions/unauthorized"); + } + + @Test public void redirect_when_failing_because_of_EmailAlreadyExistException() throws Exception { UserDto existingUser = newUserDto().setEmail("john@email.com").setExternalLogin("john.bitbucket").setExternalIdentityProvider("bitbucket"); FailWithEmailAlreadyExistException identityProvider = new FailWithEmailAlreadyExistException(existingUser); @@ -209,9 +230,16 @@ public class OAuth2CallbackFilterTest { underTest.doFilter(request, response, chain); - verify(response) - .sendRedirect("/sessions/email_already_exists?email=john%40email.com&login=john.github&provider=failing&existingLogin=john.bitbucket&existingProvider=bitbucket"); + verify(response).sendRedirect("/sessions/email_already_exists"); verify(oAuthRedirection).delete(eq(request), eq(response)); + verify(response).addCookie(cookieArgumentCaptor.capture()); + Cookie cookie = cookieArgumentCaptor.getValue(); + assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR"); + assertThat(cookie.getValue()).contains("john%40email.com"); + assertThat(cookie.getPath()).isEqualTo("/"); + assertThat(cookie.isHttpOnly()).isFalse(); + assertThat(cookie.getMaxAge()).isEqualTo(300); + assertThat(cookie.getSecure()).isFalse(); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java index e25d6918b6e..cac1b7ceaf7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java @@ -19,6 +19,7 @@ */ package org.sonar.server.authentication; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Before; @@ -58,6 +59,8 @@ public class UserSessionInitializerTest { private RequestAuthenticator authenticator = mock(RequestAuthenticator.class); private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); private MapSettings settings = new MapSettings(); + private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); + private UserSessionInitializer underTest = new UserSessionInitializer(settings.asConfig(), threadLocalSession, authenticationEvent, authenticator); @Before @@ -154,7 +157,15 @@ public class UserSessionInitializerTest { assertThat(underTest.initUserSession(request, response)).isFalse(); - verify(response).sendRedirect("/sessions/unauthorized?message=Token+id+hasn%27t+been+found"); + verify(response).sendRedirect("/sessions/unauthorized"); + verify(response).addCookie(cookieArgumentCaptor.capture()); + Cookie cookie = cookieArgumentCaptor.getValue(); + assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR"); + assertThat(cookie.getValue()).isEqualTo("Token%20id%20hasn%27t%20been%20found"); + assertThat(cookie.getPath()).isEqualTo("/"); + assertThat(cookie.isHttpOnly()).isFalse(); + assertThat(cookie.getMaxAge()).isEqualTo(300); + assertThat(cookie.getSecure()).isFalse(); } @Test @@ -165,7 +176,7 @@ public class UserSessionInitializerTest { assertThat(underTest.initUserSession(request, response)).isFalse(); - verify(response).sendRedirect("/sonarqube/sessions/unauthorized?message=Token+id+hasn%27t+been+found"); + verify(response).sendRedirect("/sonarqube/sessions/unauthorized"); } private void assertPathIsIgnored(String path) { |