3 * Copyright (C) 2009-2018 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 package org.sonar.server.authentication;
23 import java.security.SecureRandom;
24 import org.apache.commons.codec.digest.DigestUtils;
25 import org.mindrot.jbcrypt.BCrypt;
26 import org.sonar.db.DbClient;
27 import org.sonar.db.DbSession;
28 import org.sonar.db.user.UserDto;
29 import org.sonar.server.authentication.event.AuthenticationEvent.Method;
30 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
31 import org.sonar.server.authentication.event.AuthenticationException;
33 import static com.google.common.base.Preconditions.checkArgument;
34 import static java.lang.String.format;
35 import static java.util.Objects.requireNonNull;
38 * Validates the password of a "local" user (password is stored in
41 public class CredentialsLocalAuthentication {
43 private final DbClient dbClient;
44 private static final SecureRandom SECURE_RANDOM = new SecureRandom();
45 // The default hash method that must be used is BCRYPT
46 private static final HashMethod DEFAULT = HashMethod.BCRYPT;
48 public CredentialsLocalAuthentication(DbClient dbClient) {
49 this.dbClient = dbClient;
53 * This method authenticate a user with his password against the value stored in user.
54 * If authentication failed an AuthenticationException will be thrown containing the failure message.
55 * If the password must be updated because an old algorithm is used, the UserDto is updated but the session
58 public void authenticate(DbSession session, UserDto user, String password, Method method) {
59 if (user.getHashMethod() == null) {
60 throw AuthenticationException.newBuilder()
61 .setSource(Source.local(method))
62 .setLogin(user.getLogin())
63 .setMessage("null hash method")
67 HashMethod hashMethod;
69 hashMethod = HashMethod.valueOf(user.getHashMethod());
70 } catch (IllegalArgumentException ex) {
71 throw AuthenticationException.newBuilder()
72 .setSource(Source.local(method))
73 .setLogin(user.getLogin())
74 .setMessage(format("Unknown hash method [%s]", user.getHashMethod()))
78 AuthenticationResult result = hashMethod.checkCredentials(user, password);
79 if (!result.isSuccessful()) {
80 throw AuthenticationException.newBuilder()
81 .setSource(Source.local(method))
82 .setLogin(user.getLogin())
83 .setMessage(result.getFailureMessage())
87 // Upgrade the password if it's an old hashMethod
88 if (hashMethod != DEFAULT) {
89 DEFAULT.storeHashPassword(user, password);
90 dbClient.userDao().update(session, user);
95 * Method used to store the password as a hash in database.
96 * The crypted_password, salt and hash_method are set
98 public void storeHashPassword(UserDto user, String password) {
99 DEFAULT.storeHashPassword(user, password);
102 public enum HashMethod implements HashFunction {
103 SHA1(new Sha1Function()), BCRYPT(new BcryptFunction());
105 private HashFunction hashFunction;
107 HashMethod(HashFunction hashFunction) {
108 this.hashFunction = hashFunction;
112 public AuthenticationResult checkCredentials(UserDto user, String password) {
113 return hashFunction.checkCredentials(user, password);
117 public void storeHashPassword(UserDto user, String password) {
118 hashFunction.storeHashPassword(user, password);
122 private static class AuthenticationResult {
123 private final boolean successful;
124 private final String failureMessage;
126 private AuthenticationResult(boolean successful, String failureMessage) {
127 checkArgument((successful && failureMessage.isEmpty()) || (!successful && !failureMessage.isEmpty()), "Incorrect parameters");
128 this.successful = successful;
129 this.failureMessage = failureMessage;
132 public boolean isSuccessful() {
136 public String getFailureMessage() {
137 return failureMessage;
141 public interface HashFunction {
142 AuthenticationResult checkCredentials(UserDto user, String password);
144 void storeHashPassword(UserDto user, String password);
148 * Implementation of deprecated SHA1 hash function
150 private static final class Sha1Function implements HashFunction {
152 public AuthenticationResult checkCredentials(UserDto user, String password) {
153 if (user.getCryptedPassword() == null) {
154 return new AuthenticationResult(false, "null password in DB");
156 if (user.getSalt() == null) {
157 return new AuthenticationResult(false, "null salt");
159 if (!user.getCryptedPassword().equals(hash(user.getSalt(), password))) {
160 return new AuthenticationResult(false, "wrong password");
162 return new AuthenticationResult(true, "");
166 public void storeHashPassword(UserDto user, String password) {
167 requireNonNull(password, "Password cannot be null");
168 byte[] saltRandom = new byte[20];
169 SECURE_RANDOM.nextBytes(saltRandom);
170 String salt = DigestUtils.sha1Hex(saltRandom);
172 user.setHashMethod(HashMethod.SHA1.name())
173 .setCryptedPassword(hash(salt, password))
177 private static String hash(String salt, String password) {
178 return DigestUtils.sha1Hex("--" + salt + "--" + password + "--");
183 * Implementation of bcrypt hash function
185 private static final class BcryptFunction implements HashFunction {
187 public AuthenticationResult checkCredentials(UserDto user, String password) {
188 if (!BCrypt.checkpw(password, user.getCryptedPassword())) {
189 return new AuthenticationResult(false, "wrong password");
191 return new AuthenticationResult(true, "");
195 public void storeHashPassword(UserDto user, String password) {
196 requireNonNull(password, "Password cannot be null");
197 user.setHashMethod(HashMethod.BCRYPT.name())
198 .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12)))