]> source.dussan.org Git - sonarqube.git/blob
e28a278d4c93eba496762aff60fd0ec84391cadd
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2018 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20
21 package org.sonar.server.authentication;
22
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;
32
33 import static com.google.common.base.Preconditions.checkArgument;
34 import static java.lang.String.format;
35 import static java.util.Objects.requireNonNull;
36
37 /**
38  * Validates the password of a "local" user (password is stored in
39  * database).
40  */
41 public class CredentialsLocalAuthentication {
42
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;
47
48   public CredentialsLocalAuthentication(DbClient dbClient) {
49     this.dbClient = dbClient;
50   }
51
52   /**
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
56    * is not committed
57    */
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")
64         .build();
65     }
66
67     HashMethod hashMethod;
68     try {
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()))
75         .build();
76     }
77
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())
84         .build();
85     }
86
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);
91     }
92   }
93
94   /**
95    * Method used to store the password as a hash in database.
96    * The crypted_password, salt and hash_method are set
97    */
98   public void storeHashPassword(UserDto user, String password) {
99     DEFAULT.storeHashPassword(user, password);
100   }
101
102   public enum HashMethod implements HashFunction {
103     SHA1(new Sha1Function()), BCRYPT(new BcryptFunction());
104
105     private HashFunction hashFunction;
106
107     HashMethod(HashFunction hashFunction) {
108       this.hashFunction = hashFunction;
109     }
110
111     @Override
112     public AuthenticationResult checkCredentials(UserDto user, String password) {
113       return hashFunction.checkCredentials(user, password);
114     }
115
116     @Override
117     public void storeHashPassword(UserDto user, String password) {
118       hashFunction.storeHashPassword(user, password);
119     }
120   }
121
122   private static class AuthenticationResult {
123     private final boolean successful;
124     private final String failureMessage;
125
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;
130     }
131
132     public boolean isSuccessful() {
133       return successful;
134     }
135
136     public String getFailureMessage() {
137       return failureMessage;
138     }
139   }
140
141   public interface HashFunction {
142     AuthenticationResult checkCredentials(UserDto user, String password);
143
144     void storeHashPassword(UserDto user, String password);
145   }
146
147   /**
148    * Implementation of deprecated SHA1 hash function
149    */
150   private static final class Sha1Function implements HashFunction {
151     @Override
152     public AuthenticationResult checkCredentials(UserDto user, String password) {
153       if (user.getCryptedPassword() == null) {
154         return new AuthenticationResult(false, "null password in DB");
155       }
156       if (user.getSalt() == null) {
157         return new AuthenticationResult(false, "null salt");
158       }
159       if (!user.getCryptedPassword().equals(hash(user.getSalt(), password))) {
160         return new AuthenticationResult(false, "wrong password");
161       }
162       return new AuthenticationResult(true, "");
163     }
164
165     @Override
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);
171
172       user.setHashMethod(HashMethod.SHA1.name())
173         .setCryptedPassword(hash(salt, password))
174         .setSalt(salt);
175     }
176
177     private static String hash(String salt, String password) {
178       return DigestUtils.sha1Hex("--" + salt + "--" + password + "--");
179     }
180   }
181
182   /**
183    * Implementation of bcrypt hash function
184    */
185   private static final class BcryptFunction implements HashFunction {
186     @Override
187     public AuthenticationResult checkCredentials(UserDto user, String password) {
188       if (!BCrypt.checkpw(password, user.getCryptedPassword())) {
189         return new AuthenticationResult(false, "wrong password");
190       }
191       return new AuthenticationResult(true, "");
192     }
193
194     @Override
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)))
199         .setSalt(null);
200     }
201   }
202 }