From: Julien Lancelot Date: Thu, 24 May 2018 13:56:11 +0000 (+0200) Subject: SONAR-10652 Correctly fail when authentication and several emails exists X-Git-Tag: 7.5~1119 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b7976ff3de0187d28cbe371a858d51af32913853;p=sonarqube.git SONAR-10652 Correctly fail when authentication and several emails exists --- diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java index 79318d4d02a..f78aebc4f71 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java @@ -22,7 +22,6 @@ package org.sonar.db.user; import java.util.Collection; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -38,15 +37,16 @@ import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; +import static java.util.Locale.ENGLISH; import static org.sonar.db.DatabaseUtils.executeLargeInputs; import static org.sonar.db.DatabaseUtils.executeLargeInputsWithoutOutput; +import static org.sonar.db.user.UserDto.SCM_ACCOUNTS_SEPARATOR; public class UserDao implements Dao { private final System2 system2; private final UuidFactory uuidFactory; - public UserDao(System2 system2, UuidFactory uuidFactory) { this.system2 = system2; this.uuidFactory = uuidFactory; @@ -154,19 +154,18 @@ public class UserDao implements Dao { public List selectByScmAccountOrLoginOrEmail(DbSession session, String scmAccountOrLoginOrEmail) { String like = new StringBuilder().append("%") - .append(UserDto.SCM_ACCOUNTS_SEPARATOR).append(scmAccountOrLoginOrEmail) - .append(UserDto.SCM_ACCOUNTS_SEPARATOR).append("%").toString(); + .append(SCM_ACCOUNTS_SEPARATOR).append(scmAccountOrLoginOrEmail) + .append(SCM_ACCOUNTS_SEPARATOR).append("%").toString(); return mapper(session).selectNullableByScmAccountOrLoginOrEmail(scmAccountOrLoginOrEmail, like); } /** - * Search for an active user with the given email exits in database + * Search for an active user with the given emailCaseInsensitive exits in database * - * Please note that email is case insensitive, result for searching 'mail@email.com' or 'Mail@Email.com' will be the same + * Select is case insensitive. Result for searching 'mail@emailCaseInsensitive.com' or 'Mail@Email.com' is the same */ - @CheckForNull - public UserDto selectByEmail(DbSession dbSession, String email) { - return mapper(dbSession).selectByEmail(email.toLowerCase(Locale.ENGLISH)); + public List selectByEmail(DbSession dbSession, String emailCaseInsensitive) { + return mapper(dbSession).selectByEmail(emailCaseInsensitive.toLowerCase(ENGLISH)); } @CheckForNull diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java index c83c98a739a..2ab7fe93dab 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java @@ -57,8 +57,7 @@ public interface UserMapper { List selectByIds(@Param("ids") List ids); - @CheckForNull - UserDto selectByEmail(String email); + List selectByEmail(String email); @CheckForNull UserDto selectByExternalIdAndIdentityProvider(@Param("externalId") String externalId, @Param("externalIdentityProvider") String externalExternalIdentityProvider); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java index 1ca0e6a9c01..f54f8923af8 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java @@ -585,12 +585,13 @@ public class UserDaoTest { @Test public void select_by_email() { - UserDto activeUser = db.users().insertUser(); + UserDto activeUser1 = db.users().insertUser(u -> u.setEmail("user1@email.com")); + UserDto activeUser2 = db.users().insertUser(u -> u.setEmail("user1@email.com")); UserDto disableUser = db.users().insertUser(u -> u.setActive(false)); - assertThat(underTest.selectByEmail(session, activeUser.getEmail())).isNotNull(); - assertThat(underTest.selectByEmail(session, disableUser.getEmail())).isNull(); - assertThat(underTest.selectByEmail(session, "unknown")).isNull(); + assertThat(underTest.selectByEmail(session, "user1@email.com")).hasSize(2); + assertThat(underTest.selectByEmail(session, disableUser.getEmail())).isEmpty(); + assertThat(underTest.selectByEmail(session, "unknown")).isEmpty(); } @Test diff --git a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/GroupTester.java b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/GroupTester.java index 4b369210c88..3e1c0cbee85 100644 --- a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/GroupTester.java +++ b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/GroupTester.java @@ -70,11 +70,11 @@ public class GroupTester { return response.getGroupsList(); } - public GroupTester addMemberToGroups(Organizations.Organization organization, String userLogin, String... groups) { + public GroupTester addMemberToGroups(@Nullable Organizations.Organization organization, String userLogin, String... groups) { for (String group : groups) { AddUserRequest request = new AddUserRequest() .setLogin(userLogin) - .setOrganization(organization.getKey()) + .setOrganization(organization != null ? organization.getKey() : null) .setName(group); session.wsClient().userGroups().addUser(request); } diff --git a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/LoginPage.java b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/LoginPage.java index 3f765429d47..154386aacb0 100644 --- a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/LoginPage.java +++ b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/LoginPage.java @@ -45,6 +45,11 @@ public class LoginPage { return Selenide.page(Navigation.class); } + public Navigation useBaseAuth() { + Selenide.$(".oauth-providers a").click(); + return Selenide.page(Navigation.class); + } + public LoginPage submitWrongCredentials(String login, String password) { Selenide.$("#login").val(login); Selenide.$("#password").val(password); 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 7cc3041bdd3..dfc64b840e0 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 @@ -51,7 +51,7 @@ public class AuthenticationModule extends Module { RealmAuthenticator.class, BasicAuthenticator.class, ValidateAction.class, - SsoAuthenticator.class, + HttpHeadersAuthenticator.class, AuthenticatorsImpl.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticatorsImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticatorsImpl.java index 358b149f787..a1bd581afa5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticatorsImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticatorsImpl.java @@ -28,19 +28,19 @@ public class AuthenticatorsImpl implements Authenticators { private final JwtHttpHandler jwtHttpHandler; private final BasicAuthenticator basicAuthenticator; - private final SsoAuthenticator ssoAuthenticator; + private final HttpHeadersAuthenticator httpHeadersAuthenticator; - public AuthenticatorsImpl(JwtHttpHandler jwtHttpHandler, BasicAuthenticator basicAuthenticator, SsoAuthenticator ssoAuthenticator) { + public AuthenticatorsImpl(JwtHttpHandler jwtHttpHandler, BasicAuthenticator basicAuthenticator, HttpHeadersAuthenticator httpHeadersAuthenticator) { this.jwtHttpHandler = jwtHttpHandler; this.basicAuthenticator = basicAuthenticator; - this.ssoAuthenticator = ssoAuthenticator; + this.httpHeadersAuthenticator = httpHeadersAuthenticator; } // Try first to authenticate from SSO, then JWT token, then try from basic http header @Override public Optional authenticate(HttpServletRequest request, HttpServletResponse response) { // SSO authentication should come first in order to update JWT if user from header is not the same is user from JWT - Optional user = ssoAuthenticator.authenticate(request, response); + Optional user = httpHeadersAuthenticator.authenticate(request, response); if (user.isPresent()) { return user; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthenticator.java new file mode 100644 index 00000000000..5282b62bd16 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthenticator.java @@ -0,0 +1,216 @@ +/* + * 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.base.Splitter; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import javax.annotation.CheckForNull; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.sonar.api.Startable; +import org.sonar.api.config.Configuration; +import org.sonar.api.server.authentication.Display; +import org.sonar.api.server.authentication.IdentityProvider; +import org.sonar.api.server.authentication.UserIdentity; +import org.sonar.api.utils.System2; +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.event.AuthenticationEvent; +import org.sonar.server.authentication.event.AuthenticationEvent.Source; +import org.sonar.server.authentication.event.AuthenticationException; +import org.sonar.server.exceptions.BadRequestException; + +import static org.apache.commons.lang.time.DateUtils.addMinutes; +import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_EMAIL_HEADER; +import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_ENABLE; +import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_GROUPS_HEADER; +import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_LOGIN_HEADER; +import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_NAME_HEADER; +import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES; +import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; + +public class HttpHeadersAuthenticator implements Startable { + + private static final Logger LOG = Loggers.get(HttpHeadersAuthenticator.class); + + private static final Splitter COMA_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings(); + + private static final String LAST_REFRESH_TIME_TOKEN_PARAM = "ssoLastRefreshTime"; + + private static final EnumSet SETTINGS = EnumSet.of( + SONAR_WEB_SSO_LOGIN_HEADER, + SONAR_WEB_SSO_NAME_HEADER, + SONAR_WEB_SSO_EMAIL_HEADER, + SONAR_WEB_SSO_GROUPS_HEADER, + SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES); + + private final System2 system2; + private final Configuration config; + private final UserIdentityAuthenticator userIdentityAuthenticator; + private final JwtHttpHandler jwtHttpHandler; + private final AuthenticationEvent authenticationEvent; + + private boolean enabled = false; + private Map settingsByKey = new HashMap<>(); + + public HttpHeadersAuthenticator(System2 system2, Configuration config, UserIdentityAuthenticator userIdentityAuthenticator, + JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) { + this.system2 = system2; + this.config = config; + this.userIdentityAuthenticator = userIdentityAuthenticator; + this.jwtHttpHandler = jwtHttpHandler; + this.authenticationEvent = authenticationEvent; + } + + @Override + public void start() { + 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()))); + } + } + + @Override + public void stop() { + // Nothing to do + } + + public Optional authenticate(HttpServletRequest request, HttpServletResponse response) { + try { + return doAuthenticate(request, response); + } catch (BadRequestException e) { + throw AuthenticationException.newBuilder() + .setSource(Source.sso()) + .setMessage(e.getMessage()) + .build(); + } + } + + private Optional doAuthenticate(HttpServletRequest request, HttpServletResponse response) { + if (!enabled) { + return Optional.empty(); + } + Map headerValuesByNames = getHeaders(request); + String login = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_LOGIN_HEADER.getKey()); + if (login == null) { + return Optional.empty(); + } + Optional user = getUserFromToken(request, response); + if (user.isPresent() && login.equals(user.get().getLogin())) { + return user; + } + + UserDto userDto = doAuthenticate(headerValuesByNames, login); + jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response); + authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.sso()); + return Optional.of(userDto); + } + + private Optional getUserFromToken(HttpServletRequest request, HttpServletResponse response) { + Optional token = jwtHttpHandler.getToken(request, response); + if (!token.isPresent()) { + return Optional.empty(); + } + Date now = new Date(system2.now()); + int refreshIntervalInMinutes = Integer.parseInt(settingsByKey.get(SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES.getKey())); + Long lastFreshTime = (Long) token.get().getProperties().get(LAST_REFRESH_TIME_TOKEN_PARAM); + if (lastFreshTime == null || now.after(addMinutes(new Date(lastFreshTime), refreshIntervalInMinutes))) { + return Optional.empty(); + } + return Optional.of(token.get().getUserDto()); + } + + private UserDto doAuthenticate(Map headerValuesByNames, String login) { + String name = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_NAME_HEADER.getKey()); + String email = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_EMAIL_HEADER.getKey()); + UserIdentity.Builder userIdentityBuilder = UserIdentity.builder() + .setLogin(login) + .setName(name == null ? login : name) + .setEmail(email) + .setProviderLogin(login); + if (hasHeader(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey())) { + 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() + .setUserIdentity(userIdentityBuilder.build()) + .setProvider(new SsoIdentityProvider()) + .setSource(Source.sso()) + .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) + .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) + .build()); + } + + @CheckForNull + private String getHeaderValue(Map headerValuesByNames, String settingKey) { + return headerValuesByNames.get(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH)); + } + + private static Map getHeaders(HttpServletRequest request) { + Map headers = new HashMap<>(); + Collections.list(request.getHeaderNames()).forEach(header -> headers.put(header.toLowerCase(Locale.ENGLISH), request.getHeader(header))); + return headers; + } + + private boolean hasHeader(Map headerValuesByNames, String settingKey) { + return headerValuesByNames.keySet().contains(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH)); + } + + private static class SsoIdentityProvider implements IdentityProvider { + @Override + public String getKey() { + return SQ_AUTHORITY; + } + + @Override + public String getName() { + return getKey(); + } + + @Override + public Display getDisplay() { + return null; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean allowsUsersToSignUp() { + return true; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java deleted file mode 100644 index 8d59e944228..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java +++ /dev/null @@ -1,216 +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.base.Splitter; -import com.google.common.collect.ImmutableMap; -import java.util.Collections; -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import javax.annotation.CheckForNull; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.Startable; -import org.sonar.api.config.Configuration; -import org.sonar.api.server.authentication.Display; -import org.sonar.api.server.authentication.IdentityProvider; -import org.sonar.api.server.authentication.UserIdentity; -import org.sonar.api.utils.System2; -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.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; -import org.sonar.server.authentication.event.AuthenticationException; -import org.sonar.server.exceptions.BadRequestException; - -import static org.apache.commons.lang.time.DateUtils.addMinutes; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_EMAIL_HEADER; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_ENABLE; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_GROUPS_HEADER; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_LOGIN_HEADER; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_NAME_HEADER; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES; -import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; - -public class SsoAuthenticator implements Startable { - - private static final Logger LOG = Loggers.get(SsoAuthenticator.class); - - private static final Splitter COMA_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings(); - - private static final String LAST_REFRESH_TIME_TOKEN_PARAM = "ssoLastRefreshTime"; - - private static final EnumSet SETTINGS = EnumSet.of( - SONAR_WEB_SSO_LOGIN_HEADER, - SONAR_WEB_SSO_NAME_HEADER, - SONAR_WEB_SSO_EMAIL_HEADER, - SONAR_WEB_SSO_GROUPS_HEADER, - SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES); - - private final System2 system2; - private final Configuration config; - private final UserIdentityAuthenticator userIdentityAuthenticator; - private final JwtHttpHandler jwtHttpHandler; - private final AuthenticationEvent authenticationEvent; - - private boolean enabled = false; - private Map settingsByKey = new HashMap<>(); - - public SsoAuthenticator(System2 system2, Configuration config, UserIdentityAuthenticator userIdentityAuthenticator, - JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) { - this.system2 = system2; - this.config = config; - this.userIdentityAuthenticator = userIdentityAuthenticator; - this.jwtHttpHandler = jwtHttpHandler; - this.authenticationEvent = authenticationEvent; - } - - @Override - public void start() { - if (config.getBoolean(SONAR_WEB_SSO_ENABLE.getKey()).orElse(false)) { - LOG.info("SSO Authentication enabled"); - enabled = true; - SETTINGS.forEach(entry -> settingsByKey.put(entry.getKey(), config.get(entry.getKey()).orElse(entry.getDefaultValue()))); - } - } - - @Override - public void stop() { - // Nothing to do - } - - public Optional authenticate(HttpServletRequest request, HttpServletResponse response) { - try { - return doAuthenticate(request, response); - } catch (BadRequestException e) { - throw AuthenticationException.newBuilder() - .setSource(Source.sso()) - .setMessage(e.getMessage()) - .build(); - } - } - - private Optional doAuthenticate(HttpServletRequest request, HttpServletResponse response) { - if (!enabled) { - return Optional.empty(); - } - Map headerValuesByNames = getHeaders(request); - String login = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_LOGIN_HEADER.getKey()); - if (login == null) { - return Optional.empty(); - } - Optional user = getUserFromToken(request, response); - if (user.isPresent() && login.equals(user.get().getLogin())) { - return user; - } - - UserDto userDto = doAuthenticate(headerValuesByNames, login); - jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response); - authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.sso()); - return Optional.of(userDto); - } - - private Optional getUserFromToken(HttpServletRequest request, HttpServletResponse response) { - Optional token = jwtHttpHandler.getToken(request, response); - if (!token.isPresent()) { - return Optional.empty(); - } - Date now = new Date(system2.now()); - int refreshIntervalInMinutes = Integer.parseInt(settingsByKey.get(SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES.getKey())); - Long lastFreshTime = (Long) token.get().getProperties().get(LAST_REFRESH_TIME_TOKEN_PARAM); - if (lastFreshTime == null || now.after(addMinutes(new Date(lastFreshTime), refreshIntervalInMinutes))) { - return Optional.empty(); - } - return Optional.of(token.get().getUserDto()); - } - - private UserDto doAuthenticate(Map headerValuesByNames, String login) { - String name = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_NAME_HEADER.getKey()); - String email = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_EMAIL_HEADER.getKey()); - UserIdentity.Builder userIdentityBuilder = UserIdentity.builder() - .setLogin(login) - .setName(name == null ? login : name) - .setEmail(email) - .setProviderLogin(login); - if (hasHeader(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey())) { - 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() - .setUserIdentity(userIdentityBuilder.build()) - .setProvider(new SsoIdentityProvider()) - .setSource(Source.sso()) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) - .build()); - } - - @CheckForNull - private String getHeaderValue(Map headerValuesByNames, String settingKey) { - return headerValuesByNames.get(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH)); - } - - private static Map getHeaders(HttpServletRequest request) { - Map headers = new HashMap<>(); - Collections.list(request.getHeaderNames()).forEach(header -> headers.put(header.toLowerCase(Locale.ENGLISH), request.getHeader(header))); - return headers; - } - - private boolean hasHeader(Map headerValuesByNames, String settingKey) { - return headerValuesByNames.keySet().contains(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH)); - } - - private static class SsoIdentityProvider implements IdentityProvider { - @Override - public String getKey() { - return SQ_AUTHORITY; - } - - @Override - public String getName() { - return getKey(); - } - - @Override - public Display getDisplay() { - return null; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public boolean allowsUsersToSignUp() { - return true; - } - } -} 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 index 966f0d97e63..a5247b533b9 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -135,7 +136,15 @@ public class UserIdentityAuthenticatorImpl implements UserIdentityAuthenticator if (email == null) { return Optional.empty(); } - UserDto existingUser = dbClient.userDao().selectByEmail(dbSession, email); + 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(), authenticatorParameters.getUserIdentity().getProviderId()) @@ -151,14 +160,7 @@ public class UserIdentityAuthenticatorImpl implements UserIdentityAuthenticator case WARN: throw new EmailAlreadyExistsRedirectionException(email, existingUser, authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider()); case FORBID: - throw AuthenticationException.newBuilder() - .setSource(authenticatorParameters.getSource()) - .setLogin(authenticatorParameters.getUserIdentity().getLogin()) - .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(); + throw generateExistingEmailError(authenticatorParameters, email); default: throw new IllegalStateException(format("Unknown strategy %s", existingEmailStrategy)); } @@ -264,4 +266,15 @@ public class UserIdentityAuthenticatorImpl implements UserIdentityAuthenticator 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().getLogin()) + .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(); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticatorsImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticatorsImplTest.java index 0924ad34d15..492fb8bfa0b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticatorsImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticatorsImplTest.java @@ -40,12 +40,12 @@ public class AuthenticatorsImplTest { private HttpServletResponse response = mock(HttpServletResponse.class); private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); private BasicAuthenticator basicAuthenticator = mock(BasicAuthenticator.class); - private SsoAuthenticator ssoAuthenticator = mock(SsoAuthenticator.class); - private Authenticators underTest = new AuthenticatorsImpl(jwtHttpHandler, basicAuthenticator, ssoAuthenticator); + private HttpHeadersAuthenticator httpHeadersAuthenticator = mock(HttpHeadersAuthenticator.class); + private Authenticators underTest = new AuthenticatorsImpl(jwtHttpHandler, basicAuthenticator, httpHeadersAuthenticator); @Test public void authenticate_from_jwt_token() { - when(ssoAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); + when(httpHeadersAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.of(user)); assertThat(underTest.authenticate(request, response)).hasValue(user); @@ -55,7 +55,7 @@ public class AuthenticatorsImplTest { @Test public void authenticate_from_basic_header() { when(basicAuthenticator.authenticate(request)).thenReturn(Optional.of(user)); - when(ssoAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); + when(httpHeadersAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); assertThat(underTest.authenticate(request, response)).hasValue(user); @@ -67,12 +67,12 @@ public class AuthenticatorsImplTest { @Test public void authenticate_from_sso() { - when(ssoAuthenticator.authenticate(request, response)).thenReturn(Optional.of(user)); + when(httpHeadersAuthenticator.authenticate(request, response)).thenReturn(Optional.of(user)); when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); assertThat(underTest.authenticate(request, response)).hasValue(user); - verify(ssoAuthenticator).authenticate(request, response); + verify(httpHeadersAuthenticator).authenticate(request, response); verify(jwtHttpHandler, never()).validateToken(request, response); verify(response, never()).setStatus(anyInt()); } @@ -80,7 +80,7 @@ public class AuthenticatorsImplTest { @Test public void return_empty_if_not_authenticated() { when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); - when(ssoAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); + when(httpHeadersAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); when(basicAuthenticator.authenticate(request)).thenReturn(Optional.empty()); assertThat(underTest.authenticate(request, response)).isEmpty(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticatorTest.java new file mode 100644 index 00000000000..cc604880c26 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticatorTest.java @@ -0,0 +1,457 @@ +/* + * 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.ImmutableMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbTester; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.authentication.event.AuthenticationEvent; +import org.sonar.server.authentication.event.AuthenticationEvent.Source; +import org.sonar.server.es.EsTester; +import org.sonar.server.organization.DefaultOrganizationProvider; +import org.sonar.server.organization.OrganizationUpdater; +import org.sonar.server.organization.TestDefaultOrganizationProvider; +import org.sonar.server.organization.TestOrganizationFlags; +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 java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.rules.ExpectedException.none; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; + +public class HttpHeadersAuthenticatorTest { + + private MapSettings settings = new MapSettings(); + + @Rule + public ExpectedException expectedException = none(); + @Rule + public DbTester db = DbTester.create(new AlwaysIncreasingSystem2()); + @Rule + public EsTester es = EsTester.create(); + + private static final String DEFAULT_LOGIN = "john"; + private static final String DEFAULT_NAME = "John"; + private static final String DEFAULT_EMAIL = "john@doo.com"; + private static final String GROUP1 = "dev"; + private static final String GROUP2 = "admin"; + private static final String GROUPS = GROUP1 + "," + GROUP2; + + private static final Long NOW = 1_000_000L; + private static final Long CLOSE_REFRESH_TIME = NOW - 1_000L; + + private static final UserDto DEFAULT_USER = newUserDto() + .setLogin(DEFAULT_LOGIN) + .setName(DEFAULT_NAME) + .setEmail(DEFAULT_EMAIL) + .setExternalLogin(DEFAULT_LOGIN) + .setExternalIdentityProvider("sonarqube"); + + private GroupDto group1; + private GroupDto group2; + private GroupDto sonarUsers; + + private System2 system2 = mock(System2.class); + private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class); + private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); + private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); + private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient()); + + private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); + private UserIdentityAuthenticatorImpl userIdentityAuthenticator = new UserIdentityAuthenticatorImpl( + db.getDbClient(), + new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider, organizationUpdater, + new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication), + defaultOrganizationProvider, organizationFlags, mock(OrganizationUpdater.class), new DefaultGroupFinder(db.getDbClient())); + + private HttpServletResponse response = mock(HttpServletResponse.class); + private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); + private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); + + private HttpHeadersAuthenticator underTest = new HttpHeadersAuthenticator(system2, settings.asConfig(), userIdentityAuthenticator, jwtHttpHandler, authenticationEvent); + + @Before + public void setUp() throws Exception { + when(system2.now()).thenReturn(NOW); + group1 = db.users().insertGroup(db.getDefaultOrganization(), GROUP1); + group2 = db.users().insertGroup(db.getDefaultOrganization(), GROUP2); + sonarUsers = db.users().insertDefaultGroup(db.getDefaultOrganization(), "sonar-users"); + } + + @Test + public void create_user_when_authenticating_new_user() { + startWithSso(); + setNotUserInToken(); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS); + + underTest.authenticate(request, response); + + verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); + verifyTokenIsUpdated(NOW); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void use_login_when_name_is_not_provided() { + startWithSso(); + setNotUserInToken(); + + HttpServletRequest request = createRequest(DEFAULT_LOGIN, null, null, null); + underTest.authenticate(request, response); + + verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, sonarUsers); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void update_user_when_authenticating_exiting_user() { + startWithSso(); + setNotUserInToken(); + insertUser(newUserDto().setLogin(DEFAULT_LOGIN).setName("old name").setEmail("old email"), group1); + // Name, email and groups are different + HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUP2); + + underTest.authenticate(request, response); + + verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group2); + verifyTokenIsUpdated(NOW); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void remove_groups_when_group_headers_is_empty() { + startWithSso(); + setNotUserInToken(); + insertUser(DEFAULT_USER, group1); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, ""); + + underTest.authenticate(request, response); + + verityUserHasNoGroup(DEFAULT_LOGIN); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void remove_groups_when_group_headers_is_null() { + startWithSso(); + setNotUserInToken(); + insertUser(DEFAULT_USER, group1); + Map headerValuesByName = new HashMap<>(); + headerValuesByName.put("X-Forwarded-Login", DEFAULT_LOGIN); + headerValuesByName.put("X-Forwarded-Groups", null); + HttpServletRequest request = createRequest(headerValuesByName); + + underTest.authenticate(request, response); + + verityUserHasNoGroup(DEFAULT_LOGIN); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void does_not_update_groups_when_no_group_headers() { + startWithSso(); + setNotUserInToken(); + insertUser(DEFAULT_USER, group1, sonarUsers); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, null); + + underTest.authenticate(request, response); + + verityUserGroups(DEFAULT_LOGIN, group1, sonarUsers); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void does_not_update_user_when_user_is_in_token_and_refresh_time_is_close() { + startWithSso(); + UserDto user = insertUser(DEFAULT_USER, group1); + setUserInToken(user, CLOSE_REFRESH_TIME); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); + + underTest.authenticate(request, response); + + // User is not updated + verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1); + verifyTokenIsNotUpdated(); + verifyZeroInteractions(authenticationEvent); + } + + @Test + public void update_user_when_user_in_token_but_refresh_time_is_old() { + startWithSso(); + UserDto user = insertUser(DEFAULT_USER, group1); + // Refresh time was updated 6 minutes ago => more than 5 minutes + setUserInToken(user, NOW - 6 * 60 * 1000L); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); + + underTest.authenticate(request, response); + + // User is updated + verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2); + verifyTokenIsUpdated(NOW); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void update_user_when_user_in_token_but_no_refresh_time() { + startWithSso(); + UserDto user = insertUser(DEFAULT_USER, group1); + setUserInToken(user, null); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); + + underTest.authenticate(request, response); + + // User is updated + verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2); + verifyTokenIsUpdated(NOW); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void use_refresh_time_from_settings() { + settings.setProperty("sonar.web.sso.refreshIntervalInMinutes", "10"); + startWithSso(); + UserDto user = insertUser(DEFAULT_USER, group1); + // Refresh time was updated 6 minutes ago => less than 10 minutes ago so not updated + setUserInToken(user, NOW - 6 * 60 * 1000L); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); + + underTest.authenticate(request, response); + + // User is not updated + verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1); + verifyTokenIsNotUpdated(); + verifyZeroInteractions(authenticationEvent); + } + + @Test + public void update_user_when_login_from_token_is_different_than_login_from_request() { + startWithSso(); + insertUser(DEFAULT_USER, group1); + setUserInToken(DEFAULT_USER, CLOSE_REFRESH_TIME); + HttpServletRequest request = createRequest("AnotherLogin", "Another name", "Another email", GROUP2); + + underTest.authenticate(request, response); + + verifyUserInDb("AnotherLogin", "Another name", "Another email", group2, sonarUsers); + verifyTokenIsUpdated(NOW); + verify(authenticationEvent).loginSuccess(request, "AnotherLogin", Source.sso()); + } + + @Test + public void use_headers_from_settings() { + settings.setProperty("sonar.web.sso.loginHeader", "head-login"); + settings.setProperty("sonar.web.sso.nameHeader", "head-name"); + settings.setProperty("sonar.web.sso.emailHeader", "head-email"); + settings.setProperty("sonar.web.sso.groupsHeader", "head-groups"); + startWithSso(); + setNotUserInToken(); + HttpServletRequest request = createRequest(ImmutableMap.of("head-login", DEFAULT_LOGIN, "head-name", DEFAULT_NAME, "head-email", DEFAULT_EMAIL, "head-groups", GROUPS)); + + underTest.authenticate(request, response); + + verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void detect_group_header_even_with_wrong_case() { + settings.setProperty("sonar.web.sso.loginHeader", "login"); + settings.setProperty("sonar.web.sso.nameHeader", "name"); + settings.setProperty("sonar.web.sso.emailHeader", "email"); + settings.setProperty("sonar.web.sso.groupsHeader", "Groups"); + startWithSso(); + setNotUserInToken(); + HttpServletRequest request = createRequest(ImmutableMap.of("login", DEFAULT_LOGIN, "name", DEFAULT_NAME, "email", DEFAULT_EMAIL, "groups", GROUPS)); + + underTest.authenticate(request, response); + + verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void trim_groups() { + startWithSso(); + setNotUserInToken(); + HttpServletRequest request = createRequest(DEFAULT_LOGIN, null, null, " dev , admin "); + + underTest.authenticate(request, response); + + verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, group1, group2, sonarUsers); + verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); + } + + @Test + public void does_not_authenticate_when_no_header() { + startWithSso(); + setNotUserInToken(); + + underTest.authenticate(createRequest(Collections.emptyMap()), response); + + verifyUserNotAuthenticated(); + verifyTokenIsNotUpdated(); + verifyZeroInteractions(authenticationEvent); + } + + @Test + public void does_not_authenticate_when_not_enabled() { + startWithoutSso(); + + underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); + + verifyUserNotAuthenticated(); + verifyZeroInteractions(jwtHttpHandler, authenticationEvent); + } + + @Test + public void throw_AuthenticationException_when_BadRequestException_is_generated() { + startWithSso(); + setNotUserInToken(); + + expectedException.expect(authenticationException().from(Source.sso()).withoutLogin().andNoPublicMessage()); + expectedException.expectMessage("Use only letters, numbers, and .-_@ please."); + try { + underTest.authenticate(createRequest("invalid login", DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); + } finally { + verifyZeroInteractions(authenticationEvent); + } + } + + private void startWithSso() { + settings.setProperty("sonar.web.sso.enable", true); + underTest.start(); + } + + private void startWithoutSso() { + settings.setProperty("sonar.web.sso.enable", false); + underTest.start(); + } + + private void setUserInToken(UserDto user, @Nullable Long lastRefreshTime) { + when(jwtHttpHandler.getToken(any(HttpServletRequest.class), any(HttpServletResponse.class))) + .thenReturn(Optional.of(new JwtHttpHandler.Token( + user, + lastRefreshTime == null ? Collections.emptyMap() : ImmutableMap.of("ssoLastRefreshTime", lastRefreshTime)))); + } + + private void setNotUserInToken() { + when(jwtHttpHandler.getToken(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(Optional.empty()); + } + + private UserDto insertUser(UserDto user, GroupDto... groups) { + db.users().insertUser(user); + stream(groups).forEach(group -> db.users().insertMember(group, user)); + db.commit(); + return user; + } + + private static HttpServletRequest createRequest(Map headerValuesByName) { + HttpServletRequest request = mock(HttpServletRequest.class); + setHeaders(request, headerValuesByName); + return request; + } + + private static HttpServletRequest createRequest(String login, @Nullable String name, @Nullable String email, @Nullable String groups) { + Map headerValuesByName = new HashMap<>(); + headerValuesByName.put("X-Forwarded-Login", login); + if (name != null) { + headerValuesByName.put("X-Forwarded-Name", name); + } + if (email != null) { + headerValuesByName.put("X-Forwarded-Email", email); + } + if (groups != null) { + headerValuesByName.put("X-Forwarded-Groups", groups); + } + HttpServletRequest request = mock(HttpServletRequest.class); + setHeaders(request, headerValuesByName); + return request; + } + + private static void setHeaders(HttpServletRequest request, Map valuesByName) { + valuesByName.entrySet().forEach(entry -> when(request.getHeader(entry.getKey())).thenReturn(entry.getValue())); + when(request.getHeaderNames()).thenReturn(Collections.enumeration(valuesByName.keySet())); + } + + private void verifyUserInDb(String expectedLogin, String expectedName, @Nullable String expectedEmail, GroupDto... expectedGroups) { + UserDto userDto = db.users().selectUserByLogin(expectedLogin).get(); + assertThat(userDto.isActive()).isTrue(); + assertThat(userDto.getName()).isEqualTo(expectedName); + assertThat(userDto.getEmail()).isEqualTo(expectedEmail); + assertThat(userDto.getExternalLogin()).isEqualTo(expectedLogin); + assertThat(userDto.getExternalIdentityProvider()).isEqualTo("sonarqube"); + verityUserGroups(expectedLogin, expectedGroups); + } + + private void verityUserGroups(String login, GroupDto... expectedGroups) { + UserDto userDto = db.users().selectUserByLogin(login).get(); + if (expectedGroups.length == 0) { + assertThat(db.users().selectGroupIdsOfUser(userDto)).isEmpty(); + } else { + assertThat(db.users().selectGroupIdsOfUser(userDto)).containsOnly(stream(expectedGroups).map(GroupDto::getId).collect(MoreCollectors.toList()).toArray(new Integer[] {})); + } + } + + private void verityUserHasNoGroup(String login) { + verityUserGroups(login); + } + + private void verifyUserNotAuthenticated() { + assertThat(db.countRowsOfTable(db.getSession(), "users")).isZero(); + verifyTokenIsNotUpdated(); + } + + private void verifyTokenIsUpdated(long refreshTime) { + verify(jwtHttpHandler).generateToken(any(UserDto.class), eq(ImmutableMap.of("ssoLastRefreshTime", refreshTime)), any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + + private void verifyTokenIsNotUpdated() { + verify(jwtHttpHandler, never()).generateToken(any(UserDto.class), anyMap(), any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java deleted file mode 100644 index 98d8b00a15c..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java +++ /dev/null @@ -1,457 +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.ImmutableMap; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbTester; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; -import org.sonar.server.es.EsTester; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.OrganizationUpdater; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; -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 java.util.Arrays.stream; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.rules.ExpectedException.none; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; -import static org.sonar.db.user.UserTesting.newUserDto; -import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; - -public class SsoAuthenticatorTest { - - private MapSettings settings = new MapSettings(); - - @Rule - public ExpectedException expectedException = none(); - @Rule - public DbTester db = DbTester.create(new AlwaysIncreasingSystem2()); - @Rule - public EsTester es = EsTester.create(); - - private static final String DEFAULT_LOGIN = "john"; - private static final String DEFAULT_NAME = "John"; - private static final String DEFAULT_EMAIL = "john@doo.com"; - private static final String GROUP1 = "dev"; - private static final String GROUP2 = "admin"; - private static final String GROUPS = GROUP1 + "," + GROUP2; - - private static final Long NOW = 1_000_000L; - private static final Long CLOSE_REFRESH_TIME = NOW - 1_000L; - - private static final UserDto DEFAULT_USER = newUserDto() - .setLogin(DEFAULT_LOGIN) - .setName(DEFAULT_NAME) - .setEmail(DEFAULT_EMAIL) - .setExternalLogin(DEFAULT_LOGIN) - .setExternalIdentityProvider("sonarqube"); - - private GroupDto group1; - private GroupDto group2; - private GroupDto sonarUsers; - - private System2 system2 = mock(System2.class); - private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient()); - - private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); - private UserIdentityAuthenticatorImpl userIdentityAuthenticator = new UserIdentityAuthenticatorImpl( - db.getDbClient(), - new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider, organizationUpdater, - new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication), - defaultOrganizationProvider, organizationFlags, mock(OrganizationUpdater.class), new DefaultGroupFinder(db.getDbClient())); - - private HttpServletResponse response = mock(HttpServletResponse.class); - private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - - private SsoAuthenticator underTest = new SsoAuthenticator(system2, settings.asConfig(), userIdentityAuthenticator, jwtHttpHandler, authenticationEvent); - - @Before - public void setUp() throws Exception { - when(system2.now()).thenReturn(NOW); - group1 = db.users().insertGroup(db.getDefaultOrganization(), GROUP1); - group2 = db.users().insertGroup(db.getDefaultOrganization(), GROUP2); - sonarUsers = db.users().insertDefaultGroup(db.getDefaultOrganization(), "sonar-users"); - } - - @Test - public void create_user_when_authenticating_new_user() { - startWithSso(); - setNotUserInToken(); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void use_login_when_name_is_not_provided() { - startWithSso(); - setNotUserInToken(); - - HttpServletRequest request = createRequest(DEFAULT_LOGIN, null, null, null); - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void update_user_when_authenticating_exiting_user() { - startWithSso(); - setNotUserInToken(); - insertUser(newUserDto().setLogin(DEFAULT_LOGIN).setName("old name").setEmail("old email"), group1); - // Name, email and groups are different - HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUP2); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group2); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void remove_groups_when_group_headers_is_empty() { - startWithSso(); - setNotUserInToken(); - insertUser(DEFAULT_USER, group1); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, ""); - - underTest.authenticate(request, response); - - verityUserHasNoGroup(DEFAULT_LOGIN); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void remove_groups_when_group_headers_is_null() { - startWithSso(); - setNotUserInToken(); - insertUser(DEFAULT_USER, group1); - Map headerValuesByName = new HashMap<>(); - headerValuesByName.put("X-Forwarded-Login", DEFAULT_LOGIN); - headerValuesByName.put("X-Forwarded-Groups", null); - HttpServletRequest request = createRequest(headerValuesByName); - - underTest.authenticate(request, response); - - verityUserHasNoGroup(DEFAULT_LOGIN); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void does_not_update_groups_when_no_group_headers() { - startWithSso(); - setNotUserInToken(); - insertUser(DEFAULT_USER, group1, sonarUsers); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, null); - - underTest.authenticate(request, response); - - verityUserGroups(DEFAULT_LOGIN, group1, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void does_not_update_user_when_user_is_in_token_and_refresh_time_is_close() { - startWithSso(); - UserDto user = insertUser(DEFAULT_USER, group1); - setUserInToken(user, CLOSE_REFRESH_TIME); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); - - underTest.authenticate(request, response); - - // User is not updated - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1); - verifyTokenIsNotUpdated(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void update_user_when_user_in_token_but_refresh_time_is_old() { - startWithSso(); - UserDto user = insertUser(DEFAULT_USER, group1); - // Refresh time was updated 6 minutes ago => more than 5 minutes - setUserInToken(user, NOW - 6 * 60 * 1000L); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); - - underTest.authenticate(request, response); - - // User is updated - verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void update_user_when_user_in_token_but_no_refresh_time() { - startWithSso(); - UserDto user = insertUser(DEFAULT_USER, group1); - setUserInToken(user, null); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); - - underTest.authenticate(request, response); - - // User is updated - verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void use_refresh_time_from_settings() { - settings.setProperty("sonar.web.sso.refreshIntervalInMinutes", "10"); - startWithSso(); - UserDto user = insertUser(DEFAULT_USER, group1); - // Refresh time was updated 6 minutes ago => less than 10 minutes ago so not updated - setUserInToken(user, NOW - 6 * 60 * 1000L); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); - - underTest.authenticate(request, response); - - // User is not updated - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1); - verifyTokenIsNotUpdated(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void update_user_when_login_from_token_is_different_than_login_from_request() { - startWithSso(); - insertUser(DEFAULT_USER, group1); - setUserInToken(DEFAULT_USER, CLOSE_REFRESH_TIME); - HttpServletRequest request = createRequest("AnotherLogin", "Another name", "Another email", GROUP2); - - underTest.authenticate(request, response); - - verifyUserInDb("AnotherLogin", "Another name", "Another email", group2, sonarUsers); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, "AnotherLogin", Source.sso()); - } - - @Test - public void use_headers_from_settings() { - settings.setProperty("sonar.web.sso.loginHeader", "head-login"); - settings.setProperty("sonar.web.sso.nameHeader", "head-name"); - settings.setProperty("sonar.web.sso.emailHeader", "head-email"); - settings.setProperty("sonar.web.sso.groupsHeader", "head-groups"); - startWithSso(); - setNotUserInToken(); - HttpServletRequest request = createRequest(ImmutableMap.of("head-login", DEFAULT_LOGIN, "head-name", DEFAULT_NAME, "head-email", DEFAULT_EMAIL, "head-groups", GROUPS)); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void detect_group_header_even_with_wrong_case() { - settings.setProperty("sonar.web.sso.loginHeader", "login"); - settings.setProperty("sonar.web.sso.nameHeader", "name"); - settings.setProperty("sonar.web.sso.emailHeader", "email"); - settings.setProperty("sonar.web.sso.groupsHeader", "Groups"); - startWithSso(); - setNotUserInToken(); - HttpServletRequest request = createRequest(ImmutableMap.of("login", DEFAULT_LOGIN, "name", DEFAULT_NAME, "email", DEFAULT_EMAIL, "groups", GROUPS)); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void trim_groups() { - startWithSso(); - setNotUserInToken(); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, null, null, " dev , admin "); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, group1, group2, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void does_not_authenticate_when_no_header() { - startWithSso(); - setNotUserInToken(); - - underTest.authenticate(createRequest(Collections.emptyMap()), response); - - verifyUserNotAuthenticated(); - verifyTokenIsNotUpdated(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void does_not_authenticate_when_not_enabled() { - startWithoutSso(); - - underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); - - verifyUserNotAuthenticated(); - verifyZeroInteractions(jwtHttpHandler, authenticationEvent); - } - - @Test - public void throw_AuthenticationException_when_BadRequestException_is_generated() { - startWithSso(); - setNotUserInToken(); - - expectedException.expect(authenticationException().from(Source.sso()).withoutLogin().andNoPublicMessage()); - expectedException.expectMessage("Use only letters, numbers, and .-_@ please."); - try { - underTest.authenticate(createRequest("invalid login", DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - private void startWithSso() { - settings.setProperty("sonar.web.sso.enable", true); - underTest.start(); - } - - private void startWithoutSso() { - settings.setProperty("sonar.web.sso.enable", false); - underTest.start(); - } - - private void setUserInToken(UserDto user, @Nullable Long lastRefreshTime) { - when(jwtHttpHandler.getToken(any(HttpServletRequest.class), any(HttpServletResponse.class))) - .thenReturn(Optional.of(new JwtHttpHandler.Token( - user, - lastRefreshTime == null ? Collections.emptyMap() : ImmutableMap.of("ssoLastRefreshTime", lastRefreshTime)))); - } - - private void setNotUserInToken() { - when(jwtHttpHandler.getToken(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(Optional.empty()); - } - - private UserDto insertUser(UserDto user, GroupDto... groups) { - db.users().insertUser(user); - stream(groups).forEach(group -> db.users().insertMember(group, user)); - db.commit(); - return user; - } - - private static HttpServletRequest createRequest(Map headerValuesByName) { - HttpServletRequest request = mock(HttpServletRequest.class); - setHeaders(request, headerValuesByName); - return request; - } - - private static HttpServletRequest createRequest(String login, @Nullable String name, @Nullable String email, @Nullable String groups) { - Map headerValuesByName = new HashMap<>(); - headerValuesByName.put("X-Forwarded-Login", login); - if (name != null) { - headerValuesByName.put("X-Forwarded-Name", name); - } - if (email != null) { - headerValuesByName.put("X-Forwarded-Email", email); - } - if (groups != null) { - headerValuesByName.put("X-Forwarded-Groups", groups); - } - HttpServletRequest request = mock(HttpServletRequest.class); - setHeaders(request, headerValuesByName); - return request; - } - - private static void setHeaders(HttpServletRequest request, Map valuesByName) { - valuesByName.entrySet().forEach(entry -> when(request.getHeader(entry.getKey())).thenReturn(entry.getValue())); - when(request.getHeaderNames()).thenReturn(Collections.enumeration(valuesByName.keySet())); - } - - private void verifyUserInDb(String expectedLogin, String expectedName, @Nullable String expectedEmail, GroupDto... expectedGroups) { - UserDto userDto = db.users().selectUserByLogin(expectedLogin).get(); - assertThat(userDto.isActive()).isTrue(); - assertThat(userDto.getName()).isEqualTo(expectedName); - assertThat(userDto.getEmail()).isEqualTo(expectedEmail); - assertThat(userDto.getExternalLogin()).isEqualTo(expectedLogin); - assertThat(userDto.getExternalIdentityProvider()).isEqualTo("sonarqube"); - verityUserGroups(expectedLogin, expectedGroups); - } - - private void verityUserGroups(String login, GroupDto... expectedGroups) { - UserDto userDto = db.users().selectUserByLogin(login).get(); - if (expectedGroups.length == 0) { - assertThat(db.users().selectGroupIdsOfUser(userDto)).isEmpty(); - } else { - assertThat(db.users().selectGroupIdsOfUser(userDto)).containsOnly(stream(expectedGroups).map(GroupDto::getId).collect(MoreCollectors.toList()).toArray(new Integer[] {})); - } - } - - private void verityUserHasNoGroup(String login) { - verityUserGroups(login); - } - - private void verifyUserNotAuthenticated() { - assertThat(db.countRowsOfTable(db.getSession(), "users")).isZero(); - verifyTokenIsNotUpdated(); - } - - private void verifyTokenIsUpdated(long refreshTime) { - verify(jwtHttpHandler).generateToken(any(UserDto.class), eq(ImmutableMap.of("ssoLastRefreshTime", refreshTime)), any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - private void verifyTokenIsNotUpdated() { - verify(jwtHttpHandler, never()).generateToken(any(UserDto.class), anyMap(), any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - -} 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 index fe778cb894d..c58afab14bc 100644 --- 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 @@ -277,10 +277,29 @@ public class UserIdentityAuthenticatorImplTest { @Test public void throw_AuthenticationException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_FORBID() { - db.users().insertUser(newUserDto() - .setLogin("Existing user with same email") - .setActive(true) - .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.getLogin()) + .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) diff --git a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java index d45b79800cc..b2a635e0703 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java @@ -37,9 +37,9 @@ import org.sonarqube.tests.settings.SettingsTestRestartingOrchestrator; import org.sonarqube.tests.startup.StartupIndexationTest; import org.sonarqube.tests.telemetry.TelemetryOptOutTest; import org.sonarqube.tests.telemetry.TelemetryUploadTest; +import org.sonarqube.tests.user.HttpHeadersAuthenticationTest; import org.sonarqube.tests.user.OnboardingTest; import org.sonarqube.tests.user.RealmAuthenticationTest; -import org.sonarqube.tests.user.SsoAuthenticationTest; import org.sonarqube.tests.user.UserEsResilienceTest; /** @@ -61,7 +61,7 @@ import org.sonarqube.tests.user.UserEsResilienceTest; // update center UpdateCenterTest.class, RealmAuthenticationTest.class, - SsoAuthenticationTest.class, + HttpHeadersAuthenticationTest.class, OnboardingTest.class, BuiltInQualityProfilesNotificationTest.class, ActiveRuleEsResilienceTest.class, diff --git a/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java b/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java index 4545bc08ecd..f9bfa51ce65 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java +++ b/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java @@ -19,14 +19,13 @@ */ package org.sonarqube.tests.user; +import com.codeborne.selenide.Condition; import com.google.common.base.Joiner; import com.sonar.orchestrator.Orchestrator; -import java.io.File; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.ClassRule; import org.junit.Rule; @@ -41,10 +40,13 @@ import org.sonarqube.ws.client.users.DeactivateRequest; import org.sonarqube.ws.client.users.GroupsRequest; import org.sonarqube.ws.client.users.SearchRequest; +import static com.codeborne.selenide.Selenide.$; +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.io.FileUtils.readFileToString; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.sonarqube.ws.UserGroups.Group; -import static util.selenium.Selenese.runSelenese; public class BaseIdentityProviderTest { @@ -105,8 +107,11 @@ public class BaseIdentityProviderTest { // As this property is null, the plugin will throw an exception tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", null); - runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html"); + tester.openBrowser() + .logIn() + .useBaseAuth(); + $("#bd").shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")); assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); } @@ -116,21 +121,45 @@ public class BaseIdentityProviderTest { setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); tester.users().generate(u -> u.setLogin("another").setName("Another").setEmail(USER_EMAIL).setPassword("another")); - runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_when_email_already_exists.html"); + tester.openBrowser() + .logIn() + .useBaseAuth(); - File logFile = ORCHESTRATOR.getServer().getWebLogs(); - assertThat(FileUtils.readFileToString(logFile)) + $("#bd").shouldHave(Condition.text( + 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.", USER_EMAIL))); + assertThat(readFileToString(ORCHESTRATOR.getServer().getWebLogs(), UTF_8)) .doesNotContain("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"); } + @Test + public void fail_to_authenticate_user_when_email_already_exists_on_several_users() { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); + tester.users().generate(u -> u.setEmail(USER_EMAIL)); + tester.users().generate(u -> u.setEmail(USER_EMAIL)); + + tester.openBrowser() + .logIn() + .useBaseAuth(); + + $("#bd").shouldHave(Condition.text( + 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.", USER_EMAIL))); + assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); + } + @Test public void fail_to_authenticate_when_not_allowed_to_sign_up() { enablePlugin(); setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.allowsUsersToSignUp", "false"); - runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html"); + tester.openBrowser() + .logIn() + .useBaseAuth(); + $("#bd") + .shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator.")) + .shouldHave(Condition.text(format("Reason: '%s' users are not allowed to sign up", FAKE_PROVIDER_KEY))); assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); } @@ -190,13 +219,16 @@ public class BaseIdentityProviderTest { setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "true"); - runSelenese(ORCHESTRATOR, - "/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); - - File logFile = ORCHESTRATOR.getServer().getWebLogs(); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException"); + tester.openBrowser() + .logIn() + .useBaseAuth(); + $("#bd") + .shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")) + .shouldHave(Condition.text("Reason: A functional error has happened")); + assertThat(readFileToString(ORCHESTRATOR.getServer().getWebLogs(), UTF_8)) + .doesNotContain("A functional error has happened") + .doesNotContain("UnauthorizedException"); assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); } diff --git a/tests/src/test/java/org/sonarqube/tests/user/HttpHeadersAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/HttpHeadersAuthenticationTest.java new file mode 100644 index 00000000000..300c60c0f60 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/HttpHeadersAuthenticationTest.java @@ -0,0 +1,191 @@ +/* + * 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.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import java.net.URLEncoder; +import java.util.List; +import javax.annotation.Nullable; +import okhttp3.Response; +import org.apache.commons.io.FileUtils; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.qa.util.Tester; +import org.sonarqube.ws.UserGroups.Group; +import org.sonarqube.ws.Users.CreateWsResponse.User; +import org.sonarqube.ws.Users.SearchWsResponse; +import org.sonarqube.ws.client.users.SearchRequest; + +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static util.ItUtils.call; +import static util.ItUtils.newOrchestratorBuilder; + +/** + * Test authentication using HTTP headers. + *

+ * It starts its own server as it's using a different authentication system + */ +public class HttpHeadersAuthenticationTest { + + private static final String LOGIN_HEADER = "H-Login"; + private static final String NAME_HEADER = "H-Name"; + private static final String EMAIL_HEADER = "H-Email"; + private static final String GROUPS_HEADER = "H-Groups"; + + @ClassRule + public static final Orchestrator orchestrator = newOrchestratorBuilder() + .setServerProperty("sonar.web.sso.enable", "true") + .setServerProperty("sonar.web.sso.loginHeader", LOGIN_HEADER) + .setServerProperty("sonar.web.sso.nameHeader", NAME_HEADER) + .setServerProperty("sonar.web.sso.emailHeader", EMAIL_HEADER) + .setServerProperty("sonar.web.sso.groupsHeader", GROUPS_HEADER) + .build(); + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void authenticate() { + String login = tester.users().generateLogin(); + + doCall(login, "Tester", "tester@email.com", null); + + verifyUser(login, "Tester", "tester@email.com"); + } + + @Test + public void authenticate_with_only_login() { + String login = tester.users().generateLogin(); + + doCall(login, null, null, null); + + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(login)).getUsersList()) + .extracting(SearchWsResponse.User::getLogin, SearchWsResponse.User::getName, SearchWsResponse.User::hasEmail) + .containsExactlyInAnyOrder(tuple(login, login, false)); + } + + @Test + public void update_user_when_headers_are_updated() { + String login = tester.users().generateLogin(); + doCall(login, "Tester", "tester@email.com", null); + verifyUser(login, "Tester", "tester@email.com"); + + // As we don't keep the JWT token is the test, the user is updated + doCall(login, "new name", "new email", null); + verifyUser(login, "new name", "new email"); + } + + @Test + public void authenticate_with_groups() { + String login = tester.users().generateLogin(); + Group group = tester.groups().generate(); + + doCall(login, null, null, group.getName()); + + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(login)).getUsersList()) + .extracting(SearchWsResponse.User::getLogin, u -> u.getGroups().getGroupsList()) + .containsExactlyInAnyOrder(tuple(login, asList(group.getName(), "sonar-users"))); + } + + @Test + public void synchronize_groups_when_authenticating_existing_user() { + User user = tester.users().generate(); + Group group1 = tester.groups().generate(); + Group group2 = tester.groups().generate(); + Group group3 = tester.groups().generate(); + tester.groups().addMemberToGroups(null, user.getLogin(), group1.getName(), group2.getName()); + + doCall(user.getLogin(), null, null, group2.getName() + "," + group3.getName()); + + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(user.getLogin())).getUsersList()) + .extracting(SearchWsResponse.User::getLogin, u -> u.getGroups().getGroupsList()) + .containsExactlyInAnyOrder(tuple(user.getLogin(), asList(group2.getName(), group3.getName(), "sonar-users"))); + } + + @Test + public void authentication_with_local_user_is_possible_when_no_header() { + User user = tester.users().generate(); + + // Check any ws, no error should be thrown + tester.as(user.getLogin(), user.getLogin()).wsClient().system().ping(); + } + + @Test + public void display_message_in_ui_but_not_in_log_when_unauthorized_exception() throws Exception { + String login = "invalid login $"; + Response response = doCall(login, null, null, null); + + assertThat(response.code()).isEqualTo(200); + assertThat(response.request().url().toString()).contains("sessions/unauthorized"); + + List logsLines = FileUtils.readLines(orchestrator.getServer().getWebLogs(), UTF_8); + assertThat(logsLines).doesNotContain("org.sonar.server.exceptions.BadRequestException: Use only letters, numbers, and .-_@ please."); + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(login)).getUsersList()).isEmpty(); + } + + @Test + public void fail_when_email_already_exists() throws Exception { + String login = tester.users().generateLogin(); + User existingUser = tester.users().generate(); + + Response response = doCall(login, "Tester", existingUser.getEmail(), null); + + String expectedError = 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", + existingUser.getEmail()); + assertThat(response.code()).isEqualTo(200); + assertThat(response.request().url().toString()).contains(URLEncoder.encode(expectedError, UTF_8.name())); + assertThat(FileUtils.readLines(orchestrator.getServer().getWebLogs(), UTF_8)).doesNotContain(expectedError); + } + + @Test + public void fail_to_authenticate_user_when_email_already_exists_on_several_users() throws Exception { + String login = tester.users().generateLogin(); + tester.users().generate(u -> u.setEmail("john@email.com")); + tester.users().generate(u -> u.setEmail("john@email.com")); + + Response response = doCall(login, "Tester", "john@email.com", null); + + String expectedError = "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"; + assertThat(response.code()).isEqualTo(200); + assertThat(response.request().url().toString()).contains(URLEncoder.encode(expectedError, UTF_8.name())); + assertThat(FileUtils.readLines(orchestrator.getServer().getWebLogs(), UTF_8)).doesNotContain(expectedError); + } + + private static Response doCall(String login, @Nullable String name, @Nullable String email, @Nullable String groups) { + return call(orchestrator.getServer().getUrl(), + LOGIN_HEADER, login, + NAME_HEADER, name, + EMAIL_HEADER, email, + GROUPS_HEADER, groups); + } + + private void verifyUser(String login, String name, String email) { + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(login)).getUsersList()) + .extracting(SearchWsResponse.User::getLogin, SearchWsResponse.User::getName, SearchWsResponse.User::getEmail, + SearchWsResponse.User::getExternalIdentity, SearchWsResponse.User::getExternalProvider, SearchWsResponse.User::getLocal) + .containsExactlyInAnyOrder(tuple(login, name, email, login, "sonarqube", false)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java b/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java index b7e194b3785..b6a74623aa1 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java +++ b/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java @@ -21,11 +21,9 @@ package org.sonarqube.tests.user; import com.codeborne.selenide.Condition; import com.sonar.orchestrator.Orchestrator; -import java.io.File; import java.net.HttpURLConnection; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; -import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; @@ -37,10 +35,12 @@ import org.sonarqube.ws.Users.SearchWsResponse.User; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.permissions.AddUserRequest; import org.sonarqube.ws.client.users.CreateRequest; -import util.selenium.Selenese; import static com.codeborne.selenide.Condition.visible; import static com.codeborne.selenide.Selenide.$; +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.io.FileUtils.readFileToString; import static org.assertj.core.api.Assertions.assertThat; /** @@ -137,8 +137,11 @@ public class OAuth2IdentityProviderTest { // As this property is null, the plugin will throw an exception tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", null); - Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html"); + tester.openBrowser() + .logIn() + .useOAuth2(); + $("#bd").shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")); assertThatUserDoesNotExist(USER_LOGIN); } @@ -148,8 +151,13 @@ public class OAuth2IdentityProviderTest { enablePlugin(); tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp", "false"); - Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html"); + tester.openBrowser() + .logIn() + .useOAuth2(); + $("#bd") + .shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator.")) + .shouldHave(Condition.text(format("Reason: '%s' users are not allowed to sign up", FAKE_PROVIDER_KEY))); assertThatUserDoesNotExist(USER_LOGIN); } @@ -159,12 +167,16 @@ public class OAuth2IdentityProviderTest { enablePlugin(); tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", "true"); - Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); - - File logFile = orchestrator.getServer().getWebLogs(); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException"); - + tester.openBrowser() + .logIn() + .useOAuth2(); + + $("#bd") + .shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")) + .shouldHave(Condition.text("Reason: A functional error has happened")); + assertThat(readFileToString(orchestrator.getServer().getWebLogs(), UTF_8)) + .doesNotContain("A functional error has happened") + .doesNotContain("UnauthorizedException"); assertThatUserDoesNotExist(USER_LOGIN); } @@ -205,6 +217,22 @@ public class OAuth2IdentityProviderTest { assertThat(tester.users().getByLogin("another").get().getEmail()).isNullOrEmpty(); } + @Test + public void fail_to_authenticate_user_when_email_already_exists_on_several_users() { + simulateRedirectionToCallback(); + enablePlugin(); + tester.users().generate(u -> u.setEmail(USER_EMAIL)); + tester.users().generate(u -> u.setEmail(USER_EMAIL)); + + tester.openBrowser() + .logIn() + .useOAuth2(); + + $("#bd").shouldHave(Condition.text( + 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.", USER_EMAIL))); + assertThatUserDoesNotExist(USER_LOGIN); + } + @Test public void provision_user_before_authentication() { simulateRedirectionToCallback(); diff --git a/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java index c733ffab7e6..e34e6371168 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java +++ b/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java @@ -21,43 +21,30 @@ package org.sonarqube.tests.user; import com.codeborne.selenide.Condition; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.sonar.orchestrator.Orchestrator; +import java.util.Collections; import java.util.Map; import javax.annotation.CheckForNull; -import org.junit.After; -import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.wsclient.Host; -import org.sonar.wsclient.Sonar; -import org.sonar.wsclient.base.HttpException; -import org.sonar.wsclient.connectors.HttpClient4Connector; -import org.sonar.wsclient.services.AuthenticationQuery; -import org.sonar.wsclient.user.UserParameters; import org.sonarqube.qa.util.Tester; import org.sonarqube.qa.util.pageobjects.Navigation; import org.sonarqube.qa.util.pageobjects.SystemInfoPage; import org.sonarqube.qa.util.pageobjects.UsersManagementPage; +import org.sonarqube.ws.UserGroups.Group; +import org.sonarqube.ws.Users; import org.sonarqube.ws.Users.SearchWsResponse.User; -import org.sonarqube.ws.client.GetRequest; -import org.sonarqube.ws.client.WsResponse; -import org.sonarqube.ws.client.users.CreateRequest; +import org.sonarqube.ws.client.users.ChangePasswordRequest; import org.sonarqube.ws.client.users.SearchRequest; -import util.user.UserRule; -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -import static org.junit.Assert.fail; +import static util.ItUtils.expectHttpError; import static util.ItUtils.newOrchestratorBuilder; -import static util.ItUtils.newUserWsClient; import static util.ItUtils.pluginArtifact; -import static util.ItUtils.resetSettings; -import static util.selenium.Selenese.runSelenese; /** * Test REALM authentication. @@ -66,72 +53,46 @@ import static util.selenium.Selenese.runSelenese; */ public class RealmAuthenticationTest { - private static final String TECH_USER = "techUser"; - private static final String USER_LOGIN = "tester"; - private static final String ADMIN_USER_LOGIN = "admin-user"; - @Rule public ExpectedException thrown = ExpectedException.none(); - /** - * Property from security-plugin for user management. - */ - private static final String USERS_PROPERTY = "sonar.fakeauthenticator.users"; - @ClassRule public static final Orchestrator orchestrator = newOrchestratorBuilder() .addPlugin(pluginArtifact("security-plugin")) .setServerProperty("sonar.security.realm", "FakeRealm") .build(); - @Rule - public UserRule userRule = UserRule.from(orchestrator); - @Rule public Tester tester = new Tester(orchestrator).disableOrganizations(); - @Before - @After - public void resetData() { - resetSettings(orchestrator, null, USERS_PROPERTY, "sonar.security.updateUserAttributes"); - } - - @Before - public void initAdminUser() { - userRule.createAdminUser(ADMIN_USER_LOGIN, ADMIN_USER_LOGIN); - } - - @After - public void deleteAdminUser() { - userRule.resetUsers(); - } - /** * SONAR-3137, SONAR-2292 * Restriction on password length (minimum 4 characters) should be disabled, when external system enabled. */ @Test - public void shouldSynchronizeDetailsAndGroups() { + public void synchronize_details_and_groups() { // Given clean Sonar installation and no users in external system - String username = USER_LOGIN; + String username = tester.users().generateLogin(); String password = "123"; - Map users = Maps.newHashMap(); + Group group = tester.groups().generate(); // When user created in external system - users.put(username + ".password", password); - users.put(username + ".name", "Tester Testerovich"); - users.put(username + ".email", "tester@example.org"); - users.put(username + ".groups", "sonar-user"); - updateUsersInExtAuth(users); + updateUsersInExtAuth(ImmutableMap.of( + username + ".password", password, + username + ".name", "Tester Testerovich", + username + ".email", "tester@example.org", + username + ".groups", group.getName())); + // Then verifyAuthenticationIsOk(username, password); - verifyUser(); - - // with external details and groups - runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html"); + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(username)).getUsersList()) + .extracting(User::getLogin, User::getName, User::getEmail, + User::getExternalIdentity, User::getExternalProvider, User::getLocal, u -> u.getGroups().getGroupsList()) + .containsExactlyInAnyOrder(tuple(username, "Tester Testerovich", "tester@example.org", username, "sonarqube", false, asList(group.getName(), "sonar-users"))); // SONAR-4462 - SystemInfoPage page = tester.openBrowser().logIn().submitCredentials(ADMIN_USER_LOGIN).openSystemInfo(); + Users.CreateWsResponse.User adminUser = tester.users().generateAdministrator(); + SystemInfoPage page = tester.openBrowser().logIn().submitCredentials(adminUser.getLogin()).openSystemInfo(); page.getCardItem("System").shouldHaveFieldWithValue("External User Authentication", "FakeRealm"); } @@ -139,56 +100,54 @@ public class RealmAuthenticationTest { * SONAR-4034 */ @Test - public void shouldUpdateDetailsByDefault() { + public void update_details_by_default() { // Given clean Sonar installation and no users in external system - String username = USER_LOGIN; + String username = tester.users().generateLogin(); String password = "123"; - Map users = Maps.newHashMap(); // When user created in external system - users.put(username + ".password", password); - users.put(username + ".name", "Tester Testerovich"); - users.put(username + ".email", "tester@example.org"); - users.put(username + ".groups", "sonar-user"); - updateUsersInExtAuth(users); + updateUsersInExtAuth(ImmutableMap.of( + username + ".password", password, + username + ".name", "Tester Testerovich", + username + ".email", "tester@example.org")); + // Then verifyAuthenticationIsOk(username, password); - verifyUser(); - - // with external details and groups - // TODO replace by WS ? Or with new Selenese utils - runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html"); + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(username)).getUsersList()) + .extracting(User::getLogin, User::getName, User::getEmail) + .containsExactlyInAnyOrder(tuple(username, "Tester Testerovich", "tester@example.org")); // Now update user details - users.put(username + ".name", "Tester2 Testerovich"); - users.put(username + ".email", "tester2@example.org"); - updateUsersInExtAuth(users); + updateUsersInExtAuth(ImmutableMap.of( + username + ".password", password, + username + ".name", "Tester2 Testerovich", + username + ".email", "tester2@example.org")); + // Then verifyAuthenticationIsOk(username, password); // with external details and groups updated - runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details2.html"); + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(username)).getUsersList()) + .extracting(User::getLogin, User::getName, User::getEmail) + .containsExactlyInAnyOrder(tuple(username, "Tester2 Testerovich", "tester2@example.org")); } /** * SONAR-3138 */ @Test - public void shouldNotFallback() { + public void does_not_fallback() { // Given clean Sonar installation and no users in external system - String login = USER_LOGIN; + String login = tester.users().generateLogin(); String password = "1234567"; - Map users = Maps.newHashMap(); // When user created in external system - users.put(login + ".password", password); - updateUsersInExtAuth(users); + updateUsersInExtAuth(ImmutableMap.of(login + ".password", password)); // Then verifyAuthenticationIsOk(login, password); // When external system does not work - users.remove(login + ".password"); - updateUsersInExtAuth(users); + updateUsersInExtAuth(Collections.emptyMap()); // Then verifyAuthenticationIsNotOk(login, password); } @@ -197,16 +156,14 @@ public class RealmAuthenticationTest { * SONAR-4543 */ @Test - public void adminIsLocalAccountByDefault() { + public void admin_is_local_account_by_default() { // Given clean Sonar installation and no users in external system String login = "admin"; String localPassword = "admin"; String remotePassword = "nimda"; - Map users = Maps.newHashMap(); // When admin created in external system with a different password - users.put(login + ".password", remotePassword); - updateUsersInExtAuth(users); + updateUsersInExtAuth(ImmutableMap.of(login + ".password", remotePassword)); // Then this is local DB that should be used verifyAuthenticationIsNotOk(login, remotePassword); @@ -217,52 +174,49 @@ public class RealmAuthenticationTest { * SONAR-1334, SONAR-3185 (createUsers=true is default) */ @Test - public void shouldCreateNewUsers() { + public void create_new_users() { // Given clean Sonar installation and no users in external system - String username = USER_LOGIN; + String username = tester.users().generateLogin(); String password = "1234567"; - Map users = Maps.newHashMap(); // When user not exists in external system // Then verifyAuthenticationIsNotOk(username, password); // When user created in external system - users.put(username + ".password", password); - updateUsersInExtAuth(users); + updateUsersInExtAuth(ImmutableMap.of(username + ".password", password)); // Then verifyAuthenticationIsOk(username, password); - verifyUser(); + verifyUser(username); verifyAuthenticationIsNotOk(username, "wrong"); } // SONAR-3258 @Test - public void shouldAutomaticallyReactivateDeletedUser() { + public void reactivate_deleted_user() { // Given clean Sonar installation and no users in external system + Users.CreateWsResponse.User adminUser = tester.users().generateAdministrator(); // Let's create and delete the user "tester" in Sonar DB + String login = tester.users().generateLogin(); Navigation nav = tester.openBrowser(); - UsersManagementPage page = nav.logIn().submitCredentials(ADMIN_USER_LOGIN).openUsersManagement(); + UsersManagementPage page = nav.logIn().submitCredentials(adminUser.getLogin()).openUsersManagement(); page - .createUser(USER_LOGIN) + .createUser(login) .hasUsersCount(3) - .getUser(USER_LOGIN) + .getUser(login) .deactivateUser(); page.hasUsersCount(2); nav.logOut() - .logIn().submitWrongCredentials(USER_LOGIN, USER_LOGIN) + .logIn().submitWrongCredentials(login, login) .getErrorMessage().shouldHave(Condition.text("Authentication failed")); // And now update the security with the user that was deleted - String login = USER_LOGIN; String password = "1234567"; - Map users = Maps.newHashMap(); - users.put(login + ".password", password); - updateUsersInExtAuth(users); + updateUsersInExtAuth(ImmutableMap.of(login + ".password", password)); // check that the deleted/deactivated user "tester" has been reactivated and can now log in verifyAuthenticationIsOk(login, password); - verifyUser(); + verifyUser(login); } /** @@ -271,24 +225,20 @@ public class RealmAuthenticationTest { @Test public void update_password_of_technical_user() { // Create user in external authentication - updateUsersInExtAuth(ImmutableMap.of(USER_LOGIN + ".password", USER_LOGIN)); - verifyAuthenticationIsOk(USER_LOGIN, USER_LOGIN); + String login = tester.users().generateLogin(); + updateUsersInExtAuth(ImmutableMap.of(login + ".password", login)); + verifyAuthenticationIsOk(login, login); // Create technical user in db - createUserInDb(TECH_USER, "old_password"); - assertThat(checkAuthenticationThroughWebService(TECH_USER, "old_password")).isTrue(); + Users.CreateWsResponse.User techUser = tester.users().generate(u -> u.setPassword("old_password")); + verifyAuthenticationIsOk(techUser.getLogin(), "old_password"); // Updating password of technical user is allowed - updateUserPasswordInDb(TECH_USER, "new_password"); - assertThat(checkAuthenticationThroughWebService(TECH_USER, "new_password")).isTrue(); + tester.users().service().changePassword(new ChangePasswordRequest().setLogin(techUser.getLogin()).setPassword("new_password")); + verifyAuthenticationIsOk(techUser.getLogin(), "new_password"); // But updating password of none local user is not allowed - try { - updateUserPasswordInDb(USER_LOGIN, "new_password"); - fail(); - } catch (HttpException e) { - verifyHttpException(e, 400); - } + expectHttpError(400, () -> tester.users().service().changePassword(new ChangePasswordRequest().setLogin(login).setPassword("new_password"))); } /** @@ -297,16 +247,14 @@ public class RealmAuthenticationTest { @Test public void authentication_with_ws() { // Given clean Sonar installation and no users in external system - String login = USER_LOGIN; + String login = tester.users().generateLogin(); String password = "1234567"; - Map users = Maps.newHashMap(); // When user created in external system - users.put(login + ".password", password); - updateUsersInExtAuth(users); + updateUsersInExtAuth(ImmutableMap.of(login + ".password", password)); verifyAuthenticationIsOk(login, password); - verifyUser(); + verifyUser(login); verifyAuthenticationIsNotOk("wrong", password); verifyAuthenticationIsNotOk(login, "wrong"); verifyAuthenticationIsNotOk(login, null); @@ -332,71 +280,54 @@ public class RealmAuthenticationTest { @Test public void provision_user_before_authentication() { - tester.wsClient().users().create(new CreateRequest() - .setLogin(USER_LOGIN) - .setName("Tester Testerovich") + Users.CreateWsResponse.User user = tester.users().generate(u -> u.setName("Tester Testerovich") .setEmail("tester@example.org") + .setPassword(null) .setLocal("false")); // The user is created in SonarQube but doesn't exist yet in external authentication system - verifyAuthenticationIsNotOk(USER_LOGIN, "123"); + verifyAuthenticationIsNotOk(user.getLogin(), "123"); updateUsersInExtAuth(ImmutableMap.of( - USER_LOGIN + ".password", "123", - USER_LOGIN + ".name", "Tester Testerovich", - USER_LOGIN + ".email", "tester@example.org")); + user.getLogin() + ".password", "123", + user.getLogin() + ".name", "Tester Testerovich", + user.getLogin() + ".email", "tester@example.org")); - verifyAuthenticationIsOk(USER_LOGIN, "123"); - verifyUser(); + verifyAuthenticationIsOk(user.getLogin(), "123"); + verifyUser(user.getLogin()); } @Test public void fail_to_authenticate_user_when_email_already_exists() { - userRule.createUser("another", "Another", "tester@example.org", "another"); - - String username = USER_LOGIN; + Users.CreateWsResponse.User user = tester.users().generate(); + String username = tester.users().generateLogin(); String password = "123"; - Map users = Maps.newHashMap(); - users.put(username + ".password", password); - users.put(username + ".name", "Tester Testerovich"); - users.put(username + ".email", "tester@example.org"); - users.put(username + ".groups", "sonar-user"); - updateUsersInExtAuth(users); + + updateUsersInExtAuth(ImmutableMap.of( + username + ".password", password, + username + ".email", user.getEmail())); verifyAuthenticationIsNotOk(username, password); } - private void verifyHttpException(Exception e, int expectedCode) { - assertThat(e).isInstanceOf(HttpException.class); - HttpException exception = (HttpException) e; - assertThat(exception.status()).isEqualTo(expectedCode); - } + @Test + public void fail_to_authenticate_user_when_email_already_exists_on_several_users() { + Users.CreateWsResponse.User user1 = tester.users().generate(u -> u.setEmail("user@email.com")); + Users.CreateWsResponse.User user2 = tester.users().generate(u -> u.setEmail("user@email.com")); + String username = tester.users().generateLogin(); + String password = "123"; - private boolean checkAuthenticationThroughWebService(String login, String password) { - return createWsClient(login, password).find(new AuthenticationQuery()).isValid(); + updateUsersInExtAuth(ImmutableMap.of( + username + ".password", password, + username + ".email", "user@email.com")); + + verifyAuthenticationIsNotOk(username, password); } /** * Updates information about users in security-plugin. */ private void updateUsersInExtAuth(Map users) { - tester.settings().setGlobalSettings(USERS_PROPERTY, format(users)); - } - - private void createUserInDb(String login, String password) { - orchestrator.getServer().adminWsClient().userClient().create(UserParameters.create().login(login).name(login) - .password(password).passwordConfirmation(password)); - } - - private void updateUserPasswordInDb(String login, String newPassword) { - orchestrator.getServer().adminWsClient().post("/api/users/change_password", "login", login, "password", newPassword); - } - - /** - * Utility method to create {@link Sonar} with specified {@code username} and {@code password}. - * Orchestrator does not provide such method. - */ - private Sonar createWsClient(String username, String password) { - return new Sonar(new HttpClient4Connector(new Host(orchestrator.getServer().getUrl(), username, password))); + tester.settings().setGlobalSettings("sonar.fakeauthenticator.users", format(users)); } @CheckForNull @@ -412,22 +343,17 @@ public class RealmAuthenticationTest { } private void verifyAuthenticationIsOk(String login, String password) { - assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_OK); + tester.as(login, password).wsClient().system().ping(); } private void verifyAuthenticationIsNotOk(String login, String password) { - assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_UNAUTHORIZED); - } - - private void verifyUser(){ - assertThat(tester.wsClient().users().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList()) - .extracting(User::getLogin, User::getExternalIdentity, User::getExternalProvider, User::getLocal) - .containsExactlyInAnyOrder(tuple(USER_LOGIN, USER_LOGIN, "sonarqube", false)); + expectHttpError(401, () -> tester.as(login, password).wsClient().system().ping()); } - private WsResponse checkAuthenticationWithWebService(String login, String password) { - // Call any WS - return newUserWsClient(orchestrator, login, password).wsConnector().call(new GetRequest("api/rules/search")); + private void verifyUser(String login) { + assertThat(tester.wsClient().users().search(new SearchRequest().setQ(login)).getUsersList()) + .extracting(User::getLogin, User::getExternalIdentity, User::getExternalProvider, User::getLocal) + .containsExactlyInAnyOrder(tuple(login, login, "sonarqube", false)); } } diff --git a/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java deleted file mode 100644 index 146f37ea777..00000000000 --- a/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java +++ /dev/null @@ -1,166 +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.sonarqube.tests.user; - -import com.sonar.orchestrator.Orchestrator; -import java.net.URLEncoder; -import java.util.List; -import javax.annotation.Nullable; -import okhttp3.Response; -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.RuleChain; -import util.user.UserRule; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static util.ItUtils.call; -import static util.ItUtils.newOrchestratorBuilder; - -/** - * Test SSO authentication (using HTTP headers). - *

- * It starts its own server as it's using a different authentication system - */ -public class SsoAuthenticationTest { - - private static final String LOGIN_HEADER = "H-Login"; - private static final String NAME_HEADER = "H-Name"; - private static final String EMAIL_HEADER = "H-Email"; - private static final String GROUPS_HEADER = "H-Groups"; - - static final String USER_LOGIN = "tester"; - static final String USER_NAME = "Tester"; - static final String USER_EMAIL = "tester@email.com"; - - static final String GROUP_1 = "group-1"; - static final String GROUP_2 = "group-2"; - static final String GROUP_3 = "group-3"; - - @ClassRule - public static final Orchestrator orchestrator = newOrchestratorBuilder() - .setServerProperty("sonar.web.sso.enable", "true") - .setServerProperty("sonar.web.sso.loginHeader", LOGIN_HEADER) - .setServerProperty("sonar.web.sso.nameHeader", NAME_HEADER) - .setServerProperty("sonar.web.sso.emailHeader", EMAIL_HEADER) - .setServerProperty("sonar.web.sso.groupsHeader", GROUPS_HEADER) - .build(); - - private static UserRule userRule = UserRule.from(orchestrator); - - @ClassRule - public static RuleChain ruleChain = RuleChain.outerRule(orchestrator).around(userRule); - - @Before - public void resetData() { - userRule.resetUsers(); - } - - @Test - public void authenticate() { - doCall(USER_LOGIN, USER_NAME, USER_EMAIL, null); - - userRule.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL); - } - - @Test - public void authenticate_with_only_login() { - doCall(USER_LOGIN, null, null, null); - - userRule.verifyUserExists(USER_LOGIN, USER_LOGIN, null); - } - - @Test - public void update_user_when_headers_are_updated() { - doCall(USER_LOGIN, USER_NAME, USER_EMAIL, null); - userRule.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL); - - // As we don't keep the JWT token is the test, the user is updated - doCall(USER_LOGIN, "new name", "new email", null); - userRule.verifyUserExists(USER_LOGIN, "new name", "new email"); - } - - @Test - public void authenticate_with_groups() { - doCall(USER_LOGIN, null, null, GROUP_1); - - userRule.verifyUserGroupMembership(USER_LOGIN, GROUP_1, "sonar-users"); - } - - @Test - public void synchronize_groups_when_authenticating_existing_user() { - userRule.createGroup(GROUP_1); - userRule.createGroup(GROUP_2); - userRule.createGroup(GROUP_3); - userRule.createUser(USER_LOGIN, "password"); - userRule.associateGroupsToUser(USER_LOGIN, GROUP_1, GROUP_2); - - doCall(USER_LOGIN, null, null, GROUP_2 + "," + GROUP_3); - - userRule.verifyUserGroupMembership(USER_LOGIN, GROUP_2, GROUP_3, "sonar-users"); - } - - @Test - public void authentication_with_local_user_is_possible_when_no_header() { - userRule.createUser(USER_LOGIN, "password"); - - checkLocalAuthentication(USER_LOGIN, "password"); - } - - @Test - public void display_message_in_ui_but_not_in_log_when_unauthorized_exception() throws Exception { - Response response = doCall("invalid login $", null, null, null); - - assertThat(response.code()).isEqualTo(200); - assertThat(response.request().url().toString()).contains("sessions/unauthorized"); - - List logsLines = FileUtils.readLines(orchestrator.getServer().getWebLogs(), UTF_8); - assertThat(logsLines).doesNotContain("org.sonar.server.exceptions.BadRequestException: Use only letters, numbers, and .-_@ please."); - userRule.verifyUserDoesNotExist(USER_LOGIN); - } - - @Test - public void fail_when_email_already_exists() throws Exception { - userRule.createUser("another", "Another", USER_EMAIL, "another"); - - Response response = doCall(USER_LOGIN, USER_NAME, USER_EMAIL, null); - - String expectedError = "You can't sign up because email 'tester@email.com' is already used by an existing user. This means that you probably already registered with another account"; - assertThat(response.code()).isEqualTo(200); - assertThat(response.request().url().toString()).contains(URLEncoder.encode(expectedError, UTF_8.name())); - assertThat(FileUtils.readLines(orchestrator.getServer().getWebLogs(), UTF_8)).doesNotContain(expectedError); - } - - private static Response doCall(String login, @Nullable String name, @Nullable String email, @Nullable String groups) { - return call(orchestrator.getServer().getUrl(), - LOGIN_HEADER, login, - NAME_HEADER, name, - EMAIL_HEADER, email, - GROUPS_HEADER, groups); - } - - private boolean checkLocalAuthentication(String login, String password) { - String result = orchestrator.getServer().wsClient(login, password).get("/api/authentication/validate"); - return result.contains("{\"valid\":true}"); - } - -} diff --git a/tests/src/test/resources/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html b/tests/src/test/resources/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html deleted file mode 100644 index d202404dfd1..00000000000 --- a/tests/src/test/resources/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - fail_to_authenticate_when_not_allowed_to_sign_up - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
french
open/sessions/new
waitForTextcontent*Log in with Fake base identity provider*
clickcss=.oauth-providers a
waitForTextbd*You're not authorized to access this page. Please contact the administrator.*
assertTextbd*Reason: A functional error has happened*
- - diff --git a/tests/src/test/resources/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html b/tests/src/test/resources/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html deleted file mode 100644 index 47a19a2df41..00000000000 --- a/tests/src/test/resources/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - display_unauthorized_page_when_authentication_failed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
french
open/sessions/new
waitForTextcontent*Log in with Fake base identity provider*
clickcss=.oauth-providers a
waitForTextbd*You're not authorized to access this page. Please contact the administrator.*
- - diff --git a/tests/src/test/resources/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html b/tests/src/test/resources/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html deleted file mode 100644 index ddf1b837078..00000000000 --- a/tests/src/test/resources/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - fail_to_authenticate_when_not_allowed_to_sign_up - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
french
open/sessions/new
waitForTextcontent*Log in with Fake base identity provider*
clickcss=.oauth-providers a
waitForTextbd*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-base-id-provider' users are not allowed to sign up*
- - diff --git a/tests/src/test/resources/user/BaseIdentityProviderTest/fail_when_email_already_exists.html b/tests/src/test/resources/user/BaseIdentityProviderTest/fail_when_email_already_exists.html deleted file mode 100644 index b6f7e600ac3..00000000000 --- a/tests/src/test/resources/user/BaseIdentityProviderTest/fail_when_email_already_exists.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - fail_when_email_already_exists - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
french
open/sessions/new
waitForTextcontent*Log in with Fake base identity provider*
clickcss=.oauth-providers a
waitForTextbd*You're not authorized to access this page. Please contact the administrator.*
assertTextbd*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*
- - diff --git a/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details.html b/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details.html deleted file mode 100644 index 4b540f77943..00000000000 --- a/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - external_user_details - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
external_user_details
open/sessions/new
typelogintester
typepassword123
clickAndWait[type=submit]
waitForElementPresentcss=.js-user-authenticated
open/account/
waitForTextlogintester
waitForTextid=nameTester Testerovich
waitForTextid=emailtester@example.org
- - diff --git a/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details2.html b/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details2.html deleted file mode 100644 index b83aa73edfb..00000000000 --- a/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details2.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - external_user_details - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
external_user_details
open/sessions/new
typelogintester
typepassword123
clickAndWait[type=submit]
waitForElementPresentcss=.js-user-authenticated
open/account
waitForTextlogintester
waitForTextid=nameTester2 Testerovich
waitForTextid=emailtester2@example.org
- - diff --git a/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html b/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html deleted file mode 100644 index a78424c7a91..00000000000 --- a/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - fail_to_authenticate_when_not_allowed_to_sign_up - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
french
open/sessions/new
waitForTextcontent*Log in with Fake oauth2 identity provider*
clickcss=.oauth-providers a
waitForTextbd*You're not authorized to access this page. Please contact the administrator.*
assertTextbd*Reason: A functional error has happened*
- - diff --git a/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html b/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html deleted file mode 100644 index b01d24aad4c..00000000000 --- a/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - display_unauthorized_page_when_authentication_failed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
french
open/sessions/new
waitForTextcontent*Log in with Fake oauth2 identity provider*
clickcss=.oauth-providers a
waitForTextbd*You're not authorized to access this page. Please contact the administrator.*
- - diff --git a/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html b/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html deleted file mode 100644 index addc1e7818b..00000000000 --- a/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - fail_to_authenticate_when_not_allowed_to_sign_up - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
french
open/sessions/new
waitForTextcontent*Log in with Fake oauth2 identity provider*
clickcss=.oauth-providers a
waitForTextbd*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-oauth2-id-provider' users are not allowed to sign up*
- -