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;
@Override
protected void configureModule() {
add(
+ AuthenticationEventImpl.class,
AuthenticationWs.class,
InitFilter.class,
OAuth2CallbackFilter.class,
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 {
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<UserDto> authenticate(HttpServletRequest request) {
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) {
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);
}
}
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> userDto = externalAuthenticator.authenticate(userLogin, userPassword, request);
+ Optional<UserDto> userDto = externalAuthenticator.authenticate(userLogin, userPassword, request, method);
if (userDto.isPresent()) {
return userDto.get();
}
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;
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 {
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
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()));
}
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)) {
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;
+ }
+ }
}
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;
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 {
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
}
}
- public Optional<UserDto> authenticate(String userLogin, String userPassword, HttpServletRequest request) {
+ public Optional<UserDto> 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);
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);
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 {
private final Settings settings;
private final UserIdentityAuthenticator userIdentityAuthenticator;
private final JwtHttpHandler jwtHttpHandler;
+ private final AuthenticationEvent authenticationEvent;
private boolean enabled = false;
private Map<String, String> 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
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);
}
--- /dev/null
+/*
+ * 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 + '\'' +
+ '}';
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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(",")));
+ }
+
+}
--- /dev/null
+/*
+ * 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;
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 {
if (isEmpty(login) || isEmpty(password)) {
throw new UnauthorizedException();
}
- return credentialsAuthenticator.authenticate(login, password, request);
+ return credentialsAuthenticator.authenticate(login, password, request, Method.FORM);
}
@Override
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;
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;
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();
@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
underTest.authenticate(request);
- verifyZeroInteractions(credentialsAuthenticator);
+ verifyZeroInteractions(credentialsAuthenticator, authenticationEvent);
}
@Test
when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(":" + PASSWORD));
expectedException.expect(UnauthorizedException.class);
- underTest.authenticate(request);
+ try {
+ underTest.authenticate(request);
+ } finally {
+ verifyZeroInteractions(authenticationEvent);
+ }
}
@Test
assertThat(userDto.isPresent()).isTrue();
assertThat(userDto.get().getLogin()).isEqualTo(LOGIN);
+ verify(authenticationEvent).login(request, LOGIN, Source.local(BASIC_TOKEN));
}
@Test
when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:"));
expectedException.expect(UnauthorizedException.class);
- underTest.authenticate(request);
+ try {
+ underTest.authenticate(request);
+ } finally {
+ verifyZeroInteractions(authenticationEvent);
+ }
}
@Test
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)));
}
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;
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 {
UserDto userDto = executeAuthenticate();
assertThat(userDto.getLogin()).isEqualTo(LOGIN);
+ verify(authenticationEvent).login(request, LOGIN, Source.local(BASIC));
}
@Test
.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
.setLocal(true));
expectedException.expect(UnauthorizedException.class);
- executeAuthenticate();
+ try {
+ executeAuthenticate();
+ } finally {
+ verifyZeroInteractions(authenticationEvent);
+ }
}
@Test
.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;
public FakeOAuth2IdentityProvider(String key, boolean enabled) {
setKey(key);
+ setName("name of " + key);
setEnabled(enabled);
}
*/
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;
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("");
}
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
underTest.doFilter(request, response, chain);
- assertCallbackCalled();
+ assertCallbackCalled(oAuth2IdentityProvider, true);
}
@Test
underTest.doFilter(request, response, chain);
assertError("Not an OAuth2IdentityProvider: class org.sonar.server.authentication.FakeBasicIdentityProvider");
+ verifyZeroInteractions(authenticationEvent);
}
@Test
underTest.doFilter(request, response, chain);
assertError("Fail to callback authentication with 'github'");
+ verifyZeroInteractions(authenticationEvent);
}
@Test
underTest.doFilter(request, response, chain);
verify(response).sendRedirect("/sessions/unauthorized?message=Email+john%40email.com+is+already+used");
+ verifyZeroInteractions(authenticationEvent);
}
@Test
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 {
}
}
+ /**
+ * 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());
+ }
+ }
}
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;
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;
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<UserIdentity> userIdentityArgumentCaptor = ArgumentCaptor.forClass(UserIdentity.class);
+ private ArgumentCaptor<IdentityProvider> identityProviderArgumentCaptor = ArgumentCaptor.forClass(IdentityProvider.class);
- static final UserDto USER = newUserDto();
+ private Settings settings = new MapSettings();
- ArgumentCaptor<UserIdentity> userIdentityArgumentCaptor = ArgumentCaptor.forClass(UserIdentity.class);
- ArgumentCaptor<IdentityProvider> 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 {
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();
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
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");
assertThat(identityProviderArgumentCaptor.getValue().getDisplay()).isNull();
assertThat(identityProviderArgumentCaptor.getValue().isEnabled()).isTrue();
+ verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
}
@Test
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
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
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
verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
assertThat(identityProviderArgumentCaptor.getValue().allowsUsersToSignUp()).isTrue();
+ verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
}
@Test
verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
assertThat(identityProviderArgumentCaptor.getValue().allowsUsersToSignUp()).isFalse();
+ verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
}
@Test
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
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
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
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
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
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);
}
}
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;
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 {
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 {
verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2);
verifyTokenIsUpdated(NOW);
+ verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
}
@Test
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
verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group2);
verifyTokenIsUpdated(NOW);
+ verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
}
@Test
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
Map<String, String> 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
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
// User is not updated
verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1);
verifyTokenIsNotUpdated();
+ verifyZeroInteractions(authenticationEvent);
}
@Test
// User is updated
verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2);
verifyTokenIsUpdated(NOW);
+ verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
}
@Test
// User is updated
verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2);
verifyTokenIsUpdated(NOW);
+ verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
}
@Test
// User is not updated
verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1);
verifyTokenIsNotUpdated();
+ verifyZeroInteractions(authenticationEvent);
}
@Test
verifyUserInDb("AnotherLogin", "Another name", "Another email", group2);
verifyTokenIsUpdated(NOW);
+ verify(authenticationEvent).login(request, "AnotherLogin", Source.sso());
}
@Test
underTest.authenticate(request, response);
verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2);
+ verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
}
@Test
underTest.authenticate(request, response);
verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2);
+ verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
}
@Test
underTest.authenticate(request, response);
verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, group1, group2);
+ verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
}
@Test
verifyUserNotAuthenticated();
verifyTokenIsNotUpdated();
+ verifyZeroInteractions(authenticationEvent);
}
@Test
underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response);
verifyUserNotAuthenticated();
- verifyZeroInteractions(jwtHttpHandler);
+ verifyZeroInteractions(jwtHttpHandler, authenticationEvent);
}
@Test
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() {
--- /dev/null
+/*
+ * 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<String>... 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;
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+}
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 {
@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);
}
@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);