diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2018-05-24 15:56:11 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-05-29 20:20:46 +0200 |
commit | b7976ff3de0187d28cbe371a858d51af32913853 (patch) | |
tree | 2affa95eff3d313e4ab8f6ad1d336a8c496fde0b /tests/src/test/java | |
parent | 78d77d771aef671903f673658f55cc7c936e61a9 (diff) | |
download | sonarqube-b7976ff3de0187d28cbe371a858d51af32913853.tar.gz sonarqube-b7976ff3de0187d28cbe371a858d51af32913853.zip |
SONAR-10652 Correctly fail when authentication and several emails exists
Diffstat (limited to 'tests/src/test/java')
6 files changed, 379 insertions, 368 deletions
diff --git a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java index d45b79800cc..b2a635e0703 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java @@ -37,9 +37,9 @@ import org.sonarqube.tests.settings.SettingsTestRestartingOrchestrator; import org.sonarqube.tests.startup.StartupIndexationTest; import org.sonarqube.tests.telemetry.TelemetryOptOutTest; import org.sonarqube.tests.telemetry.TelemetryUploadTest; +import org.sonarqube.tests.user.HttpHeadersAuthenticationTest; import org.sonarqube.tests.user.OnboardingTest; import org.sonarqube.tests.user.RealmAuthenticationTest; -import org.sonarqube.tests.user.SsoAuthenticationTest; import org.sonarqube.tests.user.UserEsResilienceTest; /** @@ -61,7 +61,7 @@ import org.sonarqube.tests.user.UserEsResilienceTest; // update center UpdateCenterTest.class, RealmAuthenticationTest.class, - SsoAuthenticationTest.class, + HttpHeadersAuthenticationTest.class, OnboardingTest.class, BuiltInQualityProfilesNotificationTest.class, ActiveRuleEsResilienceTest.class, diff --git a/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java b/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java index 4545bc08ecd..f9bfa51ce65 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java +++ b/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java @@ -19,14 +19,13 @@ */ package org.sonarqube.tests.user; +import com.codeborne.selenide.Condition; import com.google.common.base.Joiner; import com.sonar.orchestrator.Orchestrator; -import java.io.File; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.ClassRule; import org.junit.Rule; @@ -41,10 +40,13 @@ import org.sonarqube.ws.client.users.DeactivateRequest; import org.sonarqube.ws.client.users.GroupsRequest; import org.sonarqube.ws.client.users.SearchRequest; +import static com.codeborne.selenide.Selenide.$; +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.io.FileUtils.readFileToString; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.sonarqube.ws.UserGroups.Group; -import static util.selenium.Selenese.runSelenese; public class BaseIdentityProviderTest { @@ -105,8 +107,11 @@ public class BaseIdentityProviderTest { // As this property is null, the plugin will throw an exception tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", null); - runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html"); + tester.openBrowser() + .logIn() + .useBaseAuth(); + $("#bd").shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")); assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); } @@ -116,21 +121,45 @@ public class BaseIdentityProviderTest { setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); tester.users().generate(u -> u.setLogin("another").setName("Another").setEmail(USER_EMAIL).setPassword("another")); - runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_when_email_already_exists.html"); + tester.openBrowser() + .logIn() + .useBaseAuth(); - File logFile = ORCHESTRATOR.getServer().getWebLogs(); - assertThat(FileUtils.readFileToString(logFile)) + $("#bd").shouldHave(Condition.text( + format("You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", USER_EMAIL))); + assertThat(readFileToString(ORCHESTRATOR.getServer().getWebLogs(), UTF_8)) .doesNotContain("You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account"); } @Test + public void fail_to_authenticate_user_when_email_already_exists_on_several_users() { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); + tester.users().generate(u -> u.setEmail(USER_EMAIL)); + tester.users().generate(u -> u.setEmail(USER_EMAIL)); + + tester.openBrowser() + .logIn() + .useBaseAuth(); + + $("#bd").shouldHave(Condition.text( + format("You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.", USER_EMAIL))); + assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); + } + + @Test public void fail_to_authenticate_when_not_allowed_to_sign_up() { enablePlugin(); setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.allowsUsersToSignUp", "false"); - runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html"); + tester.openBrowser() + .logIn() + .useBaseAuth(); + $("#bd") + .shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator.")) + .shouldHave(Condition.text(format("Reason: '%s' users are not allowed to sign up", FAKE_PROVIDER_KEY))); assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); } @@ -190,13 +219,16 @@ public class BaseIdentityProviderTest { setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL); tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "true"); - runSelenese(ORCHESTRATOR, - "/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); - - File logFile = ORCHESTRATOR.getServer().getWebLogs(); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException"); + tester.openBrowser() + .logIn() + .useBaseAuth(); + $("#bd") + .shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")) + .shouldHave(Condition.text("Reason: A functional error has happened")); + assertThat(readFileToString(ORCHESTRATOR.getServer().getWebLogs(), UTF_8)) + .doesNotContain("A functional error has happened") + .doesNotContain("UnauthorizedException"); assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent(); } diff --git a/tests/src/test/java/org/sonarqube/tests/user/HttpHeadersAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/HttpHeadersAuthenticationTest.java new file mode 100644 index 00000000000..300c60c0f60 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/HttpHeadersAuthenticationTest.java @@ -0,0 +1,191 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import java.net.URLEncoder; +import java.util.List; +import javax.annotation.Nullable; +import okhttp3.Response; +import org.apache.commons.io.FileUtils; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.qa.util.Tester; +import org.sonarqube.ws.UserGroups.Group; +import org.sonarqube.ws.Users.CreateWsResponse.User; +import org.sonarqube.ws.Users.SearchWsResponse; +import org.sonarqube.ws.client.users.SearchRequest; + +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static util.ItUtils.call; +import static util.ItUtils.newOrchestratorBuilder; + +/** + * Test authentication using HTTP headers. + * <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)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java b/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java index b7e194b3785..b6a74623aa1 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java +++ b/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java @@ -21,11 +21,9 @@ package org.sonarqube.tests.user; import com.codeborne.selenide.Condition; import com.sonar.orchestrator.Orchestrator; -import java.io.File; import java.net.HttpURLConnection; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; -import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; @@ -37,10 +35,12 @@ import org.sonarqube.ws.Users.SearchWsResponse.User; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.permissions.AddUserRequest; import org.sonarqube.ws.client.users.CreateRequest; -import util.selenium.Selenese; import static com.codeborne.selenide.Condition.visible; import static com.codeborne.selenide.Selenide.$; +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.io.FileUtils.readFileToString; import static org.assertj.core.api.Assertions.assertThat; /** @@ -137,8 +137,11 @@ public class OAuth2IdentityProviderTest { // As this property is null, the plugin will throw an exception tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", null); - Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html"); + tester.openBrowser() + .logIn() + .useOAuth2(); + $("#bd").shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")); assertThatUserDoesNotExist(USER_LOGIN); } @@ -148,8 +151,13 @@ public class OAuth2IdentityProviderTest { enablePlugin(); tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp", "false"); - Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html"); + tester.openBrowser() + .logIn() + .useOAuth2(); + $("#bd") + .shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator.")) + .shouldHave(Condition.text(format("Reason: '%s' users are not allowed to sign up", FAKE_PROVIDER_KEY))); assertThatUserDoesNotExist(USER_LOGIN); } @@ -159,12 +167,16 @@ public class OAuth2IdentityProviderTest { enablePlugin(); tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", "true"); - Selenese.runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); - - File logFile = orchestrator.getServer().getWebLogs(); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); - assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException"); - + tester.openBrowser() + .logIn() + .useOAuth2(); + + $("#bd") + .shouldHave(Condition.text("You're not authorized to access this page. Please contact the administrator")) + .shouldHave(Condition.text("Reason: A functional error has happened")); + assertThat(readFileToString(orchestrator.getServer().getWebLogs(), UTF_8)) + .doesNotContain("A functional error has happened") + .doesNotContain("UnauthorizedException"); assertThatUserDoesNotExist(USER_LOGIN); } @@ -206,6 +218,22 @@ public class OAuth2IdentityProviderTest { } @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(); enablePlugin(); diff --git a/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java index c733ffab7e6..e34e6371168 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java +++ b/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java @@ -21,43 +21,30 @@ package org.sonarqube.tests.user; import com.codeborne.selenide.Condition; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.sonar.orchestrator.Orchestrator; +import java.util.Collections; import java.util.Map; import javax.annotation.CheckForNull; -import org.junit.After; -import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.wsclient.Host; -import org.sonar.wsclient.Sonar; -import org.sonar.wsclient.base.HttpException; -import org.sonar.wsclient.connectors.HttpClient4Connector; -import org.sonar.wsclient.services.AuthenticationQuery; -import org.sonar.wsclient.user.UserParameters; import org.sonarqube.qa.util.Tester; import org.sonarqube.qa.util.pageobjects.Navigation; import org.sonarqube.qa.util.pageobjects.SystemInfoPage; import org.sonarqube.qa.util.pageobjects.UsersManagementPage; +import org.sonarqube.ws.UserGroups.Group; +import org.sonarqube.ws.Users; import org.sonarqube.ws.Users.SearchWsResponse.User; -import org.sonarqube.ws.client.GetRequest; -import org.sonarqube.ws.client.WsResponse; -import org.sonarqube.ws.client.users.CreateRequest; +import org.sonarqube.ws.client.users.ChangePasswordRequest; import org.sonarqube.ws.client.users.SearchRequest; -import util.user.UserRule; -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -import static org.junit.Assert.fail; +import static util.ItUtils.expectHttpError; import static util.ItUtils.newOrchestratorBuilder; -import static util.ItUtils.newUserWsClient; import static util.ItUtils.pluginArtifact; -import static util.ItUtils.resetSettings; -import static util.selenium.Selenese.runSelenese; /** * Test REALM authentication. @@ -66,18 +53,9 @@ 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")) @@ -85,53 +63,36 @@ public class RealmAuthenticationTest { .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)); } } diff --git a/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java deleted file mode 100644 index 146f37ea777..00000000000 --- a/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonarqube.tests.user; - -import com.sonar.orchestrator.Orchestrator; -import java.net.URLEncoder; -import java.util.List; -import javax.annotation.Nullable; -import okhttp3.Response; -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.RuleChain; -import util.user.UserRule; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static util.ItUtils.call; -import static util.ItUtils.newOrchestratorBuilder; - -/** - * Test SSO authentication (using HTTP headers). - * <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}"); - } - -} |