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 46bf46c7fc1b..b82e764641bc 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 1baa5f728924..32b5e69e8356 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.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 String getHashMethod() { return hashMethod; } - public UserDto setHashMethod(String hashMethod) { + public UserDto setHashMethod(@Nullable String hashMethod) { this.hashMethod = hashMethod; return this; } @@ -260,12 +260,6 @@ public UserDto setOnboarded(boolean onboarded) { 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 d699cf7ba2aa..97909a403071 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 1286768f703a..e5899b9d08a4 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 void insert_user() { .setOnboarded(true) .setSalt("1234") .setCryptedPassword("abcd") + .setHashMethod("SHA1") .setExternalIdentity("johngithub") .setExternalIdentityProvider("github") .setLocal(true) @@ -340,6 +341,7 @@ public void insert_user() { 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 void update_user() { .setOnboarded(true) .setSalt("12345") .setCryptedPassword("abcde") + .setHashMethod("BCRYPT") .setExternalIdentity("johngithub") .setExternalIdentityProvider("github") .setLocal(false) @@ -386,6 +389,7 @@ public void update_user() { 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 f34a9c226edf..15613d0f59f6 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 void decode_scm_accounts() { 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 7360daec7742..53e5be5a48c3 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 35c942769361..2303e9e1d89b 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 455c0a258353..2dee68bea27a 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 @@ * 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 void can_insert_crypted_password_after_execute() throws SQLException { } 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 761efd7ae1b6..ac661c36f38f 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 3ed62249fc40..4f256b9b13c0 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 @@ protected void configureModule() { 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 14eb28706e9a..63cb971d6813 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.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 UserDto authenticate(String userLogin, String userPassword, HttpServletRe 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 externalUser = externalAuthenticator.authenticate(userLogin, userPassword, request, method); if (externalUser.isPresent()) { @@ -68,30 +68,4 @@ private UserDto authenticate(DbSession dbSession, String userLogin, String userP .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 000000000000..c7efae31e9fa --- /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 a012215840f4..f1ec17669f41 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.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.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.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 UserUpdater(NewUserNotifier newUserNotifier, DbClient dbClient, UserIndex this.organizationCreation = organizationCreation; this.defaultGroupFinder = defaultGroupFinder; this.config = config; + this.localAuthentication = localAuthentication; } public UserDto createAndCommit(DbSession dbSession, NewUser newUser, Consumer beforeCommit, UserDto... otherUsersToIndex) { @@ -165,7 +166,7 @@ private UserDto createDto(DbSession dbSession, NewUser newUser) { String password = newUser.password(); if (password != null && validatePasswords(password, messages)) { - setEncryptedPassword(password, userDto); + localAuthentication.storeHashPassword(userDto, password); } List scmAccounts = sanitizeScmAccounts(newUser.scmAccounts()); @@ -218,10 +219,10 @@ private static boolean updateExternalIdentity(UpdateUser updateUser, UserDto use return false; } - private static boolean updatePassword(UpdateUser updateUser, UserDto userDto, List messages) { + private boolean updatePassword(UpdateUser updateUser, UserDto userDto, List 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 @@ private void updateUser(DbSession dbSession, UserDto dto) { 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 60545f475234..a8cfae3c8435 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.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 void handle(Request request, Response response) throws Exception { 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 0d34d69f420b..70de05994373 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 9e295339f308..f08063f1d7c2 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 void fail_to_authenticate_local_user_when_password_is_wrong() { .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 void fail_to_authenticate_local_user_that_have_no_password() { .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 void fail_to_authenticate_local_user_that_have_no_salt() { .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 000000000000..6ac43702ab67 --- /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 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 0ecfe073c686..69bd660fe8ce 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 e4a13809c3cc..7e6505bcbccf 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 350d8457ba4f..dbc5fe59fb89 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.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 void create_user() { 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 void reactivate_user_when_creating_user_with_existing_login() { 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 15b3d3c46a04..420daca6deb1 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.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 = 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 e865e2f368a2..94ad82cc6515 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.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 d995f5d27700..5e5c5ebf71b9 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.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 b41242f2b774..9b6637a5942b 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.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 void setUp() { } @Test - public void update_user() throws Exception { + public void update_user() { createUser(); ws.newRequest() @@ -94,7 +96,7 @@ public void update_user() throws Exception { } @Test - public void update_only_name() throws Exception { + public void update_only_name() { createUser(); ws.newRequest() @@ -105,7 +107,7 @@ public void update_only_name() throws Exception { } @Test - public void update_only_email() throws Exception { + public void update_only_email() { createUser(); ws.newRequest() @@ -116,7 +118,7 @@ public void update_only_email() throws Exception { } @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 void remove_scm_accounts() { } @Test - public void update_only_scm_accounts() throws Exception { + public void update_only_scm_accounts() { createUser(); ws.newRequest() @@ -196,7 +198,7 @@ public void update_scm_account_ordered_case_insensitive() { } @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 void update_only_scm_accounts_with_deprecated_scmAccounts_parameter() thr } @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 0bd5afff1a96..d0e7da3a51f6 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.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 @@ 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 cecce8b43fdb..a2d0f1bb1892 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'