]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6949 Implements bcrypt hash for password
authorEric Hartmann <hartmann.eric@gmail.com>
Fri, 13 Apr 2018 16:26:16 +0000 (18:26 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 17 Apr 2018 18:20:48 +0000 (20:20 +0200)
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

26 files changed:
server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDtoTest.java
server/sonar-db-migration/build.gradle
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/IncreaseCryptedPasswordSizeTest.java
server/sonar-server/build.gradle
server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java
server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/authentication/LocalAuthentication.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java
server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/LocalAuthenticationTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
sonar-core/build.gradle

index 46bf46c7fc1beb197a9114334febe96630e6f2d7..b82e764641bc832169fa84db521452103556518b 100644 (file)
@@ -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');
index 1baa5f7289241832c282f9237c02370075ecd36b..32b5e69e83569f9b10fda67739b63ea681cbc09b 100644 (file)
@@ -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)
index d699cf7ba2aa2b8d3d439991669cb99081474302..97909a403071895a11d52fad5abd96f4de6d3a84 100644 (file)
         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}
index 1286768f703afcdb1fbbc9c4b4426779abc0c400..e5899b9d08a4e4d166c7a66de8aa98aaa03f0aab 100644 (file)
@@ -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();
index f34a9c226edf650985115ddb25355ffb5b870289..15613d0f59f65cae2d705129a9b738a56b719c73 100644 (file)
@@ -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);
-  }
 }
index 7360daec774288c8127b47a9593a772cd241be3c..53e5be5a48c3497988271edd91ab180f35891d12 100644 (file)
@@ -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
 
index 35c942769361107b305b45ff79a734a22c97e97b..2303e9e1d89b20dd84aa78dc4207fe4abdbb08f4 100644 (file)
@@ -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)
     ;
index 455c0a258353c6eeb55185d67489b6fa8a49d625..2dee68bea27aeae82e90fee3749e3655f10df686 100644 (file)
@@ -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);
   }
index 761efd7ae1b6d295ba51b1d9c41d643d713aa57f..ac661c36f38fc7a640b82f8beb7f5e7aa84cbc6a 100644 (file)
@@ -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')
index 3ed62249fc403fb8cd5793360f84680b74ccc995..4f256b9b13c0d351f56bbe82deeeda017c79838b 100644 (file)
@@ -47,6 +47,7 @@ public class AuthenticationModule extends Module {
       LoginAction.class,
       LogoutAction.class,
       CredentialsAuthenticator.class,
+      LocalAuthentication.class,
       RealmAuthenticator.class,
       BasicAuthenticator.class,
       ValidateAction.class,
index 14eb28706e9ac0701fce0ffef4c89e3875d59726..63cb971d681327d9af1b8df1af0971f06ef86c87 100644 (file)
@@ -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 (file)
index 0000000..c7efae3
--- /dev/null
@@ -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);
+    }
+  }
+}
index a012215840f4374d5596fcb4b696349ce324cc51..f1ec17669f41b5a2eba8444040841dd4ea5c2ca1 100644 (file)
@@ -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)
index 60545f475234de44e7fae700478b11786021d9b2..a8cfae3c843524270ec85add79ad5d48da8aa73c 100644 (file)
@@ -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");
+    }
   }
 }
index 0d34d69f420be8e9a641ddd8548df9a865e05249..70de05994373415264823b69bd2b533f887a6ae6 100644 (file)
@@ -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);
   }
 
 }
index 9e295339f308514eadd601455595cc56250de0cc..f08063f1d7c28a2cc6b98693876e9aec3c1eb4a5 100644 (file)
@@ -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 (file)
index 0000000..6ac4370
--- /dev/null
@@ -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);
+  }
+}
index 0ecfe073c686e27f8599e5f9da7edb6d12eae5be..69bd660fe8ce630dcc368a0a47d22234021048c6 100644 (file)
@@ -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);
index e4a13809c3cc60da5e4997cc41506e3e4a428e6c..7e6505bcbccfd9ae195f96287545ef53e603a204 100644 (file)
@@ -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()));
index 350d8457ba4f2ff66846f6d8c421be0ba74e76e6..dbc5fe59fb89149ae2d99fa0a130650f0d05e182 100644 (file)
@@ -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());
index 15b3d3c46a04f185a3baaebfae88c6ec25d2d4ba..420daca6deb19dcd35a66ce0948dc6bb3dc30376 100644 (file)
@@ -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() {
index e865e2f368a29cde25cfc795d99900d0b3c8b7c0..94ad82cc651508b5160804619b5fcc867fe424eb 100644 (file)
@@ -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() {
index d995f5d27700e13c6607b0d33d3d3c386fe6a719..5e5c5ebf71b98a24ef89ac9e0e501477686d093e 100644 (file)
@@ -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
index b41242f2b774dc6ffe3c9a09d2aaa60fe23ac0ad..9b6637a5942b4fdb466fbdf3dc704ceefe04cbb7 100644 (file)
@@ -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()
index 0bd5afff1a9679be7d7533b700646e9fbc9486f8..d0e7da3a51f69cb38272659ba1a658526b4544d9 100644 (file)
@@ -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");
   }
index cecce8b43fdbc2ba6b54f57aa79278c1d6cc23db..a2d0f1bb1892198f2c7a565caf962b9069bc54db 100644 (file)
@@ -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'