3 * Copyright (C) 2009-2021 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.
20 package org.sonar.server.authentication;
22 import java.util.Optional;
24 import java.util.stream.Collectors;
25 import javax.annotation.Nullable;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.junit.rules.ExpectedException;
30 import org.sonar.api.config.internal.MapSettings;
31 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
32 import org.sonar.api.server.authentication.UserIdentity;
33 import org.sonar.api.utils.log.LogTester;
34 import org.sonar.db.DbTester;
35 import org.sonar.db.user.GroupDto;
36 import org.sonar.db.user.UserDto;
37 import org.sonar.server.authentication.event.AuthenticationEvent;
38 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
39 import org.sonar.server.authentication.event.AuthenticationException;
40 import org.sonar.server.es.EsTester;
41 import org.sonar.server.user.NewUserNotifier;
42 import org.sonar.server.user.UserUpdater;
43 import org.sonar.server.user.index.UserIndexer;
44 import org.sonar.server.usergroups.DefaultGroupFinder;
46 import static java.util.Arrays.stream;
47 import static org.assertj.core.api.Assertions.assertThat;
48 import static org.assertj.core.api.Assertions.assertThatThrownBy;
49 import static org.mockito.Mockito.mock;
50 import static org.sonar.db.user.UserTesting.newUserDto;
51 import static org.sonar.process.ProcessProperties.Property.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS;
52 import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
53 import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException;
55 public class UserRegistrarImplTest {
56 private static final String USER_LOGIN = "johndoo";
58 private static final UserIdentity USER_IDENTITY = UserIdentity.builder()
59 .setProviderId("ABCD")
60 .setProviderLogin(USER_LOGIN)
62 .setEmail("john@email.com")
65 private static final TestIdentityProvider IDENTITY_PROVIDER = new TestIdentityProvider()
67 .setName("name of github")
69 .setAllowsUsersToSignUp(true);
71 private final MapSettings settings = new MapSettings().setProperty("sonar.internal.pbkdf2.iterations", "1");
74 public ExpectedException expectedException = ExpectedException.none();
76 public DbTester db = DbTester.create(new AlwaysIncreasingSystem2());
78 public EsTester es = EsTester.create();
80 public LogTester logTester = new LogTester();
82 private final UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
83 private final CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient(), settings.asConfig());
84 private final DefaultGroupFinder groupFinder = new DefaultGroupFinder(db.getDbClient());
85 private final UserUpdater userUpdater = new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, groupFinder, settings.asConfig(), localAuthentication);
87 private final UserRegistrarImpl underTest = new UserRegistrarImpl(db.getDbClient(), userUpdater, groupFinder);
88 private GroupDto defaultGroup;
92 defaultGroup = insertDefaultGroup();
96 public void authenticate_new_user() {
97 UserDto createdUser = underTest.register(newUserRegistration());
99 UserDto user = db.users().selectUserByLogin(createdUser.getLogin()).get();
100 assertThat(user).isNotNull();
101 assertThat(user.isActive()).isTrue();
102 assertThat(user.getName()).isEqualTo("John");
103 assertThat(user.getEmail()).isEqualTo("john@email.com");
104 assertThat(user.getExternalLogin()).isEqualTo(USER_LOGIN);
105 assertThat(user.getExternalIdentityProvider()).isEqualTo("github");
106 assertThat(user.getExternalId()).isEqualTo("ABCD");
107 assertThat(user.isRoot()).isFalse();
108 checkGroupMembership(user, defaultGroup);
112 public void authenticate_new_user_with_sq_identity() {
113 TestIdentityProvider sqIdentityProvider = new TestIdentityProvider()
115 .setName("sonarqube identity name")
117 .setAllowsUsersToSignUp(true);
119 UserDto createdUser = underTest.register(UserRegistration.builder()
120 .setUserIdentity(USER_IDENTITY)
121 .setProvider(sqIdentityProvider)
122 .setSource(Source.realm(BASIC, sqIdentityProvider.getName()))
125 UserDto user = db.users().selectUserByLogin(createdUser.getLogin()).get();
126 assertThat(user).isNotNull();
127 assertThat(user.isActive()).isTrue();
128 assertThat(user.getLogin()).isEqualTo(USER_LOGIN);
129 assertThat(user.getName()).isEqualTo("John");
130 assertThat(user.getEmail()).isEqualTo("john@email.com");
131 assertThat(user.getExternalLogin()).isEqualTo(USER_LOGIN);
132 assertThat(user.getExternalIdentityProvider()).isEqualTo("sonarqube");
133 assertThat(user.getExternalId()).isEqualTo("ABCD");
134 assertThat(user.isLocal()).isFalse();
135 assertThat(user.isRoot()).isFalse();
136 checkGroupMembership(user, defaultGroup);
140 public void authenticate_new_user_generates_login() {
141 underTest.register(newUserRegistration(UserIdentity.builder()
142 .setProviderId("ABCD")
143 .setProviderLogin(USER_LOGIN)
145 .setEmail("john@email.com")
148 UserDto user = db.getDbClient().userDao().selectByEmail(db.getSession(), "john@email.com").get(0);
149 assertThat(user).isNotNull();
150 assertThat(user.isActive()).isTrue();
151 assertThat(user.getLogin()).isNotEqualTo("John Doe").startsWith("john-doe");
152 assertThat(user.getEmail()).isEqualTo("john@email.com");
153 assertThat(user.getExternalLogin()).isEqualTo(USER_LOGIN);
154 assertThat(user.getExternalIdentityProvider()).isEqualTo("github");
155 assertThat(user.getExternalId()).isEqualTo("ABCD");
159 public void authenticate_new_user_assigns_user_to_groups() {
160 GroupDto group1 = db.users().insertGroup("group1");
161 GroupDto group2 = db.users().insertGroup("group2");
163 UserDto loggedInUser = authenticate(USER_LOGIN, USER_IDENTITY.getEmail(), "group1", "group2", "group3");
165 Optional<UserDto> user = db.users().selectUserByLogin(loggedInUser.getLogin());
166 checkGroupMembership(user.get(), group1, group2, defaultGroup);
170 public void authenticate_new_user_sets_onboarded_flag_to_false_when_onboarding_setting_is_set_to_true() {
171 settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey(), true);
173 UserDto user = underTest.register(newUserRegistration());
175 assertThat(db.users().selectUserByLogin(user.getLogin()).get().isOnboarded()).isFalse();
179 public void authenticate_new_user_sets_onboarded_flag_to_true_when_onboarding_setting_is_set_to_false() {
180 settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey(), false);
182 UserDto user = underTest.register(newUserRegistration());
184 assertThat(db.users().selectUserByLogin(user.getLogin()).get().isOnboarded()).isTrue();
188 public void authenticate_new_user_sets_external_id_to_provider_login_when_id_is_null() {
189 UserIdentity newUser = UserIdentity.builder()
191 .setProviderLogin("johndoo")
195 UserDto user = underTest.register(newUserRegistration(newUser));
197 assertThat(db.users().selectUserByLogin(user.getLogin()).get())
198 .extracting(UserDto::getLogin, UserDto::getExternalId, UserDto::getExternalLogin)
199 .contains(user.getLogin(), "johndoo", "johndoo");
203 public void authenticate_new_user_with_gitlab_provider() {
204 UserRegistration registration = UserRegistration.builder()
205 .setUserIdentity(USER_IDENTITY)
206 .setProvider(new TestIdentityProvider()
208 .setName("name of gitlab")
210 .setAllowsUsersToSignUp(true))
211 .setSource(Source.local(BASIC))
214 UserDto newUser = underTest.register(registration);
216 .extracting(UserDto::getExternalIdentityProvider, UserDto::getExternalLogin)
217 .containsExactly("gitlab", USER_IDENTITY.getProviderLogin());
221 public void authenticate_new_user_throws_AuthenticationException_when_when_email_already_exists() {
222 db.users().insertUser(u -> u.setEmail("john@email.com"));
223 Source source = Source.local(BASIC);
225 expectedException.expect(authenticationException().from(source)
226 .withLogin(USER_IDENTITY.getProviderLogin())
227 .andPublicMessage("This account is already associated with another authentication method."
228 + " Sign in using the current authentication method,"
229 + " or contact your administrator to transfer your account to a different authentication method."));
230 expectedException.expectMessage("Email 'john@email.com' is already used");
232 underTest.register(newUserRegistration());
236 public void authenticate_new_user_throws_AuthenticationException_when_email_already_exists_multiple_times() {
237 db.users().insertUser(u -> u.setEmail("john@email.com"));
238 db.users().insertUser(u -> u.setEmail("john@email.com"));
239 Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName());
241 expectedException.expect(authenticationException().from(source)
242 .withLogin(USER_IDENTITY.getProviderLogin())
243 .andPublicMessage("This account is already associated with another authentication method."
244 + " Sign in using the current authentication method,"
245 + " or contact your administrator to transfer your account to a different authentication method."));
246 expectedException.expectMessage("Email 'john@email.com' is already used");
248 underTest.register(newUserRegistration(source));
252 public void authenticate_new_user_fails_when_allow_users_to_signup_is_false() {
253 TestIdentityProvider identityProvider = new TestIdentityProvider()
257 .setAllowsUsersToSignUp(false);
258 Source source = Source.realm(AuthenticationEvent.Method.FORM, identityProvider.getName());
260 expectedException.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getProviderLogin()).andPublicMessage("'github' users are not allowed to sign up"));
261 expectedException.expectMessage("User signup disabled for provider 'github'");
263 underTest.register(UserRegistration.builder()
264 .setUserIdentity(USER_IDENTITY)
265 .setProvider(identityProvider)
271 public void authenticate_existing_user_doesnt_change_group_membership() {
272 UserDto user = db.users().insertUser(u -> u.setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
273 GroupDto group1 = db.users().insertGroup("group1");
274 db.users().insertMember(group1, user);
275 db.users().insertMember(defaultGroup, user);
277 authenticate(user.getExternalLogin(), user.getEmail(), "group1");
279 checkGroupMembership(user, group1, defaultGroup);
283 public void authenticate_and_update_existing_user_matching_external_id() {
284 UserDto user = db.users().insertUser(u -> u
285 .setLogin("Old login")
287 .setEmail("Old email")
288 .setExternalId(USER_IDENTITY.getProviderId())
289 .setExternalLogin("old identity")
290 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
292 underTest.register(newUserRegistration());
294 assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
295 .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider,
297 .contains(USER_LOGIN, "John", "john@email.com", "ABCD", "johndoo", "github", true);
301 public void authenticate_and_update_existing_user_matching_external_login_and_email() {
302 UserDto user = db.users().insertUser(u -> u
303 .setLogin("Old login")
305 .setEmail(USER_IDENTITY.getEmail())
306 .setExternalId("Old id")
307 .setExternalLogin(USER_IDENTITY.getProviderLogin())
308 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
310 underTest.register(newUserRegistration());
312 assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
313 .extracting(UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider,
315 .contains("John", "john@email.com", "ABCD", "johndoo", "github", true);
319 public void authenticate_existing_user_should_not_update_login() {
320 UserDto user = db.users().insertUser(u -> u
321 .setLogin("old login")
322 .setName(USER_IDENTITY.getName())
323 .setEmail(USER_IDENTITY.getEmail())
324 .setExternalId(USER_IDENTITY.getProviderId())
325 .setExternalLogin("old identity")
326 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
328 underTest.register(newUserRegistration());
330 // no new user should be created
331 assertThat(db.countRowsOfTable(db.getSession(), "users")).isEqualTo(1);
332 assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
333 .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider,
335 .containsExactly("old login", USER_IDENTITY.getName(), USER_IDENTITY.getEmail(), USER_IDENTITY.getProviderId(), USER_IDENTITY.getProviderLogin(),
336 IDENTITY_PROVIDER.getKey(), true);
340 public void authenticate_existing_user_matching_external_login_and_email_when_external_id_is_null() {
341 UserDto user = db.users().insertUser(u -> u
343 .setExternalId("Old id")
344 .setEmail("john@email.com")
345 .setExternalLogin("johndoo")
346 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
348 underTest.register(newUserRegistration(UserIdentity.builder()
350 .setProviderLogin("johndoo")
352 .setEmail("john@email.com")
355 assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
356 .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider,
358 .contains(user.getLogin(), "John", "john@email.com", "johndoo", "johndoo", "github", true);
362 public void do_not_authenticate_gitlab_user_matching_external_login() {
363 db.users().insertUser(u -> u
364 .setLogin("Old login")
366 .setEmail(USER_IDENTITY.getEmail())
367 .setExternalId("Old id")
368 .setExternalLogin(USER_IDENTITY.getProviderLogin())
369 .setExternalIdentityProvider("gitlab"));
371 UserRegistration registration = UserRegistration.builder()
372 .setUserIdentity(USER_IDENTITY)
373 .setProvider(new TestIdentityProvider()
375 .setName("name of gitlab")
377 .setSource(Source.local(BASIC))
380 assertThatThrownBy(() -> underTest.register(registration))
381 .isInstanceOf(AuthenticationException.class)
382 .hasMessage(String.format("Login '%s' is already used", USER_IDENTITY.getProviderLogin()));
386 public void do_not_authenticate_and_update_existing_user_matching_external_login_if_emails_do_not_match() {
387 db.users().insertUser(u -> u
388 .setLogin("Old login")
390 .setEmail("another-email@sonarsource.com")
391 .setExternalId("Old id")
392 .setExternalLogin(USER_IDENTITY.getProviderLogin())
393 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
395 assertThatThrownBy(() -> underTest.register(newUserRegistration()))
396 .isInstanceOf(AuthenticationException.class)
397 .hasMessage(String.format("Login '%s' is already used", USER_IDENTITY.getProviderLogin()));
399 assertThat(logTester.logs()).contains(String.format("User with login '%s' tried to login with email '%s' which doesn't match the email on record '%s'",
400 USER_IDENTITY.getProviderLogin(), USER_IDENTITY.getEmail(), "another-email@sonarsource.com"));
404 public void authenticate_and_update_existing_user_matching_external_login_if_email_is_missing() {
405 db.users().insertUser(u -> u
406 .setLogin("Old login")
408 .setExternalId("Old id")
410 .setExternalLogin(USER_IDENTITY.getProviderLogin())
411 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
413 underTest.register(newUserRegistration());
415 Optional<UserDto> user = db.users().selectUserByLogin("Old login");
416 assertThat(user).isPresent();
417 assertThat(user.get().getEmail()).isEqualTo(USER_IDENTITY.getEmail());
421 public void do_not_authenticate_and_update_existing_user_matching_external_id_if_external_provider_does_not_match() {
422 db.users().insertUser(u -> u
423 .setLogin("Old login")
425 .setExternalId(USER_IDENTITY.getProviderId())
427 .setExternalLogin(USER_IDENTITY.getProviderLogin())
428 .setExternalIdentityProvider("Old provider"));
430 underTest.register(newUserRegistration());
431 assertThat(db.countRowsOfTable("users")).isEqualTo(2);
435 public void authenticate_existing_user_should_update_login() {
436 UserDto user = db.users().insertUser(u -> u
437 .setLogin("Old login")
438 .setExternalId(USER_IDENTITY.getProviderId())
439 .setExternalLogin("old identity")
440 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
442 underTest.register(newUserRegistration());
444 assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
445 .extracting(UserDto::getLogin, UserDto::getExternalLogin)
446 .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin());
450 public void authenticate_existing_disabled_user_should_reactivate_it() {
451 db.users().insertUser(u -> u
452 .setLogin(USER_LOGIN)
455 .setEmail(USER_IDENTITY.getEmail())
456 .setExternalId("Old id")
457 .setExternalLogin(USER_IDENTITY.getProviderLogin())
458 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
460 underTest.register(newUserRegistration());
462 UserDto userDto = db.users().selectUserByLogin(USER_LOGIN).get();
463 assertThat(userDto.isActive()).isTrue();
464 assertThat(userDto.getName()).isEqualTo(USER_IDENTITY.getName());
465 assertThat(userDto.getEmail()).isEqualTo(USER_IDENTITY.getEmail());
466 assertThat(userDto.getExternalId()).isEqualTo(USER_IDENTITY.getProviderId());
467 assertThat(userDto.getExternalLogin()).isEqualTo(USER_IDENTITY.getProviderLogin());
468 assertThat(userDto.getExternalIdentityProvider()).isEqualTo(IDENTITY_PROVIDER.getKey());
469 assertThat(userDto.isRoot()).isFalse();
473 public void authenticating_existing_user_throws_AuthenticationException_when_email_already_exists() {
474 UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
475 UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
476 UserIdentity userIdentity = UserIdentity.builder()
477 .setProviderLogin("johndoo")
479 .setEmail("john@email.com")
482 Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName());
483 expectedException.expect(authenticationException().from(source)
484 .withLogin(userIdentity.getProviderLogin())
485 .andPublicMessage("This account is already associated with another authentication method."
486 + " Sign in using the current authentication method,"
487 + " or contact your administrator to transfer your account to a different authentication method."));
488 expectedException.expectMessage("Email 'john@email.com' is already used");
490 underTest.register(newUserRegistration(userIdentity, source));
494 public void authenticate_existing_user_succeeds_when_email_has_not_changed() {
495 UserDto currentUser = db.users().insertUser(u -> u.setEmail("john@email.com")
496 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
497 UserIdentity userIdentity = UserIdentity.builder()
498 .setProviderId(currentUser.getExternalId())
499 .setProviderLogin(currentUser.getExternalLogin())
501 .setEmail("john@email.com")
504 underTest.register(newUserRegistration(userIdentity));
506 UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get();
507 assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com");
511 public void authenticate_existing_user_and_add_new_groups() {
512 UserDto user = db.users().insertUser(newUserDto()
513 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())
514 .setExternalLogin(USER_IDENTITY.getProviderLogin())
515 .setEmail(USER_IDENTITY.getEmail())
518 GroupDto group1 = db.users().insertGroup("group1");
519 GroupDto group2 = db.users().insertGroup("group2");
521 authenticate(USER_IDENTITY.getProviderLogin(), USER_IDENTITY.getEmail(), "group1", "group2", "group3");
523 checkGroupMembership(user, group1, group2);
527 public void authenticate_existing_user_and_remove_groups() {
528 UserDto user = db.users().insertUser(newUserDto()
529 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())
530 .setExternalLogin(USER_IDENTITY.getProviderLogin())
531 .setEmail(USER_IDENTITY.getEmail())
534 GroupDto group1 = db.users().insertGroup("group1");
535 GroupDto group2 = db.users().insertGroup("group2");
536 db.users().insertMember(group1, user);
537 db.users().insertMember(group2, user);
539 authenticate(USER_IDENTITY.getProviderLogin(), USER_IDENTITY.getEmail(), "group1");
541 checkGroupMembership(user, group1);
545 public void authenticate_existing_user_and_remove_all_groups_expect_default() {
546 UserDto user = db.users().insertUser(newUserDto()
547 .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())
548 .setExternalLogin(USER_IDENTITY.getProviderLogin()));
549 GroupDto group1 = db.users().insertGroup("group1");
550 GroupDto group2 = db.users().insertGroup("group2");
551 db.users().insertMember(group1, user);
552 db.users().insertMember(group2, user);
553 db.users().insertMember(defaultGroup, user);
555 authenticate(user.getExternalLogin(), user.getEmail());
557 checkGroupMembership(user, defaultGroup);
560 private static UserRegistration newUserRegistration(UserIdentity userIdentity) {
561 return newUserRegistration(userIdentity, Source.local(BASIC));
564 private static UserRegistration newUserRegistration(UserIdentity userIdentity, Source source) {
565 return UserRegistration.builder()
566 .setUserIdentity(userIdentity)
567 .setProvider(IDENTITY_PROVIDER)
572 private static UserRegistration newUserRegistration(Source source) {
573 return newUserRegistration(USER_IDENTITY, source);
576 private static UserRegistration newUserRegistration() {
577 return newUserRegistration(USER_IDENTITY, Source.local(BASIC));
580 private UserDto authenticate(String providerLogin, @Nullable String email, String... groups) {
581 return underTest.register(newUserRegistration(UserIdentity.builder()
582 .setProviderLogin(providerLogin)
585 .setGroups(Set.of(groups))
589 private void checkGroupMembership(UserDto user, GroupDto... expectedGroups) {
590 assertThat(db.users().selectGroupUuidsOfUser(user)).containsOnly(stream(expectedGroups).map(GroupDto::getUuid).collect(Collectors.toList()).toArray(new String[] {}));
593 private GroupDto insertDefaultGroup() {
594 return db.users().insertDefaultGroup();