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;
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;
public List<UserDto> 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<UserDto> selectByEmail(DbSession dbSession, String emailCaseInsensitive) {
+ return mapper(dbSession).selectByEmail(emailCaseInsensitive.toLowerCase(ENGLISH));
}
@CheckForNull
List<UserDto> selectByIds(@Param("ids") List<Integer> ids);
- @CheckForNull
- UserDto selectByEmail(String email);
+ List<UserDto> selectByEmail(String email);
@CheckForNull
UserDto selectByExternalIdAndIdentityProvider(@Param("externalId") String externalId, @Param("externalIdentityProvider") String externalExternalIdentityProvider);
@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
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);
}
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);
RealmAuthenticator.class,
BasicAuthenticator.class,
ValidateAction.class,
- SsoAuthenticator.class,
+ HttpHeadersAuthenticator.class,
AuthenticatorsImpl.class);
}
}
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<UserDto> 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<UserDto> user = ssoAuthenticator.authenticate(request, response);
+ Optional<UserDto> user = httpHeadersAuthenticator.authenticate(request, response);
if (user.isPresent()) {
return user;
}
--- /dev/null
+/*
+ * 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<ProcessProperties.Property> 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<String, String> 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<UserDto> 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<UserDto> doAuthenticate(HttpServletRequest request, HttpServletResponse response) {
+ if (!enabled) {
+ return Optional.empty();
+ }
+ Map<String, String> headerValuesByNames = getHeaders(request);
+ String login = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_LOGIN_HEADER.getKey());
+ if (login == null) {
+ return Optional.empty();
+ }
+ Optional<UserDto> 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<UserDto> getUserFromToken(HttpServletRequest request, HttpServletResponse response) {
+ Optional<JwtHttpHandler.Token> 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<String, String> 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<String, String> headerValuesByNames, String settingKey) {
+ return headerValuesByNames.get(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH));
+ }
+
+ private static Map<String, String> getHeaders(HttpServletRequest request) {
+ Map<String, String> headers = new HashMap<>();
+ Collections.list(request.getHeaderNames()).forEach(header -> headers.put(header.toLowerCase(Locale.ENGLISH), request.getHeader(header)));
+ return headers;
+ }
+
+ private boolean hasHeader(Map<String, String> 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;
+ }
+ }
+}
+++ /dev/null
-/*
- * 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<ProcessProperties.Property> 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<String, String> 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<UserDto> 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<UserDto> doAuthenticate(HttpServletRequest request, HttpServletResponse response) {
- if (!enabled) {
- return Optional.empty();
- }
- Map<String, String> headerValuesByNames = getHeaders(request);
- String login = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_LOGIN_HEADER.getKey());
- if (login == null) {
- return Optional.empty();
- }
- Optional<UserDto> 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<UserDto> getUserFromToken(HttpServletRequest request, HttpServletResponse response) {
- Optional<JwtHttpHandler.Token> 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<String, String> 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<String, String> headerValuesByNames, String settingKey) {
- return headerValuesByNames.get(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH));
- }
-
- private static Map<String, String> getHeaders(HttpServletRequest request) {
- Map<String, String> headers = new HashMap<>();
- Collections.list(request.getHeaderNames()).forEach(header -> headers.put(header.toLowerCase(Locale.ENGLISH), request.getHeader(header)));
- return headers;
- }
-
- private boolean hasHeader(Map<String, String> 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;
- }
- }
-}
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;
if (email == null) {
return Optional.empty();
}
- UserDto existingUser = dbClient.userDao().selectByEmail(dbSession, email);
+ List<UserDto> 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())
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));
}
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();
+ }
+
}
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);
@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);
@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());
}
@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();
--- /dev/null
+/*
+ * 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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));
+ }
+
+}
+++ /dev/null
-/*
- * 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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));
- }
-
-}
@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)
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;
/**
// update center
UpdateCenterTest.class,
RealmAuthenticationTest.class,
- SsoAuthenticationTest.class,
+ HttpHeadersAuthenticationTest.class,
OnboardingTest.class,
BuiltInQualityProfilesNotificationTest.class,
ActiveRuleEsResilienceTest.class,
*/
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;
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 {
// 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();
}
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();
}
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();
}
--- /dev/null
+/*
+ * 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.
+ * <p>
+ * 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<String> 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));
+ }
+
+}
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;
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;
/**
// 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);
}
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);
}
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);
}
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();
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.
*/
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<String, String> 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");
}
* 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<String, String> 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<String, String> 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);
}
* 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<String, String> 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);
* 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<String, String> 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<String, String> 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);
}
/**
@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")));
}
/**
@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<String, String> 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);
@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<String, String> 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<String, String> 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
}
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));
}
}
+++ /dev/null
-/*
- * 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).
- * <p>
- * 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<String> 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}");
- }
-
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>fail_to_authenticate_when_not_allowed_to_sign_up</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">french</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>content</td>
- <td>*Log in with Fake base identity provider*</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=.oauth-providers a</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>bd</td>
- <td>*You're not authorized to access this page. Please contact the administrator.*</td>
- </tr>
- <tr>
- <td>assertText</td>
- <td>bd</td>
- <td>*Reason: A functional error has happened*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>display_unauthorized_page_when_authentication_failed</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">french</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>content</td>
- <td>*Log in with Fake base identity provider*</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=.oauth-providers a</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>bd</td>
- <td>*You're not authorized to access this page. Please contact the administrator.*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>fail_to_authenticate_when_not_allowed_to_sign_up</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">french</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>content</td>
- <td>*Log in with Fake base identity provider*</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=.oauth-providers a</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>bd</td>
- <td>*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-base-id-provider' users are not allowed to sign up*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>fail_when_email_already_exists</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">french</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>content</td>
- <td>*Log in with Fake base identity provider*</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=.oauth-providers a</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>bd</td>
- <td>*You're not authorized to access this page. Please contact the administrator.*</td>
- </tr>
- <tr>
- <td>assertText</td>
- <td>bd</td>
- <td>*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*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-/W3C/DTD XHTML 1.0 Strict/EN" "http:/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http:/www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http:/selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>external_user_details</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">external_user_details</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>login</td>
- <td>tester</td>
- </tr>
- <tr>
- <td>type</td>
- <td>password</td>
- <td>123</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>[type=submit]</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/account/</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>login</td>
- <td>tester</td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>id=name</td>
- <td>Tester Testerovich</td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>id=email</td>
- <td>tester@example.org</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-/W3C/DTD XHTML 1.0 Strict/EN" "http:/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http:/www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http:/selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>external_user_details</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">external_user_details</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>login</td>
- <td>tester</td>
- </tr>
- <tr>
- <td>type</td>
- <td>password</td>
- <td>123</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>[type=submit]</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/account</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>login</td>
- <td>tester</td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>id=name</td>
- <td>Tester2 Testerovich</td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>id=email</td>
- <td>tester2@example.org</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>fail_to_authenticate_when_not_allowed_to_sign_up</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">french</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>content</td>
- <td>*Log in with Fake oauth2 identity provider*</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=.oauth-providers a</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>bd</td>
- <td>*You're not authorized to access this page. Please contact the administrator.*</td>
- </tr>
- <tr>
- <td>assertText</td>
- <td>bd</td>
- <td>*Reason: A functional error has happened*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>display_unauthorized_page_when_authentication_failed</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">french</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>content</td>
- <td>*Log in with Fake oauth2 identity provider*</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=.oauth-providers a</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>bd</td>
- <td>*You're not authorized to access this page. Please contact the administrator.*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>fail_to_authenticate_when_not_allowed_to_sign_up</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">french</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>content</td>
- <td>*Log in with Fake oauth2 identity provider*</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=.oauth-providers a</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>bd</td>
- <td>*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-oauth2-id-provider' users are not allowed to sign up*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>