@@ -22,7 +22,6 @@ package org.sonar.db.user; | |||
import java.util.Collection; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.function.Consumer; | |||
@@ -38,15 +37,16 @@ import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import static java.util.Locale.ENGLISH; | |||
import static org.sonar.db.DatabaseUtils.executeLargeInputs; | |||
import static org.sonar.db.DatabaseUtils.executeLargeInputsWithoutOutput; | |||
import static org.sonar.db.user.UserDto.SCM_ACCOUNTS_SEPARATOR; | |||
public class UserDao implements Dao { | |||
private final System2 system2; | |||
private final UuidFactory uuidFactory; | |||
public UserDao(System2 system2, UuidFactory uuidFactory) { | |||
this.system2 = system2; | |||
this.uuidFactory = uuidFactory; | |||
@@ -154,19 +154,18 @@ public class UserDao implements Dao { | |||
public List<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 |
@@ -57,8 +57,7 @@ public interface UserMapper { | |||
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); |
@@ -585,12 +585,13 @@ public class UserDaoTest { | |||
@Test | |||
public void select_by_email() { | |||
UserDto activeUser = db.users().insertUser(); | |||
UserDto activeUser1 = db.users().insertUser(u -> u.setEmail("user1@email.com")); | |||
UserDto activeUser2 = db.users().insertUser(u -> u.setEmail("user1@email.com")); | |||
UserDto disableUser = db.users().insertUser(u -> u.setActive(false)); | |||
assertThat(underTest.selectByEmail(session, activeUser.getEmail())).isNotNull(); | |||
assertThat(underTest.selectByEmail(session, disableUser.getEmail())).isNull(); | |||
assertThat(underTest.selectByEmail(session, "unknown")).isNull(); | |||
assertThat(underTest.selectByEmail(session, "user1@email.com")).hasSize(2); | |||
assertThat(underTest.selectByEmail(session, disableUser.getEmail())).isEmpty(); | |||
assertThat(underTest.selectByEmail(session, "unknown")).isEmpty(); | |||
} | |||
@Test |
@@ -70,11 +70,11 @@ public class GroupTester { | |||
return response.getGroupsList(); | |||
} | |||
public GroupTester addMemberToGroups(Organizations.Organization organization, String userLogin, String... groups) { | |||
public GroupTester addMemberToGroups(@Nullable Organizations.Organization organization, String userLogin, String... groups) { | |||
for (String group : groups) { | |||
AddUserRequest request = new AddUserRequest() | |||
.setLogin(userLogin) | |||
.setOrganization(organization.getKey()) | |||
.setOrganization(organization != null ? organization.getKey() : null) | |||
.setName(group); | |||
session.wsClient().userGroups().addUser(request); | |||
} |
@@ -45,6 +45,11 @@ public class LoginPage { | |||
return Selenide.page(Navigation.class); | |||
} | |||
public Navigation useBaseAuth() { | |||
Selenide.$(".oauth-providers a").click(); | |||
return Selenide.page(Navigation.class); | |||
} | |||
public LoginPage submitWrongCredentials(String login, String password) { | |||
Selenide.$("#login").val(login); | |||
Selenide.$("#password").val(password); |
@@ -51,7 +51,7 @@ public class AuthenticationModule extends Module { | |||
RealmAuthenticator.class, | |||
BasicAuthenticator.class, | |||
ValidateAction.class, | |||
SsoAuthenticator.class, | |||
HttpHeadersAuthenticator.class, | |||
AuthenticatorsImpl.class); | |||
} | |||
} |
@@ -28,19 +28,19 @@ public class AuthenticatorsImpl implements Authenticators { | |||
private final JwtHttpHandler jwtHttpHandler; | |||
private final BasicAuthenticator basicAuthenticator; | |||
private final SsoAuthenticator ssoAuthenticator; | |||
private final HttpHeadersAuthenticator httpHeadersAuthenticator; | |||
public AuthenticatorsImpl(JwtHttpHandler jwtHttpHandler, BasicAuthenticator basicAuthenticator, SsoAuthenticator ssoAuthenticator) { | |||
public AuthenticatorsImpl(JwtHttpHandler jwtHttpHandler, BasicAuthenticator basicAuthenticator, HttpHeadersAuthenticator httpHeadersAuthenticator) { | |||
this.jwtHttpHandler = jwtHttpHandler; | |||
this.basicAuthenticator = basicAuthenticator; | |||
this.ssoAuthenticator = ssoAuthenticator; | |||
this.httpHeadersAuthenticator = httpHeadersAuthenticator; | |||
} | |||
// Try first to authenticate from SSO, then JWT token, then try from basic http header | |||
@Override | |||
public Optional<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; | |||
} |
@@ -58,9 +58,9 @@ import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_NAME_HE | |||
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 { | |||
public class HttpHeadersAuthenticator implements Startable { | |||
private static final Logger LOG = Loggers.get(SsoAuthenticator.class); | |||
private static final Logger LOG = Loggers.get(HttpHeadersAuthenticator.class); | |||
private static final Splitter COMA_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings(); | |||
@@ -82,8 +82,8 @@ public class SsoAuthenticator implements Startable { | |||
private boolean enabled = false; | |||
private Map<String, String> settingsByKey = new HashMap<>(); | |||
public SsoAuthenticator(System2 system2, Configuration config, UserIdentityAuthenticator userIdentityAuthenticator, | |||
JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) { | |||
public HttpHeadersAuthenticator(System2 system2, Configuration config, UserIdentityAuthenticator userIdentityAuthenticator, | |||
JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) { | |||
this.system2 = system2; | |||
this.config = config; | |||
this.userIdentityAuthenticator = userIdentityAuthenticator; | |||
@@ -94,7 +94,7 @@ public class SsoAuthenticator implements Startable { | |||
@Override | |||
public void start() { | |||
if (config.getBoolean(SONAR_WEB_SSO_ENABLE.getKey()).orElse(false)) { | |||
LOG.info("SSO Authentication enabled"); | |||
LOG.info("HTTP headers authentication enabled"); | |||
enabled = true; | |||
SETTINGS.forEach(entry -> settingsByKey.put(entry.getKey(), config.get(entry.getKey()).orElse(entry.getDefaultValue()))); | |||
} |
@@ -23,6 +23,7 @@ import com.google.common.collect.Sets; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
@@ -135,7 +136,15 @@ public class UserIdentityAuthenticatorImpl implements UserIdentityAuthenticator | |||
if (email == null) { | |||
return Optional.empty(); | |||
} | |||
UserDto existingUser = dbClient.userDao().selectByEmail(dbSession, email); | |||
List<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()) | |||
@@ -151,14 +160,7 @@ public class UserIdentityAuthenticatorImpl implements UserIdentityAuthenticator | |||
case WARN: | |||
throw new EmailAlreadyExistsRedirectionException(email, existingUser, authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider()); | |||
case FORBID: | |||
throw AuthenticationException.newBuilder() | |||
.setSource(authenticatorParameters.getSource()) | |||
.setLogin(authenticatorParameters.getUserIdentity().getLogin()) | |||
.setMessage(format("Email '%s' is already used", email)) | |||
.setPublicMessage(format( | |||
"You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", | |||
email)) | |||
.build(); | |||
throw generateExistingEmailError(authenticatorParameters, email); | |||
default: | |||
throw new IllegalStateException(format("Unknown strategy %s", existingEmailStrategy)); | |||
} | |||
@@ -264,4 +266,15 @@ public class UserIdentityAuthenticatorImpl implements UserIdentityAuthenticator | |||
return userDto.map(u -> new UserDto[] {u}).orElse(new UserDto[] {}); | |||
} | |||
private static AuthenticationException generateExistingEmailError(UserIdentityAuthenticatorParameters authenticatorParameters, String email) { | |||
return AuthenticationException.newBuilder() | |||
.setSource(authenticatorParameters.getSource()) | |||
.setLogin(authenticatorParameters.getUserIdentity().getLogin()) | |||
.setMessage(format("Email '%s' is already used", email)) | |||
.setPublicMessage(format( | |||
"You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", | |||
email)) | |||
.build(); | |||
} | |||
} |
@@ -40,12 +40,12 @@ public class AuthenticatorsImplTest { | |||
private HttpServletResponse response = mock(HttpServletResponse.class); | |||
private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); | |||
private BasicAuthenticator basicAuthenticator = mock(BasicAuthenticator.class); | |||
private SsoAuthenticator ssoAuthenticator = mock(SsoAuthenticator.class); | |||
private Authenticators underTest = new AuthenticatorsImpl(jwtHttpHandler, basicAuthenticator, ssoAuthenticator); | |||
private HttpHeadersAuthenticator httpHeadersAuthenticator = mock(HttpHeadersAuthenticator.class); | |||
private Authenticators underTest = new AuthenticatorsImpl(jwtHttpHandler, basicAuthenticator, httpHeadersAuthenticator); | |||
@Test | |||
public void authenticate_from_jwt_token() { | |||
when(ssoAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); | |||
when(httpHeadersAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); | |||
when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.of(user)); | |||
assertThat(underTest.authenticate(request, response)).hasValue(user); | |||
@@ -55,7 +55,7 @@ public class AuthenticatorsImplTest { | |||
@Test | |||
public void authenticate_from_basic_header() { | |||
when(basicAuthenticator.authenticate(request)).thenReturn(Optional.of(user)); | |||
when(ssoAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); | |||
when(httpHeadersAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); | |||
when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); | |||
assertThat(underTest.authenticate(request, response)).hasValue(user); | |||
@@ -67,12 +67,12 @@ public class AuthenticatorsImplTest { | |||
@Test | |||
public void authenticate_from_sso() { | |||
when(ssoAuthenticator.authenticate(request, response)).thenReturn(Optional.of(user)); | |||
when(httpHeadersAuthenticator.authenticate(request, response)).thenReturn(Optional.of(user)); | |||
when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); | |||
assertThat(underTest.authenticate(request, response)).hasValue(user); | |||
verify(ssoAuthenticator).authenticate(request, response); | |||
verify(httpHeadersAuthenticator).authenticate(request, response); | |||
verify(jwtHttpHandler, never()).validateToken(request, response); | |||
verify(response, never()).setStatus(anyInt()); | |||
} | |||
@@ -80,7 +80,7 @@ public class AuthenticatorsImplTest { | |||
@Test | |||
public void return_empty_if_not_authenticated() { | |||
when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); | |||
when(ssoAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); | |||
when(httpHeadersAuthenticator.authenticate(request, response)).thenReturn(Optional.empty()); | |||
when(basicAuthenticator.authenticate(request)).thenReturn(Optional.empty()); | |||
assertThat(underTest.authenticate(request, response)).isEmpty(); |
@@ -64,7 +64,7 @@ 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 { | |||
public class HttpHeadersAuthenticatorTest { | |||
private MapSettings settings = new MapSettings(); | |||
@@ -113,7 +113,7 @@ public class SsoAuthenticatorTest { | |||
private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); | |||
private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); | |||
private SsoAuthenticator underTest = new SsoAuthenticator(system2, settings.asConfig(), userIdentityAuthenticator, jwtHttpHandler, authenticationEvent); | |||
private HttpHeadersAuthenticator underTest = new HttpHeadersAuthenticator(system2, settings.asConfig(), userIdentityAuthenticator, jwtHttpHandler, authenticationEvent); | |||
@Before | |||
public void setUp() throws Exception { |
@@ -277,10 +277,29 @@ public class UserIdentityAuthenticatorImplTest { | |||
@Test | |||
public void throw_AuthenticationException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_FORBID() { | |||
db.users().insertUser(newUserDto() | |||
.setLogin("Existing user with same email") | |||
.setActive(true) | |||
.setEmail("john@email.com")); | |||
db.users().insertUser(u -> u.setEmail("john@email.com")); | |||
Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()); | |||
expectedException.expect(authenticationException().from(source) | |||
.withLogin(USER_IDENTITY.getLogin()) | |||
.andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " + | |||
"This means that you probably already registered with another account.")); | |||
expectedException.expectMessage("Email 'john@email.com' is already used"); | |||
underTest.authenticate(UserIdentityAuthenticatorParameters.builder() | |||
.setUserIdentity(USER_IDENTITY) | |||
.setProvider(IDENTITY_PROVIDER) | |||
.setSource(source) | |||
.setExistingEmailStrategy(FORBID) | |||
.setExistingEmailStrategy(ExistingEmailStrategy.FORBID) | |||
.setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW) | |||
.build()); | |||
} | |||
@Test | |||
public void throw_AuthenticationException_when_authenticating_new_user_and_email_already_exists_multiple_times() { | |||
db.users().insertUser(u -> u.setEmail("john@email.com")); | |||
db.users().insertUser(u -> u.setEmail("john@email.com")); | |||
Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()); | |||
expectedException.expect(authenticationException().from(source) |
@@ -37,9 +37,9 @@ import org.sonarqube.tests.settings.SettingsTestRestartingOrchestrator; | |||
import org.sonarqube.tests.startup.StartupIndexationTest; | |||
import org.sonarqube.tests.telemetry.TelemetryOptOutTest; | |||
import org.sonarqube.tests.telemetry.TelemetryUploadTest; | |||
import org.sonarqube.tests.user.HttpHeadersAuthenticationTest; | |||
import org.sonarqube.tests.user.OnboardingTest; | |||
import org.sonarqube.tests.user.RealmAuthenticationTest; | |||
import org.sonarqube.tests.user.SsoAuthenticationTest; | |||
import org.sonarqube.tests.user.UserEsResilienceTest; | |||
/** | |||
@@ -61,7 +61,7 @@ import org.sonarqube.tests.user.UserEsResilienceTest; | |||
// update center | |||
UpdateCenterTest.class, | |||
RealmAuthenticationTest.class, | |||
SsoAuthenticationTest.class, | |||
HttpHeadersAuthenticationTest.class, | |||
OnboardingTest.class, | |||
BuiltInQualityProfilesNotificationTest.class, | |||
ActiveRuleEsResilienceTest.class, |
@@ -19,14 +19,13 @@ | |||
*/ | |||
package org.sonarqube.tests.user; | |||
import com.codeborne.selenide.Condition; | |||
import com.google.common.base.Joiner; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import java.io.File; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import java.util.stream.Collectors; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.After; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
@@ -41,10 +40,13 @@ import org.sonarqube.ws.client.users.DeactivateRequest; | |||
import org.sonarqube.ws.client.users.GroupsRequest; | |||
import org.sonarqube.ws.client.users.SearchRequest; | |||
import static com.codeborne.selenide.Selenide.$; | |||
import static java.lang.String.format; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static org.apache.commons.io.FileUtils.readFileToString; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.tuple; | |||
import static org.sonarqube.ws.UserGroups.Group; | |||
import static util.selenium.Selenese.runSelenese; | |||
public class BaseIdentityProviderTest { | |||
@@ -105,8 +107,11 @@ public class BaseIdentityProviderTest { | |||
// As this property is null, the plugin will throw an exception | |||
tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", null); | |||
runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html"); | |||
tester.openBrowser() | |||
.logIn() | |||
.useBaseAuth(); | |||
$("#bd").shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")); | |||
assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); | |||
} | |||
@@ -116,21 +121,45 @@ public class BaseIdentityProviderTest { | |||
setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); | |||
tester.users().generate(u -> u.setLogin("another").setName("Another").setEmail(USER_EMAIL).setPassword("another")); | |||
runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_when_email_already_exists.html"); | |||
tester.openBrowser() | |||
.logIn() | |||
.useBaseAuth(); | |||
File logFile = ORCHESTRATOR.getServer().getWebLogs(); | |||
assertThat(FileUtils.readFileToString(logFile)) | |||
$("#bd").shouldHave(Condition.text( | |||
format("You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", USER_EMAIL))); | |||
assertThat(readFileToString(ORCHESTRATOR.getServer().getWebLogs(), UTF_8)) | |||
.doesNotContain("You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account"); | |||
} | |||
@Test | |||
public void fail_to_authenticate_user_when_email_already_exists_on_several_users() { | |||
enablePlugin(); | |||
setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); | |||
tester.users().generate(u -> u.setEmail(USER_EMAIL)); | |||
tester.users().generate(u -> u.setEmail(USER_EMAIL)); | |||
tester.openBrowser() | |||
.logIn() | |||
.useBaseAuth(); | |||
$("#bd").shouldHave(Condition.text( | |||
format("You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", USER_EMAIL))); | |||
assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); | |||
} | |||
@Test | |||
public void fail_to_authenticate_when_not_allowed_to_sign_up() { | |||
enablePlugin(); | |||
setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); | |||
tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.allowsUsersToSignUp", "false"); | |||
runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html"); | |||
tester.openBrowser() | |||
.logIn() | |||
.useBaseAuth(); | |||
$("#bd") | |||
.shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator.")) | |||
.shouldHave(Condition.text(format("Reason: '%s' users are not allowed to sign up", FAKE_PROVIDER_KEY))); | |||
assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); | |||
} | |||
@@ -190,13 +219,16 @@ public class BaseIdentityProviderTest { | |||
setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); | |||
tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "true"); | |||
runSelenese(ORCHESTRATOR, | |||
"/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); | |||
File logFile = ORCHESTRATOR.getServer().getWebLogs(); | |||
assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); | |||
assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException"); | |||
tester.openBrowser() | |||
.logIn() | |||
.useBaseAuth(); | |||
$("#bd") | |||
.shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")) | |||
.shouldHave(Condition.text("Reason: A functional error has happened")); | |||
assertThat(readFileToString(ORCHESTRATOR.getServer().getWebLogs(), UTF_8)) | |||
.doesNotContain("A functional error has happened") | |||
.doesNotContain("UnauthorizedException"); | |||
assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); | |||
} | |||
@@ -0,0 +1,191 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonarqube.tests.user; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import java.net.URLEncoder; | |||
import java.util.List; | |||
import javax.annotation.Nullable; | |||
import okhttp3.Response; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonarqube.qa.util.Tester; | |||
import org.sonarqube.ws.UserGroups.Group; | |||
import org.sonarqube.ws.Users.CreateWsResponse.User; | |||
import org.sonarqube.ws.Users.SearchWsResponse; | |||
import org.sonarqube.ws.client.users.SearchRequest; | |||
import static java.lang.String.format; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static java.util.Arrays.asList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.groups.Tuple.tuple; | |||
import static util.ItUtils.call; | |||
import static util.ItUtils.newOrchestratorBuilder; | |||
/** | |||
* Test authentication using HTTP headers. | |||
* <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)); | |||
} | |||
} |
@@ -21,11 +21,9 @@ package org.sonarqube.tests.user; | |||
import com.codeborne.selenide.Condition; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import java.io.File; | |||
import java.net.HttpURLConnection; | |||
import okhttp3.mockwebserver.MockResponse; | |||
import okhttp3.mockwebserver.MockWebServer; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
@@ -37,10 +35,12 @@ import org.sonarqube.ws.Users.SearchWsResponse.User; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.permissions.AddUserRequest; | |||
import org.sonarqube.ws.client.users.CreateRequest; | |||
import util.selenium.Selenese; | |||
import static com.codeborne.selenide.Condition.visible; | |||
import static com.codeborne.selenide.Selenide.$; | |||
import static java.lang.String.format; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static org.apache.commons.io.FileUtils.readFileToString; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
/** | |||
@@ -137,8 +137,11 @@ public class OAuth2IdentityProviderTest { | |||
// As this property is null, the plugin will throw an exception | |||
tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", null); | |||
Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html"); | |||
tester.openBrowser() | |||
.logIn() | |||
.useOAuth2(); | |||
$("#bd").shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")); | |||
assertThatUserDoesNotExist(USER_LOGIN); | |||
} | |||
@@ -148,8 +151,13 @@ public class OAuth2IdentityProviderTest { | |||
enablePlugin(); | |||
tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp", "false"); | |||
Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html"); | |||
tester.openBrowser() | |||
.logIn() | |||
.useOAuth2(); | |||
$("#bd") | |||
.shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator.")) | |||
.shouldHave(Condition.text(format("Reason: '%s' users are not allowed to sign up", FAKE_PROVIDER_KEY))); | |||
assertThatUserDoesNotExist(USER_LOGIN); | |||
} | |||
@@ -159,12 +167,16 @@ public class OAuth2IdentityProviderTest { | |||
enablePlugin(); | |||
tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", "true"); | |||
Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); | |||
File logFile = orchestrator.getServer().getWebLogs(); | |||
assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); | |||
assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException"); | |||
tester.openBrowser() | |||
.logIn() | |||
.useOAuth2(); | |||
$("#bd") | |||
.shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")) | |||
.shouldHave(Condition.text("Reason: A functional error has happened")); | |||
assertThat(readFileToString(orchestrator.getServer().getWebLogs(), UTF_8)) | |||
.doesNotContain("A functional error has happened") | |||
.doesNotContain("UnauthorizedException"); | |||
assertThatUserDoesNotExist(USER_LOGIN); | |||
} | |||
@@ -205,6 +217,22 @@ public class OAuth2IdentityProviderTest { | |||
assertThat(tester.users().getByLogin("another").get().getEmail()).isNullOrEmpty(); | |||
} | |||
@Test | |||
public void fail_to_authenticate_user_when_email_already_exists_on_several_users() { | |||
simulateRedirectionToCallback(); | |||
enablePlugin(); | |||
tester.users().generate(u -> u.setEmail(USER_EMAIL)); | |||
tester.users().generate(u -> u.setEmail(USER_EMAIL)); | |||
tester.openBrowser() | |||
.logIn() | |||
.useOAuth2(); | |||
$("#bd").shouldHave(Condition.text( | |||
format("You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", USER_EMAIL))); | |||
assertThatUserDoesNotExist(USER_LOGIN); | |||
} | |||
@Test | |||
public void provision_user_before_authentication() { | |||
simulateRedirectionToCallback(); |
@@ -21,43 +21,30 @@ package org.sonarqube.tests.user; | |||
import com.codeborne.selenide.Condition; | |||
import com.google.common.collect.ImmutableMap; | |||
import com.google.common.collect.Maps; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import java.util.Collections; | |||
import java.util.Map; | |||
import javax.annotation.CheckForNull; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.wsclient.Host; | |||
import org.sonar.wsclient.Sonar; | |||
import org.sonar.wsclient.base.HttpException; | |||
import org.sonar.wsclient.connectors.HttpClient4Connector; | |||
import org.sonar.wsclient.services.AuthenticationQuery; | |||
import org.sonar.wsclient.user.UserParameters; | |||
import org.sonarqube.qa.util.Tester; | |||
import org.sonarqube.qa.util.pageobjects.Navigation; | |||
import org.sonarqube.qa.util.pageobjects.SystemInfoPage; | |||
import org.sonarqube.qa.util.pageobjects.UsersManagementPage; | |||
import org.sonarqube.ws.UserGroups.Group; | |||
import org.sonarqube.ws.Users; | |||
import org.sonarqube.ws.Users.SearchWsResponse.User; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.WsResponse; | |||
import org.sonarqube.ws.client.users.CreateRequest; | |||
import org.sonarqube.ws.client.users.ChangePasswordRequest; | |||
import org.sonarqube.ws.client.users.SearchRequest; | |||
import util.user.UserRule; | |||
import static java.net.HttpURLConnection.HTTP_OK; | |||
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; | |||
import static java.util.Arrays.asList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.groups.Tuple.tuple; | |||
import static org.junit.Assert.fail; | |||
import static util.ItUtils.expectHttpError; | |||
import static util.ItUtils.newOrchestratorBuilder; | |||
import static util.ItUtils.newUserWsClient; | |||
import static util.ItUtils.pluginArtifact; | |||
import static util.ItUtils.resetSettings; | |||
import static util.selenium.Selenese.runSelenese; | |||
/** | |||
* Test REALM authentication. | |||
@@ -66,72 +53,46 @@ import static util.selenium.Selenese.runSelenese; | |||
*/ | |||
public class RealmAuthenticationTest { | |||
private static final String TECH_USER = "techUser"; | |||
private static final String USER_LOGIN = "tester"; | |||
private static final String ADMIN_USER_LOGIN = "admin-user"; | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
/** | |||
* Property from security-plugin for user management. | |||
*/ | |||
private static final String USERS_PROPERTY = "sonar.fakeauthenticator.users"; | |||
@ClassRule | |||
public static final Orchestrator orchestrator = newOrchestratorBuilder() | |||
.addPlugin(pluginArtifact("security-plugin")) | |||
.setServerProperty("sonar.security.realm", "FakeRealm") | |||
.build(); | |||
@Rule | |||
public UserRule userRule = UserRule.from(orchestrator); | |||
@Rule | |||
public Tester tester = new Tester(orchestrator).disableOrganizations(); | |||
@Before | |||
@After | |||
public void resetData() { | |||
resetSettings(orchestrator, null, USERS_PROPERTY, "sonar.security.updateUserAttributes"); | |||
} | |||
@Before | |||
public void initAdminUser() { | |||
userRule.createAdminUser(ADMIN_USER_LOGIN, ADMIN_USER_LOGIN); | |||
} | |||
@After | |||
public void deleteAdminUser() { | |||
userRule.resetUsers(); | |||
} | |||
/** | |||
* SONAR-3137, SONAR-2292 | |||
* Restriction on password length (minimum 4 characters) should be disabled, when external system enabled. | |||
*/ | |||
@Test | |||
public void shouldSynchronizeDetailsAndGroups() { | |||
public void synchronize_details_and_groups() { | |||
// Given clean Sonar installation and no users in external system | |||
String username = USER_LOGIN; | |||
String username = tester.users().generateLogin(); | |||
String password = "123"; | |||
Map<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"); | |||
} | |||
@@ -139,56 +100,54 @@ public class RealmAuthenticationTest { | |||
* SONAR-4034 | |||
*/ | |||
@Test | |||
public void shouldUpdateDetailsByDefault() { | |||
public void update_details_by_default() { | |||
// Given clean Sonar installation and no users in external system | |||
String username = USER_LOGIN; | |||
String username = tester.users().generateLogin(); | |||
String password = "123"; | |||
Map<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); | |||
} | |||
@@ -197,16 +156,14 @@ public class RealmAuthenticationTest { | |||
* SONAR-4543 | |||
*/ | |||
@Test | |||
public void adminIsLocalAccountByDefault() { | |||
public void admin_is_local_account_by_default() { | |||
// Given clean Sonar installation and no users in external system | |||
String login = "admin"; | |||
String localPassword = "admin"; | |||
String remotePassword = "nimda"; | |||
Map<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); | |||
@@ -217,52 +174,49 @@ public class RealmAuthenticationTest { | |||
* SONAR-1334, SONAR-3185 (createUsers=true is default) | |||
*/ | |||
@Test | |||
public void shouldCreateNewUsers() { | |||
public void create_new_users() { | |||
// Given clean Sonar installation and no users in external system | |||
String username = USER_LOGIN; | |||
String username = tester.users().generateLogin(); | |||
String password = "1234567"; | |||
Map<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); | |||
} | |||
/** | |||
@@ -271,24 +225,20 @@ public class RealmAuthenticationTest { | |||
@Test | |||
public void update_password_of_technical_user() { | |||
// Create user in external authentication | |||
updateUsersInExtAuth(ImmutableMap.of(USER_LOGIN + ".password", USER_LOGIN)); | |||
verifyAuthenticationIsOk(USER_LOGIN, USER_LOGIN); | |||
String login = tester.users().generateLogin(); | |||
updateUsersInExtAuth(ImmutableMap.of(login + ".password", login)); | |||
verifyAuthenticationIsOk(login, login); | |||
// Create technical user in db | |||
createUserInDb(TECH_USER, "old_password"); | |||
assertThat(checkAuthenticationThroughWebService(TECH_USER, "old_password")).isTrue(); | |||
Users.CreateWsResponse.User techUser = tester.users().generate(u -> u.setPassword("old_password")); | |||
verifyAuthenticationIsOk(techUser.getLogin(), "old_password"); | |||
// Updating password of technical user is allowed | |||
updateUserPasswordInDb(TECH_USER, "new_password"); | |||
assertThat(checkAuthenticationThroughWebService(TECH_USER, "new_password")).isTrue(); | |||
tester.users().service().changePassword(new ChangePasswordRequest().setLogin(techUser.getLogin()).setPassword("new_password")); | |||
verifyAuthenticationIsOk(techUser.getLogin(), "new_password"); | |||
// But updating password of none local user is not allowed | |||
try { | |||
updateUserPasswordInDb(USER_LOGIN, "new_password"); | |||
fail(); | |||
} catch (HttpException e) { | |||
verifyHttpException(e, 400); | |||
} | |||
expectHttpError(400, () -> tester.users().service().changePassword(new ChangePasswordRequest().setLogin(login).setPassword("new_password"))); | |||
} | |||
/** | |||
@@ -297,16 +247,14 @@ public class RealmAuthenticationTest { | |||
@Test | |||
public void authentication_with_ws() { | |||
// Given clean Sonar installation and no users in external system | |||
String login = USER_LOGIN; | |||
String login = tester.users().generateLogin(); | |||
String password = "1234567"; | |||
Map<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); | |||
@@ -332,71 +280,54 @@ public class RealmAuthenticationTest { | |||
@Test | |||
public void provision_user_before_authentication() { | |||
tester.wsClient().users().create(new CreateRequest() | |||
.setLogin(USER_LOGIN) | |||
.setName("Tester Testerovich") | |||
Users.CreateWsResponse.User user = tester.users().generate(u -> u.setName("Tester Testerovich") | |||
.setEmail("tester@example.org") | |||
.setPassword(null) | |||
.setLocal("false")); | |||
// The user is created in SonarQube but doesn't exist yet in external authentication system | |||
verifyAuthenticationIsNotOk(USER_LOGIN, "123"); | |||
verifyAuthenticationIsNotOk(user.getLogin(), "123"); | |||
updateUsersInExtAuth(ImmutableMap.of( | |||
USER_LOGIN + ".password", "123", | |||
USER_LOGIN + ".name", "Tester Testerovich", | |||
USER_LOGIN + ".email", "tester@example.org")); | |||
user.getLogin() + ".password", "123", | |||
user.getLogin() + ".name", "Tester Testerovich", | |||
user.getLogin() + ".email", "tester@example.org")); | |||
verifyAuthenticationIsOk(USER_LOGIN, "123"); | |||
verifyUser(); | |||
verifyAuthenticationIsOk(user.getLogin(), "123"); | |||
verifyUser(user.getLogin()); | |||
} | |||
@Test | |||
public void fail_to_authenticate_user_when_email_already_exists() { | |||
userRule.createUser("another", "Another", "tester@example.org", "another"); | |||
String username = USER_LOGIN; | |||
Users.CreateWsResponse.User user = tester.users().generate(); | |||
String username = tester.users().generateLogin(); | |||
String password = "123"; | |||
Map<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 | |||
@@ -412,22 +343,17 @@ public class RealmAuthenticationTest { | |||
} | |||
private void verifyAuthenticationIsOk(String login, String password) { | |||
assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_OK); | |||
tester.as(login, password).wsClient().system().ping(); | |||
} | |||
private void verifyAuthenticationIsNotOk(String login, String password) { | |||
assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_UNAUTHORIZED); | |||
} | |||
private void verifyUser(){ | |||
assertThat(tester.wsClient().users().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList()) | |||
.extracting(User::getLogin, User::getExternalIdentity, User::getExternalProvider, User::getLocal) | |||
.containsExactlyInAnyOrder(tuple(USER_LOGIN, USER_LOGIN, "sonarqube", false)); | |||
expectHttpError(401, () -> tester.as(login, password).wsClient().system().ping()); | |||
} | |||
private WsResponse checkAuthenticationWithWebService(String login, String password) { | |||
// Call any WS | |||
return newUserWsClient(orchestrator, login, password).wsConnector().call(new GetRequest("api/rules/search")); | |||
private void verifyUser(String login) { | |||
assertThat(tester.wsClient().users().search(new SearchRequest().setQ(login)).getUsersList()) | |||
.extracting(User::getLogin, User::getExternalIdentity, User::getExternalProvider, User::getLocal) | |||
.containsExactlyInAnyOrder(tuple(login, login, "sonarqube", false)); | |||
} | |||
} |
@@ -1,166 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonarqube.tests.user; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import java.net.URLEncoder; | |||
import java.util.List; | |||
import javax.annotation.Nullable; | |||
import okhttp3.Response; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Test; | |||
import org.junit.rules.RuleChain; | |||
import util.user.UserRule; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static util.ItUtils.call; | |||
import static util.ItUtils.newOrchestratorBuilder; | |||
/** | |||
* Test SSO authentication (using HTTP headers). | |||
* <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}"); | |||
} | |||
} |
@@ -1,44 +0,0 @@ | |||
<?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> |
@@ -1,39 +0,0 @@ | |||
<?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> |
@@ -1,39 +0,0 @@ | |||
<?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> |
@@ -1,44 +0,0 @@ | |||
<?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> |
@@ -1,64 +0,0 @@ | |||
<?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> |
@@ -1,64 +0,0 @@ | |||
<?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> |
@@ -1,44 +0,0 @@ | |||
<?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> |
@@ -1,39 +0,0 @@ | |||
<?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> |
@@ -1,39 +0,0 @@ | |||
<?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> |