From: Simon Brandhof Date: Fri, 30 Nov 2018 12:58:26 +0000 (+0100) Subject: SONARCLOUD-213 rename UserIdentityAuthenticator X-Git-Tag: 7.5~23 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=db838dea4daed77db392bdc284ed45b4b8078d8b;p=sonarqube.git SONARCLOUD-213 rename UserIdentityAuthenticator to UserRegistrar to help understanding its responsibility. --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java index ccdf9bbfea8..770f18d47a8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java @@ -37,7 +37,7 @@ public class AuthenticationModule extends Module { IdentityProviderRepository.class, BaseContextFactory.class, OAuth2ContextFactory.class, - UserIdentityAuthenticatorImpl.class, + UserRegistrarImpl.class, OAuthCsrfVerifier.class, UserSessionInitializer.class, JwtSerializer.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java index 686dcd83138..6aa0ac3c6f8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java @@ -25,8 +25,8 @@ import org.sonar.api.platform.Server; import org.sonar.api.server.authentication.BaseIdentityProvider; import org.sonar.api.server.authentication.UserIdentity; import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy; +import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy; +import org.sonar.server.authentication.UserRegistration.UpdateLoginStrategy; import org.sonar.server.authentication.event.AuthenticationEvent.Source; import org.sonar.server.user.ThreadLocalUserSession; import org.sonar.server.user.UserSessionFactory; @@ -34,15 +34,15 @@ import org.sonar.server.user.UserSessionFactory; public class BaseContextFactory { private final ThreadLocalUserSession threadLocalUserSession; - private final UserIdentityAuthenticator userIdentityAuthenticator; + private final UserRegistrar userRegistrar; private final Server server; private final JwtHttpHandler jwtHttpHandler; private final UserSessionFactory userSessionFactory; - public BaseContextFactory(UserIdentityAuthenticator userIdentityAuthenticator, Server server, JwtHttpHandler jwtHttpHandler, - ThreadLocalUserSession threadLocalUserSession, UserSessionFactory userSessionFactory) { + public BaseContextFactory(UserRegistrar userRegistrar, Server server, JwtHttpHandler jwtHttpHandler, + ThreadLocalUserSession threadLocalUserSession, UserSessionFactory userSessionFactory) { this.userSessionFactory = userSessionFactory; - this.userIdentityAuthenticator = userIdentityAuthenticator; + this.userRegistrar = userRegistrar; this.server = server; this.jwtHttpHandler = jwtHttpHandler; this.threadLocalUserSession = threadLocalUserSession; @@ -80,8 +80,8 @@ public class BaseContextFactory { @Override public void authenticate(UserIdentity userIdentity) { - UserDto userDto = userIdentityAuthenticator.authenticate( - UserIdentityAuthenticatorParameters.builder() + UserDto userDto = userRegistrar.register( + UserRegistration.builder() .setUserIdentity(userIdentity) .setProvider(identityProvider) .setSource(Source.external(identityProvider)) diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsExternalAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsExternalAuthentication.java index 0f5edacfe63..d6cbd92ca35 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsExternalAuthentication.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsExternalAuthentication.java @@ -37,8 +37,8 @@ import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy; +import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy; +import org.sonar.server.authentication.UserRegistration.UpdateLoginStrategy; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationEvent.Source; import org.sonar.server.authentication.event.AuthenticationException; @@ -58,7 +58,7 @@ public class CredentialsExternalAuthentication implements Startable { private final Configuration config; private final SecurityRealmFactory securityRealmFactory; - private final UserIdentityAuthenticator userIdentityAuthenticator; + private final UserRegistrar userRegistrar; private final AuthenticationEvent authenticationEvent; private SecurityRealm realm; @@ -67,10 +67,10 @@ public class CredentialsExternalAuthentication implements Startable { private ExternalGroupsProvider externalGroupsProvider; public CredentialsExternalAuthentication(Configuration config, SecurityRealmFactory securityRealmFactory, - UserIdentityAuthenticator userIdentityAuthenticator, AuthenticationEvent authenticationEvent) { + UserRegistrar userRegistrar, AuthenticationEvent authenticationEvent) { this.config = config; this.securityRealmFactory = securityRealmFactory; - this.userIdentityAuthenticator = userIdentityAuthenticator; + this.userRegistrar = userRegistrar; this.authenticationEvent = authenticationEvent; } @@ -143,8 +143,8 @@ public class CredentialsExternalAuthentication implements Startable { Collection groups = externalGroupsProvider.doGetGroups(context); userIdentityBuilder.setGroups(new HashSet<>(groups)); } - return userIdentityAuthenticator.authenticate( - UserIdentityAuthenticatorParameters.builder() + return userRegistrar.register( + UserRegistration.builder() .setUserIdentity(userIdentityBuilder.build()) .setProvider(new ExternalIdentityProvider()) .setSource(realmEventSource(method)) diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java index 11ddd8377fb..6b42ee427ab 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java @@ -42,8 +42,8 @@ import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.db.user.UserDto; import org.sonar.process.ProcessProperties; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy; +import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy; +import org.sonar.server.authentication.UserRegistration.UpdateLoginStrategy; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationEvent.Source; import org.sonar.server.authentication.event.AuthenticationException; @@ -70,7 +70,7 @@ public class HttpHeadersAuthentication implements Startable { private static final String LAST_REFRESH_TIME_TOKEN_PARAM = "ssoLastRefreshTime"; - private static final EnumSet SETTINGS = EnumSet.of( + private static final EnumSet PROPERTIES = EnumSet.of( SONAR_WEB_SSO_LOGIN_HEADER, SONAR_WEB_SSO_NAME_HEADER, SONAR_WEB_SSO_EMAIL_HEADER, @@ -79,18 +79,18 @@ public class HttpHeadersAuthentication implements Startable { private final System2 system2; private final Configuration config; - private final UserIdentityAuthenticator userIdentityAuthenticator; + private final UserRegistrar userRegistrar; private final JwtHttpHandler jwtHttpHandler; private final AuthenticationEvent authenticationEvent; + private final Map settingsByKey = new HashMap<>(); private boolean enabled = false; - private Map settingsByKey = new HashMap<>(); - public HttpHeadersAuthentication(System2 system2, Configuration config, UserIdentityAuthenticator userIdentityAuthenticator, + public HttpHeadersAuthentication(System2 system2, Configuration config, UserRegistrar userRegistrar, JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) { this.system2 = system2; this.config = config; - this.userIdentityAuthenticator = userIdentityAuthenticator; + this.userRegistrar = userRegistrar; this.jwtHttpHandler = jwtHttpHandler; this.authenticationEvent = authenticationEvent; } @@ -100,7 +100,7 @@ public class HttpHeadersAuthentication implements Startable { if (config.getBoolean(SONAR_WEB_SSO_ENABLE.getKey()).orElse(false)) { LOG.info("HTTP headers authentication enabled"); enabled = true; - SETTINGS.forEach(entry -> settingsByKey.put(entry.getKey(), config.get(entry.getKey()).orElse(entry.getDefaultValue()))); + PROPERTIES.forEach(entry -> settingsByKey.put(entry.getKey(), config.get(entry.getKey()).orElse(entry.getDefaultValue()))); } } @@ -166,8 +166,8 @@ public class HttpHeadersAuthentication implements Startable { String groupsValue = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey()); userIdentityBuilder.setGroups(groupsValue == null ? Collections.emptySet() : new HashSet<>(COMA_SPLITTER.splitToList(groupsValue))); } - return userIdentityAuthenticator.authenticate( - UserIdentityAuthenticatorParameters.builder() + return userRegistrar.register( + UserRegistration.builder() .setUserIdentity(userIdentityBuilder.build()) .setProvider(new SsoIdentityProvider()) .setSource(Source.sso()) diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java index 26549396c6d..1fd9118349f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java @@ -28,8 +28,8 @@ import org.sonar.api.server.ServerSide; import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.UserIdentity; import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy; +import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy; +import org.sonar.server.authentication.UserRegistration.UpdateLoginStrategy; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.user.ThreadLocalUserSession; import org.sonar.server.user.UserSessionFactory; @@ -41,17 +41,17 @@ import static org.sonar.server.authentication.OAuth2CallbackFilter.CALLBACK_PATH public class OAuth2ContextFactory { private final ThreadLocalUserSession threadLocalUserSession; - private final UserIdentityAuthenticator userIdentityAuthenticator; + private final UserRegistrar userRegistrar; private final Server server; private final OAuthCsrfVerifier csrfVerifier; private final JwtHttpHandler jwtHttpHandler; private final UserSessionFactory userSessionFactory; private final OAuth2AuthenticationParameters oAuthParameters; - public OAuth2ContextFactory(ThreadLocalUserSession threadLocalUserSession, UserIdentityAuthenticator userIdentityAuthenticator, Server server, - OAuthCsrfVerifier csrfVerifier, JwtHttpHandler jwtHttpHandler, UserSessionFactory userSessionFactory, OAuth2AuthenticationParameters oAuthParameters) { + public OAuth2ContextFactory(ThreadLocalUserSession threadLocalUserSession, UserRegistrar userRegistrar, Server server, + OAuthCsrfVerifier csrfVerifier, JwtHttpHandler jwtHttpHandler, UserSessionFactory userSessionFactory, OAuth2AuthenticationParameters oAuthParameters) { this.threadLocalUserSession = threadLocalUserSession; - this.userIdentityAuthenticator = userIdentityAuthenticator; + this.userRegistrar = userRegistrar; this.server = server; this.csrfVerifier = csrfVerifier; this.jwtHttpHandler = jwtHttpHandler; @@ -133,8 +133,8 @@ public class OAuth2ContextFactory { public void authenticate(UserIdentity userIdentity) { Boolean allowEmailShift = oAuthParameters.getAllowEmailShift(request).orElse(false); Boolean allowUpdateLogin = oAuthParameters.getAllowUpdateLogin(request).orElse(false); - UserDto userDto = userIdentityAuthenticator.authenticate( - UserIdentityAuthenticatorParameters.builder() + UserDto userDto = userRegistrar.register( + UserRegistration.builder() .setUserIdentity(userIdentity) .setProvider(identityProvider) .setSource(AuthenticationEvent.Source.oauth2(identityProvider)) diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java deleted file mode 100644 index d56ad688867..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 org.sonar.db.user.UserDto; - -public interface UserIdentityAuthenticator { - - UserDto authenticate(UserIdentityAuthenticatorParameters authenticatorParameters); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticatorImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticatorImpl.java deleted file mode 100644 index be21b9b02b0..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticatorImpl.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * 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 com.google.common.collect.Sets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.api.server.authentication.IdentityProvider; -import org.sonar.api.server.authentication.UserIdentity; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserGroupDto; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy; -import org.sonar.server.authentication.event.AuthenticationException; -import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException; -import org.sonar.server.authentication.exception.UpdateLoginRedirectionException; -import org.sonar.server.organization.DefaultOrganization; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.OrganizationFlags; -import org.sonar.server.organization.OrganizationUpdater; -import org.sonar.server.user.ExternalIdentity; -import org.sonar.server.user.NewUser; -import org.sonar.server.user.UpdateUser; -import org.sonar.server.user.UserUpdater; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static com.google.common.base.Preconditions.checkState; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static java.util.Objects.requireNonNull; -import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; -import static org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy; - -public class UserIdentityAuthenticatorImpl implements UserIdentityAuthenticator { - - private static final Logger LOGGER = Loggers.get(UserIdentityAuthenticatorImpl.class); - - private final DbClient dbClient; - private final UserUpdater userUpdater; - private final DefaultOrganizationProvider defaultOrganizationProvider; - private final OrganizationFlags organizationFlags; - private final OrganizationUpdater organizationUpdater; - private final DefaultGroupFinder defaultGroupFinder; - - public UserIdentityAuthenticatorImpl(DbClient dbClient, UserUpdater userUpdater, DefaultOrganizationProvider defaultOrganizationProvider, OrganizationFlags organizationFlags, - OrganizationUpdater organizationUpdater, DefaultGroupFinder defaultGroupFinder) { - this.dbClient = dbClient; - this.userUpdater = userUpdater; - this.defaultOrganizationProvider = defaultOrganizationProvider; - this.organizationFlags = organizationFlags; - this.organizationUpdater = organizationUpdater; - this.defaultGroupFinder = defaultGroupFinder; - } - - @Override - public UserDto authenticate(UserIdentityAuthenticatorParameters authenticatorParameters) { - try (DbSession dbSession = dbClient.openSession(false)) { - UserDto userDto = getUser(dbSession, authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider()); - if (userDto == null) { - return registerNewUser(dbSession, null, authenticatorParameters); - } - if (!userDto.isActive()) { - return registerNewUser(dbSession, userDto, authenticatorParameters); - } - return registerExistingUser(dbSession, userDto, authenticatorParameters); - } - } - - @CheckForNull - private UserDto getUser(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider) { - UserDto user = dbClient.userDao().selectByExternalIdAndIdentityProvider(dbSession, getProviderIdOrProviderLogin(userIdentity), provider.getKey()); - if (user != null) { - return user; - } - // We need to search by login because : - // 1. user may have been provisioned, - // 2. user may have been disabled. - String login = userIdentity.getLogin(); - if (login == null) { - return null; - } - return dbClient.userDao().selectByLogin(dbSession, login); - } - - private UserDto registerNewUser(DbSession dbSession, @Nullable UserDto disabledUser, UserIdentityAuthenticatorParameters authenticatorParameters) { - Optional otherUserToIndex = detectEmailUpdate(dbSession, authenticatorParameters); - NewUser newUser = createNewUser(authenticatorParameters); - if (disabledUser == null) { - return userUpdater.createAndCommit(dbSession, newUser, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex)); - } - return userUpdater.reactivateAndCommit(dbSession, disabledUser, newUser, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex)); - } - - private UserDto registerExistingUser(DbSession dbSession, UserDto userDto, UserIdentityAuthenticatorParameters authenticatorParameters) { - UpdateUser update = new UpdateUser() - .setEmail(authenticatorParameters.getUserIdentity().getEmail()) - .setName(authenticatorParameters.getUserIdentity().getName()) - .setExternalIdentity(new ExternalIdentity( - authenticatorParameters.getProvider().getKey(), - authenticatorParameters.getUserIdentity().getProviderLogin(), - authenticatorParameters.getUserIdentity().getProviderId())); - String login = authenticatorParameters.getUserIdentity().getLogin(); - if (login != null) { - update.setLogin(login); - } - detectLoginUpdate(dbSession, userDto, update, authenticatorParameters); - Optional otherUserToIndex = detectEmailUpdate(dbSession, authenticatorParameters); - userUpdater.updateAndCommit(dbSession, userDto, update, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex)); - return userDto; - } - - private Optional detectEmailUpdate(DbSession dbSession, UserIdentityAuthenticatorParameters authenticatorParameters) { - String email = authenticatorParameters.getUserIdentity().getEmail(); - if (email == null) { - return Optional.empty(); - } - List existingUsers = dbClient.userDao().selectByEmail(dbSession, email); - if (existingUsers.isEmpty()) { - return Optional.empty(); - } - if (existingUsers.size() > 1) { - throw generateExistingEmailError(authenticatorParameters, email); - } - - UserDto existingUser = existingUsers.get(0); - if (existingUser == null - || Objects.equals(existingUser.getLogin(), authenticatorParameters.getUserIdentity().getLogin()) - || (Objects.equals(existingUser.getExternalId(), getProviderIdOrProviderLogin(authenticatorParameters.getUserIdentity())) - && Objects.equals(existingUser.getExternalIdentityProvider(), authenticatorParameters.getProvider().getKey()))) { - return Optional.empty(); - } - ExistingEmailStrategy existingEmailStrategy = authenticatorParameters.getExistingEmailStrategy(); - switch (existingEmailStrategy) { - case ALLOW: - existingUser.setEmail(null); - dbClient.userDao().update(dbSession, existingUser); - return Optional.of(existingUser); - case WARN: - throw new EmailAlreadyExistsRedirectionException(email, existingUser, authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider()); - case FORBID: - throw generateExistingEmailError(authenticatorParameters, email); - default: - throw new IllegalStateException(format("Unknown strategy %s", existingEmailStrategy)); - } - } - - private void detectLoginUpdate(DbSession dbSession, UserDto user, UpdateUser update, UserIdentityAuthenticatorParameters authenticatorParameters) { - String newLogin = update.login(); - if (!update.isLoginChanged() || user.getLogin().equals(newLogin)) { - return; - } - if (!organizationFlags.isEnabled(dbSession)) { - return; - } - String personalOrganizationUuid = user.getOrganizationUuid(); - if (personalOrganizationUuid == null) { - return; - } - Optional personalOrganization = dbClient.organizationDao().selectByUuid(dbSession, personalOrganizationUuid); - checkState(personalOrganization.isPresent(), - "Cannot find personal organization uuid '%s' for user '%s'", personalOrganizationUuid, user.getLogin()); - UpdateLoginStrategy updateLoginStrategy = authenticatorParameters.getUpdateLoginStrategy(); - switch (updateLoginStrategy) { - case ALLOW: - organizationUpdater.updateOrganizationKey(dbSession, personalOrganization.get(), requireNonNull(newLogin, "new login cannot be null")); - return; - case WARN: - throw new UpdateLoginRedirectionException(authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider(), user, personalOrganization.get()); - default: - throw new IllegalStateException(format("Unknown strategy %s", updateLoginStrategy)); - } - } - - private void syncGroups(DbSession dbSession, UserIdentity userIdentity, UserDto userDto) { - if (!userIdentity.shouldSyncGroups()) { - return; - } - String userLogin = userDto.getLogin(); - Set userGroups = new HashSet<>(dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(userLogin)).get(userLogin)); - Set identityGroups = userIdentity.getGroups(); - LOGGER.debug("List of groups returned by the identity provider '{}'", identityGroups); - - Collection groupsToAdd = Sets.difference(identityGroups, userGroups); - Collection groupsToRemove = Sets.difference(userGroups, identityGroups); - Collection allGroups = new ArrayList<>(groupsToAdd); - allGroups.addAll(groupsToRemove); - DefaultOrganization defaultOrganization = defaultOrganizationProvider.get(); - Map groupsByName = dbClient.groupDao().selectByNames(dbSession, defaultOrganization.getUuid(), allGroups) - .stream() - .collect(uniqueIndex(GroupDto::getName)); - - addGroups(dbSession, userDto, groupsToAdd, groupsByName); - removeGroups(dbSession, userDto, groupsToRemove, groupsByName); - } - - private void addGroups(DbSession dbSession, UserDto userDto, Collection groupsToAdd, Map groupsByName) { - groupsToAdd.stream().map(groupsByName::get).filter(Objects::nonNull).forEach( - groupDto -> { - LOGGER.debug("Adding group '{}' to user '{}'", groupDto.getName(), userDto.getLogin()); - dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId())); - }); - } - - private void removeGroups(DbSession dbSession, UserDto userDto, Collection groupsToRemove, Map groupsByName) { - Optional defaultGroup = getDefaultGroup(dbSession); - groupsToRemove.stream().map(groupsByName::get) - .filter(Objects::nonNull) - // user should be member of default group only when organizations are disabled, as the IdentityProvider API doesn't handle yet - // organizations - .filter(group -> !defaultGroup.isPresent() || !group.getId().equals(defaultGroup.get().getId())) - .forEach(groupDto -> { - LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin()); - dbClient.userGroupDao().delete(dbSession, groupDto.getId(), userDto.getId()); - }); - } - - private Optional getDefaultGroup(DbSession dbSession) { - return organizationFlags.isEnabled(dbSession) ? Optional.empty() : Optional.of(defaultGroupFinder.findDefaultGroup(dbSession, defaultOrganizationProvider.get().getUuid())); - } - - private static NewUser createNewUser(UserIdentityAuthenticatorParameters authenticatorParameters) { - String identityProviderKey = authenticatorParameters.getProvider().getKey(); - if (!authenticatorParameters.getProvider().allowsUsersToSignUp()) { - throw AuthenticationException.newBuilder() - .setSource(authenticatorParameters.getSource()) - .setLogin(authenticatorParameters.getUserIdentity().getProviderLogin()) - .setMessage(format("User signup disabled for provider '%s'", identityProviderKey)) - .setPublicMessage(format("'%s' users are not allowed to sign up", identityProviderKey)) - .build(); - } - return NewUser.builder() - .setLogin(authenticatorParameters.getUserIdentity().getLogin()) - .setEmail(authenticatorParameters.getUserIdentity().getEmail()) - .setName(authenticatorParameters.getUserIdentity().getName()) - .setExternalIdentity( - new ExternalIdentity( - identityProviderKey, - authenticatorParameters.getUserIdentity().getProviderLogin(), - authenticatorParameters.getUserIdentity().getProviderId())) - .build(); - } - - private static UserDto[] toArray(Optional userDto) { - return userDto.map(u -> new UserDto[] {u}).orElse(new UserDto[] {}); - } - - private static AuthenticationException generateExistingEmailError(UserIdentityAuthenticatorParameters authenticatorParameters, String email) { - return AuthenticationException.newBuilder() - .setSource(authenticatorParameters.getSource()) - .setLogin(authenticatorParameters.getUserIdentity().getProviderLogin()) - .setMessage(format("Email '%s' is already used", email)) - .setPublicMessage(format( - "You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", - email)) - .build(); - } - - private static String getProviderIdOrProviderLogin(UserIdentity userIdentity) { - String providerId = userIdentity.getProviderId(); - return providerId == null ? userIdentity.getProviderLogin() : providerId; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticatorParameters.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticatorParameters.java deleted file mode 100644 index 224946eb2db..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticatorParameters.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 org.sonar.api.server.authentication.IdentityProvider; -import org.sonar.api.server.authentication.UserIdentity; -import org.sonar.server.authentication.event.AuthenticationEvent; - -import static java.util.Objects.requireNonNull; - -class UserIdentityAuthenticatorParameters { - - /** - * Strategy to be executed when the email of the user is already used by another user - */ - enum ExistingEmailStrategy { - /** - * Authentication is allowed, the email is moved from other user to current user - */ - ALLOW, - /** - * Authentication process is stopped, the user is redirected to a page explaining that the email is already used - */ - WARN, - /** - * Forbid authentication of the user - */ - FORBID - } - - /** - * Strategy to be executed when the login of the user is updated - */ - enum UpdateLoginStrategy { - /** - * Authentication is allowed, the login of the user updated - */ - ALLOW, - /** - * Authentication process is stopped, the user is redirected to a page explaining that the login will be updated. - * It only happens when personal organizations are activated - */ - WARN - } - - private final UserIdentity userIdentity; - private final IdentityProvider provider; - private final AuthenticationEvent.Source source; - private final ExistingEmailStrategy existingEmailStrategy; - private final UpdateLoginStrategy updateLoginStrategy; - - UserIdentityAuthenticatorParameters(Builder builder) { - this.userIdentity = builder.userIdentity; - this.provider = builder.provider; - this.source = builder.source; - this.existingEmailStrategy = builder.existingEmailStrategy; - this.updateLoginStrategy = builder.updateLoginStrategy; - } - - public UserIdentity getUserIdentity() { - return userIdentity; - } - - public IdentityProvider getProvider() { - return provider; - } - - public AuthenticationEvent.Source getSource() { - return source; - } - - public ExistingEmailStrategy getExistingEmailStrategy() { - return existingEmailStrategy; - } - - public UpdateLoginStrategy getUpdateLoginStrategy() { - return updateLoginStrategy; - } - - static UserIdentityAuthenticatorParameters.Builder builder() { - return new Builder(); - } - - public static class Builder { - private UserIdentity userIdentity; - private IdentityProvider provider; - private AuthenticationEvent.Source source; - private ExistingEmailStrategy existingEmailStrategy; - private UpdateLoginStrategy updateLoginStrategy; - - public Builder setUserIdentity(UserIdentity userIdentity) { - this.userIdentity = userIdentity; - return this; - } - - public Builder setProvider(IdentityProvider provider) { - this.provider = provider; - return this; - } - - public Builder setSource(AuthenticationEvent.Source source) { - this.source = source; - return this; - } - - /** - * Strategy to be executed when the email of the user is already used by another user - */ - public Builder setExistingEmailStrategy(ExistingEmailStrategy existingEmailStrategy) { - this.existingEmailStrategy = existingEmailStrategy; - return this; - } - - /** - * Strategy to be executed when the login of the user has changed - */ - public Builder setUpdateLoginStrategy(UpdateLoginStrategy updateLoginStrategy) { - this.updateLoginStrategy = updateLoginStrategy; - return this; - } - - public UserIdentityAuthenticatorParameters build() { - requireNonNull(userIdentity, "userIdentity must be set"); - requireNonNull(provider, "identityProvider must be set"); - requireNonNull(source, "Source must be set"); - requireNonNull(existingEmailStrategy, "existingEmailStrategy must be set "); - requireNonNull(updateLoginStrategy, "updateLoginStrategy must be set"); - return new UserIdentityAuthenticatorParameters(this); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrar.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrar.java new file mode 100644 index 00000000000..a57b77cfc16 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrar.java @@ -0,0 +1,29 @@ +/* + * 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 org.sonar.db.user.UserDto; + +public interface UserRegistrar { + + UserDto register(UserRegistration registration); + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java new file mode 100644 index 00000000000..7a89684c456 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java @@ -0,0 +1,293 @@ +/* + * 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 com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.server.authentication.IdentityProvider; +import org.sonar.api.server.authentication.UserIdentity; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserGroupDto; +import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy; +import org.sonar.server.authentication.event.AuthenticationException; +import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException; +import org.sonar.server.authentication.exception.UpdateLoginRedirectionException; +import org.sonar.server.organization.DefaultOrganization; +import org.sonar.server.organization.DefaultOrganizationProvider; +import org.sonar.server.organization.OrganizationFlags; +import org.sonar.server.organization.OrganizationUpdater; +import org.sonar.server.user.ExternalIdentity; +import org.sonar.server.user.NewUser; +import org.sonar.server.user.UpdateUser; +import org.sonar.server.user.UserUpdater; +import org.sonar.server.usergroups.DefaultGroupFinder; + +import static com.google.common.base.Preconditions.checkState; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; +import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; +import static org.sonar.server.authentication.UserRegistration.UpdateLoginStrategy; + +public class UserRegistrarImpl implements UserRegistrar { + + private static final Logger LOGGER = Loggers.get(UserRegistrarImpl.class); + + private final DbClient dbClient; + private final UserUpdater userUpdater; + private final DefaultOrganizationProvider defaultOrganizationProvider; + private final OrganizationFlags organizationFlags; + private final OrganizationUpdater organizationUpdater; + private final DefaultGroupFinder defaultGroupFinder; + + public UserRegistrarImpl(DbClient dbClient, UserUpdater userUpdater, DefaultOrganizationProvider defaultOrganizationProvider, OrganizationFlags organizationFlags, + OrganizationUpdater organizationUpdater, DefaultGroupFinder defaultGroupFinder) { + this.dbClient = dbClient; + this.userUpdater = userUpdater; + this.defaultOrganizationProvider = defaultOrganizationProvider; + this.organizationFlags = organizationFlags; + this.organizationUpdater = organizationUpdater; + this.defaultGroupFinder = defaultGroupFinder; + } + + @Override + public UserDto register(UserRegistration registration) { + try (DbSession dbSession = dbClient.openSession(false)) { + UserDto userDto = getUser(dbSession, registration.getUserIdentity(), registration.getProvider()); + if (userDto == null) { + return registerNewUser(dbSession, null, registration); + } + if (!userDto.isActive()) { + return registerNewUser(dbSession, userDto, registration); + } + return registerExistingUser(dbSession, userDto, registration); + } + } + + @CheckForNull + private UserDto getUser(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider) { + UserDto user = dbClient.userDao().selectByExternalIdAndIdentityProvider(dbSession, getProviderIdOrProviderLogin(userIdentity), provider.getKey()); + if (user != null) { + return user; + } + // We need to search by login because : + // 1. user may have been provisioned, + // 2. user may have been disabled. + String login = userIdentity.getLogin(); + if (login == null) { + return null; + } + return dbClient.userDao().selectByLogin(dbSession, login); + } + + private UserDto registerNewUser(DbSession dbSession, @Nullable UserDto disabledUser, UserRegistration authenticatorParameters) { + Optional otherUserToIndex = detectEmailUpdate(dbSession, authenticatorParameters); + NewUser newUser = createNewUser(authenticatorParameters); + if (disabledUser == null) { + return userUpdater.createAndCommit(dbSession, newUser, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex)); + } + return userUpdater.reactivateAndCommit(dbSession, disabledUser, newUser, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex)); + } + + private UserDto registerExistingUser(DbSession dbSession, UserDto userDto, UserRegistration authenticatorParameters) { + UpdateUser update = new UpdateUser() + .setEmail(authenticatorParameters.getUserIdentity().getEmail()) + .setName(authenticatorParameters.getUserIdentity().getName()) + .setExternalIdentity(new ExternalIdentity( + authenticatorParameters.getProvider().getKey(), + authenticatorParameters.getUserIdentity().getProviderLogin(), + authenticatorParameters.getUserIdentity().getProviderId())); + String login = authenticatorParameters.getUserIdentity().getLogin(); + if (login != null) { + update.setLogin(login); + } + detectLoginUpdate(dbSession, userDto, update, authenticatorParameters); + Optional otherUserToIndex = detectEmailUpdate(dbSession, authenticatorParameters); + userUpdater.updateAndCommit(dbSession, userDto, update, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex)); + return userDto; + } + + private Optional detectEmailUpdate(DbSession dbSession, UserRegistration authenticatorParameters) { + String email = authenticatorParameters.getUserIdentity().getEmail(); + if (email == null) { + return Optional.empty(); + } + List existingUsers = dbClient.userDao().selectByEmail(dbSession, email); + if (existingUsers.isEmpty()) { + return Optional.empty(); + } + if (existingUsers.size() > 1) { + throw generateExistingEmailError(authenticatorParameters, email); + } + + UserDto existingUser = existingUsers.get(0); + if (existingUser == null + || Objects.equals(existingUser.getLogin(), authenticatorParameters.getUserIdentity().getLogin()) + || (Objects.equals(existingUser.getExternalId(), getProviderIdOrProviderLogin(authenticatorParameters.getUserIdentity())) + && Objects.equals(existingUser.getExternalIdentityProvider(), authenticatorParameters.getProvider().getKey()))) { + return Optional.empty(); + } + ExistingEmailStrategy existingEmailStrategy = authenticatorParameters.getExistingEmailStrategy(); + switch (existingEmailStrategy) { + case ALLOW: + existingUser.setEmail(null); + dbClient.userDao().update(dbSession, existingUser); + return Optional.of(existingUser); + case WARN: + throw new EmailAlreadyExistsRedirectionException(email, existingUser, authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider()); + case FORBID: + throw generateExistingEmailError(authenticatorParameters, email); + default: + throw new IllegalStateException(format("Unknown strategy %s", existingEmailStrategy)); + } + } + + private void detectLoginUpdate(DbSession dbSession, UserDto user, UpdateUser update, UserRegistration authenticatorParameters) { + String newLogin = update.login(); + if (!update.isLoginChanged() || user.getLogin().equals(newLogin)) { + return; + } + if (!organizationFlags.isEnabled(dbSession)) { + return; + } + String personalOrganizationUuid = user.getOrganizationUuid(); + if (personalOrganizationUuid == null) { + return; + } + Optional personalOrganization = dbClient.organizationDao().selectByUuid(dbSession, personalOrganizationUuid); + checkState(personalOrganization.isPresent(), + "Cannot find personal organization uuid '%s' for user '%s'", personalOrganizationUuid, user.getLogin()); + UpdateLoginStrategy updateLoginStrategy = authenticatorParameters.getUpdateLoginStrategy(); + switch (updateLoginStrategy) { + case ALLOW: + organizationUpdater.updateOrganizationKey(dbSession, personalOrganization.get(), requireNonNull(newLogin, "new login cannot be null")); + return; + case WARN: + throw new UpdateLoginRedirectionException(authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider(), user, personalOrganization.get()); + default: + throw new IllegalStateException(format("Unknown strategy %s", updateLoginStrategy)); + } + } + + private void syncGroups(DbSession dbSession, UserIdentity userIdentity, UserDto userDto) { + if (!userIdentity.shouldSyncGroups()) { + return; + } + String userLogin = userDto.getLogin(); + Set userGroups = new HashSet<>(dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(userLogin)).get(userLogin)); + Set identityGroups = userIdentity.getGroups(); + LOGGER.debug("List of groups returned by the identity provider '{}'", identityGroups); + + Collection groupsToAdd = Sets.difference(identityGroups, userGroups); + Collection groupsToRemove = Sets.difference(userGroups, identityGroups); + Collection allGroups = new ArrayList<>(groupsToAdd); + allGroups.addAll(groupsToRemove); + DefaultOrganization defaultOrganization = defaultOrganizationProvider.get(); + Map groupsByName = dbClient.groupDao().selectByNames(dbSession, defaultOrganization.getUuid(), allGroups) + .stream() + .collect(uniqueIndex(GroupDto::getName)); + + addGroups(dbSession, userDto, groupsToAdd, groupsByName); + removeGroups(dbSession, userDto, groupsToRemove, groupsByName); + } + + private void addGroups(DbSession dbSession, UserDto userDto, Collection groupsToAdd, Map groupsByName) { + groupsToAdd.stream().map(groupsByName::get).filter(Objects::nonNull).forEach( + groupDto -> { + LOGGER.debug("Adding group '{}' to user '{}'", groupDto.getName(), userDto.getLogin()); + dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId())); + }); + } + + private void removeGroups(DbSession dbSession, UserDto userDto, Collection groupsToRemove, Map groupsByName) { + Optional defaultGroup = getDefaultGroup(dbSession); + groupsToRemove.stream().map(groupsByName::get) + .filter(Objects::nonNull) + // user should be member of default group only when organizations are disabled, as the IdentityProvider API doesn't handle yet + // organizations + .filter(group -> !defaultGroup.isPresent() || !group.getId().equals(defaultGroup.get().getId())) + .forEach(groupDto -> { + LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin()); + dbClient.userGroupDao().delete(dbSession, groupDto.getId(), userDto.getId()); + }); + } + + private Optional getDefaultGroup(DbSession dbSession) { + return organizationFlags.isEnabled(dbSession) ? Optional.empty() : Optional.of(defaultGroupFinder.findDefaultGroup(dbSession, defaultOrganizationProvider.get().getUuid())); + } + + private static NewUser createNewUser(UserRegistration authenticatorParameters) { + String identityProviderKey = authenticatorParameters.getProvider().getKey(); + if (!authenticatorParameters.getProvider().allowsUsersToSignUp()) { + throw AuthenticationException.newBuilder() + .setSource(authenticatorParameters.getSource()) + .setLogin(authenticatorParameters.getUserIdentity().getProviderLogin()) + .setMessage(format("User signup disabled for provider '%s'", identityProviderKey)) + .setPublicMessage(format("'%s' users are not allowed to sign up", identityProviderKey)) + .build(); + } + return NewUser.builder() + .setLogin(authenticatorParameters.getUserIdentity().getLogin()) + .setEmail(authenticatorParameters.getUserIdentity().getEmail()) + .setName(authenticatorParameters.getUserIdentity().getName()) + .setExternalIdentity( + new ExternalIdentity( + identityProviderKey, + authenticatorParameters.getUserIdentity().getProviderLogin(), + authenticatorParameters.getUserIdentity().getProviderId())) + .build(); + } + + private static UserDto[] toArray(Optional userDto) { + return userDto.map(u -> new UserDto[] {u}).orElse(new UserDto[] {}); + } + + private static AuthenticationException generateExistingEmailError(UserRegistration authenticatorParameters, String email) { + return AuthenticationException.newBuilder() + .setSource(authenticatorParameters.getSource()) + .setLogin(authenticatorParameters.getUserIdentity().getProviderLogin()) + .setMessage(format("Email '%s' is already used", email)) + .setPublicMessage(format( + "You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", + email)) + .build(); + } + + private static String getProviderIdOrProviderLogin(UserIdentity userIdentity) { + String providerId = userIdentity.getProviderId(); + return providerId == null ? userIdentity.getProviderLogin() : providerId; + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistration.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistration.java new file mode 100644 index 00000000000..eda9af3ccea --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistration.java @@ -0,0 +1,149 @@ +/* + * 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 org.sonar.api.server.authentication.IdentityProvider; +import org.sonar.api.server.authentication.UserIdentity; +import org.sonar.server.authentication.event.AuthenticationEvent; + +import static java.util.Objects.requireNonNull; + +class UserRegistration { + + /** + * Strategy to be executed when the email of the user is already used by another user + */ + enum ExistingEmailStrategy { + /** + * Authentication is allowed, the email is moved from other user to current user + */ + ALLOW, + /** + * Authentication process is stopped, the user is redirected to a page explaining that the email is already used + */ + WARN, + /** + * Forbid authentication of the user + */ + FORBID + } + + /** + * Strategy to be executed when the login of the user is updated + */ + enum UpdateLoginStrategy { + /** + * Authentication is allowed, the login of the user updated + */ + ALLOW, + /** + * Authentication process is stopped, the user is redirected to a page explaining that the login will be updated. + * It only happens when personal organizations are activated + */ + WARN + } + + private final UserIdentity userIdentity; + private final IdentityProvider provider; + private final AuthenticationEvent.Source source; + private final ExistingEmailStrategy existingEmailStrategy; + private final UpdateLoginStrategy updateLoginStrategy; + + UserRegistration(Builder builder) { + this.userIdentity = builder.userIdentity; + this.provider = builder.provider; + this.source = builder.source; + this.existingEmailStrategy = builder.existingEmailStrategy; + this.updateLoginStrategy = builder.updateLoginStrategy; + } + + public UserIdentity getUserIdentity() { + return userIdentity; + } + + public IdentityProvider getProvider() { + return provider; + } + + public AuthenticationEvent.Source getSource() { + return source; + } + + public ExistingEmailStrategy getExistingEmailStrategy() { + return existingEmailStrategy; + } + + public UpdateLoginStrategy getUpdateLoginStrategy() { + return updateLoginStrategy; + } + + static UserRegistration.Builder builder() { + return new Builder(); + } + + public static class Builder { + private UserIdentity userIdentity; + private IdentityProvider provider; + private AuthenticationEvent.Source source; + private ExistingEmailStrategy existingEmailStrategy; + private UpdateLoginStrategy updateLoginStrategy; + + public Builder setUserIdentity(UserIdentity userIdentity) { + this.userIdentity = userIdentity; + return this; + } + + public Builder setProvider(IdentityProvider provider) { + this.provider = provider; + return this; + } + + public Builder setSource(AuthenticationEvent.Source source) { + this.source = source; + return this; + } + + /** + * Strategy to be executed when the email of the user is already used by another user + */ + public Builder setExistingEmailStrategy(ExistingEmailStrategy existingEmailStrategy) { + this.existingEmailStrategy = existingEmailStrategy; + return this; + } + + /** + * Strategy to be executed when the login of the user has changed + */ + public Builder setUpdateLoginStrategy(UpdateLoginStrategy updateLoginStrategy) { + this.updateLoginStrategy = updateLoginStrategy; + return this; + } + + public UserRegistration build() { + requireNonNull(userIdentity, "userIdentity must be set"); + requireNonNull(provider, "identityProvider must be set"); + requireNonNull(source, "Source must be set"); + requireNonNull(existingEmailStrategy, "existingEmailStrategy must be set "); + requireNonNull(updateLoginStrategy, "updateLoginStrategy must be set"); + return new UserRegistration(this); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java index 70cd7748e26..e939a1580d7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java @@ -54,7 +54,7 @@ public class BaseContextFactoryTest { private ThreadLocalUserSession threadLocalUserSession = mock(ThreadLocalUserSession.class); - private TestUserIdentityAuthenticator userIdentityAuthenticator = new TestUserIdentityAuthenticator(); + private TestUserRegistrar userIdentityAuthenticator = new TestUserRegistrar(); private Server server = mock(Server.class); private HttpServletRequest request = mock(HttpServletRequest.class); diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsExternalAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsExternalAuthenticationTest.java index e0aed101188..221c31c55ca 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsExternalAuthenticationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsExternalAuthenticationTest.java @@ -44,7 +44,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; -import static org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy.FORBID; +import static org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy.FORBID; import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC; import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN; import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; @@ -67,7 +67,7 @@ public class CredentialsExternalAuthenticationTest { private ExternalUsersProvider externalUsersProvider = mock(ExternalUsersProvider.class); private ExternalGroupsProvider externalGroupsProvider = mock(ExternalGroupsProvider.class); - private TestUserIdentityAuthenticator userIdentityAuthenticator = new TestUserIdentityAuthenticator(); + private TestUserRegistrar userIdentityAuthenticator = new TestUserRegistrar(); private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); private HttpServletRequest request = mock(HttpServletRequest.class); diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticationTest.java index e21f7eebb86..ee0033eaf2e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticationTest.java @@ -103,7 +103,7 @@ public class HttpHeadersAuthenticationTest { private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); - private UserIdentityAuthenticatorImpl userIdentityAuthenticator = new UserIdentityAuthenticatorImpl( + private UserRegistrarImpl userIdentityAuthenticator = new UserRegistrarImpl( db.getDbClient(), new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider, organizationUpdater, new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication), diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java index 77dc77f459e..6836f19c872 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java @@ -32,8 +32,8 @@ import org.sonar.api.platform.Server; import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.UserIdentity; import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy; +import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy; +import org.sonar.server.authentication.UserRegistration.UpdateLoginStrategy; import org.sonar.server.user.TestUserSessionFactory; import org.sonar.server.user.ThreadLocalUserSession; import org.sonar.server.user.UserSession; @@ -62,7 +62,7 @@ public class OAuth2ContextFactoryTest { public ExpectedException thrown = ExpectedException.none(); private ThreadLocalUserSession threadLocalUserSession = mock(ThreadLocalUserSession.class); - private TestUserIdentityAuthenticator userIdentityAuthenticator = new TestUserIdentityAuthenticator(); + private TestUserRegistrar userIdentityAuthenticator = new TestUserRegistrar(); private Server server = mock(Server.class); private OAuthCsrfVerifier csrfVerifier = mock(OAuthCsrfVerifier.class); private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserIdentityAuthenticator.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserIdentityAuthenticator.java deleted file mode 100644 index bfb42c3ed68..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserIdentityAuthenticator.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 org.sonar.db.user.UserDto; -import org.sonar.db.user.UserTesting; - -public class TestUserIdentityAuthenticator implements UserIdentityAuthenticator { - - private UserIdentityAuthenticatorParameters authenticatorParameters; - - @Override - public UserDto authenticate(UserIdentityAuthenticatorParameters authenticatorParameters) { - this.authenticatorParameters = authenticatorParameters; - String providerId = authenticatorParameters.getUserIdentity().getProviderId(); - return UserTesting.newUserDto() - .setLocal(false) - .setLogin(authenticatorParameters.getUserIdentity().getLogin()) - .setExternalLogin(authenticatorParameters.getUserIdentity().getProviderLogin()) - .setExternalId(providerId == null ? authenticatorParameters.getUserIdentity().getProviderLogin() : providerId) - .setExternalIdentityProvider(authenticatorParameters.getProvider().getKey()); - } - - boolean isAuthenticated() { - return authenticatorParameters != null; - } - - UserIdentityAuthenticatorParameters getAuthenticatorParameters() { - return authenticatorParameters; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserRegistrar.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserRegistrar.java new file mode 100644 index 00000000000..6bfe4969703 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserRegistrar.java @@ -0,0 +1,49 @@ +/* + * 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 org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTesting; + +public class TestUserRegistrar implements UserRegistrar { + + private UserRegistration authenticatorParameters; + + @Override + public UserDto register(UserRegistration registration) { + this.authenticatorParameters = registration; + String providerId = registration.getUserIdentity().getProviderId(); + return UserTesting.newUserDto() + .setLocal(false) + .setLogin(registration.getUserIdentity().getLogin()) + .setExternalLogin(registration.getUserIdentity().getProviderLogin()) + .setExternalId(providerId == null ? registration.getUserIdentity().getProviderLogin() : providerId) + .setExternalIdentityProvider(registration.getProvider().getKey()); + } + + boolean isAuthenticated() { + return authenticatorParameters != null; + } + + UserRegistration getAuthenticatorParameters() { + return authenticatorParameters; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorImplTest.java deleted file mode 100644 index d054ddb9492..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorImplTest.java +++ /dev/null @@ -1,894 +0,0 @@ -/* - * 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.stream.Collectors; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.ResourceTypes; -import org.sonar.api.server.authentication.UserIdentity; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; -import org.sonar.core.util.UuidFactoryFast; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbTester; -import org.sonar.db.component.ResourceTypesRule; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy; -import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; -import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException; -import org.sonar.server.authentication.exception.UpdateLoginRedirectionException; -import org.sonar.server.es.EsTester; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.OrganizationUpdater; -import org.sonar.server.organization.OrganizationUpdaterImpl; -import org.sonar.server.organization.OrganizationValidationImpl; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionServiceImpl; -import org.sonar.server.user.NewUserNotifier; -import org.sonar.server.user.UserUpdater; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static com.google.common.collect.Sets.newHashSet; -import static java.util.Arrays.stream; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.sonar.core.config.CorePropertyDefinitions.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS; -import static org.sonar.db.user.UserTesting.newUserDto; -import static org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy.FORBID; -import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC; -import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; - -public class UserIdentityAuthenticatorImplTest { - - private static String USER_LOGIN = "github-johndoo"; - - private static UserIdentity USER_IDENTITY = UserIdentity.builder() - .setProviderId("ABCD") - .setProviderLogin("johndoo") - .setLogin(USER_LOGIN) - .setName("John") - .setEmail("john@email.com") - .build(); - - private static TestIdentityProvider IDENTITY_PROVIDER = new TestIdentityProvider() - .setKey("github") - .setName("name of github") - .setEnabled(true) - .setAllowsUsersToSignUp(true); - - private MapSettings settings = new MapSettings(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public DbTester db = DbTester.create(new AlwaysIncreasingSystem2()); - @Rule - public EsTester es = EsTester.create(); - private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); - private UserUpdater userUpdater = new UserUpdater( - mock(NewUserNotifier.class), - db.getDbClient(), - userIndexer, - organizationFlags, - defaultOrganizationProvider, - organizationUpdater, - new DefaultGroupFinder(db.getDbClient()), - settings.asConfig(), - localAuthentication); - - private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); - private PermissionService permissionService = new PermissionServiceImpl(resourceTypes); - - private UserIdentityAuthenticatorImpl underTest = new UserIdentityAuthenticatorImpl(db.getDbClient(), userUpdater, defaultOrganizationProvider, organizationFlags, - new OrganizationUpdaterImpl(db.getDbClient(), mock(System2.class), UuidFactoryFast.getInstance(), - new OrganizationValidationImpl(), settings.asConfig(), null, null, null, permissionService), - new DefaultGroupFinder(db.getDbClient())); - - @Test - public void authenticate_new_user() { - organizationFlags.setEnabled(true); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.realm(BASIC, IDENTITY_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - UserDto user = db.users().selectUserByLogin(USER_LOGIN).get(); - assertThat(user).isNotNull(); - assertThat(user.isActive()).isTrue(); - assertThat(user.getName()).isEqualTo("John"); - assertThat(user.getEmail()).isEqualTo("john@email.com"); - assertThat(user.getExternalLogin()).isEqualTo("johndoo"); - assertThat(user.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(user.getExternalId()).isEqualTo("ABCD"); - assertThat(user.isRoot()).isFalse(); - checkGroupMembership(user); - } - - @Test - public void authenticate_new_user_generate_login_when_no_login_provided() { - organizationFlags.setEnabled(true); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderId("ABCD") - .setProviderLogin("johndoo") - .setName("John Doe") - .setEmail("john@email.com") - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.realm(BASIC, IDENTITY_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - UserDto user = db.getDbClient().userDao().selectByEmail(db.getSession(), "john@email.com").get(0); - assertThat(user).isNotNull(); - assertThat(user.isActive()).isTrue(); - assertThat(user.getLogin()).isNotEqualTo("John Doe").startsWith("john-doe"); - assertThat(user.getEmail()).isEqualTo("john@email.com"); - assertThat(user.getExternalLogin()).isEqualTo("johndoo"); - assertThat(user.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(user.getExternalId()).isEqualTo("ABCD"); - } - - @Test - public void authenticate_new_user_with_groups() { - organizationFlags.setEnabled(true); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); - - authenticate(USER_LOGIN, "group1", "group2", "group3"); - - Optional user = db.users().selectUserByLogin(USER_LOGIN); - checkGroupMembership(user.get(), group1, group2); - } - - @Test - public void authenticate_new_user_and_force_default_group_when_organizations_are_disabled() { - organizationFlags.setEnabled(false); - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto defaultGroup = insertDefaultGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(defaultGroup, user); - - authenticate(user.getLogin(), "group1"); - - checkGroupMembership(user, group1, defaultGroup); - } - - @Test - public void does_not_force_default_group_when_authenticating_new_user_if_organizations_are_enabled() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto defaultGroup = insertDefaultGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(defaultGroup, user); - - authenticate(user.getLogin(), "group1"); - - checkGroupMembership(user, group1); - } - - @Test - public void authenticate_new_user_sets_onboarded_flag_to_false_when_onboarding_setting_is_set_to_true() { - organizationFlags.setEnabled(true); - settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, true); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isFalse(); - } - - @Test - public void authenticate_new_user_sets_onboarded_flag_to_true_when_onboarding_setting_is_set_to_false() { - organizationFlags.setEnabled(true); - settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, false); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isTrue(); - } - - @Test - public void external_id_is_set_to_provider_login_when_null() { - organizationFlags.setEnabled(true); - UserIdentity newUser = UserIdentity.builder() - .setProviderId(null) - .setLogin("john") - .setProviderLogin("johndoo") - .setName("JOhn") - .build(); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(newUser) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.users().selectUserByLogin(newUser.getLogin()).get()) - .extracting(UserDto::getLogin, UserDto::getExternalId, UserDto::getExternalLogin) - .contains("john", "johndoo", "johndoo"); - } - - @Test - public void authenticate_new_user_update_existing_user_email_when_strategy_is_ALLOW() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserIdentity newUser = UserIdentity.builder() - .setProviderLogin("johndoo") - .setLogin("new_login") - .setName(existingUser.getName()) - .setEmail(existingUser.getEmail()) - .build(); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(newUser) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - UserDto newUserReloaded = db.users().selectUserByLogin(newUser.getLogin()).get(); - assertThat(newUserReloaded.getEmail()).isEqualTo(existingUser.getEmail()); - UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get(); - assertThat(existingUserReloaded.getEmail()).isNull(); - } - - @Test - public void throw_EmailAlreadyExistException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_WARN() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserIdentity newUser = UserIdentity.builder() - .setProviderLogin("johndoo") - .setLogin("new_login") - .setName(existingUser.getName()) - .setEmail(existingUser.getEmail()) - .build(); - - expectedException.expect(EmailAlreadyExistsRedirectionException.class); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(newUser) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.WARN) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - @Test - public void throw_AuthenticationException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_FORBID() { - db.users().insertUser(u -> u.setEmail("john@email.com")); - Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()); - - expectedException.expect(authenticationException().from(source) - .withLogin(USER_IDENTITY.getProviderLogin()) - .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " + - "This means that you probably already registered with another account.")); - expectedException.expectMessage("Email 'john@email.com' is already used"); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(source) - .setExistingEmailStrategy(FORBID) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - @Test - public void throw_AuthenticationException_when_authenticating_new_user_and_email_already_exists_multiple_times() { - db.users().insertUser(u -> u.setEmail("john@email.com")); - db.users().insertUser(u -> u.setEmail("john@email.com")); - Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()); - - expectedException.expect(authenticationException().from(source) - .withLogin(USER_IDENTITY.getProviderLogin()) - .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " + - "This means that you probably already registered with another account.")); - expectedException.expectMessage("Email 'john@email.com' is already used"); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(source) - .setExistingEmailStrategy(FORBID) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - @Test - public void fail_to_authenticate_new_user_when_allow_users_to_signup_is_false() { - TestIdentityProvider identityProvider = new TestIdentityProvider() - .setKey("github") - .setName("Github") - .setEnabled(true) - .setAllowsUsersToSignUp(false); - Source source = Source.realm(AuthenticationEvent.Method.FORM, identityProvider.getName()); - - expectedException.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getProviderLogin()).andPublicMessage("'github' users are not allowed to sign up")); - expectedException.expectMessage("User signup disabled for provider 'github'"); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(identityProvider) - .setSource(source) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - @Test - public void authenticate_and_update_existing_user_matching_login() { - db.users().insertUser(u -> u - .setLogin(USER_LOGIN) - .setName("Old name") - .setEmail("Old email") - .setExternalId("old id") - .setExternalLogin("old identity") - .setExternalIdentityProvider("old provide")); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.users().selectUserByLogin(USER_LOGIN).get()) - .extracting(UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, UserDto::isActive) - .contains("John", "john@email.com", "ABCD", "johndoo", "github", true); - } - - @Test - public void authenticate_and_update_existing_user_matching_external_id() { - UserDto user = db.users().insertUser(u -> u - .setLogin("Old login") - .setName("Old name") - .setEmail("Old email") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.users().selectUserByLogin("Old login")).isNotPresent(); - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .contains(USER_LOGIN, "John", "john@email.com", "ABCD", "johndoo", "github", true); - } - - @Test - public void authenticate_existing_user_and_update_only_login() { - UserDto user = db.users().insertUser(u -> u - .setLogin("old login") - .setName(USER_IDENTITY.getName()) - .setEmail(USER_IDENTITY.getEmail()) - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.users().selectUserByLogin("Old login")).isNotPresent(); - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .containsExactlyInAnyOrder(USER_LOGIN, USER_IDENTITY.getName(), USER_IDENTITY.getEmail(), USER_IDENTITY.getProviderId(), USER_IDENTITY.getProviderLogin(), - IDENTITY_PROVIDER.getKey(), - true); - } - - @Test - public void authenticate_existing_user_and_update_only_identity_provider_key() { - UserDto user = db.users().insertUser(u -> u - .setLogin(USER_LOGIN) - .setName(USER_IDENTITY.getName()) - .setEmail(USER_IDENTITY.getEmail()) - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin(USER_IDENTITY.getProviderLogin()) - .setExternalIdentityProvider("old identity provider")); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .containsExactlyInAnyOrder(USER_LOGIN, USER_IDENTITY.getName(), USER_IDENTITY.getEmail(), USER_IDENTITY.getProviderId(), USER_IDENTITY.getProviderLogin(), - IDENTITY_PROVIDER.getKey(), - true); - } - - @Test - public void authenticate_existing_user_matching_login_when_external_id_is_null() { - UserDto user = db.users().insertUser(u -> u - .setLogin(USER_LOGIN) - .setName("Old name") - .setEmail("Old email") - .setExternalId("Old id") - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderId(null) - .setProviderLogin("johndoo") - .setLogin(USER_LOGIN) - .setName("John") - .setEmail("john@email.com") - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .contains(user.getLogin(), "John", "john@email.com", "johndoo", "johndoo", "github", true); - } - - @Test - public void authenticate_existing_user_when_login_is_not_provided() { - UserDto user = db.users().insertUser(u -> u.setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderId(user.getExternalId()) - .setProviderLogin(user.getExternalLogin()) - // No login provided - .setName(user.getName()) - .setEmail(user.getEmail()) - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - // No new user is created - assertThat(db.countRowsOfTable(db.getSession(), "users")).isEqualTo(1); - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .contains(user.getLogin(), user.getName(), user.getEmail(), user.getExternalId(), user.getExternalLogin(), user.getExternalIdentityProvider(), true); - } - - @Test - public void authenticate_existing_user_with_login_update_and_strategy_is_ALLOW() { - UserDto user = db.users().insertUser(u -> u - .setLogin("Old login") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getExternalLogin) - .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin()); - } - - @Test - public void authenticate_existing_user_with_login_update_and_personal_org_does_not_exits_and_strategy_is_WARN() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(u -> u - .setLogin("Old login") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()) - .setOrganizationUuid(null)); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.WARN) - .build()); - - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getExternalLogin) - .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin()); - } - - @Test - public void throw_UpdateLoginRedirectionException_when_authenticating_with_login_update_and_personal_org_exists_and_strategy_is_WARN() { - organizationFlags.setEnabled(true); - OrganizationDto organization = db.organizations().insert(o -> o.setKey("Old login")); - db.users().insertUser(u -> u - .setLogin("Old login") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()) - .setOrganizationUuid(organization.getUuid())); - - expectedException.expect(UpdateLoginRedirectionException.class); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.WARN) - .build()); - } - - @Test - public void authenticate_existing_user_and_update_personal_og_key_when_personal_org_exists_and_strategy_is_ALLOW() { - organizationFlags.setEnabled(true); - OrganizationDto personalOrganization = db.organizations().insert(o -> o.setKey("Old login")); - UserDto user = db.users().insertUser(u -> u - .setLogin("Old login") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()) - .setOrganizationUuid(personalOrganization.getUuid())); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getExternalLogin) - .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin()); - OrganizationDto organizationReloaded = db.getDbClient().organizationDao().selectByUuid(db.getSession(), personalOrganization.getUuid()).get(); - assertThat(organizationReloaded.getKey()).isEqualTo(USER_LOGIN); - } - - @Test - public void fail_to_authenticate_existing_user_when_personal_org_does_not_exist() { - organizationFlags.setEnabled(true); - db.users().insertUser(u -> u - .setLogin("Old login") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()) - .setOrganizationUuid("unknown")); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Cannot find personal organization uuid 'unknown' for user 'Old login'"); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - @Test - public void authenticate_existing_disabled_user() { - organizationFlags.setEnabled(true); - db.users().insertUser(u -> u - .setLogin(USER_LOGIN) - .setActive(false) - .setName("Old name") - .setEmail("Old email") - .setExternalId("old id") - .setExternalLogin("old identity") - .setExternalIdentityProvider("old provide")); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - UserDto userDto = db.users().selectUserByLogin(USER_LOGIN).get(); - assertThat(userDto.isActive()).isTrue(); - assertThat(userDto.getName()).isEqualTo("John"); - assertThat(userDto.getEmail()).isEqualTo("john@email.com"); - assertThat(userDto.getExternalId()).isEqualTo("ABCD"); - assertThat(userDto.getExternalLogin()).isEqualTo("johndoo"); - assertThat(userDto.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(userDto.isRoot()).isFalse(); - } - - @Test - public void authenticate_existing_user_when_email_already_exists_and_strategy_is_ALLOW() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); - UserIdentity userIdentity = UserIdentity.builder() - .setLogin(currentUser.getLogin()) - .setProviderLogin("johndoo") - .setName("John") - .setEmail("john@email.com") - .build(); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(userIdentity) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get(); - assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com"); - UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get(); - assertThat(existingUserReloaded.getEmail()).isNull(); - } - - @Test - public void throw_EmailAlreadyExistException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_WARN() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); - UserIdentity userIdentity = UserIdentity.builder() - .setLogin(currentUser.getLogin()) - .setProviderLogin("johndoo") - .setName("John") - .setEmail("john@email.com") - .build(); - - expectedException.expect(EmailAlreadyExistsRedirectionException.class); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(userIdentity) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.WARN) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - @Test - public void throw_AuthenticationException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_FORBID() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); - UserIdentity userIdentity = UserIdentity.builder() - .setLogin(currentUser.getLogin()) - .setProviderLogin("johndoo") - .setName("John") - .setEmail("john@email.com") - .build(); - - expectedException.expect(authenticationException().from(Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName())) - .withLogin(userIdentity.getProviderLogin()) - .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " + - "This means that you probably already registered with another account.")); - expectedException.expectMessage("Email 'john@email.com' is already used"); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(userIdentity) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - @Test - public void does_not_fail_to_authenticate_user_when_email_has_not_changed_and_strategy_is_FORBID() { - organizationFlags.setEnabled(true); - UserDto currentUser = db.users().insertUser(u -> u.setEmail("john@email.com") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - UserIdentity userIdentity = UserIdentity.builder() - .setLogin(currentUser.getLogin()) - .setProviderId(currentUser.getExternalId()) - .setProviderLogin(currentUser.getExternalLogin()) - .setName("John") - .setEmail("john@email.com") - .build(); - - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(userIdentity) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get(); - assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com"); - } - - @Test - public void authenticate_existing_user_and_add_new_groups() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(newUserDto() - .setLogin(USER_LOGIN) - .setActive(true) - .setName("John")); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); - - authenticate(USER_LOGIN, "group1", "group2", "group3"); - - checkGroupMembership(user, group1, group2); - } - - @Test - public void authenticate_existing_user_and_remove_groups() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(newUserDto() - .setLogin(USER_LOGIN) - .setActive(true) - .setName("John")); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); - db.users().insertMember(group1, user); - db.users().insertMember(group2, user); - - authenticate(USER_LOGIN, "group1"); - - checkGroupMembership(user, group1); - } - - @Test - public void authenticate_existing_user_and_remove_all_groups_expect_default_when_organizations_are_disabled() { - organizationFlags.setEnabled(false); - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); - GroupDto defaultGroup = insertDefaultGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(group2, user); - db.users().insertMember(defaultGroup, user); - - authenticate(user.getLogin()); - - checkGroupMembership(user, defaultGroup); - } - - @Test - public void does_not_force_default_group_when_authenticating_existing_user_when_organizations_are_enabled() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto defaultGroup = insertDefaultGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(defaultGroup, user); - - authenticate(user.getLogin(), "group1"); - - checkGroupMembership(user, group1); - } - - @Test - public void ignore_groups_on_non_default_organizations() { - organizationFlags.setEnabled(true); - OrganizationDto org = db.organizations().insert(); - UserDto user = db.users().insertUser(newUserDto() - .setLogin(USER_LOGIN) - .setActive(true) - .setName("John")); - String groupName = "a-group"; - GroupDto groupInDefaultOrg = db.users().insertGroup(db.getDefaultOrganization(), groupName); - GroupDto groupInOrg = db.users().insertGroup(org, groupName); - - // adding a group with the same name than in non-default organization - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderLogin("johndoo") - .setLogin(user.getLogin()) - .setName(user.getName()) - .setGroups(newHashSet(groupName)) - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - - checkGroupMembership(user, groupInDefaultOrg); - } - - private void authenticate(String login, String... groups) { - underTest.authenticate(UserIdentityAuthenticatorParameters.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderLogin("johndoo") - .setLogin(login) - .setName("John") - // No group - .setGroups(stream(groups).collect(MoreCollectors.toSet())) - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - private void checkGroupMembership(UserDto user, GroupDto... expectedGroups) { - assertThat(db.users().selectGroupIdsOfUser(user)).containsOnly(stream(expectedGroups).map(GroupDto::getId).collect(Collectors.toList()).toArray(new Integer[] {})); - } - - private GroupDto insertDefaultGroup() { - return db.users().insertDefaultGroup(db.getDefaultOrganization(), "sonar-users"); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java new file mode 100644 index 00000000000..0950f6658e6 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java @@ -0,0 +1,894 @@ +/* + * 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.stream.Collectors; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.server.authentication.UserIdentity; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbTester; +import org.sonar.db.component.ResourceTypesRule; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy; +import org.sonar.server.authentication.UserRegistration.UpdateLoginStrategy; +import org.sonar.server.authentication.event.AuthenticationEvent; +import org.sonar.server.authentication.event.AuthenticationEvent.Source; +import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException; +import org.sonar.server.authentication.exception.UpdateLoginRedirectionException; +import org.sonar.server.es.EsTester; +import org.sonar.server.organization.DefaultOrganizationProvider; +import org.sonar.server.organization.OrganizationUpdater; +import org.sonar.server.organization.OrganizationUpdaterImpl; +import org.sonar.server.organization.OrganizationValidationImpl; +import org.sonar.server.organization.TestDefaultOrganizationProvider; +import org.sonar.server.organization.TestOrganizationFlags; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.permission.PermissionServiceImpl; +import org.sonar.server.user.NewUserNotifier; +import org.sonar.server.user.UserUpdater; +import org.sonar.server.user.index.UserIndexer; +import org.sonar.server.usergroups.DefaultGroupFinder; + +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.sonar.core.config.CorePropertyDefinitions.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS; +import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy.FORBID; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC; +import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; + +public class UserRegistrarImplTest { + + private static String USER_LOGIN = "github-johndoo"; + + private static UserIdentity USER_IDENTITY = UserIdentity.builder() + .setProviderId("ABCD") + .setProviderLogin("johndoo") + .setLogin(USER_LOGIN) + .setName("John") + .setEmail("john@email.com") + .build(); + + private static TestIdentityProvider IDENTITY_PROVIDER = new TestIdentityProvider() + .setKey("github") + .setName("name of github") + .setEnabled(true) + .setAllowsUsersToSignUp(true); + + private MapSettings settings = new MapSettings(); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public DbTester db = DbTester.create(new AlwaysIncreasingSystem2()); + @Rule + public EsTester es = EsTester.create(); + private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); + private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); + private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class); + private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); + private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); + private UserUpdater userUpdater = new UserUpdater( + mock(NewUserNotifier.class), + db.getDbClient(), + userIndexer, + organizationFlags, + defaultOrganizationProvider, + organizationUpdater, + new DefaultGroupFinder(db.getDbClient()), + settings.asConfig(), + localAuthentication); + + private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); + private PermissionService permissionService = new PermissionServiceImpl(resourceTypes); + + private UserRegistrarImpl underTest = new UserRegistrarImpl(db.getDbClient(), userUpdater, defaultOrganizationProvider, organizationFlags, + new OrganizationUpdaterImpl(db.getDbClient(), mock(System2.class), UuidFactoryFast.getInstance(), + new OrganizationValidationImpl(), settings.asConfig(), null, null, null, permissionService), + new DefaultGroupFinder(db.getDbClient())); + + @Test + public void authenticate_new_user() { + organizationFlags.setEnabled(true); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.realm(BASIC, IDENTITY_PROVIDER.getName())) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + UserDto user = db.users().selectUserByLogin(USER_LOGIN).get(); + assertThat(user).isNotNull(); + assertThat(user.isActive()).isTrue(); + assertThat(user.getName()).isEqualTo("John"); + assertThat(user.getEmail()).isEqualTo("john@email.com"); + assertThat(user.getExternalLogin()).isEqualTo("johndoo"); + assertThat(user.getExternalIdentityProvider()).isEqualTo("github"); + assertThat(user.getExternalId()).isEqualTo("ABCD"); + assertThat(user.isRoot()).isFalse(); + checkGroupMembership(user); + } + + @Test + public void authenticate_new_user_generate_login_when_no_login_provided() { + organizationFlags.setEnabled(true); + + underTest.register(UserRegistration.builder() + .setUserIdentity(UserIdentity.builder() + .setProviderId("ABCD") + .setProviderLogin("johndoo") + .setName("John Doe") + .setEmail("john@email.com") + .build()) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.realm(BASIC, IDENTITY_PROVIDER.getName())) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + UserDto user = db.getDbClient().userDao().selectByEmail(db.getSession(), "john@email.com").get(0); + assertThat(user).isNotNull(); + assertThat(user.isActive()).isTrue(); + assertThat(user.getLogin()).isNotEqualTo("John Doe").startsWith("john-doe"); + assertThat(user.getEmail()).isEqualTo("john@email.com"); + assertThat(user.getExternalLogin()).isEqualTo("johndoo"); + assertThat(user.getExternalIdentityProvider()).isEqualTo("github"); + assertThat(user.getExternalId()).isEqualTo("ABCD"); + } + + @Test + public void authenticate_new_user_with_groups() { + organizationFlags.setEnabled(true); + GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); + GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); + + authenticate(USER_LOGIN, "group1", "group2", "group3"); + + Optional user = db.users().selectUserByLogin(USER_LOGIN); + checkGroupMembership(user.get(), group1, group2); + } + + @Test + public void authenticate_new_user_and_force_default_group_when_organizations_are_disabled() { + organizationFlags.setEnabled(false); + UserDto user = db.users().insertUser(); + GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); + GroupDto defaultGroup = insertDefaultGroup(); + db.users().insertMember(group1, user); + db.users().insertMember(defaultGroup, user); + + authenticate(user.getLogin(), "group1"); + + checkGroupMembership(user, group1, defaultGroup); + } + + @Test + public void does_not_force_default_group_when_authenticating_new_user_if_organizations_are_enabled() { + organizationFlags.setEnabled(true); + UserDto user = db.users().insertUser(); + GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); + GroupDto defaultGroup = insertDefaultGroup(); + db.users().insertMember(group1, user); + db.users().insertMember(defaultGroup, user); + + authenticate(user.getLogin(), "group1"); + + checkGroupMembership(user, group1); + } + + @Test + public void authenticate_new_user_sets_onboarded_flag_to_false_when_onboarding_setting_is_set_to_true() { + organizationFlags.setEnabled(true); + settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, true); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isFalse(); + } + + @Test + public void authenticate_new_user_sets_onboarded_flag_to_true_when_onboarding_setting_is_set_to_false() { + organizationFlags.setEnabled(true); + settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, false); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isTrue(); + } + + @Test + public void external_id_is_set_to_provider_login_when_null() { + organizationFlags.setEnabled(true); + UserIdentity newUser = UserIdentity.builder() + .setProviderId(null) + .setLogin("john") + .setProviderLogin("johndoo") + .setName("JOhn") + .build(); + + underTest.register(UserRegistration.builder() + .setUserIdentity(newUser) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.users().selectUserByLogin(newUser.getLogin()).get()) + .extracting(UserDto::getLogin, UserDto::getExternalId, UserDto::getExternalLogin) + .contains("john", "johndoo", "johndoo"); + } + + @Test + public void authenticate_new_user_update_existing_user_email_when_strategy_is_ALLOW() { + organizationFlags.setEnabled(true); + UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); + UserIdentity newUser = UserIdentity.builder() + .setProviderLogin("johndoo") + .setLogin("new_login") + .setName(existingUser.getName()) + .setEmail(existingUser.getEmail()) + .build(); + + underTest.register(UserRegistration.builder() + .setUserIdentity(newUser) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + UserDto newUserReloaded = db.users().selectUserByLogin(newUser.getLogin()).get(); + assertThat(newUserReloaded.getEmail()).isEqualTo(existingUser.getEmail()); + UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get(); + assertThat(existingUserReloaded.getEmail()).isNull(); + } + + @Test + public void throw_EmailAlreadyExistException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_WARN() { + organizationFlags.setEnabled(true); + UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); + UserIdentity newUser = UserIdentity.builder() + .setProviderLogin("johndoo") + .setLogin("new_login") + .setName(existingUser.getName()) + .setEmail(existingUser.getEmail()) + .build(); + + expectedException.expect(EmailAlreadyExistsRedirectionException.class); + + underTest.register(UserRegistration.builder() + .setUserIdentity(newUser) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.WARN) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + @Test + public void throw_AuthenticationException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_FORBID() { + db.users().insertUser(u -> u.setEmail("john@email.com")); + Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()); + + expectedException.expect(authenticationException().from(source) + .withLogin(USER_IDENTITY.getProviderLogin()) + .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " + + "This means that you probably already registered with another account.")); + expectedException.expectMessage("Email 'john@email.com' is already used"); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(source) + .setExistingEmailStrategy(FORBID) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + @Test + public void throw_AuthenticationException_when_authenticating_new_user_and_email_already_exists_multiple_times() { + db.users().insertUser(u -> u.setEmail("john@email.com")); + db.users().insertUser(u -> u.setEmail("john@email.com")); + Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()); + + expectedException.expect(authenticationException().from(source) + .withLogin(USER_IDENTITY.getProviderLogin()) + .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " + + "This means that you probably already registered with another account.")); + expectedException.expectMessage("Email 'john@email.com' is already used"); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(source) + .setExistingEmailStrategy(FORBID) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + @Test + public void fail_to_authenticate_new_user_when_allow_users_to_signup_is_false() { + TestIdentityProvider identityProvider = new TestIdentityProvider() + .setKey("github") + .setName("Github") + .setEnabled(true) + .setAllowsUsersToSignUp(false); + Source source = Source.realm(AuthenticationEvent.Method.FORM, identityProvider.getName()); + + expectedException.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getProviderLogin()).andPublicMessage("'github' users are not allowed to sign up")); + expectedException.expectMessage("User signup disabled for provider 'github'"); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(identityProvider) + .setSource(source) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + @Test + public void authenticate_and_update_existing_user_matching_login() { + db.users().insertUser(u -> u + .setLogin(USER_LOGIN) + .setName("Old name") + .setEmail("Old email") + .setExternalId("old id") + .setExternalLogin("old identity") + .setExternalIdentityProvider("old provide")); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.users().selectUserByLogin(USER_LOGIN).get()) + .extracting(UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, UserDto::isActive) + .contains("John", "john@email.com", "ABCD", "johndoo", "github", true); + } + + @Test + public void authenticate_and_update_existing_user_matching_external_id() { + UserDto user = db.users().insertUser(u -> u + .setLogin("Old login") + .setName("Old name") + .setEmail("Old email") + .setExternalId(USER_IDENTITY.getProviderId()) + .setExternalLogin("old identity") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.users().selectUserByLogin("Old login")).isNotPresent(); + assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) + .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, + UserDto::isActive) + .contains(USER_LOGIN, "John", "john@email.com", "ABCD", "johndoo", "github", true); + } + + @Test + public void authenticate_existing_user_and_update_only_login() { + UserDto user = db.users().insertUser(u -> u + .setLogin("old login") + .setName(USER_IDENTITY.getName()) + .setEmail(USER_IDENTITY.getEmail()) + .setExternalId(USER_IDENTITY.getProviderId()) + .setExternalLogin("old identity") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.users().selectUserByLogin("Old login")).isNotPresent(); + assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) + .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, + UserDto::isActive) + .containsExactlyInAnyOrder(USER_LOGIN, USER_IDENTITY.getName(), USER_IDENTITY.getEmail(), USER_IDENTITY.getProviderId(), USER_IDENTITY.getProviderLogin(), + IDENTITY_PROVIDER.getKey(), + true); + } + + @Test + public void authenticate_existing_user_and_update_only_identity_provider_key() { + UserDto user = db.users().insertUser(u -> u + .setLogin(USER_LOGIN) + .setName(USER_IDENTITY.getName()) + .setEmail(USER_IDENTITY.getEmail()) + .setExternalId(USER_IDENTITY.getProviderId()) + .setExternalLogin(USER_IDENTITY.getProviderLogin()) + .setExternalIdentityProvider("old identity provider")); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) + .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, + UserDto::isActive) + .containsExactlyInAnyOrder(USER_LOGIN, USER_IDENTITY.getName(), USER_IDENTITY.getEmail(), USER_IDENTITY.getProviderId(), USER_IDENTITY.getProviderLogin(), + IDENTITY_PROVIDER.getKey(), + true); + } + + @Test + public void authenticate_existing_user_matching_login_when_external_id_is_null() { + UserDto user = db.users().insertUser(u -> u + .setLogin(USER_LOGIN) + .setName("Old name") + .setEmail("Old email") + .setExternalId("Old id") + .setExternalLogin("old identity") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); + + underTest.register(UserRegistration.builder() + .setUserIdentity(UserIdentity.builder() + .setProviderId(null) + .setProviderLogin("johndoo") + .setLogin(USER_LOGIN) + .setName("John") + .setEmail("john@email.com") + .build()) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) + .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, + UserDto::isActive) + .contains(user.getLogin(), "John", "john@email.com", "johndoo", "johndoo", "github", true); + } + + @Test + public void authenticate_existing_user_when_login_is_not_provided() { + UserDto user = db.users().insertUser(u -> u.setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); + + underTest.register(UserRegistration.builder() + .setUserIdentity(UserIdentity.builder() + .setProviderId(user.getExternalId()) + .setProviderLogin(user.getExternalLogin()) + // No login provided + .setName(user.getName()) + .setEmail(user.getEmail()) + .build()) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + // No new user is created + assertThat(db.countRowsOfTable(db.getSession(), "users")).isEqualTo(1); + assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) + .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, + UserDto::isActive) + .contains(user.getLogin(), user.getName(), user.getEmail(), user.getExternalId(), user.getExternalLogin(), user.getExternalIdentityProvider(), true); + } + + @Test + public void authenticate_existing_user_with_login_update_and_strategy_is_ALLOW() { + UserDto user = db.users().insertUser(u -> u + .setLogin("Old login") + .setExternalId(USER_IDENTITY.getProviderId()) + .setExternalLogin("old identity") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) + .extracting(UserDto::getLogin, UserDto::getExternalLogin) + .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin()); + } + + @Test + public void authenticate_existing_user_with_login_update_and_personal_org_does_not_exits_and_strategy_is_WARN() { + organizationFlags.setEnabled(true); + UserDto user = db.users().insertUser(u -> u + .setLogin("Old login") + .setExternalId(USER_IDENTITY.getProviderId()) + .setExternalLogin("old identity") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()) + .setOrganizationUuid(null)); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.WARN) + .build()); + + assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) + .extracting(UserDto::getLogin, UserDto::getExternalLogin) + .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin()); + } + + @Test + public void throw_UpdateLoginRedirectionException_when_authenticating_with_login_update_and_personal_org_exists_and_strategy_is_WARN() { + organizationFlags.setEnabled(true); + OrganizationDto organization = db.organizations().insert(o -> o.setKey("Old login")); + db.users().insertUser(u -> u + .setLogin("Old login") + .setExternalId(USER_IDENTITY.getProviderId()) + .setExternalLogin("old identity") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()) + .setOrganizationUuid(organization.getUuid())); + + expectedException.expect(UpdateLoginRedirectionException.class); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.WARN) + .build()); + } + + @Test + public void authenticate_existing_user_and_update_personal_og_key_when_personal_org_exists_and_strategy_is_ALLOW() { + organizationFlags.setEnabled(true); + OrganizationDto personalOrganization = db.organizations().insert(o -> o.setKey("Old login")); + UserDto user = db.users().insertUser(u -> u + .setLogin("Old login") + .setExternalId(USER_IDENTITY.getProviderId()) + .setExternalLogin("old identity") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()) + .setOrganizationUuid(personalOrganization.getUuid())); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) + .extracting(UserDto::getLogin, UserDto::getExternalLogin) + .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin()); + OrganizationDto organizationReloaded = db.getDbClient().organizationDao().selectByUuid(db.getSession(), personalOrganization.getUuid()).get(); + assertThat(organizationReloaded.getKey()).isEqualTo(USER_LOGIN); + } + + @Test + public void fail_to_authenticate_existing_user_when_personal_org_does_not_exist() { + organizationFlags.setEnabled(true); + db.users().insertUser(u -> u + .setLogin("Old login") + .setExternalId(USER_IDENTITY.getProviderId()) + .setExternalLogin("old identity") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()) + .setOrganizationUuid("unknown")); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Cannot find personal organization uuid 'unknown' for user 'Old login'"); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + @Test + public void authenticate_existing_disabled_user() { + organizationFlags.setEnabled(true); + db.users().insertUser(u -> u + .setLogin(USER_LOGIN) + .setActive(false) + .setName("Old name") + .setEmail("Old email") + .setExternalId("old id") + .setExternalLogin("old identity") + .setExternalIdentityProvider("old provide")); + + underTest.register(UserRegistration.builder() + .setUserIdentity(USER_IDENTITY) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + UserDto userDto = db.users().selectUserByLogin(USER_LOGIN).get(); + assertThat(userDto.isActive()).isTrue(); + assertThat(userDto.getName()).isEqualTo("John"); + assertThat(userDto.getEmail()).isEqualTo("john@email.com"); + assertThat(userDto.getExternalId()).isEqualTo("ABCD"); + assertThat(userDto.getExternalLogin()).isEqualTo("johndoo"); + assertThat(userDto.getExternalIdentityProvider()).isEqualTo("github"); + assertThat(userDto.isRoot()).isFalse(); + } + + @Test + public void authenticate_existing_user_when_email_already_exists_and_strategy_is_ALLOW() { + organizationFlags.setEnabled(true); + UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); + UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); + UserIdentity userIdentity = UserIdentity.builder() + .setLogin(currentUser.getLogin()) + .setProviderLogin("johndoo") + .setName("John") + .setEmail("john@email.com") + .build(); + + underTest.register(UserRegistration.builder() + .setUserIdentity(userIdentity) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get(); + assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com"); + UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get(); + assertThat(existingUserReloaded.getEmail()).isNull(); + } + + @Test + public void throw_EmailAlreadyExistException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_WARN() { + organizationFlags.setEnabled(true); + UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); + UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); + UserIdentity userIdentity = UserIdentity.builder() + .setLogin(currentUser.getLogin()) + .setProviderLogin("johndoo") + .setName("John") + .setEmail("john@email.com") + .build(); + + expectedException.expect(EmailAlreadyExistsRedirectionException.class); + + underTest.register(UserRegistration.builder() + .setUserIdentity(userIdentity) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.WARN) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + @Test + public void throw_AuthenticationException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_FORBID() { + organizationFlags.setEnabled(true); + UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); + UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); + UserIdentity userIdentity = UserIdentity.builder() + .setLogin(currentUser.getLogin()) + .setProviderLogin("johndoo") + .setName("John") + .setEmail("john@email.com") + .build(); + + expectedException.expect(authenticationException().from(Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName())) + .withLogin(userIdentity.getProviderLogin()) + .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " + + "This means that you probably already registered with another account.")); + expectedException.expectMessage("Email 'john@email.com' is already used"); + + underTest.register(UserRegistration.builder() + .setUserIdentity(userIdentity) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName())) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + @Test + public void does_not_fail_to_authenticate_user_when_email_has_not_changed_and_strategy_is_FORBID() { + organizationFlags.setEnabled(true); + UserDto currentUser = db.users().insertUser(u -> u.setEmail("john@email.com") + .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); + UserIdentity userIdentity = UserIdentity.builder() + .setLogin(currentUser.getLogin()) + .setProviderId(currentUser.getExternalId()) + .setProviderLogin(currentUser.getExternalLogin()) + .setName("John") + .setEmail("john@email.com") + .build(); + + underTest.register(UserRegistration.builder() + .setUserIdentity(userIdentity) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get(); + assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com"); + } + + @Test + public void authenticate_existing_user_and_add_new_groups() { + organizationFlags.setEnabled(true); + UserDto user = db.users().insertUser(newUserDto() + .setLogin(USER_LOGIN) + .setActive(true) + .setName("John")); + GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); + GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); + + authenticate(USER_LOGIN, "group1", "group2", "group3"); + + checkGroupMembership(user, group1, group2); + } + + @Test + public void authenticate_existing_user_and_remove_groups() { + organizationFlags.setEnabled(true); + UserDto user = db.users().insertUser(newUserDto() + .setLogin(USER_LOGIN) + .setActive(true) + .setName("John")); + GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); + GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); + db.users().insertMember(group1, user); + db.users().insertMember(group2, user); + + authenticate(USER_LOGIN, "group1"); + + checkGroupMembership(user, group1); + } + + @Test + public void authenticate_existing_user_and_remove_all_groups_expect_default_when_organizations_are_disabled() { + organizationFlags.setEnabled(false); + UserDto user = db.users().insertUser(); + GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); + GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); + GroupDto defaultGroup = insertDefaultGroup(); + db.users().insertMember(group1, user); + db.users().insertMember(group2, user); + db.users().insertMember(defaultGroup, user); + + authenticate(user.getLogin()); + + checkGroupMembership(user, defaultGroup); + } + + @Test + public void does_not_force_default_group_when_authenticating_existing_user_when_organizations_are_enabled() { + organizationFlags.setEnabled(true); + UserDto user = db.users().insertUser(); + GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); + GroupDto defaultGroup = insertDefaultGroup(); + db.users().insertMember(group1, user); + db.users().insertMember(defaultGroup, user); + + authenticate(user.getLogin(), "group1"); + + checkGroupMembership(user, group1); + } + + @Test + public void ignore_groups_on_non_default_organizations() { + organizationFlags.setEnabled(true); + OrganizationDto org = db.organizations().insert(); + UserDto user = db.users().insertUser(newUserDto() + .setLogin(USER_LOGIN) + .setActive(true) + .setName("John")); + String groupName = "a-group"; + GroupDto groupInDefaultOrg = db.users().insertGroup(db.getDefaultOrganization(), groupName); + GroupDto groupInOrg = db.users().insertGroup(org, groupName); + + // adding a group with the same name than in non-default organization + underTest.register(UserRegistration.builder() + .setUserIdentity(UserIdentity.builder() + .setProviderLogin("johndoo") + .setLogin(user.getLogin()) + .setName(user.getName()) + .setGroups(newHashSet(groupName)) + .build()) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + + checkGroupMembership(user, groupInDefaultOrg); + } + + private void authenticate(String login, String... groups) { + underTest.register(UserRegistration.builder() + .setUserIdentity(UserIdentity.builder() + .setProviderLogin("johndoo") + .setLogin(login) + .setName("John") + // No group + .setGroups(stream(groups).collect(MoreCollectors.toSet())) + .build()) + .setProvider(IDENTITY_PROVIDER) + .setSource(Source.local(BASIC)) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + private void checkGroupMembership(UserDto user, GroupDto... expectedGroups) { + assertThat(db.users().selectGroupIdsOfUser(user)).containsOnly(stream(expectedGroups).map(GroupDto::getId).collect(Collectors.toList()).toArray(new Integer[] {})); + } + + private GroupDto insertDefaultGroup() { + return db.users().insertDefaultGroup(db.getDefaultOrganization(), "sonar-users"); + } + +}