aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Hartmann <hartmann.eric@gmail.com>2018-04-13 18:26:16 +0200
committerSonarTech <sonartech@sonarsource.com>2018-04-17 20:20:48 +0200
commit7f88e7c22d4b61d36cbb4a62990f5fe144e80c2d (patch)
tree5b65b9b899b164fc95cfabbacb6fef3da66b0727
parentf7adccdc63aa7b93ed20425a3224da0c5e6a5bba (diff)
downloadsonarqube-7f88e7c22d4b61d36cbb4a62990f5fe144e80c2d.tar.gz
sonarqube-7f88e7c22d4b61d36cbb4a62990f5fe144e80c2d.zip
SONAR-6949 Implements bcrypt hash for password
Extract hash mechanism into a single class LocalAuthentication Implements SHA1 (deprecated) and bcrypt hash Set bcrypt as default Update the hash of a user during authentication if hash method was SHA1
-rw-r--r--server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java14
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml1
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java4
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDtoTest.java17
-rw-r--r--server/sonar-db-migration/build.gradle1
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java2
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/IncreaseCryptedPasswordSizeTest.java7
-rw-r--r--server/sonar-server/build.gradle1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java38
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/LocalAuthentication.java200
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java24
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java17
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/LocalAuthenticationTest.java186
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java11
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java18
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java7
-rw-r--r--sonar-core/build.gradle1
26 files changed, 476 insertions, 108 deletions
diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql b/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql
index 46bf46c7fc1..b82e764641b 100644
--- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql
+++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql
@@ -1,4 +1,4 @@
-INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, IS_ROOT, ONBOARDED, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', false, false, '1418215735482', '1418215735482');
+INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, HASH_METHOD, IS_ROOT, ONBOARDED, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, '$2a$12$uCkkXmhW5ThVK8mpBvnXOOJRLd64LJeHTeCkSuB3lfaR2N0AYBaSi', null, 'BCRYPT', false, false, '1418215735482', '1418215735482');
ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
INSERT INTO GROUPS(ID, ORGANIZATION_UUID, NAME, DESCRIPTION, CREATED_AT, UPDATED_AT) VALUES (1, 'AVdqnciQUUs7Zd3KPvFD', 'sonar-administrators', 'System administrators', '2011-09-26 22:27:51.0', '2011-09-26 22:27:51.0');
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
index 1baa5f72892..32b5e69e835 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
@@ -25,11 +25,8 @@ import java.util.ArrayList;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-import org.apache.commons.codec.digest.DigestUtils;
import org.sonar.core.user.DefaultUser;
-import static java.util.Objects.requireNonNull;
-
/**
* @since 3.2
*/
@@ -44,8 +41,11 @@ public class UserDto {
private String scmAccounts;
private String externalIdentity;
private String externalIdentityProvider;
+ // Hashed password that may be null in case of external authentication
private String cryptedPassword;
+ // Salt used for SHA1, null when bcrypt is used or for external authentication
private String salt;
+ // Hash method used to generate cryptedPassword, my be null in case of external authentication
private String hashMethod;
private Long createdAt;
private Long updatedAt;
@@ -192,7 +192,7 @@ public class UserDto {
return hashMethod;
}
- public UserDto setHashMethod(String hashMethod) {
+ public UserDto setHashMethod(@Nullable String hashMethod) {
this.hashMethod = hashMethod;
return this;
}
@@ -260,12 +260,6 @@ public class UserDto {
return this;
}
- public static String encryptPassword(String password, String salt) {
- requireNonNull(password, "Password cannot be empty");
- requireNonNull(salt, "Salt cannot be empty");
- return DigestUtils.sha1Hex("--" + salt + "--" + password + "--");
- }
-
public DefaultUser toUser() {
return new DefaultUser()
.setLogin(login)
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
index d699cf7ba2a..97909a40307 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
@@ -221,6 +221,7 @@
onboarded = #{user.onboarded, jdbcType=BOOLEAN},
salt = #{user.salt, jdbcType=VARCHAR},
crypted_password = #{user.cryptedPassword, jdbcType=BIGINT},
+ hash_method = #{user.hashMethod, jdbcType=VARCHAR},
updated_at = #{now, jdbcType=BIGINT},
homepage_type = #{user.homepageType, jdbcType=VARCHAR},
homepage_parameter = #{user.homepageParameter, jdbcType=VARCHAR}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
index 1286768f703..e5899b9d08a 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
@@ -319,6 +319,7 @@ public class UserDaoTest {
.setOnboarded(true)
.setSalt("1234")
.setCryptedPassword("abcd")
+ .setHashMethod("SHA1")
.setExternalIdentity("johngithub")
.setExternalIdentityProvider("github")
.setLocal(true)
@@ -340,6 +341,7 @@ public class UserDaoTest {
assertThat(user.getScmAccounts()).isEqualTo(",jo.hn,john2,");
assertThat(user.getSalt()).isEqualTo("1234");
assertThat(user.getCryptedPassword()).isEqualTo("abcd");
+ assertThat(user.getHashMethod()).isEqualTo("SHA1");
assertThat(user.getExternalIdentity()).isEqualTo("johngithub");
assertThat(user.getExternalIdentityProvider()).isEqualTo("github");
assertThat(user.isLocal()).isTrue();
@@ -368,6 +370,7 @@ public class UserDaoTest {
.setOnboarded(true)
.setSalt("12345")
.setCryptedPassword("abcde")
+ .setHashMethod("BCRYPT")
.setExternalIdentity("johngithub")
.setExternalIdentityProvider("github")
.setLocal(false)
@@ -386,6 +389,7 @@ public class UserDaoTest {
assertThat(reloaded.getScmAccounts()).isEqualTo(",jo.hn,john2,johndoo,");
assertThat(reloaded.getSalt()).isEqualTo("12345");
assertThat(reloaded.getCryptedPassword()).isEqualTo("abcde");
+ assertThat(reloaded.getHashMethod()).isEqualTo("BCRYPT");
assertThat(reloaded.getExternalIdentity()).isEqualTo("johngithub");
assertThat(reloaded.getExternalIdentityProvider()).isEqualTo("github");
assertThat(reloaded.isLocal()).isFalse();
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDtoTest.java
index f34a9c226ed..15613d0f59f 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDtoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDtoTest.java
@@ -46,21 +46,4 @@ public class UserDtoTest {
assertThat(UserDto.decodeScmAccounts("\nfoo\n")).containsOnly("foo");
assertThat(UserDto.decodeScmAccounts("\nfoo\nbar\n")).containsOnly("foo", "bar");
}
-
- @Test
- public void encrypt_password() {
- assertThat(UserDto.encryptPassword("PASSWORD", "0242b0b4c0a93ddfe09dd886de50bc25ba000b51")).isEqualTo("540e4fc4be4e047db995bc76d18374a5b5db08cc");
- }
-
- @Test
- public void fail_to_encrypt_password_when_password_is_null() {
- expectedException.expect(NullPointerException.class);
- UserDto.encryptPassword(null, "salt");
- }
-
- @Test
- public void fail_to_encrypt_password_when_salt_is_null() {
- expectedException.expect(NullPointerException.class);
- UserDto.encryptPassword("password", null);
- }
}
diff --git a/server/sonar-db-migration/build.gradle b/server/sonar-db-migration/build.gradle
index 7360daec774..53e5be5a48c 100644
--- a/server/sonar-db-migration/build.gradle
+++ b/server/sonar-db-migration/build.gradle
@@ -18,6 +18,7 @@ dependencies {
testCompile 'org.assertj:assertj-core'
testCompile 'org.dbunit:dbunit'
testCompile 'org.mockito:mockito-core'
+ testCompile 'org.mindrot:jbcrypt'
testCompile project(':sonar-testing-harness')
testCompile project(':server:sonar-db-core').sourceSets.test.output
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java
index 35c94276936..2303e9e1d89 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java
@@ -27,7 +27,7 @@ public class DbVersion72 implements DbVersion {
@Override
public void addSteps(MigrationStepRegistry registry) {
registry
- .add(2100, "Increase size of CRYPTED_PASSWORD", IncreaseCryptedPasswordSize.class)
+ .add(2100, "Increase size of USERS.CRYPTED_PASSWORD", IncreaseCryptedPasswordSize.class)
.add(2101, "Add HASH_METHOD to table users", AddHashMethodToUsersTable.class)
.add(2102, "Populate HASH_METHOD on table users", PopulateHashMethodOnUsers.class)
;
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/IncreaseCryptedPasswordSizeTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/IncreaseCryptedPasswordSizeTest.java
index 455c0a25835..2dee68bea27 100644
--- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/IncreaseCryptedPasswordSizeTest.java
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/IncreaseCryptedPasswordSizeTest.java
@@ -1,4 +1,4 @@
-package org.sonar.server.platform.db.migration.version.v72;/*
+/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
@@ -17,12 +17,12 @@ package org.sonar.server.platform.db.migration.version.v72;/*
* 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.platform.db.migration.version.v72;
import java.sql.SQLException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.mindrot.jbcrypt.BCrypt;
import org.sonar.db.CoreDbTester;
import static org.assertj.core.api.Assertions.assertThat;
@@ -53,9 +53,10 @@ public class IncreaseCryptedPasswordSizeTest {
}
private void insertRow() {
+ // bcrypt hash is 60 characters
db.executeInsert(
"USERS",
- "CRYPTED_PASSWORD", BCrypt.hashpw("a", BCrypt.gensalt()),
+ "CRYPTED_PASSWORD", "$2a$10$8tscphgcElKF5vOBer4H.OVfLKpPIH74hK.rxyhOP5HVyZHyfgRGy",
"IS_ROOT", false,
"ONBOARDED", false);
}
diff --git a/server/sonar-server/build.gradle b/server/sonar-server/build.gradle
index 761efd7ae1b..ac661c36f38 100644
--- a/server/sonar-server/build.gradle
+++ b/server/sonar-server/build.gradle
@@ -45,6 +45,7 @@ dependencies {
compile 'org.slf4j:jul-to-slf4j'
compile 'org.slf4j:slf4j-api'
compile 'org.sonarsource.update-center:sonar-update-center-common'
+ compile 'org.mindrot:jbcrypt'
compile project(':server:sonar-db-dao')
compile project(':server:sonar-db-migration')
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 3ed62249fc4..4f256b9b13c 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
@@ -47,6 +47,7 @@ public class AuthenticationModule extends Module {
LoginAction.class,
LogoutAction.class,
CredentialsAuthenticator.class,
+ LocalAuthentication.class,
RealmAuthenticator.class,
BasicAuthenticator.class,
ValidateAction.class,
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 14eb28706e9..63cb971d681 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,8 +20,6 @@
package org.sonar.server.authentication;
import java.util.Optional;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -29,7 +27,6 @@ import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
-import static org.sonar.db.user.UserDto.encryptPassword;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
@@ -38,11 +35,14 @@ public class CredentialsAuthenticator {
private final DbClient dbClient;
private final RealmAuthenticator externalAuthenticator;
private final AuthenticationEvent authenticationEvent;
+ private final LocalAuthentication localAuthentication;
- public CredentialsAuthenticator(DbClient dbClient, RealmAuthenticator externalAuthenticator, AuthenticationEvent authenticationEvent) {
+ public CredentialsAuthenticator(DbClient dbClient, RealmAuthenticator externalAuthenticator, AuthenticationEvent authenticationEvent,
+ LocalAuthentication localAuthentication) {
this.dbClient = dbClient;
this.externalAuthenticator = externalAuthenticator;
this.authenticationEvent = authenticationEvent;
+ this.localAuthentication = localAuthentication;
}
public UserDto authenticate(String userLogin, String userPassword, HttpServletRequest request, Method method) {
@@ -54,9 +54,9 @@ public class CredentialsAuthenticator {
private UserDto authenticate(DbSession dbSession, String userLogin, String userPassword, HttpServletRequest request, Method method) {
UserDto localUser = dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin);
if (localUser != null && localUser.isLocal()) {
- UserDto userDto = authenticateFromDb(localUser, userPassword, method);
+ localAuthentication.authenticate(dbSession, localUser, userPassword, method);
authenticationEvent.loginSuccess(request, userLogin, Source.local(method));
- return userDto;
+ return localUser;
}
Optional<UserDto> externalUser = externalAuthenticator.authenticate(userLogin, userPassword, request, method);
if (externalUser.isPresent()) {
@@ -68,30 +68,4 @@ public class CredentialsAuthenticator {
.setMessage(localUser != null && !localUser.isLocal() ? "User is not local" : "No active user for login")
.build();
}
-
- private static UserDto authenticateFromDb(UserDto userDto, String userPassword, Method method) {
- String cryptedPassword = userDto.getCryptedPassword();
- String salt = userDto.getSalt();
- String failureCause = checkPassword(cryptedPassword, salt, userPassword);
- if (failureCause == null) {
- return userDto;
- }
- throw AuthenticationException.newBuilder()
- .setSource(Source.local(method))
- .setLogin(userDto.getLogin())
- .setMessage(failureCause)
- .build();
- }
-
- @CheckForNull
- private static String checkPassword(@Nullable String cryptedPassword, @Nullable String salt, String userPassword) {
- if (cryptedPassword == null) {
- return "null password in DB";
- } else if (salt == null) {
- return "null salt";
- } else if (!cryptedPassword.equals(encryptPassword(userPassword, salt))) {
- return "wrong password";
- }
- return null;
- }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/LocalAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/LocalAuthentication.java
new file mode 100644
index 00000000000..c7efae31e9f
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/LocalAuthentication.java
@@ -0,0 +1,200 @@
+/*
+ * 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.security.SecureRandom;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.mindrot.jbcrypt.BCrypt;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent.Method;
+import org.sonar.server.authentication.event.AuthenticationEvent.Source;
+import org.sonar.server.authentication.event.AuthenticationException;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * This class is responsible of handling local authentication
+ */
+public class LocalAuthentication {
+
+ private final DbClient dbClient;
+ private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+ // The default hash method that must be used is BCRYPT
+ private static final HashMethod DEFAULT = HashMethod.BCRYPT;
+
+ public LocalAuthentication(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ /**
+ * This method authenticate a user with his password against the value stored in user.
+ * If authentication failed an AuthenticationException will be thrown containing the failure message.
+ * If the password must be updated because an old algorithm is used, the UserDto is updated but the session
+ * is not committed
+ */
+ public void authenticate(DbSession session, UserDto user, String password, Method method) {
+ if (user.getHashMethod() == null) {
+ throw AuthenticationException.newBuilder()
+ .setSource(Source.local(method))
+ .setLogin(user.getLogin())
+ .setMessage("null hash method")
+ .build();
+ }
+
+ HashMethod hashMethod;
+ try {
+ hashMethod = HashMethod.valueOf(user.getHashMethod());
+ } catch (IllegalArgumentException ex) {
+ throw AuthenticationException.newBuilder()
+ .setSource(Source.local(method))
+ .setLogin(user.getLogin())
+ .setMessage(format("Unknown hash method [%s]", user.getHashMethod()))
+ .build();
+ }
+
+ AuthenticationResult result = hashMethod.checkCredentials(user, password);
+ if (!result.isSuccessful()) {
+ throw AuthenticationException.newBuilder()
+ .setSource(Source.local(method))
+ .setLogin(user.getLogin())
+ .setMessage(result.getFailureMessage())
+ .build();
+ }
+
+ // Upgrade the password if it's an old hashMethod
+ if (hashMethod != DEFAULT) {
+ DEFAULT.storeHashPassword(user, password);
+ dbClient.userDao().update(session, user);
+ }
+ }
+
+ /**
+ * Method used to store the password as a hash in database.
+ * The crypted_password, salt and hash_method are set
+ */
+ public void storeHashPassword(UserDto user, String password) {
+ DEFAULT.storeHashPassword(user, password);
+ }
+
+ public enum HashMethod implements HashFunction {
+ SHA1(new Sha1Function()), BCRYPT(new BcryptFunction());
+
+ private HashFunction hashFunction;
+
+ HashMethod(HashFunction hashFunction) {
+ this.hashFunction = hashFunction;
+ }
+
+ @Override
+ public AuthenticationResult checkCredentials(UserDto user, String password) {
+ return hashFunction.checkCredentials(user, password);
+ }
+
+ @Override
+ public void storeHashPassword(UserDto user, String password) {
+ hashFunction.storeHashPassword(user, password);
+ }
+ }
+
+ private static class AuthenticationResult {
+ private final boolean successful;
+ private final String failureMessage;
+
+ private AuthenticationResult(boolean successful, String failureMessage) {
+ checkArgument((successful && failureMessage.isEmpty()) || (!successful && !failureMessage.isEmpty()), "Incorrect parameters");
+ this.successful = successful;
+ this.failureMessage = failureMessage;
+ }
+
+ public boolean isSuccessful() {
+ return successful;
+ }
+
+ public String getFailureMessage() {
+ return failureMessage;
+ }
+ }
+
+ public interface HashFunction {
+ AuthenticationResult checkCredentials(UserDto user, String password);
+ void storeHashPassword(UserDto user, String password);
+ }
+
+ /**
+ * Implementation of deprecated SHA1 hash function
+ */
+ private static final class Sha1Function implements HashFunction {
+ @Override
+ public AuthenticationResult checkCredentials(UserDto user, String password) {
+ if (user.getCryptedPassword() == null) {
+ return new AuthenticationResult(false, "null password in DB");
+ }
+ if (user.getSalt() == null) {
+ return new AuthenticationResult(false, "null salt");
+ }
+ if (!user.getCryptedPassword().equals(hash(user.getSalt(), password))) {
+ return new AuthenticationResult(false, "wrong password");
+ }
+ return new AuthenticationResult(true, "");
+ }
+
+ @Override
+ public void storeHashPassword(UserDto user, String password) {
+ requireNonNull(password, "Password cannot be null");
+ byte[] saltRandom = new byte[20];
+ SECURE_RANDOM.nextBytes(saltRandom);
+ String salt = DigestUtils.sha1Hex(saltRandom);
+
+ user.setHashMethod(HashMethod.SHA1.name())
+ .setCryptedPassword(hash(salt, password))
+ .setSalt(salt);
+ }
+
+ private String hash(String salt, String password) {
+ return DigestUtils.sha1Hex("--" + salt + "--" + password + "--");
+ }
+ }
+
+ /**
+ * Implementation of bcrypt hash function
+ */
+ private static final class BcryptFunction implements HashFunction {
+ @Override
+ public AuthenticationResult checkCredentials(UserDto user, String password) {
+ if (!BCrypt.checkpw(password, user.getCryptedPassword())) {
+ return new AuthenticationResult(false, "wrong password");
+ }
+ return new AuthenticationResult(true, "");
+ }
+
+ @Override
+ public void storeHashPassword(UserDto user, String password) {
+ requireNonNull(password, "Password cannot be null");
+ user.setHashMethod(HashMethod.BCRYPT.name())
+ .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12)))
+ .setSalt(null);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
index a012215840f..f1ec17669f4 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
@@ -27,11 +27,9 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
-import java.util.Random;
import java.util.function.Consumer;
import java.util.stream.Stream;
import javax.annotation.Nullable;
-import org.apache.commons.codec.digest.DigestUtils;
import org.sonar.api.config.Configuration;
import org.sonar.api.platform.NewUserHandler;
import org.sonar.api.server.ServerSide;
@@ -41,6 +39,7 @@ import org.sonar.db.organization.OrganizationMemberDto;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserGroupDto;
+import org.sonar.server.authentication.LocalAuthentication;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.OrganizationCreation;
import org.sonar.server.organization.OrganizationFlags;
@@ -56,7 +55,6 @@ import static java.util.Arrays.stream;
import static java.util.stream.Stream.concat;
import static org.sonar.core.config.CorePropertyDefinitions.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS;
import static org.sonar.core.util.stream.MoreCollectors.toList;
-import static org.sonar.db.user.UserDto.encryptPassword;
import static org.sonar.server.ws.WsUtils.checkFound;
import static org.sonar.server.ws.WsUtils.checkRequest;
@@ -83,9 +81,11 @@ public class UserUpdater {
private final OrganizationCreation organizationCreation;
private final DefaultGroupFinder defaultGroupFinder;
private final Configuration config;
+ private final LocalAuthentication localAuthentication;
public UserUpdater(NewUserNotifier newUserNotifier, DbClient dbClient, UserIndexer userIndexer, OrganizationFlags organizationFlags,
- DefaultOrganizationProvider defaultOrganizationProvider, OrganizationCreation organizationCreation, DefaultGroupFinder defaultGroupFinder, Configuration config) {
+ DefaultOrganizationProvider defaultOrganizationProvider, OrganizationCreation organizationCreation, DefaultGroupFinder defaultGroupFinder, Configuration config,
+ LocalAuthentication localAuthentication) {
this.newUserNotifier = newUserNotifier;
this.dbClient = dbClient;
this.userIndexer = userIndexer;
@@ -94,6 +94,7 @@ public class UserUpdater {
this.organizationCreation = organizationCreation;
this.defaultGroupFinder = defaultGroupFinder;
this.config = config;
+ this.localAuthentication = localAuthentication;
}
public UserDto createAndCommit(DbSession dbSession, NewUser newUser, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) {
@@ -165,7 +166,7 @@ public class UserUpdater {
String password = newUser.password();
if (password != null && validatePasswords(password, messages)) {
- setEncryptedPassword(password, userDto);
+ localAuthentication.storeHashPassword(userDto, password);
}
List<String> scmAccounts = sanitizeScmAccounts(newUser.scmAccounts());
@@ -218,10 +219,10 @@ public class UserUpdater {
return false;
}
- private static boolean updatePassword(UpdateUser updateUser, UserDto userDto, List<String> messages) {
+ private boolean updatePassword(UpdateUser updateUser, UserDto userDto, List<String> messages) {
String password = updateUser.password();
if (updateUser.isPasswordChanged() && validatePasswords(password, messages) && checkPasswordChangeAllowed(userDto, messages)) {
- setEncryptedPassword(password, userDto);
+ localAuthentication.storeHashPassword(userDto, password);
return true;
}
return false;
@@ -377,15 +378,6 @@ public class UserUpdater {
dbClient.userDao().update(dbSession, dto);
}
- private static void setEncryptedPassword(String password, UserDto userDto) {
- Random random = new SecureRandom();
- byte[] salt = new byte[32];
- random.nextBytes(salt);
- String saltHex = DigestUtils.sha1Hex(salt);
- userDto.setSalt(saltHex);
- userDto.setCryptedPassword(encryptPassword(password, saltHex));
- }
-
private void notifyNewUser(String login, String name, @Nullable String email) {
newUserNotifier.onNewUser(NewUserHandler.Context.builder()
.setLogin(login)
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java
index 60545f47523..a8cfae3c843 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java
@@ -25,13 +25,13 @@ import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.LocalAuthentication;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
import org.sonar.server.user.UpdateUser;
import org.sonar.server.user.UserSession;
import org.sonar.server.user.UserUpdater;
-import static com.google.common.base.Preconditions.checkArgument;
-import static org.sonar.db.user.UserDto.encryptPassword;
-
public class ChangePasswordAction implements UsersWsAction {
private static final String PARAM_LOGIN = "login";
@@ -41,11 +41,13 @@ public class ChangePasswordAction implements UsersWsAction {
private final DbClient dbClient;
private final UserUpdater userUpdater;
private final UserSession userSession;
+ private final LocalAuthentication localAuthentication;
- public ChangePasswordAction(DbClient dbClient, UserUpdater userUpdater, UserSession userSession) {
+ public ChangePasswordAction(DbClient dbClient, UserUpdater userUpdater, UserSession userSession, LocalAuthentication localAuthentication) {
this.dbClient = dbClient;
this.userUpdater = userUpdater;
this.userSession = userSession;
+ this.localAuthentication = localAuthentication;
}
@Override
@@ -97,7 +99,10 @@ public class ChangePasswordAction implements UsersWsAction {
private void checkCurrentPassword(DbSession dbSession, String login, String password) {
UserDto user = dbClient.userDao().selectOrFailByLogin(dbSession, login);
- String cryptedPassword = encryptPassword(password, user.getSalt());
- checkArgument(cryptedPassword.equals(user.getCryptedPassword()), "Incorrect password");
+ try {
+ localAuthentication.authenticate(dbSession, user, password, AuthenticationEvent.Method.BASIC);
+ } catch (AuthenticationException ex) {
+ throw new IllegalArgumentException("Incorrect password");
+ }
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java
index 0d34d69f420..70de0599437 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java
@@ -30,7 +30,7 @@ public class AuthenticationModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new AuthenticationModule().configure(container);
- assertThat(container.size()).isEqualTo(2 + 22);
+ assertThat(container.size()).isEqualTo(2 + 23);
}
}
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 9e295339f30..f08063f1d7c 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
@@ -62,14 +62,16 @@ public class CredentialsAuthenticatorTest {
private RealmAuthenticator externalAuthenticator = mock(RealmAuthenticator.class);
private HttpServletRequest request = mock(HttpServletRequest.class);
private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
+ private LocalAuthentication localAuthentication = new LocalAuthentication(dbClient);
- private CredentialsAuthenticator underTest = new CredentialsAuthenticator(dbClient, externalAuthenticator, authenticationEvent);
+ private CredentialsAuthenticator underTest = new CredentialsAuthenticator(dbClient, externalAuthenticator, authenticationEvent, localAuthentication);
@Test
public void authenticate_local_user() {
insertUser(newUserDto()
.setLogin(LOGIN)
.setCryptedPassword(CRYPTED_PASSWORD)
+ .setHashMethod(LocalAuthentication.HashMethod.SHA1.name())
.setSalt(SALT)
.setLocal(true));
@@ -84,6 +86,7 @@ public class CredentialsAuthenticatorTest {
.setLogin(LOGIN)
.setCryptedPassword("Wrong password")
.setSalt("Wrong salt")
+ .setHashMethod(LocalAuthentication.HashMethod.SHA1.name())
.setLocal(true));
expectedException.expect(authenticationException().from(Source.local(BASIC)).withLogin(LOGIN).andNoPublicMessage());
@@ -130,6 +133,7 @@ public class CredentialsAuthenticatorTest {
.setLogin(LOGIN)
.setCryptedPassword(null)
.setSalt(SALT)
+ .setHashMethod(LocalAuthentication.HashMethod.SHA1.name())
.setLocal(true));
expectedException.expect(authenticationException().from(Source.local(BASIC)).withLogin(LOGIN).andNoPublicMessage());
@@ -147,6 +151,7 @@ public class CredentialsAuthenticatorTest {
.setLogin(LOGIN)
.setCryptedPassword(CRYPTED_PASSWORD)
.setSalt(null)
+ .setHashMethod(LocalAuthentication.HashMethod.SHA1.name())
.setLocal(true));
expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withLogin(LOGIN).andNoPublicMessage());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/LocalAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/LocalAuthenticationTest.java
new file mode 100644
index 00000000000..6ac43702ab6
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/LocalAuthenticationTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.Optional;
+import java.util.Random;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mindrot.jbcrypt.BCrypt;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.authentication.LocalAuthentication.HashMethod.BCRYPT;
+import static org.sonar.server.authentication.LocalAuthentication.HashMethod.SHA1;
+
+public class LocalAuthenticationTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private static final Random RANDOM = new Random();
+
+ private LocalAuthentication underTest = new LocalAuthentication(db.getDbClient());
+
+ @Test
+ public void incorrect_hash_should_throw_AuthenticationException() {
+ UserDto user = new UserDto()
+ .setHashMethod("ALGON2");
+
+ expectedException.expect(AuthenticationException.class);
+ expectedException.expectMessage("Unknown hash method [ALGON2]");
+
+ underTest.authenticate(db.getSession(), user, "whatever", AuthenticationEvent.Method.BASIC);
+ }
+
+ @Test
+ public void null_hash_should_throw_AuthenticationException() {
+ UserDto user = new UserDto();
+
+ expectedException.expect(AuthenticationException.class);
+ expectedException.expectMessage("null hash method");
+
+ underTest.authenticate(db.getSession(), user, "whatever", AuthenticationEvent.Method.BASIC);
+ }
+
+ @Test
+ public void authentication_with_bcrypt_with_correct_password_should_work() {
+ String password = randomAlphanumeric(60);
+
+ UserDto user = new UserDto()
+ .setHashMethod(BCRYPT.name())
+ .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12)));
+
+ underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
+ }
+
+ @Test
+ public void authentication_with_sha1_with_correct_password_should_work() {
+ String password = randomAlphanumeric(60);
+
+ byte[] saltRandom = new byte[20];
+ RANDOM.nextBytes(saltRandom);
+ String salt = DigestUtils.sha1Hex(saltRandom);
+
+ UserDto user = new UserDto()
+ .setHashMethod(SHA1.name())
+ .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--"))
+ .setSalt(salt);
+
+ underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
+ }
+
+ @Test
+ public void authentication_with_sha1_with_incorrect_password_should_throw_AuthenticationException() {
+ String password = randomAlphanumeric(60);
+
+ byte[] saltRandom = new byte[20];
+ RANDOM.nextBytes(saltRandom);
+ String salt = DigestUtils.sha1Hex(saltRandom);
+
+ UserDto user = new UserDto()
+ .setHashMethod(SHA1.name())
+ .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--"))
+ .setSalt(salt);
+
+ expectedException.expect(AuthenticationException.class);
+ expectedException.expectMessage("wrong password");
+
+ underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
+ }
+
+ @Test
+ public void authentication_with_sha1_with_empty_password_should_throw_AuthenticationException() {
+ byte[] saltRandom = new byte[20];
+ RANDOM.nextBytes(saltRandom);
+ String salt = DigestUtils.sha1Hex(saltRandom);
+
+ UserDto user = new UserDto()
+ .setHashMethod(SHA1.name())
+ .setSalt(salt);
+
+ expectedException.expect(AuthenticationException.class);
+ expectedException.expectMessage("null password in DB");
+
+ underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
+ }
+
+ @Test
+ public void authentication_with_sha1_with_empty_salt_should_throw_AuthenticationException() {
+ String password = randomAlphanumeric(60);
+
+ UserDto user = new UserDto()
+ .setHashMethod(SHA1.name())
+ .setCryptedPassword(DigestUtils.sha1Hex("--0242b0b4c0a93ddfe09dd886de50bc25ba000b51--" + password + "--"));
+
+ expectedException.expect(AuthenticationException.class);
+ expectedException.expectMessage("null salt");
+
+ underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
+ }
+
+ @Test
+ public void authentication_with_bcrypt_with_incorrect_password_should_throw_AuthenticationException() {
+ String password = randomAlphanumeric(60);
+
+ UserDto user = new UserDto()
+ .setHashMethod(BCRYPT.name())
+ .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12)));
+
+ expectedException.expect(AuthenticationException.class);
+ expectedException.expectMessage("wrong password");
+
+ underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
+ }
+
+ @Test
+ public void authentication_upgrade_hash_function_when_SHA1_was_used() {
+ String password = randomAlphanumeric(60);
+
+ byte[] saltRandom = new byte[20];
+ RANDOM.nextBytes(saltRandom);
+ String salt = DigestUtils.sha1Hex(saltRandom);
+
+ UserDto user = new UserDto()
+ .setLogin("myself")
+ .setHashMethod(SHA1.name())
+ .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--"))
+ .setSalt(salt);
+ db.users().insertUser(user);
+
+ underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
+
+ Optional<UserDto> myself = db.users().selectUserByLogin("myself");
+ assertThat(myself).isPresent();
+ assertThat(myself.get().getHashMethod()).isEqualTo(BCRYPT.name());
+ assertThat(myself.get().getSalt()).isNull();
+
+ // authentication must work with upgraded hash method
+ underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.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 0ecfe073c68..69bd660fe8c 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
@@ -100,12 +100,13 @@ public class SsoAuthenticatorTest {
private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
private UserIdentityAuthenticator userIdentityAuthenticator = new UserIdentityAuthenticator(
db.getDbClient(),
new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider, organizationCreation,
- new DefaultGroupFinder(db.getDbClient()), settings.asConfig()),
+ new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication),
defaultOrganizationProvider, organizationFlags, new DefaultGroupFinder(db.getDbClient()));
private HttpServletResponse response = mock(HttpServletResponse.class);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
index e4a13809c3c..7e6505bcbcc 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
@@ -84,6 +84,7 @@ public class UserIdentityAuthenticatorTest {
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private UserUpdater userUpdater = new UserUpdater(
mock(NewUserNotifier.class),
db.getDbClient(),
@@ -92,7 +93,8 @@ public class UserIdentityAuthenticatorTest {
defaultOrganizationProvider,
organizationCreation,
new DefaultGroupFinder(db.getDbClient()),
- settings.asConfig());
+ settings.asConfig(),
+ localAuthentication);
private UserIdentityAuthenticator underTest = new UserIdentityAuthenticator(db.getDbClient(), userUpdater, defaultOrganizationProvider, organizationFlags,
new DefaultGroupFinder(db.getDbClient()));
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java
index 350d8457ba4..dbc5fe59fb8 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java
@@ -38,6 +38,8 @@ import org.sonar.db.DbTester;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.GroupTesting;
import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.LocalAuthentication;
+import org.sonar.server.authentication.LocalAuthentication.HashMethod;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.organization.DefaultOrganizationProvider;
@@ -86,8 +88,9 @@ public class UserUpdaterCreateTest {
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
private MapSettings settings = new MapSettings();
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private UserUpdater underTest = new UserUpdater(newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, organizationCreation,
- new DefaultGroupFinder(dbClient), settings.asConfig());
+ new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication);
@Test
public void create_user() {
@@ -110,7 +113,8 @@ public class UserUpdaterCreateTest {
assertThat(dto.isActive()).isTrue();
assertThat(dto.isLocal()).isTrue();
- assertThat(dto.getSalt()).isNotNull();
+ assertThat(dto.getSalt()).isNull();
+ assertThat(dto.getHashMethod()).isEqualTo(HashMethod.BCRYPT.name());
assertThat(dto.getCryptedPassword()).isNotNull();
assertThat(dto.getCreatedAt())
.isPositive()
@@ -618,7 +622,8 @@ public class UserUpdaterCreateTest {
assertThat(dto.getScmAccounts()).isNull();
assertThat(dto.isLocal()).isTrue();
- assertThat(dto.getSalt()).isNotNull().isNotEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
+ assertThat(dto.getSalt()).isNull();
+ assertThat(dto.getHashMethod()).isEqualTo(HashMethod.BCRYPT.name());
assertThat(dto.getCryptedPassword()).isNotNull().isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
assertThat(dto.getCreatedAt()).isEqualTo(user.getCreatedAt());
assertThat(dto.getUpdatedAt()).isGreaterThan(user.getCreatedAt());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java
index 15b3d3c46a0..420daca6deb 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java
@@ -36,6 +36,7 @@ import org.sonar.db.DbTester;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserTesting;
+import org.sonar.server.authentication.LocalAuthentication;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.organization.DefaultOrganizationProvider;
@@ -71,15 +72,15 @@ public class UserUpdaterUpdateTest {
private DbClient dbClient = db.getDbClient();
private NewUserNotifier newUserNotifier = mock(NewUserNotifier.class);
- private ArgumentCaptor<NewUserHandler.Context> newUserHandler = ArgumentCaptor.forClass(NewUserHandler.Context.class);
private DbSession session = db.getSession();
private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
private MapSettings settings = new MapSettings();
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private UserUpdater underTest = new UserUpdater(newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, organizationCreation,
- new DefaultGroupFinder(dbClient), settings.asConfig());
+ new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication);
@Test
public void update_user() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java
index e865e2f368a..94ad82cc651 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java
@@ -25,6 +25,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.db.DbTester;
+import org.sonar.server.authentication.LocalAuthentication;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
@@ -56,15 +57,17 @@ public class ChangePasswordActionTest {
public UserSessionRule userSessionRule = UserSessionRule.standalone().logIn();
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private UserUpdater userUpdater = new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), new UserIndexer(db.getDbClient(), es.client()),
organizationFlags,
TestDefaultOrganizationProvider.from(db),
mock(OrganizationCreation.class),
new DefaultGroupFinder(db.getDbClient()),
- new MapSettings().asConfig());
+ new MapSettings().asConfig(),
+ localAuthentication);
- private WsTester tester = new WsTester(new UsersWs(new ChangePasswordAction(db.getDbClient(), userUpdater, userSessionRule)));
+ private WsTester tester = new WsTester(new UsersWs(new ChangePasswordAction(db.getDbClient(), userUpdater, userSessionRule, localAuthentication)));
@Before
public void setUp() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
index d995f5d2770..5e5c5ebf71b 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
@@ -34,6 +34,7 @@ import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.LocalAuthentication;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.organization.DefaultOrganizationProvider;
@@ -82,10 +83,11 @@ public class CreateActionTest {
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private WsActionTester tester = new WsActionTester(new CreateAction(
db.getDbClient(),
new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider,
- organizationCreation, new DefaultGroupFinder(db.getDbClient()), settings.asConfig()),
+ organizationCreation, new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication),
userSessionRule));
@Before
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java
index b41242f2b77..9b6637a5942 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java
@@ -30,6 +30,7 @@ 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.LocalAuthentication;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
@@ -70,10 +71,11 @@ public class UpdateActionTest {
private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private WsActionTester ws = new WsActionTester(new UpdateAction(
new UserUpdater(mock(NewUserNotifier.class), dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, ORGANIZATION_CREATION_NOT_USED_FOR_UPDATE,
- new DefaultGroupFinder(db.getDbClient()), settings.asConfig()), userSession, new UserJsonWriter(userSession), dbClient));
+ new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication), userSession, new UserJsonWriter(userSession), dbClient));
@Before
public void setUp() {
@@ -81,7 +83,7 @@ public class UpdateActionTest {
}
@Test
- public void update_user() throws Exception {
+ public void update_user() {
createUser();
ws.newRequest()
@@ -94,7 +96,7 @@ public class UpdateActionTest {
}
@Test
- public void update_only_name() throws Exception {
+ public void update_only_name() {
createUser();
ws.newRequest()
@@ -105,7 +107,7 @@ public class UpdateActionTest {
}
@Test
- public void update_only_email() throws Exception {
+ public void update_only_email() {
createUser();
ws.newRequest()
@@ -116,7 +118,7 @@ public class UpdateActionTest {
}
@Test
- public void blank_email_is_updated_to_null() throws Exception {
+ public void blank_email_is_updated_to_null() {
createUser();
ws.newRequest()
@@ -143,7 +145,7 @@ public class UpdateActionTest {
}
@Test
- public void update_only_scm_accounts() throws Exception {
+ public void update_only_scm_accounts() {
createUser();
ws.newRequest()
@@ -196,7 +198,7 @@ public class UpdateActionTest {
}
@Test
- public void update_only_scm_accounts_with_deprecated_scmAccounts_parameter() throws Exception {
+ public void update_only_scm_accounts_with_deprecated_scmAccounts_parameter() {
createUser();
ws.newRequest()
@@ -210,7 +212,7 @@ public class UpdateActionTest {
}
@Test
- public void update_only_scm_accounts_with_deprecated_scm_accounts_parameter() throws Exception {
+ public void update_only_scm_accounts_with_deprecated_scm_accounts_parameter() {
createUser();
ws.newRequest()
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
index 0bd5afff1a9..d0e7da3a51f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
@@ -24,6 +24,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.server.authentication.LocalAuthentication;
import org.sonar.server.issue.ws.AvatarResolver;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.UserUpdater;
@@ -36,15 +38,18 @@ import static org.mockito.Mockito.mock;
public class UsersWsTest {
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();
+ @Rule
+ public DbTester db = DbTester.create();
private WebService.Controller controller;
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
@Before
public void setUp() {
WsTester tester = new WsTester(new UsersWs(
new CreateAction(mock(DbClient.class), mock(UserUpdater.class), userSessionRule),
new UpdateAction(mock(UserUpdater.class), userSessionRule, mock(UserJsonWriter.class), mock(DbClient.class)),
- new ChangePasswordAction(mock(DbClient.class), mock(UserUpdater.class), userSessionRule),
+ new ChangePasswordAction(mock(DbClient.class), mock(UserUpdater.class), userSessionRule, localAuthentication),
new SearchAction(userSessionRule, mock(UserIndex.class), mock(DbClient.class), mock(AvatarResolver.class))));
controller = tester.controller("api/users");
}
diff --git a/sonar-core/build.gradle b/sonar-core/build.gradle
index cecce8b43fd..a2d0f1bb189 100644
--- a/sonar-core/build.gradle
+++ b/sonar-core/build.gradle
@@ -20,7 +20,6 @@ dependencies {
compile 'org.picocontainer:picocontainer'
compile 'org.slf4j:slf4j-api'
compile 'org.sonarsource.update-center:sonar-update-center-common'
- compile 'org.mindrot:jbcrypt'
compile project(path: ':sonar-plugin-api', configuration: 'shadow')
compileOnly 'com.google.code.findbugs:jsr305'