aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-server
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-11-29 18:04:01 +0100
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-12-01 16:55:11 +0100
commitec5d3d86508242b24f94151073eabf1cb1f9dd20 (patch)
tree295be00326566e298aa4f3fcccfc0d015d7fde61 /server/sonar-server
parent99edb798a22b9020408eb4331c77f01b76b033cb (diff)
downloadsonarqube-ec5d3d86508242b24f94151073eabf1cb1f9dd20.tar.gz
sonarqube-ec5d3d86508242b24f94151073eabf1cb1f9dd20.zip
SONAR-8416 add event log on error in OAuth2 and Base authent
Diffstat (limited to 'server/sonar-server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java20
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java92
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java80
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationException.java14
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java6
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/FakeBasicIdentityProvider.java1
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java129
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java12
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/JwtSerializerTest.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java43
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java6
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java6
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionMatcher.java47
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java11
20 files changed, 339 insertions, 173 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 c17c7028e3f..dc830b0ad73 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
@@ -23,14 +23,14 @@ import com.google.common.base.Charsets;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletResponse;
-import org.sonar.api.server.authentication.UnauthorizedException;
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 java.net.URLEncoder.encode;
-public class AuthenticationError {
+final class AuthenticationError {
private static final String UNAUTHORIZED_PATH = "/sessions/unauthorized";
private static final String UNAUTHORIZED_PATH_WITH_MESSAGE = UNAUTHORIZED_PATH + "?message=%s";
@@ -40,22 +40,26 @@ public class AuthenticationError {
// Utility class
}
- public static void handleError(Exception e, HttpServletResponse response, String message) {
+ static void handleError(Exception e, HttpServletResponse response, String message) {
LOGGER.error(message, e);
redirectToUnauthorized(response);
}
- public static void handleError(HttpServletResponse response, String message) {
+ static void handleError(HttpServletResponse response, String message) {
LOGGER.error(message);
redirectToUnauthorized(response);
}
- public static void handleUnauthorizedError(UnauthorizedException e, HttpServletResponse response) {
+ static void handleAuthenticationError(AuthenticationException e, HttpServletResponse response) {
redirectTo(response, getPath(e));
}
- private static String getPath(UnauthorizedException e) {
- return format(UNAUTHORIZED_PATH_WITH_MESSAGE, encodeMessage(e.getMessage()));
+ private static String getPath(AuthenticationException e) {
+ String publicMessage = e.getPublicMessage();
+ if (publicMessage == null || publicMessage.isEmpty()) {
+ return UNAUTHORIZED_PATH;
+ }
+ return format(UNAUTHORIZED_PATH_WITH_MESSAGE, encodeMessage(publicMessage));
}
private static String encodeMessage(String message) {
@@ -66,7 +70,7 @@ public class AuthenticationError {
}
}
- private static void redirectToUnauthorized(HttpServletResponse response) {
+ public static void redirectToUnauthorized(HttpServletResponse response) {
redirectTo(response, UNAUTHORIZED_PATH);
}
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 00e4a5b6e46..a95f788b810 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
@@ -20,6 +20,7 @@
package org.sonar.server.authentication;
import java.io.IOException;
+import javax.annotation.CheckForNull;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
@@ -33,11 +34,14 @@ import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.web.ServletFilter;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
+import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError;
import static org.sonar.server.authentication.AuthenticationError.handleError;
-import static org.sonar.server.authentication.AuthenticationError.handleUnauthorizedError;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
public class InitFilter extends ServletFilter {
@@ -47,12 +51,15 @@ public class InitFilter extends ServletFilter {
private final BaseContextFactory baseContextFactory;
private final OAuth2ContextFactory oAuth2ContextFactory;
private final Server server;
+ private final AuthenticationEvent authenticationEvent;
- public InitFilter(IdentityProviderRepository identityProviderRepository, BaseContextFactory baseContextFactory, OAuth2ContextFactory oAuth2ContextFactory, Server server) {
+ public InitFilter(IdentityProviderRepository identityProviderRepository, BaseContextFactory baseContextFactory,
+ OAuth2ContextFactory oAuth2ContextFactory, Server server, AuthenticationEvent authenticationEvent) {
this.identityProviderRepository = identityProviderRepository;
this.baseContextFactory = baseContextFactory;
this.oAuth2ContextFactory = oAuth2ContextFactory;
this.server = server;
+ this.authenticationEvent = authenticationEvent;
}
@Override
@@ -63,35 +70,80 @@ public class InitFilter extends ServletFilter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
- String requestURI = httpRequest.getRequestURI();
- String keyProvider = "";
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ IdentityProvider provider = resolveProviderOrHandleResponse(httpRequest, httpResponse);
+ if (provider != null) {
+ handleProvider(httpRequest, httpResponse, provider);
+ }
+ }
+
+ @CheckForNull
+ private IdentityProvider resolveProviderOrHandleResponse(HttpServletRequest request, HttpServletResponse response) {
+ String requestURI = request.getRequestURI();
+ String providerKey = extractKeyProvider(requestURI, server.getContextPath() + INIT_CONTEXT);
+ if (providerKey == null) {
+ handleError(response, "No provider key found in URI");
+ return null;
+ }
try {
- keyProvider = extractKeyProvider(requestURI, server.getContextPath() + INIT_CONTEXT);
- IdentityProvider provider = identityProviderRepository.getEnabledByKey(keyProvider);
- if (provider instanceof BaseIdentityProvider) {
- BaseIdentityProvider baseIdentityProvider = (BaseIdentityProvider) provider;
- baseIdentityProvider.init(baseContextFactory.newContext(httpRequest, (HttpServletResponse) response, baseIdentityProvider));
- } else if (provider instanceof OAuth2IdentityProvider) {
- OAuth2IdentityProvider oAuth2IdentityProvider = (OAuth2IdentityProvider) provider;
- oAuth2IdentityProvider.init(oAuth2ContextFactory.newContext(httpRequest, (HttpServletResponse) response, oAuth2IdentityProvider));
- } else {
- throw new UnsupportedOperationException(format("Unsupported IdentityProvider class: %s ", provider.getClass()));
- }
- } catch (UnauthorizedException e) {
- handleUnauthorizedError(e, (HttpServletResponse) response);
+ return identityProviderRepository.getEnabledByKey(providerKey);
} catch (Exception e) {
- handleError(e, (HttpServletResponse) response, format("Fail to initialize authentication with provider '%s'", keyProvider));
+ handleError(e, response, format("Failed to retrieve IdentityProvider for key '%s'", providerKey));
+ return null;
}
}
- public static String extractKeyProvider(String requestUri, String context) {
+ @CheckForNull
+ private static String extractKeyProvider(String requestUri, String context) {
if (requestUri.contains(context)) {
String key = requestUri.replace(context, "");
if (!isNullOrEmpty(key)) {
return key;
}
}
- throw new IllegalArgumentException("A valid identity provider key is required.");
+ return null;
+ }
+
+ private void handleProvider(HttpServletRequest request, HttpServletResponse response, IdentityProvider provider) {
+ try {
+ if (provider instanceof BaseIdentityProvider) {
+ handleBaseIdentityProvider(request, response, (BaseIdentityProvider) provider);
+ } else if (provider instanceof OAuth2IdentityProvider) {
+ handleOAuth2IdentityProvider(request, response, (OAuth2IdentityProvider) provider);
+ } else {
+ handleError(response, format("Unsupported IdentityProvider class: %s", provider.getClass()));
+ }
+ } catch (AuthenticationException e) {
+ authenticationEvent.failure(request, e);
+ handleAuthenticationError(e, response);
+ } catch (Exception e) {
+ handleError(e, response, format("Fail to initialize authentication with provider '%s'", provider.getKey()));
+ }
+ }
+
+ private void handleBaseIdentityProvider(HttpServletRequest request, HttpServletResponse response, BaseIdentityProvider provider) {
+ try {
+ provider.init(baseContextFactory.newContext(request, response, provider));
+ } catch (UnauthorizedException e) {
+ throw AuthenticationException.newBuilder()
+ .setSource(Source.external(provider))
+ .setMessage(e.getMessage())
+ .setPublicMessage(e.getMessage())
+ .build();
+ }
+ }
+
+ private void handleOAuth2IdentityProvider(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider provider) {
+ try {
+ provider.init(oAuth2ContextFactory.newContext(request, response, provider));
+ } catch (UnauthorizedException e) {
+ throw AuthenticationException.newBuilder()
+ .setSource(Source.oauth2(provider))
+ .setMessage(e.getMessage())
+ .setPublicMessage(e.getMessage())
+ .build();
+ }
}
@Override
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 b208b9aa281..d376634d3db 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
@@ -28,7 +28,6 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.sonar.api.CoreProperties;
import org.sonar.api.platform.Server;
import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
@@ -36,11 +35,12 @@ import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.web.ServletFilter;
import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
+import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError;
import static org.sonar.server.authentication.AuthenticationError.handleError;
-import static org.sonar.server.authentication.AuthenticationError.handleUnauthorizedError;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
public class OAuth2CallbackFilter extends ServletFilter {
@@ -68,29 +68,31 @@ public class OAuth2CallbackFilter extends ServletFilter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
- String requestUri = httpRequest.getRequestURI();
- String keyProvider = "";
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ IdentityProvider provider = resolveProviderOrHandleResponse(httpRequest, httpResponse);
+ if (provider != null) {
+ handleProvider(httpRequest, (HttpServletResponse) response, provider);
+ }
+ }
+
+ @CheckForNull
+ private IdentityProvider resolveProviderOrHandleResponse(HttpServletRequest request, HttpServletResponse response) {
+ String requestUri = request.getRequestURI();
+ String providerKey = extractKeyProvider(requestUri, server.getContextPath() + CALLBACK_PATH);
+ if (providerKey == null) {
+ handleError(response, "No provider key found in URI");
+ return null;
+ }
try {
- keyProvider = extractKeyProvider(requestUri, server.getContextPath() + CALLBACK_PATH);
- IdentityProvider provider = identityProviderRepository.getEnabledByKey(keyProvider);
- if (provider instanceof OAuth2IdentityProvider) {
- OAuth2IdentityProvider oauthProvider = (OAuth2IdentityProvider) provider;
- WrappedContext context = new WrappedContext(oAuth2ContextFactory.newCallback(httpRequest, (HttpServletResponse) response, oauthProvider));
- oauthProvider.callback(context);
- if (context.isAuthenticated()) {
- authenticationEvent.login(httpRequest, context.getLogin(), Source.oauth2(oauthProvider));
- }
- } else {
- handleError((HttpServletResponse) response, format("Not an OAuth2IdentityProvider: %s", provider.getClass()));
- }
- } catch (UnauthorizedException e) {
- handleUnauthorizedError(e, (HttpServletResponse) response);
+ return identityProviderRepository.getEnabledByKey(providerKey);
} catch (Exception e) {
- handleError(e, (HttpServletResponse) response,
- keyProvider.isEmpty() ? "Fail to callback authentication" : format("Fail to callback authentication with '%s'", keyProvider));
+ handleError(e, response, format("Failed to retrieve IdentityProvider for key '%s'", providerKey));
+ return null;
}
}
+ @CheckForNull
private static String extractKeyProvider(String requestUri, String context) {
if (requestUri.contains(context)) {
String key = requestUri.replace(context, "");
@@ -98,7 +100,43 @@ public class OAuth2CallbackFilter extends ServletFilter {
return key;
}
}
- throw new IllegalArgumentException(String.format("A valid identity provider key is required. Please check that property '%s' is valid.", CoreProperties.SERVER_BASE_URL));
+ return null;
+ }
+
+ private void handleProvider(HttpServletRequest request, HttpServletResponse response, IdentityProvider provider) {
+ try {
+ if (provider instanceof OAuth2IdentityProvider) {
+ handleOAuth2Provider(response, request, (OAuth2IdentityProvider) provider);
+ } else {
+ handleError(response, format("Not an OAuth2IdentityProvider: %s", provider.getClass()));
+ }
+ } catch (AuthenticationException e) {
+ authenticationEvent.failure(request, e);
+ handleAuthenticationError(e, response);
+ } catch (Exception e) {
+ handleError(e, response, format("Fail to callback authentication with '%s'", provider.getKey()));
+ }
+ }
+
+ private void handleOAuth2Provider(HttpServletResponse response, HttpServletRequest httpRequest, OAuth2IdentityProvider oAuth2Provider) {
+ OAuth2CallbackFilter.WrappedContext context = new OAuth2CallbackFilter.WrappedContext(oAuth2ContextFactory.newCallback(httpRequest, response, oAuth2Provider));
+ try {
+ oAuth2Provider.callback(context);
+ } catch (UnauthorizedException e) {
+ throw AuthenticationException.newBuilder()
+ .setSource(Source.oauth2(oAuth2Provider))
+ .setMessage(e.getMessage())
+ .setPublicMessage(e.getMessage())
+ .build();
+ }
+ if (context.isAuthenticated()) {
+ authenticationEvent.login(httpRequest, context.getLogin(), Source.oauth2(oAuth2Provider));
+ } else {
+ throw AuthenticationException.newBuilder()
+ .setSource(Source.oauth2(oAuth2Provider))
+ .setMessage("Plugin did not call authenticate")
+ .build();
+ }
}
@Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java
index 9cac78e0f90..cd451874871 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java
@@ -36,13 +36,13 @@ import org.sonar.api.Startable;
import org.sonar.api.config.Settings;
import org.sonar.api.server.authentication.Display;
import org.sonar.api.server.authentication.IdentityProvider;
-import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
import org.sonar.server.exceptions.BadRequestException;
import static org.apache.commons.lang.StringUtils.defaultIfBlank;
@@ -119,7 +119,10 @@ public class SsoAuthenticator implements Startable {
try {
return doAuthenticate(request, response);
} catch (BadRequestException e) {
- throw new UnauthorizedException(e.getMessage(), e);
+ throw AuthenticationException.newBuilder()
+ .setSource(Source.sso())
+ .setMessage(e.getMessage())
+ .build();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java
index 497bf058b0b..d0eeac4f932 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java
@@ -24,6 +24,7 @@ import java.util.Objects;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.sonar.api.server.authentication.BaseIdentityProvider;
+import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import static com.google.common.base.Preconditions.checkArgument;
@@ -127,7 +128,7 @@ public interface AuthenticationEvent {
return JWT_INSTANCE;
}
- public static Source external(BaseIdentityProvider identityProvider) {
+ public static Source external(IdentityProvider identityProvider) {
return new Source(
Method.EXTERNAL, Provider.EXTERNAL,
requireNonNull(identityProvider, "identityProvider can't be null").getName());
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationException.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationException.java
index 64ae64ea40a..f60c3f5b04a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationException.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationException.java
@@ -38,11 +38,13 @@ public class AuthenticationException extends RuntimeException {
private final AuthenticationEvent.Source source;
@CheckForNull
private final String login;
+ private final String publicMessage;
private AuthenticationException(Builder builder) {
super(builder.message);
this.source = requireNonNull(builder.source, "source can't be null");
this.login = builder.login;
+ this.publicMessage = builder.publicMessage;
}
public AuthenticationEvent.Source getSource() {
@@ -54,6 +56,11 @@ public class AuthenticationException extends RuntimeException {
return login;
}
+ @CheckForNull
+ public String getPublicMessage() {
+ return publicMessage;
+ }
+
public static Builder newBuilder() {
return new Builder();
}
@@ -65,6 +72,8 @@ public class AuthenticationException extends RuntimeException {
private String login;
@CheckForNull
private String message;
+ @CheckForNull
+ private String publicMessage;
private Builder() {
// use static factory method
@@ -85,6 +94,11 @@ public class AuthenticationException extends RuntimeException {
return this;
}
+ public Builder setPublicMessage(String publicMessage) {
+ this.publicMessage = publicMessage;
+ return this;
+ }
+
public AuthenticationException build() {
return new AuthenticationException(this);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java
index 271fa5e2f94..73ca6d740d4 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java
@@ -30,14 +30,11 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.server.authentication.UserSessionInitializer;
import org.sonar.server.organization.DefaultOrganizationCache;
import org.sonar.server.platform.Platform;
import org.sonar.server.setting.ThreadLocalSettings;
-import static org.sonar.server.authentication.AuthenticationError.handleUnauthorizedError;
-
public class UserSessionFilter implements Filter {
private final Platform platform;
@@ -67,10 +64,6 @@ public class UserSessionFilter implements Filter {
} finally {
settings.unload();
}
- } catch (UnauthorizedException e) {
- // Functional exceptions generated by Identity provider API (for instance, when authenticating with SSO and a bad login is given)
- // should redirect the user to a unauthorized page with the error message
- handleUnauthorizedError(e, response);
} finally {
defaultOrganizationCache.unload();
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java
index 9191da50352..2cdb832e8de 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java
@@ -121,7 +121,7 @@ public class BasicAuthenticatorTest {
public void fail_to_authenticate_when_no_login() throws Exception {
when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(":" + PASSWORD));
- expectedException.expect(authenticationException().from(Source.local(BASIC)).withoutLogin());
+ expectedException.expect(authenticationException().from(Source.local(BASIC)).withoutLogin().andNoPublicMessage());
try {
underTest.authenticate(request);
} finally {
@@ -157,7 +157,7 @@ public class BasicAuthenticatorTest {
when(userTokenAuthenticator.authenticate("token")).thenReturn(Optional.empty());
when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:"));
- expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withoutLogin());
+ expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withoutLogin().andNoPublicMessage());
try {
underTest.authenticate(request);
} finally {
@@ -171,7 +171,7 @@ public class BasicAuthenticatorTest {
when(userTokenAuthenticator.authenticate("token")).thenReturn(Optional.of("Unknown user"));
when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:"));
- expectedException.expect(authenticationException().from(Source.local(Method.BASIC_TOKEN)).withoutLogin());
+ expectedException.expect(authenticationException().from(Source.local(Method.BASIC_TOKEN)).withoutLogin().andNoPublicMessage());
try {
underTest.authenticate(request);
} finally {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java
index cd5f1d13b6c..2ba2a6fb839 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java
@@ -87,7 +87,7 @@ public class CredentialsAuthenticatorTest {
.setSalt("Wrong salt")
.setLocal(true));
- expectedException.expect(authenticationException().from(Source.local(BASIC)).withLogin(LOGIN));
+ expectedException.expect(authenticationException().from(Source.local(BASIC)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage("wrong password");
try {
executeAuthenticate(BASIC);
@@ -116,7 +116,7 @@ public class CredentialsAuthenticatorTest {
.setLogin(LOGIN)
.setLocal(false));
- expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withLogin(LOGIN));
+ expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage("User is not local");
try {
executeAuthenticate(BASIC_TOKEN);
@@ -133,7 +133,7 @@ public class CredentialsAuthenticatorTest {
.setSalt(SALT)
.setLocal(true));
- expectedException.expect(authenticationException().from(Source.local(BASIC)).withLogin(LOGIN));
+ expectedException.expect(authenticationException().from(Source.local(BASIC)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage("null password in DB");
try {
executeAuthenticate(BASIC);
@@ -150,7 +150,7 @@ public class CredentialsAuthenticatorTest {
.setSalt(null)
.setLocal(true));
- expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withLogin(LOGIN));
+ expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage("null salt");
try {
executeAuthenticate(BASIC_TOKEN);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeBasicIdentityProvider.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeBasicIdentityProvider.java
index a0ab806fb10..a6495c25a1f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeBasicIdentityProvider.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeBasicIdentityProvider.java
@@ -27,6 +27,7 @@ class FakeBasicIdentityProvider extends TestIdentityProvider implements BaseIden
public FakeBasicIdentityProvider(String key, boolean enabled) {
setKey(key);
+ setName("name of " + key);
setEnabled(enabled);
}
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 e4b147416fe..10dabe5f749 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
@@ -19,11 +19,6 @@
*/
package org.sonar.server.authentication;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -31,6 +26,7 @@ import org.junit.Before;
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;
@@ -39,36 +35,47 @@ import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
public class InitFilterTest {
- static String OAUTH2_PROVIDER_KEY = "github";
- static String BASIC_PROVIDER_KEY = "openid";
+ private static final String OAUTH2_PROVIDER_KEY = "github";
+ private static final String BASIC_PROVIDER_KEY = "openid";
@Rule
public LogTester logTester = new LogTester();
@Rule
public ExpectedException thrown = ExpectedException.none();
-
@Rule
public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule();
- BaseContextFactory baseContextFactory = mock(BaseContextFactory.class);
- OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class);
- Server server = mock(Server.class);
+ private BaseContextFactory baseContextFactory = mock(BaseContextFactory.class);
+ private OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class);
+ private Server server = mock(Server.class);
- HttpServletRequest request = mock(HttpServletRequest.class);
- HttpServletResponse response = mock(HttpServletResponse.class);
- FilterChain chain = mock(FilterChain.class);
+ private HttpServletRequest request = mock(HttpServletRequest.class);
+ private HttpServletResponse response = mock(HttpServletResponse.class);
+ private FilterChain chain = mock(FilterChain.class);
- FakeOAuth2IdentityProvider oAuth2IdentityProvider = new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true);
- OAuth2IdentityProvider.InitContext oauth2Context = mock(OAuth2IdentityProvider.InitContext.class);
+ private FakeOAuth2IdentityProvider oAuth2IdentityProvider = new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true);
+ private OAuth2IdentityProvider.InitContext oauth2Context = mock(OAuth2IdentityProvider.InitContext.class);
- FakeBasicIdentityProvider baseIdentityProvider = new FakeBasicIdentityProvider(BASIC_PROVIDER_KEY, true);
- BaseIdentityProvider.Context baseContext = mock(BaseIdentityProvider.Context.class);
+ private FakeBasicIdentityProvider baseIdentityProvider = new FakeBasicIdentityProvider(BASIC_PROVIDER_KEY, true);
+ private BaseIdentityProvider.Context baseContext = mock(BaseIdentityProvider.Context.class);
+ private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
- InitFilter underTest = new InitFilter(identityProviderRepository, baseContextFactory, oAuth2ContextFactory, server);
+ private ArgumentCaptor<AuthenticationException> authenticationExceptionCaptor = ArgumentCaptor.forClass(AuthenticationException.class);
+
+ private InitFilter underTest = new InitFilter(identityProviderRepository, baseContextFactory, oAuth2ContextFactory, server, authenticationEvent);
@Before
public void setUp() throws Exception {
@@ -91,6 +98,7 @@ public class InitFilterTest {
underTest.doFilter(request, response, chain);
assertOAuth2InitCalled();
+ verifyZeroInteractions(authenticationEvent);
}
@Test
@@ -101,6 +109,7 @@ public class InitFilterTest {
underTest.doFilter(request, response, chain);
assertOAuth2InitCalled();
+ verifyZeroInteractions(authenticationEvent);
}
@Test
@@ -111,6 +120,7 @@ public class InitFilterTest {
underTest.doFilter(request, response, chain);
assertBasicInitCalled();
+ verifyZeroInteractions(authenticationEvent);
}
@Test
@@ -119,52 +129,32 @@ public class InitFilterTest {
underTest.doFilter(request, response, chain);
- assertError("Fail to initialize authentication with provider ''");
+ assertError("No provider key found in URI");
+ verifyZeroInteractions(authenticationEvent);
}
@Test
- public void fail_if_uri_doesnt_contains_callback() throws Exception {
+ public void fail_if_uri_does_not_contains_callback() throws Exception {
when(request.getRequestURI()).thenReturn("/sessions/init");
underTest.doFilter(request, response, chain);
- assertError("Fail to initialize authentication with provider ''");
+ assertError("No provider key found in URI");
+ verifyZeroInteractions(authenticationEvent);
}
@Test
public void fail_if_identity_provider_class_is_unsupported() throws Exception {
- final String unsupportedKey = "unsupported";
+ String unsupportedKey = "unsupported";
when(request.getRequestURI()).thenReturn("/sessions/init/" + unsupportedKey);
- identityProviderRepository.addIdentityProvider(new IdentityProvider() {
- @Override
- public String getKey() {
- return unsupportedKey;
- }
-
- @Override
- public String getName() {
- return null;
- }
-
- @Override
- public Display getDisplay() {
- return null;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public boolean allowsUsersToSignUp() {
- return false;
- }
- });
+ IdentityProvider identityProvider = new UnsupportedIdentityProvider(unsupportedKey);
+ identityProviderRepository.addIdentityProvider(identityProvider);
underTest.doFilter(request, response, chain);
- assertError("Fail to initialize authentication with provider 'unsupported'");
+ assertError("Unsupported IdentityProvider class: class org.sonar.server.authentication.InitFilterTest$UnsupportedIdentityProvider");
+ verifyZeroInteractions(authenticationEvent);
+
}
@Test
@@ -176,6 +166,12 @@ public class InitFilterTest {
underTest.doFilter(request, response, chain);
verify(response).sendRedirect("/sessions/unauthorized?message=Email+john%40email.com+is+already+used");
+ verify(authenticationEvent).failure(eq(request), authenticationExceptionCaptor.capture());
+ AuthenticationException authenticationException = authenticationExceptionCaptor.getValue();
+ assertThat(authenticationException).hasMessage("Email john@email.com is already used");
+ assertThat(authenticationException.getSource()).isEqualTo(AuthenticationEvent.Source.external(identityProvider));
+ assertThat(authenticationException.getLogin()).isNull();
+ assertThat(authenticationException.getPublicMessage()).isEqualTo("Email john@email.com is already used");
}
private void assertOAuth2InitCalled() {
@@ -205,4 +201,37 @@ public class InitFilterTest {
throw new UnauthorizedException("Email john@email.com is already used");
}
}
+
+ private static class UnsupportedIdentityProvider implements IdentityProvider {
+ private final String unsupportedKey;
+
+ public UnsupportedIdentityProvider(String unsupportedKey) {
+ this.unsupportedKey = unsupportedKey;
+ }
+
+ @Override
+ public String getKey() {
+ return unsupportedKey;
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public Display getDisplay() {
+ return null;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean allowsUsersToSignUp() {
+ return false;
+ }
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java
index 96e1fe51347..d3044aa6375 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java
@@ -80,7 +80,7 @@ public class JwtCsrfVerifierTest {
mockRequestCsrf("other value");
mockPostJavaWsRequest();
- thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN));
+ thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage());
thrown.expectMessage("wrong CSFR in request");
underTest.verifyState(request, CSRF_STATE, LOGIN);
}
@@ -90,7 +90,7 @@ public class JwtCsrfVerifierTest {
mockRequestCsrf(CSRF_STATE);
mockPostJavaWsRequest();
- thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN));
+ thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage());
thrown.expectMessage("missing reference CSRF value");
underTest.verifyState(request, null, LOGIN);
}
@@ -100,7 +100,7 @@ public class JwtCsrfVerifierTest {
mockRequestCsrf(CSRF_STATE);
mockPostJavaWsRequest();
- thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN));
+ thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage());
thrown.expectMessage("missing reference CSRF value");
underTest.verifyState(request, "", LOGIN);
}
@@ -111,7 +111,7 @@ public class JwtCsrfVerifierTest {
when(request.getRequestURI()).thenReturn(JAVA_WS_URL);
when(request.getMethod()).thenReturn("POST");
- thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN));
+ thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage());
thrown.expectMessage("wrong CSFR in request");
underTest.verifyState(request, CSRF_STATE, LOGIN);
}
@@ -122,7 +122,7 @@ public class JwtCsrfVerifierTest {
when(request.getRequestURI()).thenReturn(JAVA_WS_URL);
when(request.getMethod()).thenReturn("PUT");
- thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN));
+ thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage());
thrown.expectMessage("wrong CSFR in request");
underTest.verifyState(request, CSRF_STATE, LOGIN);
}
@@ -133,7 +133,7 @@ public class JwtCsrfVerifierTest {
when(request.getRequestURI()).thenReturn(JAVA_WS_URL);
when(request.getMethod()).thenReturn("DELETE");
- thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN));
+ thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage());
thrown.expectMessage("wrong CSFR in request");
underTest.verifyState(request, CSRF_STATE, LOGIN);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtSerializerTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtSerializerTest.java
index d54f1519dc6..357c586b060 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtSerializerTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtSerializerTest.java
@@ -156,7 +156,7 @@ public class JwtSerializerTest {
.signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY))
.compact();
- expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN));
+ expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN).andNoPublicMessage());
expectedException.expectMessage("Token id hasn't been found");
underTest.decode(token);
}
@@ -174,7 +174,7 @@ public class JwtSerializerTest {
.signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY))
.compact();
- expectedException.expect(authenticationException().from(Source.jwt()).withoutLogin());
+ expectedException.expect(authenticationException().from(Source.jwt()).withoutLogin().andNoPublicMessage());
expectedException.expectMessage("Token subject hasn't been found");
underTest.decode(token);
}
@@ -192,7 +192,7 @@ public class JwtSerializerTest {
.signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY))
.compact();
- expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN));
+ expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN).andNoPublicMessage());
expectedException.expectMessage("Token expiration date hasn't been found");
underTest.decode(token);
}
@@ -209,7 +209,7 @@ public class JwtSerializerTest {
.signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY))
.compact();
- expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN));
+ expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN).andNoPublicMessage());
expectedException.expectMessage("Token creation date hasn't been found");
underTest.decode(token);
}
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 bc1c8b935ce..defff6598ca 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
@@ -26,6 +26,7 @@ import org.junit.Before;
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;
@@ -33,8 +34,10 @@ import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -63,6 +66,8 @@ public class OAuth2CallbackFilterTest {
private FakeOAuth2IdentityProvider oAuth2IdentityProvider = new WellbehaveFakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true, LOGIN);
private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
+ private ArgumentCaptor<AuthenticationException> authenticationExceptionCaptor = ArgumentCaptor.forClass(AuthenticationException.class);
+
private OAuth2CallbackFilter underTest = new OAuth2CallbackFilter(identityProviderRepository, oAuth2ContextFactory, server, authenticationEvent);
@Before
@@ -84,7 +89,8 @@ public class OAuth2CallbackFilterTest {
underTest.doFilter(request, response, chain);
- assertCallbackCalled(oAuth2IdentityProvider, true);
+ assertCallbackCalled(oAuth2IdentityProvider);
+ verify(authenticationEvent).login(request, LOGIN, Source.oauth2(oAuth2IdentityProvider));
}
@Test
@@ -96,7 +102,13 @@ public class OAuth2CallbackFilterTest {
underTest.doFilter(request, response, chain);
- assertCallbackCalled(identityProvider, false);
+ assertCallbackCalled(identityProvider);
+ verify(authenticationEvent).failure(eq(request), authenticationExceptionCaptor.capture());
+ AuthenticationException authenticationException = authenticationExceptionCaptor.getValue();
+ assertThat(authenticationException).hasMessage("Plugin did not call authenticate");
+ assertThat(authenticationException.getSource()).isEqualTo(Source.oauth2(identityProvider));
+ assertThat(authenticationException.getLogin()).isNull();
+ assertThat(authenticationException.getPublicMessage()).isNull();
}
@Test
@@ -106,7 +118,8 @@ public class OAuth2CallbackFilterTest {
underTest.doFilter(request, response, chain);
- assertCallbackCalled(oAuth2IdentityProvider, true);
+ assertCallbackCalled(oAuth2IdentityProvider);
+ verify(authenticationEvent).login(request, LOGIN, Source.oauth2(oAuth2IdentityProvider));
}
@Test
@@ -128,14 +141,16 @@ public class OAuth2CallbackFilterTest {
underTest.doFilter(request, response, chain);
- assertError("Fail to callback authentication with 'github'");
+ assertError("Failed to retrieve IdentityProvider for key 'github'");
verifyZeroInteractions(authenticationEvent);
}
@Test
public void redirect_when_failing_because_of_UnauthorizedExceptionException() throws Exception {
- TestIdentityProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider()
+ FailWithUnauthorizedExceptionIdProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider();
+ identityProvider
.setKey("failing")
+ .setName("name of failing")
.setEnabled(true);
when(request.getRequestURI()).thenReturn("/oauth2/callback/" + identityProvider.getKey());
identityProviderRepository.addIdentityProvider(identityProvider);
@@ -143,29 +158,27 @@ public class OAuth2CallbackFilterTest {
underTest.doFilter(request, response, chain);
verify(response).sendRedirect("/sessions/unauthorized?message=Email+john%40email.com+is+already+used");
- verifyZeroInteractions(authenticationEvent);
+ verify(authenticationEvent).failure(eq(request), authenticationExceptionCaptor.capture());
+ AuthenticationException authenticationException = authenticationExceptionCaptor.getValue();
+ assertThat(authenticationException).hasMessage("Email john@email.com is already used");
+ assertThat(authenticationException.getSource()).isEqualTo(Source.oauth2(identityProvider));
+ assertThat(authenticationException.getLogin()).isNull();
+ assertThat(authenticationException.getPublicMessage()).isEqualTo("Email john@email.com is already used");
}
@Test
public void fail_when_no_oauth2_provider_provided() throws Exception {
- String providerKey = "openid";
when(request.getRequestURI()).thenReturn("/oauth2/callback");
- identityProviderRepository.addIdentityProvider(new FakeBasicIdentityProvider(providerKey, true));
underTest.doFilter(request, response, chain);
- assertError("Fail to callback authentication");
+ assertError("No provider key found in URI");
verifyZeroInteractions(authenticationEvent);
}
- private void assertCallbackCalled(FakeOAuth2IdentityProvider oAuth2IdentityProvider, boolean expectLoginLog) {
+ private void assertCallbackCalled(FakeOAuth2IdentityProvider oAuth2IdentityProvider) {
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
assertThat(oAuth2IdentityProvider.isCallbackCalled()).isTrue();
- if (expectLoginLog) {
- verify(authenticationEvent).login(request, LOGIN, Source.oauth2(oAuth2IdentityProvider));
- } else {
- verifyZeroInteractions(authenticationEvent);
- }
}
private void assertError(String expectedError) throws Exception {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java
index 5f1825d6b1e..9704a0932fe 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java
@@ -91,7 +91,7 @@ public class OAuthCsrfVerifierTest {
when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("OAUTHSTATE", sha1Hex("state"))});
when(request.getParameter("state")).thenReturn("other value");
- thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin());
+ thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin().andNoPublicMessage());
thrown.expectMessage("CSRF state value is invalid");
underTest.verifyState(request, response, identityProvider);
}
@@ -101,7 +101,7 @@ public class OAuthCsrfVerifierTest {
when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("OAUTHSTATE", null)});
when(request.getParameter("state")).thenReturn("state");
- thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin());
+ thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin().andNoPublicMessage());
thrown.expectMessage("CSRF state value is invalid");
underTest.verifyState(request, response, identityProvider);
}
@@ -111,7 +111,7 @@ public class OAuthCsrfVerifierTest {
when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("OAUTHSTATE", sha1Hex("state"))});
when(request.getParameter("state")).thenReturn("");
- thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin());
+ thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin().andNoPublicMessage());
thrown.expectMessage("CSRF state value is invalid");
underTest.verifyState(request, response, identityProvider);
}
@@ -120,7 +120,7 @@ public class OAuthCsrfVerifierTest {
public void fail_with_AuthenticationException_when_cookie_is_missing() throws Exception {
when(request.getCookies()).thenReturn(new Cookie[] {});
- thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin());
+ thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin().andNoPublicMessage());
thrown.expectMessage("Cookie 'OAUTHSTATE' is missing");
underTest.verifyState(request, response, identityProvider);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java
index 7651e4cdd6e..1256494838e 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java
@@ -239,7 +239,7 @@ public class RealmAuthenticatorTest {
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null);
- expectedException.expect(authenticationException().from(Source.realm(BASIC, REALM_NAME)).withLogin(LOGIN));
+ expectedException.expect(authenticationException().from(Source.realm(BASIC, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage("No user details");
try {
underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
@@ -255,7 +255,7 @@ public class RealmAuthenticatorTest {
when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(false);
- expectedException.expect(authenticationException().from(Source.realm(BASIC, REALM_NAME)).withLogin(LOGIN));
+ expectedException.expect(authenticationException().from(Source.realm(BASIC, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage("realm returned authenticate=false");
try {
underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
@@ -272,7 +272,7 @@ public class RealmAuthenticatorTest {
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(new UserDetails());
- expectedException.expect(authenticationException().from(Source.realm(BASIC_TOKEN, REALM_NAME)).withLogin(LOGIN));
+ expectedException.expect(authenticationException().from(Source.realm(BASIC_TOKEN, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage(expectedMessage);
try {
underTest.authenticate(LOGIN, PASSWORD, request, BASIC_TOKEN);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java
index ab974915556..da40bd0bc47 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java
@@ -34,7 +34,6 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.config.MapSettings;
import org.sonar.api.config.Settings;
-import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.AlwaysIncreasingSystem2;
import org.sonar.core.util.stream.Collectors;
@@ -61,6 +60,7 @@ import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.db.user.UserTesting.newUserDto;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
+import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException;
public class SsoAuthenticatorTest {
@@ -339,11 +339,11 @@ public class SsoAuthenticatorTest {
}
@Test
- public void throw_UnauthorizedException_when_BadRequestException_is_generated() throws Exception {
+ public void throw_AuthenticationException_when_BadRequestException_is_generated() throws Exception {
startWithSso();
setNotUserInToken();
- expectedException.expect(UnauthorizedException.class);
+ expectedException.expect(authenticationException().from(Source.sso()).withoutLogin().andNoPublicMessage());
expectedException.expectMessage("user.bad_login");
try {
underTest.authenticate(createRequest("invalid login", DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
index a0b7e682a0e..6c649243caa 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
@@ -361,7 +361,7 @@ public class UserIdentityAuthenticatorTest {
.setAllowsUsersToSignUp(false);
Source source = Source.realm(Method.FORM, identityProvider.getName());
- thrown.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getLogin()));
+ thrown.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getLogin()).andNoPublicMessage());
thrown.expectMessage("'github' users are not allowed to sign up");
underTest.authenticate(USER_IDENTITY, identityProvider, source);
}
@@ -374,7 +374,7 @@ public class UserIdentityAuthenticatorTest {
.setEmail("john@email.com"));
Source source = Source.realm(Method.FORM, IDENTITY_PROVIDER.getName());
- thrown.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getLogin()));
+ thrown.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getLogin()).andNoPublicMessage());
thrown.expectMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
"This means that you probably already registered with another account.");
underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, source);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionMatcher.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionMatcher.java
index a0f1be6d976..2c64be7ceca 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionMatcher.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionMatcher.java
@@ -33,7 +33,7 @@ import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
* <p>
* Usage:
* <pre>
- * expectedException.expect(authenticationException().from(Source.local(Method.BASIC_TOKEN)).withoutLogin());
+ * expectedException.expect(authenticationException().from(Source.local(Method.BASIC_TOKEN)).withLogin("foo").andNoPublicMessage());
* </pre>
* </p>
*/
@@ -41,10 +41,13 @@ public class AuthenticationExceptionMatcher extends TypeSafeMatcher<Throwable> {
private final Source source;
@CheckForNull
private final String login;
+ @CheckForNull
+ private final String publicMessage;
- private AuthenticationExceptionMatcher(Source source, @Nullable String login) {
+ private AuthenticationExceptionMatcher(Source source, @Nullable String login, @Nullable String publicMessage) {
this.source = requireNonNull(source, "source can't be null");
this.login = login;
+ this.publicMessage = publicMessage;
}
public static Builder authenticationException() {
@@ -53,19 +56,29 @@ public class AuthenticationExceptionMatcher extends TypeSafeMatcher<Throwable> {
public static class Builder {
private Source source;
+ private String login;
public Builder from(Source source) {
this.source = checkSource(source);
return this;
}
- public AuthenticationExceptionMatcher withLogin(String login) {
- requireNonNull(login, "expected login can't be null");
- return new AuthenticationExceptionMatcher(checkSource(source), login);
+ public Builder withLogin(String login) {
+ this.login = requireNonNull(login, "expected login can't be null");
+ return this;
+ }
+
+ public Builder withoutLogin() {
+ this.login = null;
+ return this;
+ }
+
+ public AuthenticationExceptionMatcher andNoPublicMessage() {
+ return new AuthenticationExceptionMatcher(this.source, this.login, null);
}
- public AuthenticationExceptionMatcher withoutLogin() {
- return new AuthenticationExceptionMatcher(checkSource(source), null);
+ public AuthenticationExceptionMatcher andPublicMessage(String publicMessage){
+ return new AuthenticationExceptionMatcher(this.source, this.login, requireNonNull(publicMessage));
}
private static Source checkSource(Source source) {
@@ -98,6 +111,17 @@ public class AuthenticationExceptionMatcher extends TypeSafeMatcher<Throwable> {
return "login is \"" + login + "\"";
}
+ String publicMessage = authenticationException.getPublicMessage();
+ if (this.publicMessage == null) {
+ if (publicMessage != null) {
+ return "publicMessage is \"" + publicMessage + "\"";
+ }
+ } else if (publicMessage == null) {
+ return "publicMessage is null";
+ } else if (!this.publicMessage.equals(publicMessage)) {
+ return "publicMessage is \"" + publicMessage + "\"";
+ }
+
return null;
}
@@ -105,9 +129,14 @@ public class AuthenticationExceptionMatcher extends TypeSafeMatcher<Throwable> {
public void describeTo(Description description) {
description.appendText("AuthenticationException with source ").appendText(source.toString());
if (login == null) {
- description.appendText(" and no login");
+ description.appendText(", no login");
+ } else {
+ description.appendText(", login \"").appendText(login).appendText("\"");
+ }
+ if (publicMessage == null) {
+ description.appendText(" and no publicMessage");
} else {
- description.appendText(" and login \"").appendText(login).appendText("\"");
+ description.appendText(" and publicMessage \"").appendText(publicMessage).appendText("\"");
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java
index d04643132d1..3768bfc388b 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java
@@ -29,7 +29,6 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;
-import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.server.authentication.UserSessionInitializer;
import org.sonar.server.organization.DefaultOrganizationCache;
import org.sonar.server.platform.Platform;
@@ -205,16 +204,6 @@ public class UserSessionFilterTest {
}
@Test
- public void send_redirect_when_catching_functional_unauthorized_errors() throws Exception {
- mockUserSessionInitializer(true);
- doThrow(new UnauthorizedException("bad login")).when(userSessionInitializer).initUserSession(request, response);
-
- underTest.doFilter(request, response, chain);
-
- verify(response).sendRedirect("/sessions/unauthorized?message=bad+login");
- }
-
- @Test
public void just_for_fun_and_coverage() throws ServletException {
UserSessionFilter filter = new UserSessionFilter();
filter.init(mock(FilterConfig.class));