import static java.nio.charset.StandardCharsets.UTF_8;
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;
return Optional.empty();
}
- String[] credentials = getCredentials(authorizationHeader);
- String login = credentials[0];
- String password = credentials[1];
- UserDto userDto = authenticate(login, password, request);
+ Credentials credentials = extractCredentials(authorizationHeader);
+ UserDto userDto = authenticate(credentials, request);
return Optional.of(userDto);
}
- private static String[] getCredentials(String authorizationHeader) {
+ private static Credentials extractCredentials(String authorizationHeader) {
String basicAuthEncoded = authorizationHeader.substring(6);
String basicAuthDecoded = getDecodedBasicAuth(basicAuthEncoded);
}
String login = basicAuthDecoded.substring(0, semiColonPos);
String password = basicAuthDecoded.substring(semiColonPos + 1);
- return new String[] {login, password};
+ return new Credentials(login, password);
}
private static String getDecodedBasicAuth(String basicAuthEncoded) {
}
}
- private UserDto authenticate(String login, String password, HttpServletRequest request) {
- if (isEmpty(password)) {
- UserDto userDto = authenticateFromUserToken(login);
+ private UserDto authenticate(Credentials credentials, HttpServletRequest request) {
+ if (credentials.getPassword().isEmpty()) {
+ UserDto userDto = authenticateFromUserToken(credentials.getLogin());
authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.local(Method.BASIC_TOKEN));
return userDto;
} else {
- return credentialsAuthenticator.authenticate(login, password, request, Method.BASIC);
+ return credentialsAuthenticator.authenticate(credentials, request, Method.BASIC);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.apache.commons.lang.StringUtils;
+
+@Immutable
+public class Credentials {
+
+ private final String login;
+ private final String password;
+
+ public Credentials(String login, @Nullable String password) {
+ this.login = Objects.requireNonNull(login, "login must not be null");
+ this.password = StringUtils.defaultString(password, "");
+ }
+
+ /**
+ * Non-empty login
+ */
+ public String getLogin() {
+ return login;
+ }
+
+ /**
+ * Non-null password. Can be empty.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Credentials that = (Credentials) o;
+ return login.equals(that.login) && password.equals(that.password);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(login, password);
+ }
+}
this.localAuthentication = localAuthentication;
}
- public UserDto authenticate(String userLogin, String userPassword, HttpServletRequest request, Method method) {
+ public UserDto authenticate(Credentials credentials, HttpServletRequest request, Method method) {
try (DbSession dbSession = dbClient.openSession(false)) {
- return authenticate(dbSession, userLogin, userPassword, request, method);
+ return authenticate(dbSession, credentials, request, method);
}
}
- private UserDto authenticate(DbSession dbSession, String userLogin, String userPassword, HttpServletRequest request, Method method) {
- UserDto localUser = dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin);
+ private UserDto authenticate(DbSession dbSession, Credentials credentials, HttpServletRequest request, Method method) {
+ UserDto localUser = dbClient.userDao().selectActiveUserByLogin(dbSession, credentials.getLogin());
if (localUser != null && localUser.isLocal()) {
- localAuthentication.authenticate(dbSession, localUser, userPassword, method);
+ localAuthentication.authenticate(dbSession, localUser, credentials.getPassword(), method);
dbSession.commit();
- authenticationEvent.loginSuccess(request, userLogin, Source.local(method));
+ authenticationEvent.loginSuccess(request, localUser.getLogin(), Source.local(method));
return localUser;
}
- Optional<UserDto> externalUser = externalAuthenticator.authenticate(userLogin, userPassword, request, method);
+ Optional<UserDto> externalUser = externalAuthenticator.authenticate(credentials, request, method);
if (externalUser.isPresent()) {
return externalUser.get();
}
throw AuthenticationException.newBuilder()
.setSource(Source.local(method))
- .setLogin(userLogin)
+ .setLogin(credentials.getLogin())
.setMessage(localUser != null && !localUser.isLocal() ? "User is not local" : "No active user for login")
.build();
}
}
}
- public Optional<UserDto> authenticate(String userLogin, String userPassword, HttpServletRequest request, AuthenticationEvent.Method method) {
+ public Optional<UserDto> authenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) {
if (realm == null) {
return Optional.empty();
}
- return Optional.of(doAuthenticate(getLogin(userLogin), userPassword, request, method));
+ return Optional.of(doAuthenticate(sanitize(credentials), request, method));
}
- private UserDto doAuthenticate(String userLogin, String userPassword, HttpServletRequest request, AuthenticationEvent.Method method) {
+ private UserDto doAuthenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) {
try {
- ExternalUsersProvider.Context externalUsersProviderContext = new ExternalUsersProvider.Context(userLogin, request);
+ ExternalUsersProvider.Context externalUsersProviderContext = new ExternalUsersProvider.Context(credentials.getLogin(), request);
UserDetails details = externalUsersProvider.doGetUserDetails(externalUsersProviderContext);
if (details == null) {
throw AuthenticationException.newBuilder()
.setSource(realmEventSource(method))
- .setLogin(userLogin)
+ .setLogin(credentials.getLogin())
.setMessage("No user details")
.build();
}
- Authenticator.Context authenticatorContext = new Authenticator.Context(userLogin, userPassword, request);
+ Authenticator.Context authenticatorContext = new Authenticator.Context(credentials.getLogin(), credentials.getPassword(), request);
boolean status = authenticator.doAuthenticate(authenticatorContext);
if (!status) {
throw AuthenticationException.newBuilder()
.setSource(realmEventSource(method))
- .setLogin(userLogin)
+ .setLogin(credentials.getLogin())
.setMessage("Realm returned authenticate=false")
.build();
}
- UserDto userDto = synchronize(userLogin, details, request, method);
- authenticationEvent.loginSuccess(request, userLogin, realmEventSource(method));
+ UserDto userDto = synchronize(credentials.getLogin(), details, request, method);
+ authenticationEvent.loginSuccess(request, credentials.getLogin(), realmEventSource(method));
return userDto;
} catch (AuthenticationException e) {
throw e;
LOG.error("Error during authentication", e);
throw AuthenticationException.newBuilder()
.setSource(realmEventSource(method))
- .setLogin(userLogin)
+ .setLogin(credentials.getLogin())
.setMessage(e.getMessage())
.build();
}
.build());
}
- private String getLogin(String userLogin) {
+ private Credentials sanitize(Credentials credentials) {
if (config.getBoolean("sonar.authenticator.downcase").orElse(false)) {
- return userLogin.toLowerCase(Locale.ENGLISH);
+ return new Credentials(credentials.getLogin().toLowerCase(Locale.ENGLISH), credentials.getPassword());
}
- return userLogin;
+ return credentials;
}
private static class ExternalIdentityProvider implements IdentityProvider {
*/
package org.sonar.server.authentication.ws;
-import javax.annotation.Nullable;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.ServletFilter;
import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.Credentials;
import org.sonar.server.authentication.CredentialsAuthenticator;
import org.sonar.server.authentication.JwtHttpHandler;
import org.sonar.server.authentication.event.AuthenticationEvent;
return;
}
- String login = request.getParameter("login");
- String password = request.getParameter("password");
try {
- UserDto userDto = authenticate(request, login, password);
+ UserDto userDto = authenticate(request);
jwtHttpHandler.generateToken(userDto, request, response);
threadLocalUserSession.set(userSessionFactory.create(userDto));
} catch (AuthenticationException e) {
}
}
- private UserDto authenticate(HttpServletRequest request, @Nullable String login, @Nullable String password) {
+ private UserDto authenticate(HttpServletRequest request) {
+ String login = request.getParameter("login");
+ String password = request.getParameter("password");
if (isEmpty(login) || isEmpty(password)) {
throw AuthenticationException.newBuilder()
.setSource(Source.local(Method.FORM))
.setMessage("Empty login and/or password")
.build();
}
- return credentialsAuthenticator.authenticate(login, password, request, Method.FORM);
+ return credentialsAuthenticator.authenticate(new Credentials(login, password), request, Method.FORM);
}
@Override
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserTesting;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method;
-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;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException;
public class BasicAuthenticatorTest {
private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();
- private static final String LOGIN = "login";
- private static final String PASSWORD = "password";
- private static final String CREDENTIALS_IN_BASE64 = toBase64(LOGIN + ":" + PASSWORD);
+ private static final String A_LOGIN = "login";
+ private static final String A_PASSWORD = "password";
+ private static final String CREDENTIALS_IN_BASE64 = toBase64(A_LOGIN + ":" + A_PASSWORD);
- private static final UserDto USER = UserTesting.newUserDto().setLogin(LOGIN);
+ private static final UserDto USER = UserTesting.newUserDto().setLogin(A_LOGIN);
@Rule
public ExpectedException expectedException = none();
private DbClient dbClient = db.getDbClient();
- private DbSession dbSession = db.getSession();
-
private CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class);
private UserTokenAuthenticator userTokenAuthenticator = mock(UserTokenAuthenticator.class);
@Test
public void authenticate_from_basic_http_header() {
when(request.getHeader("Authorization")).thenReturn("Basic " + CREDENTIALS_IN_BASE64);
- when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC)).thenReturn(USER);
+ Credentials credentials = new Credentials(A_LOGIN, A_PASSWORD);
+ when(credentialsAuthenticator.authenticate(credentials, request, BASIC)).thenReturn(USER);
underTest.authenticate(request);
- verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, BASIC);
+ verify(credentialsAuthenticator).authenticate(credentials, request, BASIC);
verifyNoMoreInteractions(authenticationEvent);
}
@Test
public void authenticate_from_basic_http_header_with_password_containing_semi_colon() {
String password = "!ascii-only:-)@";
- when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(LOGIN + ":" + password));
- when(credentialsAuthenticator.authenticate(LOGIN, password, request, BASIC)).thenReturn(USER);
+ when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(A_LOGIN + ":" + password));
+ when(credentialsAuthenticator.authenticate(new Credentials(A_LOGIN, password), request, BASIC)).thenReturn(USER);
underTest.authenticate(request);
- verify(credentialsAuthenticator).authenticate(LOGIN, password, request, BASIC);
+ verify(credentialsAuthenticator).authenticate(new Credentials(A_LOGIN, password), request, BASIC);
verifyNoMoreInteractions(authenticationEvent);
}
@Test
public void fail_to_authenticate_when_no_login() {
- when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(":" + PASSWORD));
+ when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(":" + A_PASSWORD));
expectedException.expect(authenticationException().from(Source.local(BASIC)).withoutLogin().andNoPublicMessage());
try {
@Test
public void authenticate_external_user() {
- when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC)).thenReturn(Optional.of(newUserDto()));
+ when(externalAuthenticator.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).thenReturn(Optional.of(newUserDto()));
insertUser(newUserDto()
.setLogin(LOGIN)
.setLocal(false));
executeAuthenticate(BASIC);
- verify(externalAuthenticator).authenticate(LOGIN, PASSWORD, request, BASIC);
+ verify(externalAuthenticator).authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
verifyZeroInteractions(authenticationEvent);
}
@Test
public void fail_to_authenticate_authenticate_external_user_when_no_external_authentication() {
- when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC_TOKEN)).thenReturn(Optional.empty());
+ when(externalAuthenticator.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN)).thenReturn(Optional.empty());
insertUser(newUserDto()
.setLogin(LOGIN)
.setLocal(false));
}
private UserDto executeAuthenticate(AuthenticationEvent.Method method) {
- return underTest.authenticate(LOGIN, PASSWORD, request, method);
+ return underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, method);
}
private UserDto insertUser(UserDto userDto) {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CredentialsTest {
+
+ @Test
+ public void password_can_be_empty_but_not_null() {
+ Credentials underTest = new Credentials("foo", "");
+ assertThat(underTest.getPassword()).isEqualTo("");
+
+ underTest = new Credentials("foo", null);
+ assertThat(underTest.getPassword()).isEqualTo("");
+
+ underTest = new Credentials("foo", " ");
+ assertThat(underTest.getPassword()).isEqualTo(" ");
+
+ underTest = new Credentials("foo", "bar");
+ assertThat(underTest.getPassword()).isEqualTo("bar");
+ }
+
+ @Test
+ public void test_equality() {
+ assertThat(new Credentials("foo", "bar")).isEqualTo(new Credentials("foo", "bar"));
+ assertThat(new Credentials("foo", "bar")).isNotEqualTo(new Credentials("foo", "baaaar"));
+ assertThat(new Credentials("foo", "bar")).isNotEqualTo(new Credentials("foooooo", "bar"));
+ assertThat(new Credentials("foo", "bar")).isNotEqualTo(new Credentials("foo", null));
+
+ assertThat(new Credentials("foo", "bar").hashCode()).isEqualTo(new Credentials("foo", "bar").hashCode());
+ }
+}
userDetails.setEmail("email");
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+ underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getExistingEmailStrategy()).isEqualTo(FORBID);
userDetails.setEmail("email");
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+ underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getKey()).isEqualTo("sonarqube");
userDetails.setEmail("email");
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+ underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
userDetails.setName(null);
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+ underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo(LOGIN);
expectedException.expect(authenticationException().from(Source.realm(BASIC, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage("No user details");
try {
- underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+ underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
} finally {
verifyZeroInteractions(authenticationEvent);
}
expectedException.expect(authenticationException().from(Source.realm(BASIC, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage("Realm returned authenticate=false");
try {
- underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+ underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
} finally {
verifyZeroInteractions(authenticationEvent);
}
expectedException.expect(authenticationException().from(Source.realm(BASIC_TOKEN, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage());
expectedException.expectMessage(expectedMessage);
try {
- underTest.authenticate(LOGIN, PASSWORD, request, BASIC_TOKEN);
+ underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN);
} finally {
verifyZeroInteractions(authenticationEvent);
}
@Test
public void return_empty_user_when_no_realm() {
- assertThat(underTest.authenticate(LOGIN, PASSWORD, request, BASIC)).isEmpty();
+ assertThat(underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).isEmpty();
verifyNoMoreInteractions(authenticationEvent);
}
UserDetails userDetails = new UserDetails();
userDetails.setName("name");
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- underTest.authenticate(login, PASSWORD, request, BASIC);
+ underTest.authenticate(new Credentials(login, PASSWORD), request, BASIC);
}
}
import org.sonar.db.DbTester;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserTesting;
+import org.sonar.server.authentication.Credentials;
import org.sonar.server.authentication.CredentialsAuthenticator;
import org.sonar.server.authentication.JwtHttpHandler;
import org.sonar.server.authentication.event.AuthenticationEvent;
@Test
public void do_authenticate() throws Exception {
- when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request, FORM)).thenReturn(user);
+ when(credentialsAuthenticator.authenticate(new Credentials(LOGIN, PASSWORD), request, FORM)).thenReturn(user);
executeRequest(LOGIN, PASSWORD);
assertThat(threadLocalUserSession.isLoggedIn()).isTrue();
- verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, FORM);
+ verify(credentialsAuthenticator).authenticate(new Credentials(LOGIN, PASSWORD), request, FORM);
verify(jwtHttpHandler).generateToken(user, request, response);
verifyZeroInteractions(chain);
verifyZeroInteractions(authenticationEvent);
}
@Test
- public void ignore_get_request() throws Exception {
+ public void ignore_get_request() {
when(request.getMethod()).thenReturn("GET");
underTest.doFilter(request, response, chain);
@Test
public void return_authorized_code_when_unauthorized_exception_is_thrown() throws Exception {
- doThrow(new UnauthorizedException("error !")).when(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, FORM);
+ doThrow(new UnauthorizedException("error !")).when(credentialsAuthenticator).authenticate(new Credentials(LOGIN, PASSWORD), request, FORM);
executeRequest(LOGIN, PASSWORD);