]> source.dussan.org Git - sonarqube.git/blob
e7cafffc61a9809db19c08b7a79953e7dc7dbe55
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2021 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 package org.sonar.server.authentication;
21
22 import java.util.Optional;
23 import java.util.Random;
24 import org.apache.commons.codec.digest.DigestUtils;
25 import org.junit.Before;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.junit.rules.ExpectedException;
29 import org.mindrot.jbcrypt.BCrypt;
30 import org.sonar.api.config.internal.MapSettings;
31 import org.sonar.db.DbTester;
32 import org.sonar.db.user.UserDto;
33 import org.sonar.server.authentication.event.AuthenticationEvent;
34 import org.sonar.server.authentication.event.AuthenticationException;
35
36 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
37 import static org.assertj.core.api.Assertions.assertThat;
38 import static org.sonar.db.user.UserTesting.newUserDto;
39 import static org.sonar.server.authentication.CredentialsLocalAuthentication.HashMethod.BCRYPT;
40 import static org.sonar.server.authentication.CredentialsLocalAuthentication.HashMethod.PBKDF2;
41 import static org.sonar.server.authentication.CredentialsLocalAuthentication.HashMethod.SHA1;
42
43 public class CredentialsLocalAuthenticationTest {
44   @Rule
45   public ExpectedException expectedException = ExpectedException.none();
46   @Rule
47   public DbTester db = DbTester.create();
48
49   private static final Random RANDOM = new Random();
50   private static final MapSettings settings = new MapSettings();
51
52   private CredentialsLocalAuthentication underTest = new CredentialsLocalAuthentication(db.getDbClient(), settings.asConfig());
53
54   @Before
55   public void setup() {
56     settings.setProperty("sonar.internal.pbkdf2.iterations", 1);
57   }
58
59   @Test
60   public void incorrect_hash_should_throw_AuthenticationException() {
61     UserDto user = newUserDto()
62       .setHashMethod("ALGON2");
63
64     expectedException.expect(AuthenticationException.class);
65     expectedException.expectMessage("Unknown hash method [ALGON2]");
66
67     underTest.authenticate(db.getSession(), user, "whatever", AuthenticationEvent.Method.BASIC);
68   }
69
70   @Test
71   public void null_hash_should_throw_AuthenticationException() {
72     UserDto user = newUserDto();
73
74     expectedException.expect(AuthenticationException.class);
75     expectedException.expectMessage("null hash method");
76
77     underTest.authenticate(db.getSession(), user, "whatever", AuthenticationEvent.Method.BASIC);
78   }
79
80   @Test
81   public void authentication_with_bcrypt_with_correct_password_should_work() {
82     String password = randomAlphanumeric(60);
83
84     UserDto user = newUserDto()
85       .setHashMethod(BCRYPT.name())
86       .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12)));
87
88     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
89   }
90
91   @Test
92   public void authentication_with_sha1_with_correct_password_should_work() {
93     String password = randomAlphanumeric(60);
94
95     byte[] saltRandom = new byte[20];
96     RANDOM.nextBytes(saltRandom);
97     String salt = DigestUtils.sha1Hex(saltRandom);
98
99     UserDto user = newUserDto()
100       .setHashMethod(SHA1.name())
101       .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--"))
102       .setSalt(salt);
103
104     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
105   }
106
107   @Test
108   public void authentication_with_sha1_with_incorrect_password_should_throw_AuthenticationException() {
109     String password = randomAlphanumeric(60);
110
111     byte[] saltRandom = new byte[20];
112     RANDOM.nextBytes(saltRandom);
113     String salt = DigestUtils.sha1Hex(saltRandom);
114
115     UserDto user = newUserDto()
116       .setHashMethod(SHA1.name())
117       .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--"))
118       .setSalt(salt);
119
120     expectedException.expect(AuthenticationException.class);
121     expectedException.expectMessage("wrong password");
122
123     underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
124   }
125
126   @Test
127   public void authentication_with_sha1_with_empty_password_should_throw_AuthenticationException() {
128     byte[] saltRandom = new byte[20];
129     RANDOM.nextBytes(saltRandom);
130     String salt = DigestUtils.sha1Hex(saltRandom);
131
132     UserDto user = newUserDto()
133       .setCryptedPassword(null)
134       .setHashMethod(SHA1.name())
135       .setSalt(salt);
136
137     expectedException.expect(AuthenticationException.class);
138     expectedException.expectMessage("null password in DB");
139
140     underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
141   }
142
143   @Test
144   public void authentication_with_sha1_with_empty_salt_should_throw_AuthenticationException() {
145     String password = randomAlphanumeric(60);
146
147     UserDto user = newUserDto()
148       .setHashMethod(SHA1.name())
149       .setCryptedPassword(DigestUtils.sha1Hex("--0242b0b4c0a93ddfe09dd886de50bc25ba000b51--" + password + "--"))
150       .setSalt(null);
151
152     expectedException.expect(AuthenticationException.class);
153     expectedException.expectMessage("null salt");
154
155     underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
156   }
157
158   @Test
159   public void authentication_with_bcrypt_with_incorrect_password_should_throw_AuthenticationException() {
160     String password = randomAlphanumeric(60);
161
162     UserDto user = newUserDto()
163       .setHashMethod(BCRYPT.name())
164       .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12)));
165
166     expectedException.expect(AuthenticationException.class);
167     expectedException.expectMessage("wrong password");
168
169     underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
170   }
171
172   @Test
173   public void authentication_upgrade_hash_function_when_SHA1_was_used() {
174     String password = randomAlphanumeric(60);
175
176     byte[] saltRandom = new byte[20];
177     RANDOM.nextBytes(saltRandom);
178     String salt = DigestUtils.sha1Hex(saltRandom);
179
180     UserDto user = newUserDto()
181       .setLogin("myself")
182       .setHashMethod(SHA1.name())
183       .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--"))
184       .setSalt(salt);
185     db.users().insertUser(user);
186
187     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
188
189     Optional<UserDto> myself = db.users().selectUserByLogin("myself");
190     assertThat(myself).isPresent();
191     assertThat(myself.get().getHashMethod()).isEqualTo(PBKDF2.name());
192     assertThat(myself.get().getSalt()).isNotNull();
193
194     // authentication must work with upgraded hash method
195     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
196   }
197
198   @Test
199   public void authentication_upgrade_hash_function_when_BCRYPT_was_used() {
200     String password = randomAlphanumeric(60);
201
202     byte[] saltRandom = new byte[20];
203     RANDOM.nextBytes(saltRandom);
204     String salt = DigestUtils.sha1Hex(saltRandom);
205
206     UserDto user = newUserDto()
207       .setLogin("myself")
208       .setHashMethod(BCRYPT.name())
209       .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12)))
210       .setSalt(salt);
211     db.users().insertUser(user);
212
213     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
214
215     Optional<UserDto> myself = db.users().selectUserByLogin("myself");
216     assertThat(myself).isPresent();
217     assertThat(myself.get().getHashMethod()).isEqualTo(PBKDF2.name());
218     assertThat(myself.get().getSalt()).isNotNull();
219
220     // authentication must work with upgraded hash method
221     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
222   }
223
224   @Test
225   public void authentication_updates_db_if_PBKDF2_iterations_changes() {
226     String password = randomAlphanumeric(60);
227
228     UserDto user = newUserDto().setLogin("myself");
229     db.users().insertUser(user);
230     underTest.storeHashPassword(user, password);
231
232     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
233     assertThat(user.getCryptedPassword()).startsWith("1$");
234
235     settings.setProperty("sonar.internal.pbkdf2.iterations", 3);
236     CredentialsLocalAuthentication underTest = new CredentialsLocalAuthentication(db.getDbClient(), settings.asConfig());
237
238     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
239     assertThat(user.getCryptedPassword()).startsWith("3$");
240     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
241   }
242
243   @Test
244   public void authentication_with_pbkdf2_with_correct_password_should_work() {
245     String password = randomAlphanumeric(60);
246     UserDto user = newUserDto()
247       .setHashMethod(PBKDF2.name());
248
249     underTest.storeHashPassword(user, password);
250     assertThat(user.getCryptedPassword()).hasSize(88 + 2);
251     assertThat(user.getCryptedPassword()).startsWith("1$");
252     assertThat(user.getSalt()).hasSize(28);
253
254     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
255   }
256
257   @Test
258   public void authentication_with_pbkdf2_with_default_number_of_iterations() {
259     settings.clear();
260     CredentialsLocalAuthentication underTest = new CredentialsLocalAuthentication(db.getDbClient(), settings.asConfig());
261
262     String password = randomAlphanumeric(60);
263     UserDto user = newUserDto()
264       .setHashMethod(PBKDF2.name());
265
266     underTest.storeHashPassword(user, password);
267     assertThat(user.getCryptedPassword()).hasSize(88 + 7);
268     assertThat(user.getCryptedPassword()).startsWith("100000$");
269     assertThat(user.getSalt()).hasSize(28);
270
271     underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC);
272   }
273
274   @Test
275   public void authentication_with_pbkdf2_with_incorrect_password_should_throw_AuthenticationException() {
276     UserDto user = newUserDto()
277       .setHashMethod(PBKDF2.name())
278       .setCryptedPassword("1$hash")
279       .setSalt("salt");
280
281     expectedException.expect(AuthenticationException.class);
282     expectedException.expectMessage("wrong password");
283
284     underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
285   }
286
287   @Test
288   public void authentication_with_pbkdf2_with_invalid_password_should_throw_AuthenticationException() {
289     String password = randomAlphanumeric(60);
290
291     byte[] saltRandom = new byte[20];
292     RANDOM.nextBytes(saltRandom);
293     String salt = DigestUtils.sha1Hex(saltRandom);
294
295     UserDto user = newUserDto()
296       .setHashMethod(PBKDF2.name())
297       .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--"))
298       .setSalt(salt);
299
300     expectedException.expect(AuthenticationException.class);
301     expectedException.expectMessage("invalid hash stored");
302
303     underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
304   }
305
306   @Test
307   public void authentication_with_pbkdf2_with_empty_password_should_throw_AuthenticationException() {
308     byte[] saltRandom = new byte[20];
309     RANDOM.nextBytes(saltRandom);
310     String salt = DigestUtils.sha1Hex(saltRandom);
311
312     UserDto user = newUserDto()
313       .setCryptedPassword(null)
314       .setHashMethod(PBKDF2.name())
315       .setSalt(salt);
316
317     expectedException.expect(AuthenticationException.class);
318     expectedException.expectMessage("null password in DB");
319
320     underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
321   }
322
323   @Test
324   public void authentication_with_pbkdf2_with_empty_salt_should_throw_AuthenticationException() {
325     String password = randomAlphanumeric(60);
326
327     UserDto user = newUserDto()
328       .setHashMethod(PBKDF2.name())
329       .setCryptedPassword(DigestUtils.sha1Hex("--0242b0b4c0a93ddfe09dd886de50bc25ba000b51--" + password + "--"))
330       .setSalt(null);
331
332     expectedException.expect(AuthenticationException.class);
333     expectedException.expectMessage("null salt");
334
335     underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC);
336   }
337 }