From: Sébastien Lesaint Date: Fri, 25 Nov 2016 10:41:10 +0000 (+0100) Subject: SONAR-8416 add log (INFO) when user successfuly log in X-Git-Tag: 6.3-RC1~891 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6aed8b579aafbbb6d9057b1fec4645590051750d;p=sonarqube.git SONAR-8416 add log (INFO) when user successfuly log in --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java index 4d810053907..639a9db840e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java @@ -20,6 +20,7 @@ package org.sonar.server.authentication; import org.sonar.core.platform.Module; +import org.sonar.server.authentication.event.AuthenticationEventImpl; import org.sonar.server.authentication.ws.AuthenticationWs; import org.sonar.server.authentication.ws.LoginAction; import org.sonar.server.authentication.ws.ValidateAction; @@ -28,6 +29,7 @@ public class AuthenticationModule extends Module { @Override protected void configureModule() { add( + AuthenticationEventImpl.class, AuthenticationWs.class, InitFilter.class, OAuth2CallbackFilter.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java index d87da6dcca4..1e1b2bdffb9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java @@ -27,11 +27,14 @@ import javax.servlet.http.HttpServletRequest; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.UserDto; +import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.usertoken.UserTokenAuthenticator; import static java.util.Locale.ENGLISH; import static org.apache.commons.lang.StringUtils.isEmpty; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method; +import static org.sonar.server.authentication.event.AuthenticationEvent.Source; public class BasicAuthenticator { @@ -43,12 +46,14 @@ public class BasicAuthenticator { private final DbClient dbClient; private final CredentialsAuthenticator credentialsAuthenticator; private final UserTokenAuthenticator userTokenAuthenticator; + private final AuthenticationEvent authenticationEvent; public BasicAuthenticator(DbClient dbClient, CredentialsAuthenticator credentialsAuthenticator, - UserTokenAuthenticator userTokenAuthenticator) { + UserTokenAuthenticator userTokenAuthenticator, AuthenticationEvent authenticationEvent) { this.dbClient = dbClient; this.credentialsAuthenticator = credentialsAuthenticator; this.userTokenAuthenticator = userTokenAuthenticator; + this.authenticationEvent = authenticationEvent; } public Optional authenticate(HttpServletRequest request) { @@ -60,7 +65,8 @@ public class BasicAuthenticator { String[] credentials = getCredentials(authorizationHeader); String login = credentials[0]; String password = credentials[1]; - return Optional.of(authenticate(login, password, request)); + UserDto userDto = authenticate(login, password, request); + return Optional.of(userDto); } private static String[] getCredentials(String authorizationHeader) { @@ -86,9 +92,11 @@ public class BasicAuthenticator { private UserDto authenticate(String login, String password, HttpServletRequest request) { if (isEmpty(password)) { - return authenticateFromUserToken(login); + UserDto userDto = authenticateFromUserToken(login); + authenticationEvent.login(request, userDto.getLogin(), Source.local(Method.BASIC_TOKEN)); + return userDto; } else { - return credentialsAuthenticator.authenticate(login, password, request); + return credentialsAuthenticator.authenticate(login, password, request, Method.BASIC); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java index 3706b8a072c..7c2d168621b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java @@ -20,40 +20,46 @@ package org.sonar.server.authentication; -import static org.sonar.db.user.UserDto.encryptPassword; - import java.util.Optional; import javax.servlet.http.HttpServletRequest; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.UserDto; +import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.exceptions.UnauthorizedException; +import static org.sonar.db.user.UserDto.encryptPassword; +import static org.sonar.server.authentication.event.AuthenticationEvent.*; + public class CredentialsAuthenticator { private final DbClient dbClient; private final RealmAuthenticator externalAuthenticator; + private final AuthenticationEvent authenticationEvent; - public CredentialsAuthenticator(DbClient dbClient, RealmAuthenticator externalAuthenticator) { + public CredentialsAuthenticator(DbClient dbClient, RealmAuthenticator externalAuthenticator, AuthenticationEvent authenticationEvent) { this.dbClient = dbClient; this.externalAuthenticator = externalAuthenticator; + this.authenticationEvent = authenticationEvent; } - public UserDto authenticate(String userLogin, String userPassword, HttpServletRequest request) { + public UserDto authenticate(String userLogin, String userPassword, HttpServletRequest request, Method method) { DbSession dbSession = dbClient.openSession(false); try { - return authenticate(dbSession, userLogin, userPassword, request); + return authenticate(dbSession, userLogin, userPassword, request, method); } finally { dbClient.closeSession(dbSession); } } - private UserDto authenticate(DbSession dbSession, String userLogin, String userPassword, HttpServletRequest request) { + private UserDto authenticate(DbSession dbSession, String userLogin, String userPassword, HttpServletRequest request, Method method) { UserDto user = dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin); if (user != null && user.isLocal()) { - return authenticateFromDb(user, userPassword); + UserDto userDto = authenticateFromDb(user, userPassword); + authenticationEvent.login(request, userLogin, Source.local(method)); + return userDto; } - Optional userDto = externalAuthenticator.authenticate(userLogin, userPassword, request); + Optional userDto = externalAuthenticator.authenticate(userLogin, userPassword, request, method); if (userDto.isPresent()) { return userDto.get(); } 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 f1ae0c5dcb9..e38f63adfb6 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 @@ -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; @@ -32,12 +33,15 @@ 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.api.server.authentication.UserIdentity; import org.sonar.api.web.ServletFilter; +import org.sonar.server.authentication.event.AuthenticationEvent; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; 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 { @@ -46,11 +50,14 @@ public class OAuth2CallbackFilter extends ServletFilter { private final IdentityProviderRepository identityProviderRepository; private final OAuth2ContextFactory oAuth2ContextFactory; private final Server server; + private final AuthenticationEvent authenticationEvent; - public OAuth2CallbackFilter(IdentityProviderRepository identityProviderRepository, OAuth2ContextFactory oAuth2ContextFactory, Server server) { + public OAuth2CallbackFilter(IdentityProviderRepository identityProviderRepository, OAuth2ContextFactory oAuth2ContextFactory, + Server server, AuthenticationEvent authenticationEvent) { this.identityProviderRepository = identityProviderRepository; this.oAuth2ContextFactory = oAuth2ContextFactory; this.server = server; + this.authenticationEvent = authenticationEvent; } @Override @@ -68,7 +75,11 @@ public class OAuth2CallbackFilter extends ServletFilter { IdentityProvider provider = identityProviderRepository.getEnabledByKey(keyProvider); if (provider instanceof OAuth2IdentityProvider) { OAuth2IdentityProvider oauthProvider = (OAuth2IdentityProvider) provider; - oauthProvider.callback(oAuth2ContextFactory.newCallback(httpRequest, (HttpServletResponse) response, oauthProvider)); + WrappedContext context = new WrappedContext(oAuth2ContextFactory.newCallback(httpRequest, (HttpServletResponse) response, oauthProvider)); + oauthProvider.callback(context); + if (context.isAuthenticated()) { + authenticationEvent.login(httpRequest, context.getLogin(), Source.oauth2(provider.getName())); + } } else { handleError((HttpServletResponse) response, format("Not an OAuth2IdentityProvider: %s", provider.getClass())); } @@ -76,12 +87,11 @@ public class OAuth2CallbackFilter extends ServletFilter { handleUnauthorizedError(e, (HttpServletResponse) response); } catch (Exception e) { handleError(e, (HttpServletResponse) response, - keyProvider.isEmpty() ? "Fail to callback authentication" : - format("Fail to callback authentication with '%s'", keyProvider)); + keyProvider.isEmpty() ? "Fail to callback authentication" : format("Fail to callback authentication with '%s'", keyProvider)); } } - public static String extractKeyProvider(String requestUri, String context) { + private static String extractKeyProvider(String requestUri, String context) { if (requestUri.contains(context)) { String key = requestUri.replace(context, ""); if (!isNullOrEmpty(key)) { @@ -100,4 +110,56 @@ public class OAuth2CallbackFilter extends ServletFilter { public void destroy() { // Nothing to do } + + private static final class WrappedContext implements OAuth2IdentityProvider.CallbackContext { + private final OAuth2IdentityProvider.CallbackContext delegate; + private boolean authenticated = false; + @CheckForNull + private String login; + + private WrappedContext(OAuth2IdentityProvider.CallbackContext delegate) { + this.delegate = delegate; + } + + @Override + public String getCallbackUrl() { + return delegate.getCallbackUrl(); + } + + @Override + public HttpServletRequest getRequest() { + return delegate.getRequest(); + } + + @Override + public HttpServletResponse getResponse() { + return delegate.getResponse(); + } + + @Override + public void verifyCsrfState() { + delegate.verifyCsrfState(); + } + + @Override + public void redirectToRequestedPage() { + delegate.redirectToRequestedPage(); + } + + @Override + public void authenticate(UserIdentity userIdentity) { + delegate.authenticate(userIdentity); + this.authenticated = true; + this.login = userIdentity.getLogin(); + } + + public boolean isAuthenticated() { + return authenticated; + } + + @CheckForNull + public String getLogin() { + return login; + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java index 76ccd170e91..f2ab92b7482 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java @@ -38,6 +38,7 @@ import org.sonar.api.server.authentication.UserIdentity; 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.exceptions.UnauthorizedException; import org.sonar.server.user.SecurityRealmFactory; @@ -45,6 +46,7 @@ import static java.util.Objects.requireNonNull; import static org.apache.commons.lang.StringUtils.isEmpty; import static org.apache.commons.lang.StringUtils.trimToNull; import static org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS; +import static org.sonar.server.authentication.event.AuthenticationEvent.Source; import static org.sonar.server.user.UserUpdater.SQ_AUTHORITY; public class RealmAuthenticator implements Startable { @@ -54,16 +56,19 @@ public class RealmAuthenticator implements Startable { private final Settings settings; private final SecurityRealmFactory securityRealmFactory; private final UserIdentityAuthenticator userIdentityAuthenticator; + private final AuthenticationEvent authenticationEvent; private SecurityRealm realm; private Authenticator authenticator; private ExternalUsersProvider externalUsersProvider; private ExternalGroupsProvider externalGroupsProvider; - public RealmAuthenticator(Settings settings, SecurityRealmFactory securityRealmFactory, UserIdentityAuthenticator userIdentityAuthenticator) { + public RealmAuthenticator(Settings settings, SecurityRealmFactory securityRealmFactory, + UserIdentityAuthenticator userIdentityAuthenticator, AuthenticationEvent authenticationEvent) { this.settings = settings; this.securityRealmFactory = securityRealmFactory; this.userIdentityAuthenticator = userIdentityAuthenticator; + this.authenticationEvent = authenticationEvent; } @Override @@ -76,14 +81,14 @@ public class RealmAuthenticator implements Startable { } } - public Optional authenticate(String userLogin, String userPassword, HttpServletRequest request) { + public Optional authenticate(String userLogin, String userPassword, HttpServletRequest request, AuthenticationEvent.Method method) { if (realm == null) { return Optional.empty(); } - return Optional.of(doAuthenticate(getLogin(userLogin), userPassword, request)); + return Optional.of(doAuthenticate(getLogin(userLogin), userPassword, request, method)); } - private UserDto doAuthenticate(String userLogin, String userPassword, HttpServletRequest request) { + private UserDto doAuthenticate(String userLogin, String userPassword, HttpServletRequest request, AuthenticationEvent.Method method) { try { ExternalUsersProvider.Context externalUsersProviderContext = new ExternalUsersProvider.Context(userLogin, request); UserDetails details = externalUsersProvider.doGetUserDetails(externalUsersProviderContext); @@ -95,7 +100,9 @@ public class RealmAuthenticator implements Startable { if (!status) { throw new UnauthorizedException("Fail to authenticate from external provider"); } - return synchronize(userLogin, details, request); + UserDto userDto = synchronize(userLogin, details, request); + authenticationEvent.login(request, userLogin, Source.realm(method, realm.getName())); + return userDto; } catch (Exception e) { // It seems that with Realm API it's expected to log the error and to not authenticate the user LOG.error("Error during authentication", e); 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 a1d3608fc78..292e101cf9c 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 @@ -42,10 +42,12 @@ 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.exceptions.BadRequestException; import static org.apache.commons.lang.StringUtils.defaultIfBlank; import static org.apache.commons.lang.time.DateUtils.addMinutes; +import static org.sonar.server.authentication.event.AuthenticationEvent.Source; import static org.sonar.server.user.UserUpdater.SQ_AUTHORITY; public class SsoAuthenticator implements Startable { @@ -84,15 +86,18 @@ public class SsoAuthenticator implements Startable { private final Settings settings; private final UserIdentityAuthenticator userIdentityAuthenticator; private final JwtHttpHandler jwtHttpHandler; + private final AuthenticationEvent authenticationEvent; private boolean enabled = false; private Map settingsByKey = new HashMap<>(); - public SsoAuthenticator(System2 system2, Settings settings, UserIdentityAuthenticator userIdentityAuthenticator, JwtHttpHandler jwtHttpHandler) { + public SsoAuthenticator(System2 system2, Settings settings, UserIdentityAuthenticator userIdentityAuthenticator, + JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) { this.system2 = system2; this.settings = settings; this.userIdentityAuthenticator = userIdentityAuthenticator; this.jwtHttpHandler = jwtHttpHandler; + this.authenticationEvent = authenticationEvent; } @Override @@ -134,6 +139,7 @@ public class SsoAuthenticator implements Startable { UserDto userDto = doAuthenticate(headerValuesByNames, login); jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response); + authenticationEvent.login(request, userDto.getLogin(), Source.sso()); return Optional.of(userDto); } 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 new file mode 100644 index 00000000000..a66e0e00efd --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.authentication.event; + +import java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +public interface AuthenticationEvent { + + void login(HttpServletRequest request, String login, Source source); + + enum Method { + BASIC, BASIC_TOKEN, FORM, FORM_TOKEN, SSO, OAUTH2, EXTERNAL + } + + enum Provider { + LOCAL, SSO, REALM, EXTERNAL + } + + class Source { + private static final String LOCAL_PROVIDER_NAME = "local"; + private static final Source SSO_INSTANCE = new Source(Method.SSO, Provider.SSO, "sso"); + + private final Method method; + private final Provider provider; + private final String providerName; + + private Source(Method method, Provider provider, String providerName) { + this.method = requireNonNull(method, "method can't be null"); + this.provider = requireNonNull(provider, "provider can't be null"); + this.providerName = requireNonNull(providerName, "provider name can't be null"); + checkArgument(!providerName.isEmpty(), "provider name can't be empty"); + } + + public static Source local(Method method) { + return new Source(method, Provider.LOCAL, LOCAL_PROVIDER_NAME); + } + + public static Source oauth2(String providerName) { + return new Source(Method.OAUTH2, Provider.EXTERNAL, providerName); + } + + public static Source realm(Method method, String providerName) { + return new Source(method, Provider.REALM, providerName); + } + + public static Source sso() { + return SSO_INSTANCE; + } + + public Method getMethod() { + return method; + } + + public Provider getProvider() { + return provider; + } + + public String getProviderName() { + return providerName; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Source source = (Source) o; + return method == source.method && + provider == source.provider && + providerName.equals(source.providerName); + } + + @Override + public int hashCode() { + return Objects.hash(method, provider, providerName); + } + + @Override + public String toString() { + return "Source{" + + "method=" + method + + ", provider=" + provider + + ", providerName='" + providerName + '\'' + + '}'; + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java new file mode 100644 index 00000000000..5a96961cb7c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.authentication.event; + +import com.google.common.base.Joiner; +import java.util.Collections; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.util.stream.Collectors; + +public class AuthenticationEventImpl implements AuthenticationEvent { + private static final Logger LOGGER = Loggers.get("auth.event"); + + @Override + public void login(HttpServletRequest request, @Nullable String login, Source source) { + LOGGER.info("login success [method|{}][provider|{}|{}][IP|{}|{}][login|{}]", + source.getMethod(), source.getProvider(), source.getProviderName(), request.getRemoteAddr(), getAllIps(request), + login == null ? "" : login); + } + + private static String getAllIps(HttpServletRequest request) { + return Collections.list(request.getHeaders("X-Forwarded-For")).stream().collect(Collectors.join(Joiner.on(","))); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java new file mode 100644 index 00000000000..be2ac2fad2d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.authentication.event; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java index 3a959c4fd12..ab057b08271 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java @@ -39,6 +39,7 @@ import org.sonar.server.user.ThreadLocalUserSession; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static org.apache.commons.lang.StringUtils.isEmpty; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method; public class LoginAction extends ServletFilter { @@ -88,7 +89,7 @@ public class LoginAction extends ServletFilter { if (isEmpty(login) || isEmpty(password)) { throw new UnauthorizedException(); } - return credentialsAuthenticator.authenticate(login, password, request); + return credentialsAuthenticator.authenticate(login, password, request, Method.FORM); } @Override 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 a5098b5a942..0a0a30c9258 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 @@ -23,7 +23,6 @@ package org.sonar.server.authentication; import java.util.Base64; import java.util.Optional; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -33,6 +32,7 @@ import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTesting; +import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.usertoken.UserTokenAuthenticator; @@ -41,18 +41,22 @@ import static org.assertj.core.api.Java6Assertions.assertThat; import static org.junit.rules.ExpectedException.none; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import static org.sonar.server.authentication.event.AuthenticationEvent.Source; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN; public class BasicAuthenticatorTest { private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); - static final String LOGIN = "login"; - static final String PASSWORD = "password"; - static final String CREDENTIALS_IN_BASE64 = toBase64(LOGIN + ":" + PASSWORD); + private static final String LOGIN = "login"; + private static final String PASSWORD = "password"; + private static final String CREDENTIALS_IN_BASE64 = toBase64(LOGIN + ":" + PASSWORD); - static final UserDto USER = UserTesting.newUserDto().setLogin(LOGIN); + private static final UserDto USER = UserTesting.newUserDto().setLogin(LOGIN); @Rule public ExpectedException expectedException = none(); @@ -60,44 +64,47 @@ public class BasicAuthenticatorTest { @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); - DbClient dbClient = dbTester.getDbClient(); + private DbClient dbClient = dbTester.getDbClient(); - DbSession dbSession = dbTester.getSession(); + private DbSession dbSession = dbTester.getSession(); - CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class); - UserTokenAuthenticator userTokenAuthenticator = mock(UserTokenAuthenticator.class); + private CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class); + private UserTokenAuthenticator userTokenAuthenticator = mock(UserTokenAuthenticator.class); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); + private HttpServletRequest request = mock(HttpServletRequest.class); - BasicAuthenticator underTest = new BasicAuthenticator(dbClient, credentialsAuthenticator, userTokenAuthenticator); + private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); + + private BasicAuthenticator underTest = new BasicAuthenticator(dbClient, credentialsAuthenticator, userTokenAuthenticator, authenticationEvent); @Test public void authenticate_from_basic_http_header() throws Exception { when(request.getHeader("Authorization")).thenReturn("Basic " + CREDENTIALS_IN_BASE64); - when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(USER); + when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC)).thenReturn(USER); underTest.authenticate(request); - verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request); + verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, BASIC); + verifyNoMoreInteractions(authenticationEvent); } @Test public void authenticate_from_basic_http_header_with_password_containing_semi_colon() throws Exception { String password = "!ascii-only:-)@"; when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(LOGIN + ":" + password)); - when(credentialsAuthenticator.authenticate(LOGIN, password, request)).thenReturn(USER); + when(credentialsAuthenticator.authenticate(LOGIN, password, request, BASIC)).thenReturn(USER); underTest.authenticate(request); - verify(credentialsAuthenticator).authenticate(LOGIN, password, request); + verify(credentialsAuthenticator).authenticate(LOGIN, password, request, BASIC); + verifyNoMoreInteractions(authenticationEvent); } @Test public void does_not_authenticate_when_no_authorization_header() throws Exception { underTest.authenticate(request); - verifyZeroInteractions(credentialsAuthenticator); + verifyZeroInteractions(credentialsAuthenticator, authenticationEvent); } @Test @@ -106,7 +113,7 @@ public class BasicAuthenticatorTest { underTest.authenticate(request); - verifyZeroInteractions(credentialsAuthenticator); + verifyZeroInteractions(credentialsAuthenticator, authenticationEvent); } @Test @@ -114,7 +121,11 @@ public class BasicAuthenticatorTest { when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(":" + PASSWORD)); expectedException.expect(UnauthorizedException.class); - underTest.authenticate(request); + try { + underTest.authenticate(request); + } finally { + verifyZeroInteractions(authenticationEvent); + } } @Test @@ -136,6 +147,7 @@ public class BasicAuthenticatorTest { assertThat(userDto.isPresent()).isTrue(); assertThat(userDto.get().getLogin()).isEqualTo(LOGIN); + verify(authenticationEvent).login(request, LOGIN, Source.local(BASIC_TOKEN)); } @Test @@ -145,7 +157,11 @@ public class BasicAuthenticatorTest { when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); expectedException.expect(UnauthorizedException.class); - underTest.authenticate(request); + try { + underTest.authenticate(request); + } finally { + verifyZeroInteractions(authenticationEvent); + } } @Test @@ -155,16 +171,20 @@ public class BasicAuthenticatorTest { when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); expectedException.expect(UnauthorizedException.class); - underTest.authenticate(request); + try { + underTest.authenticate(request); + } finally { + verifyZeroInteractions(authenticationEvent); + } } - private UserDto insertUser(UserDto userDto){ + private UserDto insertUser(UserDto userDto) { dbClient.userDao().insert(dbSession, userDto); dbSession.commit(); return userDto; } - private static String toBase64(String text){ + private static String toBase64(String text) { return new String(BASE64_ENCODER.encode(text.getBytes(UTF_8))); } 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 e4686805f2b..5b900f56c67 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 @@ -20,16 +20,8 @@ package org.sonar.server.authentication; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.rules.ExpectedException.none; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.db.user.UserTesting.newUserDto; - import java.util.Optional; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -38,30 +30,40 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.user.UserDto; +import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.exceptions.UnauthorizedException; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.rules.ExpectedException.none; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +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.AuthenticationEvent.Method.BASIC; + public class CredentialsAuthenticatorTest { - static final String LOGIN = "LOGIN"; - static final String PASSWORD = "PASSWORD"; - static final String SALT = "0242b0b4c0a93ddfe09dd886de50bc25ba000b51"; - static final String CRYPTED_PASSWORD = "540e4fc4be4e047db995bc76d18374a5b5db08cc"; + private static final String LOGIN = "LOGIN"; + private static final String PASSWORD = "PASSWORD"; + private static final String SALT = "0242b0b4c0a93ddfe09dd886de50bc25ba000b51"; + private static final String CRYPTED_PASSWORD = "540e4fc4be4e047db995bc76d18374a5b5db08cc"; @Rule public ExpectedException expectedException = none(); - @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); - DbClient dbClient = dbTester.getDbClient(); + private DbClient dbClient = dbTester.getDbClient(); - DbSession dbSession = dbTester.getSession(); + private DbSession dbSession = dbTester.getSession(); - RealmAuthenticator externalAuthenticator = mock(RealmAuthenticator.class); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); + private RealmAuthenticator externalAuthenticator = mock(RealmAuthenticator.class); + private HttpServletRequest request = mock(HttpServletRequest.class); + private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - CredentialsAuthenticator underTest = new CredentialsAuthenticator(dbClient, externalAuthenticator); + private CredentialsAuthenticator underTest = new CredentialsAuthenticator(dbClient, externalAuthenticator, authenticationEvent); @Test public void authenticate_local_user() throws Exception { @@ -73,6 +75,7 @@ public class CredentialsAuthenticatorTest { UserDto userDto = executeAuthenticate(); assertThat(userDto.getLogin()).isEqualTo(LOGIN); + verify(authenticationEvent).login(request, LOGIN, Source.local(BASIC)); } @Test @@ -84,30 +87,39 @@ public class CredentialsAuthenticatorTest { .setLocal(true)); expectedException.expect(UnauthorizedException.class); - executeAuthenticate(); + try { + executeAuthenticate(); + } finally { + verifyZeroInteractions(authenticationEvent); + } } @Test public void authenticate_external_user() throws Exception { - when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(Optional.of(newUserDto())); + when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC)).thenReturn(Optional.of(newUserDto())); insertUser(newUserDto() .setLogin(LOGIN) .setLocal(false)); executeAuthenticate(); - verify(externalAuthenticator).authenticate(LOGIN, PASSWORD, request); + verify(externalAuthenticator).authenticate(LOGIN, PASSWORD, request, BASIC); + verifyZeroInteractions(authenticationEvent); } @Test public void fail_to_authenticate_authenticate_external_user_when_no_external_authentication() throws Exception { - when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(Optional.empty()); + when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC)).thenReturn(Optional.empty()); insertUser(newUserDto() .setLogin(LOGIN) .setLocal(false)); expectedException.expect(UnauthorizedException.class); - executeAuthenticate(); + try { + executeAuthenticate(); + } finally { + verifyZeroInteractions(authenticationEvent); + } } @Test @@ -119,7 +131,11 @@ public class CredentialsAuthenticatorTest { .setLocal(true)); expectedException.expect(UnauthorizedException.class); - executeAuthenticate(); + try { + executeAuthenticate(); + } finally { + verifyZeroInteractions(authenticationEvent); + } } @Test @@ -131,14 +147,18 @@ public class CredentialsAuthenticatorTest { .setLocal(true)); expectedException.expect(UnauthorizedException.class); - executeAuthenticate(); + try { + executeAuthenticate(); + } finally { + verifyZeroInteractions(authenticationEvent); + } } - private UserDto executeAuthenticate(){ - return underTest.authenticate(LOGIN, PASSWORD, request); + private UserDto executeAuthenticate() { + return underTest.authenticate(LOGIN, PASSWORD, request, BASIC); } - private UserDto insertUser(UserDto userDto){ + private UserDto insertUser(UserDto userDto) { dbClient.userDao().insert(dbSession, userDto); dbSession.commit(); return userDto; diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeOAuth2IdentityProvider.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeOAuth2IdentityProvider.java index b174a2988f9..7a8aef6fc1e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeOAuth2IdentityProvider.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeOAuth2IdentityProvider.java @@ -28,6 +28,7 @@ class FakeOAuth2IdentityProvider extends TestIdentityProvider implements OAuth2I public FakeOAuth2IdentityProvider(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/OAuth2CallbackFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java index b0d6635464c..6ff937e9c60 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 @@ -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; @@ -34,37 +29,45 @@ import org.junit.rules.ExpectedException; 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; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.server.authentication.event.AuthenticationEvent; + +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.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.server.authentication.event.AuthenticationEvent.Source; public class OAuth2CallbackFilterTest { - static String OAUTH2_PROVIDER_KEY = "github"; + private static final String OAUTH2_PROVIDER_KEY = "github"; + private static final String LOGIN = "foo"; @Rule public LogTester logTester = new LogTester(); - @Rule public ExpectedException thrown = ExpectedException.none(); - @Rule public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule(); - OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class); + private OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - Server server = mock(Server.class); - FilterChain chain = mock(FilterChain.class); + private HttpServletRequest request = mock(HttpServletRequest.class); + private HttpServletResponse response = mock(HttpServletResponse.class); + private Server server = mock(Server.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 WellbehaveFakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true, LOGIN); + private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - OAuth2CallbackFilter underTest = new OAuth2CallbackFilter(identityProviderRepository, oAuth2ContextFactory, server); + private OAuth2CallbackFilter underTest = new OAuth2CallbackFilter(identityProviderRepository, oAuth2ContextFactory, server, authenticationEvent); @Before public void setUp() throws Exception { - when(oAuth2ContextFactory.newContext(request, response, oAuth2IdentityProvider)).thenReturn(oauth2Context); + when(oAuth2ContextFactory.newCallback(request, response, oAuth2IdentityProvider)).thenReturn(mock(OAuth2IdentityProvider.CallbackContext.class)); when(server.getContextPath()).thenReturn(""); } @@ -81,7 +84,19 @@ public class OAuth2CallbackFilterTest { underTest.doFilter(request, response, chain); - assertCallbackCalled(); + assertCallbackCalled(oAuth2IdentityProvider, true); + } + + @Test + public void do_filter_with_context_no_log_if_provider_did_not_call_authenticate_on_context() throws Exception { + when(server.getContextPath()).thenReturn("/sonarqube"); + when(request.getRequestURI()).thenReturn("/sonarqube/oauth2/callback/" + OAUTH2_PROVIDER_KEY); + FakeOAuth2IdentityProvider identityProvider = new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true); + identityProviderRepository.addIdentityProvider(identityProvider); + + underTest.doFilter(request, response, chain); + + assertCallbackCalled(identityProvider, false); } @Test @@ -91,7 +106,7 @@ public class OAuth2CallbackFilterTest { underTest.doFilter(request, response, chain); - assertCallbackCalled(); + assertCallbackCalled(oAuth2IdentityProvider, true); } @Test @@ -103,6 +118,7 @@ public class OAuth2CallbackFilterTest { underTest.doFilter(request, response, chain); assertError("Not an OAuth2IdentityProvider: class org.sonar.server.authentication.FakeBasicIdentityProvider"); + verifyZeroInteractions(authenticationEvent); } @Test @@ -113,6 +129,7 @@ public class OAuth2CallbackFilterTest { underTest.doFilter(request, response, chain); assertError("Fail to callback authentication with 'github'"); + verifyZeroInteractions(authenticationEvent); } @Test @@ -126,6 +143,7 @@ public class OAuth2CallbackFilterTest { underTest.doFilter(request, response, chain); verify(response).sendRedirect("/sessions/unauthorized?message=Email+john%40email.com+is+already+used"); + verifyZeroInteractions(authenticationEvent); } @Test @@ -137,11 +155,17 @@ public class OAuth2CallbackFilterTest { underTest.doFilter(request, response, chain); assertError("Fail to callback authentication"); + verifyZeroInteractions(authenticationEvent); } - private void assertCallbackCalled() { + private void assertCallbackCalled(FakeOAuth2IdentityProvider oAuth2IdentityProvider, boolean expectLoginLog) { assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty(); assertThat(oAuth2IdentityProvider.isCallbackCalled()).isTrue(); + if (expectLoginLog) { + verify(authenticationEvent).login(request, LOGIN, Source.oauth2(oAuth2IdentityProvider.getName())); + } else { + verifyZeroInteractions(authenticationEvent); + } } private void assertError(String expectedError) throws Exception { @@ -163,4 +187,26 @@ public class OAuth2CallbackFilterTest { } } + /** + * An extension of {@link FakeOAuth2IdentityProvider} that actually call {@link org.sonar.api.server.authentication.OAuth2IdentityProvider.CallbackContext#authenticate(UserIdentity)}. + */ + private static class WellbehaveFakeOAuth2IdentityProvider extends FakeOAuth2IdentityProvider { + private final String login; + + public WellbehaveFakeOAuth2IdentityProvider(String key, boolean enabled, String login) { + super(key, enabled); + this.login = login; + } + + @Override + public void callback(CallbackContext context) { + super.callback(context); + context.authenticate(UserIdentity.builder() + .setLogin(login) + .setProviderLogin(login) + .setEmail(login + "@toto.com") + .setName("name of " + login) + .build()); + } + } } 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 36682e42270..1d5ac67de48 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 @@ -21,7 +21,7 @@ package org.sonar.server.authentication; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -36,6 +36,7 @@ import org.sonar.api.security.UserDetails; import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.api.server.authentication.UserIdentity; import org.sonar.db.user.UserDto; +import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.user.SecurityRealmFactory; @@ -46,36 +47,45 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +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.Method.BASIC; public class RealmAuthenticatorTest { + private static final String LOGIN = "LOGIN"; + private static final String PASSWORD = "PASSWORD"; + + private static final UserDto USER = newUserDto(); + private static final String REALM_NAME = "realm name"; + @Rule public ExpectedException expectedException = none(); - static final String LOGIN = "LOGIN"; - static final String PASSWORD = "PASSWORD"; + private ArgumentCaptor userIdentityArgumentCaptor = ArgumentCaptor.forClass(UserIdentity.class); + private ArgumentCaptor identityProviderArgumentCaptor = ArgumentCaptor.forClass(IdentityProvider.class); - static final UserDto USER = newUserDto(); + private Settings settings = new MapSettings(); - ArgumentCaptor userIdentityArgumentCaptor = ArgumentCaptor.forClass(UserIdentity.class); - ArgumentCaptor identityProviderArgumentCaptor = ArgumentCaptor.forClass(IdentityProvider.class); + private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class); + private SecurityRealm realm = mock(SecurityRealm.class); + private Authenticator authenticator = mock(Authenticator.class); + private ExternalUsersProvider externalUsersProvider = mock(ExternalUsersProvider.class); + private ExternalGroupsProvider externalGroupsProvider = mock(ExternalGroupsProvider.class); - Settings settings = new MapSettings(); + private UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class); + private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class); - SecurityRealm realm = mock(SecurityRealm.class); - Authenticator authenticator = mock(Authenticator.class); - ExternalUsersProvider externalUsersProvider = mock(ExternalUsersProvider.class); - ExternalGroupsProvider externalGroupsProvider = mock(ExternalGroupsProvider.class); + private HttpServletRequest request = mock(HttpServletRequest.class); - UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class); + private RealmAuthenticator underTest = new RealmAuthenticator(settings, securityRealmFactory, userIdentityAuthenticator, authenticationEvent); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - - RealmAuthenticator underTest = new RealmAuthenticator(settings, securityRealmFactory, userIdentityAuthenticator); + @Before + public void setUp() throws Exception { + when(realm.getName()).thenReturn(REALM_NAME); + } @Test public void authenticate() throws Exception { @@ -87,7 +97,7 @@ public class RealmAuthenticatorTest { when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER); - underTest.authenticate(LOGIN, PASSWORD, request); + underTest.authenticate(LOGIN, PASSWORD, request, BASIC); verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture()); UserIdentity userIdentity = userIdentityArgumentCaptor.getValue(); @@ -96,6 +106,7 @@ public class RealmAuthenticatorTest { assertThat(userIdentity.getName()).isEqualTo("name"); assertThat(userIdentity.getEmail()).isEqualTo("email"); assertThat(userIdentity.shouldSyncGroups()).isFalse(); + verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -108,7 +119,7 @@ public class RealmAuthenticatorTest { when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER); - underTest.authenticate(LOGIN, PASSWORD, request); + underTest.authenticate(LOGIN, PASSWORD, request, BASIC); verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture()); @@ -116,6 +127,7 @@ public class RealmAuthenticatorTest { assertThat(identityProviderArgumentCaptor.getValue().getName()).isEqualTo("sonarqube"); assertThat(identityProviderArgumentCaptor.getValue().getDisplay()).isNull(); assertThat(identityProviderArgumentCaptor.getValue().isEnabled()).isTrue(); + verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -127,10 +139,11 @@ public class RealmAuthenticatorTest { when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER); - underTest.authenticate(LOGIN, PASSWORD, request); + underTest.authenticate(LOGIN, PASSWORD, request, BASIC); verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture()); assertThat(identityProviderArgumentCaptor.getValue().getName()).isEqualTo("sonarqube"); + verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -145,6 +158,7 @@ public class RealmAuthenticatorTest { UserIdentity userIdentity = userIdentityArgumentCaptor.getValue(); assertThat(userIdentity.shouldSyncGroups()).isTrue(); assertThat(userIdentity.getGroups()).containsOnly("group1", "group2"); + verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -156,10 +170,11 @@ public class RealmAuthenticatorTest { when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER); - underTest.authenticate(LOGIN, PASSWORD, request); + underTest.authenticate(LOGIN, PASSWORD, request, BASIC); verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture()); assertThat(userIdentityArgumentCaptor.getValue().getName()).isEqualTo(LOGIN); + verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -171,6 +186,7 @@ public class RealmAuthenticatorTest { verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture()); assertThat(identityProviderArgumentCaptor.getValue().allowsUsersToSignUp()).isTrue(); + verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -182,6 +198,7 @@ public class RealmAuthenticatorTest { verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture()); assertThat(identityProviderArgumentCaptor.getValue().allowsUsersToSignUp()).isFalse(); + verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -195,6 +212,7 @@ public class RealmAuthenticatorTest { UserIdentity userIdentity = userIdentityArgumentCaptor.getValue(); assertThat(userIdentity.getLogin()).isEqualTo("login"); assertThat(userIdentity.getProviderLogin()).isEqualTo("login"); + verify(authenticationEvent).login(request, "login", AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -208,6 +226,7 @@ public class RealmAuthenticatorTest { UserIdentity userIdentity = userIdentityArgumentCaptor.getValue(); assertThat(userIdentity.getLogin()).isEqualTo("LoGiN"); assertThat(userIdentity.getProviderLogin()).isEqualTo("LoGiN"); + verify(authenticationEvent).login(request, "LoGiN", AuthenticationEvent.Source.realm(BASIC, REALM_NAME)); } @Test @@ -218,7 +237,11 @@ public class RealmAuthenticatorTest { when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null); expectedException.expect(UnauthorizedException.class); - underTest.authenticate(LOGIN, PASSWORD, request); + try { + underTest.authenticate(LOGIN, PASSWORD, request, BASIC); + } finally { + verifyZeroInteractions(authenticationEvent); + } } @Test @@ -229,7 +252,11 @@ public class RealmAuthenticatorTest { when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(false); expectedException.expect(UnauthorizedException.class); - underTest.authenticate(LOGIN, PASSWORD, request); + try { + underTest.authenticate(LOGIN, PASSWORD, request, BASIC); + } finally { + verifyZeroInteractions(authenticationEvent); + } } @Test @@ -240,12 +267,17 @@ public class RealmAuthenticatorTest { when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null); expectedException.expect(UnauthorizedException.class); - underTest.authenticate(LOGIN, PASSWORD, request); + try { + underTest.authenticate(LOGIN, PASSWORD, request, BASIC); + } finally { + verifyZeroInteractions(authenticationEvent); + } } @Test public void return_empty_user_when_no_realm() throws Exception { - assertThat(underTest.authenticate(LOGIN, PASSWORD, request)).isEmpty(); + assertThat(underTest.authenticate(LOGIN, PASSWORD, request, BASIC)).isEmpty(); + verifyNoMoreInteractions(authenticationEvent); } @Test @@ -293,7 +325,7 @@ public class RealmAuthenticatorTest { UserDetails userDetails = new UserDetails(); userDetails.setName("name"); when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); - underTest.authenticate(login, PASSWORD, request); + underTest.authenticate(login, PASSWORD, request, BASIC); } } 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 9ed6449b1db..ab974915556 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 @@ -41,6 +41,7 @@ import org.sonar.core.util.stream.Collectors; import org.sonar.db.DbTester; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; +import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.organization.TestDefaultOrganizationProvider; import org.sonar.server.user.NewUserNotifier; @@ -59,6 +60,7 @@ import static org.mockito.Mockito.verify; 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; public class SsoAuthenticatorTest { @@ -99,8 +101,9 @@ public class SsoAuthenticatorTest { private HttpServletResponse response = mock(HttpServletResponse.class); private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); + private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - private SsoAuthenticator underTest = new SsoAuthenticator(system2, settings, userIdentityAuthenticator, jwtHttpHandler); + private SsoAuthenticator underTest = new SsoAuthenticator(system2, settings, userIdentityAuthenticator, jwtHttpHandler, authenticationEvent); @Before public void setUp() throws Exception { @@ -120,6 +123,7 @@ public class SsoAuthenticatorTest { verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2); verifyTokenIsUpdated(NOW); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -127,9 +131,11 @@ public class SsoAuthenticatorTest { startWithSso(); setNotUserInToken(); - underTest.authenticate(createRequest(DEFAULT_LOGIN, null, null, null), response); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, null, null, null); + underTest.authenticate(request, response); verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -144,6 +150,7 @@ public class SsoAuthenticatorTest { verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group2); verifyTokenIsUpdated(NOW); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -151,10 +158,12 @@ public class SsoAuthenticatorTest { startWithSso(); setNotUserInToken(); insertUser(DEFAULT_USER, group1); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, ""); - underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, ""), response); + underTest.authenticate(request, response); verityUserHasNoGroup(DEFAULT_LOGIN); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -165,10 +174,12 @@ public class SsoAuthenticatorTest { Map headerValuesByName = new HashMap<>(); headerValuesByName.put("X-Forwarded-Login", DEFAULT_LOGIN); headerValuesByName.put("X-Forwarded-Groups", null); + HttpServletRequest request = createRequest(headerValuesByName); - underTest.authenticate(createRequest(headerValuesByName), response); + underTest.authenticate(request, response); verityUserHasNoGroup(DEFAULT_LOGIN); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -176,10 +187,12 @@ public class SsoAuthenticatorTest { startWithSso(); setNotUserInToken(); insertUser(DEFAULT_USER, group1); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, null); - underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, null), response); + underTest.authenticate(request, response); verityUserGroups(DEFAULT_LOGIN, group1); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -194,6 +207,7 @@ public class SsoAuthenticatorTest { // User is not updated verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1); verifyTokenIsNotUpdated(); + verifyZeroInteractions(authenticationEvent); } @Test @@ -209,6 +223,7 @@ public class SsoAuthenticatorTest { // User is updated verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2); verifyTokenIsUpdated(NOW); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -223,6 +238,7 @@ public class SsoAuthenticatorTest { // User is updated verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2); verifyTokenIsUpdated(NOW); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -239,6 +255,7 @@ public class SsoAuthenticatorTest { // User is not updated verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1); verifyTokenIsNotUpdated(); + verifyZeroInteractions(authenticationEvent); } @Test @@ -252,6 +269,7 @@ public class SsoAuthenticatorTest { verifyUserInDb("AnotherLogin", "Another name", "Another email", group2); verifyTokenIsUpdated(NOW); + verify(authenticationEvent).login(request, "AnotherLogin", Source.sso()); } @Test @@ -267,6 +285,7 @@ public class SsoAuthenticatorTest { underTest.authenticate(request, response); verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -282,6 +301,7 @@ public class SsoAuthenticatorTest { underTest.authenticate(request, response); verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -293,6 +313,7 @@ public class SsoAuthenticatorTest { underTest.authenticate(request, response); verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, group1, group2); + verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso()); } @Test @@ -304,6 +325,7 @@ public class SsoAuthenticatorTest { verifyUserNotAuthenticated(); verifyTokenIsNotUpdated(); + verifyZeroInteractions(authenticationEvent); } @Test @@ -313,7 +335,7 @@ public class SsoAuthenticatorTest { underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); verifyUserNotAuthenticated(); - verifyZeroInteractions(jwtHttpHandler); + verifyZeroInteractions(jwtHttpHandler, authenticationEvent); } @Test @@ -323,7 +345,11 @@ public class SsoAuthenticatorTest { expectedException.expect(UnauthorizedException.class); expectedException.expectMessage("user.bad_login"); - underTest.authenticate(createRequest("invalid login", DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); + try { + underTest.authenticate(createRequest("invalid login", DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); + } finally { + verifyZeroInteractions(authenticationEvent); + } } private void startWithSso() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java new file mode 100644 index 00000000000..3cff532ccb8 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.authentication.event; + +import com.google.common.base.Joiner; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method; +import static org.sonar.server.authentication.event.AuthenticationEvent.Source; + +public class AuthenticationEventImplTest { + @Rule + public LogTester logTester = new LogTester(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private AuthenticationEventImpl underTest = new AuthenticationEventImpl(); + + @Test + public void login_fails_with_NPE_if_request_is_null() { + expectedException.expect(NullPointerException.class); + + underTest.login(null, "login", Source.sso()); + } + + @Test + public void login_fails_with_NPE_if_source_is_null() { + expectedException.expect(NullPointerException.class); + + underTest.login(mock(HttpServletRequest.class), "login", null); + } + + @Test + public void login_creates_INFO_log_with_empty_login_if_login_argument_is_null() { + underTest.login(mockRequest(), null, Source.sso()); + + verifyLog("login success [method|SSO][provider|SSO|sso][IP||][login|]"); + } + + @Test + public void login_creates_INFO_log_with_method_provider_and_login() { + underTest.login(mockRequest(), "foo", Source.realm(Method.BASIC, "some provider name")); + + verifyLog("login success [method|BASIC][provider|REALM|some provider name][IP||][login|foo]"); + } + + @Test + public void login_logs_remote_ip_from_request() { + underTest.login(mockRequest("1.2.3.4"), "foo", Source.realm(Method.EXTERNAL, "bar")); + + verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|][login|foo]"); + } + + @Test + public void login_logs_X_Forwarded_For_header_from_request() { + HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5")); + underTest.login(request, "foo", Source.realm(Method.EXTERNAL, "bar")); + + verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|2.3.4.5][login|foo]"); + } + + @Test + public void login_logs_X_Forwarded_For_header_from_request_and_supports_multiple_headers() { + HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5", "6.5.4.3"), asList("9.5.6.7"), asList("6.3.2.4")); + underTest.login(request, "foo", Source.realm(Method.EXTERNAL, "bar")); + + verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|2.3.4.5,6.5.4.3,9.5.6.7,6.3.2.4][login|foo]"); + } + + private void verifyLog(String expected) { + assertThat(logTester.logs()).hasSize(1); + assertThat(logTester.logs(LoggerLevel.INFO)) + .containsOnly(expected); + } + + private static HttpServletRequest mockRequest() { + return mockRequest(""); + } + + private static HttpServletRequest mockRequest(String remoteAddr, List... remoteIps) { + HttpServletRequest res = mock(HttpServletRequest.class); + when(res.getRemoteAddr()).thenReturn(remoteAddr); + when(res.getHeaders("X-Forwarded-For")) + .thenReturn(Collections.enumeration( + Arrays.stream(remoteIps) + .map(Joiner.on(",")::join) + .collect(Collectors.toList()))); + return res; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java new file mode 100644 index 00000000000..95e6b22f525 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.authentication.event; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method; +import static org.sonar.server.authentication.event.AuthenticationEvent.Provider; +import static org.sonar.server.authentication.event.AuthenticationEvent.Source; + +public class AuthenticationEventSourceTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void local_fails_with_NPE_if_method_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("method can't be null"); + + Source.local(null); + } + + @Test + public void local_creates_source_instance_with_specified_method_and_hardcoded_provider_and_provider_name() { + Source underTest = Source.local(Method.BASIC_TOKEN); + + assertThat(underTest.getMethod()).isEqualTo(Method.BASIC_TOKEN); + assertThat(underTest.getProvider()).isEqualTo(Provider.LOCAL); + assertThat(underTest.getProviderName()).isEqualTo("local"); + } + + @Test + public void oauth2_fails_with_NPE_if_providerName_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("provider name can't be null"); + + Source.oauth2(null); + } + + @Test + public void oauth2_creates_source_instance_with_specified_provider_name_and_hardcoded_provider_and_method() { + Source underTest = Source.oauth2("some name"); + + assertThat(underTest.getMethod()).isEqualTo(Method.OAUTH2); + assertThat(underTest.getProvider()).isEqualTo(Provider.EXTERNAL); + assertThat(underTest.getProviderName()).isEqualTo("some name"); + } + + @Test + public void oauth2_fails_with_IAE_if_providerName_is_empty() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("provider name can't be empty"); + + Source.oauth2(""); + } + + @Test + public void realm_fails_with_NPE_if_method_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("method can't be null"); + + Source.realm(null, "name"); + } + + @Test + public void realm_fails_with_NPE_if_providerName_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("provider name can't be null"); + + Source.realm(Method.BASIC, null); + } + + @Test + public void realm_fails_with_IAE_if_providerName_is_empty() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("provider name can't be empty"); + + Source.realm(Method.BASIC, ""); + } + + @Test + public void realm_creates_source_instance_with_specified_method_and_provider_name_and_hardcoded_provider() { + Source underTest = Source.realm(Method.BASIC, "some name"); + + assertThat(underTest.getMethod()).isEqualTo(Method.BASIC); + assertThat(underTest.getProvider()).isEqualTo(Provider.REALM); + assertThat(underTest.getProviderName()).isEqualTo("some name"); + } + + @Test + public void sso_returns_source_instance_with_hardcoded_method_provider_and_providerName() { + Source underTest = Source.sso(); + + assertThat(underTest.getMethod()).isEqualTo(Method.SSO); + assertThat(underTest.getProvider()).isEqualTo(Provider.SSO); + assertThat(underTest.getProviderName()).isEqualTo("sso"); + + assertThat(underTest).isSameAs(Source.sso()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java index e37274ce3ce..7120cd89dfc 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java @@ -45,31 +45,32 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method.FORM; public class LoginActionTest { - static final String LOGIN = "LOGIN"; - static final String PASSWORD = "PASSWORD"; + private static final String LOGIN = "LOGIN"; + private static final String PASSWORD = "PASSWORD"; @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); - DbClient dbClient = dbTester.getDbClient(); + private DbClient dbClient = dbTester.getDbClient(); - DbSession dbSession = dbTester.getSession(); + private DbSession dbSession = dbTester.getSession(); - ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession(); + private ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession(); - 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); - CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class); - JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); + private CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class); + private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); - UserDto user = UserTesting.newUserDto().setLogin(LOGIN); + private UserDto user = UserTesting.newUserDto().setLogin(LOGIN); - LoginAction underTest = new LoginAction(dbClient, credentialsAuthenticator, jwtHttpHandler, threadLocalUserSession); + private LoginAction underTest = new LoginAction(dbClient, credentialsAuthenticator, jwtHttpHandler, threadLocalUserSession); @Before public void setUp() throws Exception { @@ -87,12 +88,12 @@ public class LoginActionTest { @Test public void do_authenticate() throws Exception { - when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(user); + when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request, FORM)).thenReturn(user); executeRequest(LOGIN, PASSWORD); assertThat(threadLocalUserSession.isLoggedIn()).isTrue(); - verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request); + verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, FORM); verify(jwtHttpHandler).generateToken(user, request, response); verifyZeroInteractions(chain); } @@ -108,7 +109,7 @@ public class LoginActionTest { @Test public void return_authorized_code_when_unauthorized_exception_is_thrown() throws Exception { - doThrow(new UnauthorizedException()).when(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request); + doThrow(new UnauthorizedException()).when(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, FORM); executeRequest(LOGIN, PASSWORD);