it.user.RealmAuthenticationTest.java Source code

Java tutorial

Introduction

Here is the source code for it.user.RealmAuthenticationTest.java

Source

/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact 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 it.user;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.sonar.orchestrator.Orchestrator;
import java.util.Map;
import java.util.Objects;
import javax.annotation.CheckForNull;
import org.apache.commons.lang.RandomStringUtils;
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.ConnectionException;
import org.sonar.wsclient.connectors.HttpClient4Connector;
import org.sonar.wsclient.services.AuthenticationQuery;
import org.sonar.wsclient.services.UserPropertyCreateQuery;
import org.sonar.wsclient.services.UserPropertyQuery;
import org.sonar.wsclient.user.UserParameters;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.HttpConnector;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.WsClientFactories;
import org.sonarqube.ws.client.WsResponse;
import util.user.UserRule;

import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static util.ItUtils.pluginArtifact;
import static util.ItUtils.setServerProperty;
import static util.selenium.Selenese.runSelenese;

/**
 * Test REALM authentication.
 *
 * It starts its own server as it's using a different authentication system
 */
public class RealmAuthenticationTest {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    static final String TECH_USER = "techUser";
    static final String USER_LOGIN = "tester";

    /**
     * Property from security-plugin for user management.
     */
    private static final String USERS_PROPERTY = "sonar.fakeauthenticator.users";
    private static String AUTHORIZED = "authorized";
    private static String NOT_AUTHORIZED = "not authorized";

    @ClassRule
    public static final Orchestrator orchestrator = Orchestrator.builderEnv()
            .addPlugin(pluginArtifact("security-plugin")).setServerProperty("sonar.security.realm", "FakeRealm")
            .build();

    @ClassRule
    public static UserRule USER_RULE = UserRule.from(orchestrator);

    @Before
    @After
    public void resetData() throws Exception {
        setServerProperty(orchestrator, USERS_PROPERTY, null);
        setServerProperty(orchestrator, "sonar.security.updateUserAttributes", null);
        setServerProperty(orchestrator, "sonar.authenticator.createUsers", null);
        USER_RULE.resetUsers();
    }

    /**
     * SONAR-3137, SONAR-2292
     * Restriction on password length (minimum 4 characters) should be disabled, when external system enabled.
     */
    @Test
    public void shouldSynchronizeDetailsAndGroups() {
        // Given clean Sonar installation and no users in external system
        String username = USER_LOGIN;
        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);
        // Then
        assertThat(loginAttempt(username, password)).isEqualTo(AUTHORIZED);
        // with external details and groups
        runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html");

        // SONAR-4462
        runSelenese(orchestrator, "/user/ExternalAuthenticationTest/system-info.html");
    }

    /**
     * SONAR-4034
     */
    @Test
    public void shouldUpdateDetailsByDefault() {
        // Given clean Sonar installation and no users in external system
        String username = USER_LOGIN;
        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);
        // Then
        assertThat(loginAttempt(username, password)).isEqualTo(AUTHORIZED);
        // with external details and groups
        // TODO replace by WS ? Or with new Selenese utils
        runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html");

        // Now update user details
        users.put(username + ".name", "Tester2 Testerovich");
        users.put(username + ".email", "tester2@example.org");
        updateUsersInExtAuth(users);
        // Then
        assertThat(loginAttempt(username, password)).isEqualTo(AUTHORIZED);
        // with external details and groups updated
        runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details2.html");
    }

    /**
     * SONAR-3138
     */
    @Test
    public void shouldNotFallback() {
        // Given clean Sonar installation and no users in external system
        String login = USER_LOGIN;
        String password = "1234567";
        Map<String, String> users = Maps.newHashMap();

        // When user created in external system
        users.put(login + ".password", password);
        updateUsersInExtAuth(users);
        // Then
        assertThat(loginAttempt(login, password)).isEqualTo(AUTHORIZED);

        // When external system does not work
        users.remove(login + ".password");
        updateUsersInExtAuth(users);
        // Then
        assertThat(loginAttempt(login, password)).isEqualTo(NOT_AUTHORIZED);
    }

    /**
     * SONAR-4543
     */
    @Test
    public void adminIsLocalAccountByDefault() {
        // 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);

        // Then this is local DB that should be used
        assertThat(loginAttempt(login, remotePassword)).isEqualTo(NOT_AUTHORIZED);
        assertThat(loginAttempt(login, localPassword)).isEqualTo(AUTHORIZED);
    }

    /**
     * SONAR-1334, SONAR-3185 (createUsers=true is default)
     */
    @Test
    public void shouldCreateNewUsers() {
        // Given clean Sonar installation and no users in external system
        String username = USER_LOGIN;
        String password = "1234567";
        Map<String, String> users = Maps.newHashMap();

        // When user not exists in external system
        // Then
        assertThat(loginAttempt(username, password)).isEqualTo(NOT_AUTHORIZED);

        // When user created in external system
        users.put(username + ".password", password);
        updateUsersInExtAuth(users);
        // Then
        assertThat(loginAttempt(username, password)).isEqualTo(AUTHORIZED);
        assertThat(loginAttempt(username, "wrong")).isEqualTo(NOT_AUTHORIZED);
    }

    /**
     * SONAR-1334 (createUsers=false)
     */
    @Test
    public void shouldNotCreateNewUsers() {
        // Given clean Sonar installation and no users in external system
        setServerProperty(orchestrator, "sonar.authenticator.createUsers", "false");
        // Use a random user name because if we use existing disabled user then it doesn't work because rails doesn't handle this case
        // (it's using User.find_by_login to know if user exists or not
        String username = RandomStringUtils.randomAlphanumeric(20);
        String password = "1234567";
        Map<String, String> users = Maps.newHashMap();

        // When user not exists in external system
        // Then
        assertThat(loginAttempt(username, password)).isEqualTo(NOT_AUTHORIZED);

        // When user created in external system
        users.put(username + ".password", password);
        updateUsersInExtAuth(users);
        // Then
        assertThat(loginAttempt(username, password)).isEqualTo(NOT_AUTHORIZED);
    }

    // SONAR-3258
    @Test
    public void shouldAutomaticallyReactivateDeletedUser() throws Exception {
        // Given clean Sonar installation and no users in external system

        // Let's create and delete the user "tester" in Sonar DB
        runSelenese(orchestrator, "/user/ExternalAuthenticationTest/create-and-delete-user.html");

        // 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);
        // check that the deleted/deactivated user "tester" has been reactivated and can now log in
        assertThat(loginAttempt(login, password)).isEqualTo(AUTHORIZED);
    }

    /**
     * SONAR-7036
     */
    @Test
    public void update_password_of_technical_user() throws Exception {
        // Create user in external authentication
        updateUsersInExtAuth(ImmutableMap.of(USER_LOGIN + ".password", USER_LOGIN));
        assertThat(loginAttempt(USER_LOGIN, USER_LOGIN)).isEqualTo(AUTHORIZED);

        // Create technical user in db
        createUserInDb(TECH_USER, "old_password");
        assertThat(checkAuthenticationThroughWebService(TECH_USER, "old_password")).isTrue();

        // Updating password of technical user is allowed
        updateUserPasswordInDb(TECH_USER, "new_password");
        assertThat(checkAuthenticationThroughWebService(TECH_USER, "new_password")).isTrue();

        // But updating password of none local user is not allowed
        try {
            updateUserPasswordInDb(USER_LOGIN, "new_password");
            fail();
        } catch (HttpException e) {
            verifyHttpException(e, 400);
        }
    }

    /**
     * SONAR-7640
     */
    @Test
    public void authentication_with_ws() throws Exception {
        // Given clean Sonar installation and no users in external system
        String login = USER_LOGIN;
        String password = "1234567";
        Map<String, String> users = Maps.newHashMap();

        // When user created in external system
        users.put(login + ".password", password);
        updateUsersInExtAuth(users);

        assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_OK);
        assertThat(checkAuthenticationWithWebService("wrong", password).code()).isEqualTo(HTTP_UNAUTHORIZED);
        assertThat(checkAuthenticationWithWebService(login, "wrong").code()).isEqualTo(HTTP_UNAUTHORIZED);
        assertThat(checkAuthenticationWithWebService(login, null).code()).isEqualTo(HTTP_UNAUTHORIZED);
        assertThat(checkAuthenticationWithWebService(null, null).code()).isEqualTo(HTTP_OK);

        setServerProperty(orchestrator, "sonar.forceAuthentication", "true");

        assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_OK);
        assertThat(checkAuthenticationWithWebService("wrong", password).code()).isEqualTo(HTTP_UNAUTHORIZED);
        assertThat(checkAuthenticationWithWebService(login, "wrong").code()).isEqualTo(HTTP_UNAUTHORIZED);
        assertThat(checkAuthenticationWithWebService(login, null).code()).isEqualTo(HTTP_UNAUTHORIZED);
        assertThat(checkAuthenticationWithWebService(null, null).code()).isEqualTo(HTTP_UNAUTHORIZED);
    }

    @Test
    public void allow_user_login_with_2_characters() {
        String username = "jo";
        String password = "1234567";
        updateUsersInExtAuth(ImmutableMap.of(username + ".password", password));

        assertThat(loginAttempt(username, password)).isEqualTo(AUTHORIZED);
    }

    protected void verifyHttpException(Exception e, int expectedCode) {
        assertThat(e).isInstanceOf(HttpException.class);
        HttpException exception = (HttpException) e;
        assertThat(exception.status()).isEqualTo(expectedCode);
    }

    private boolean checkAuthenticationThroughWebService(String login, String password) {
        return createWsClient(login, password).find(new AuthenticationQuery()).isValid();
    }

    /**
     * Utility method to check that user can be authorized.
     *
     * @throws IllegalStateException
     */
    private String loginAttempt(String username, String password) {
        String expectedValue = Long.toString(System.currentTimeMillis());
        Sonar wsClient = createWsClient(username, password);
        try {
            wsClient.create(new UserPropertyCreateQuery("auth", expectedValue));
        } catch (ConnectionException e) {
            return NOT_AUTHORIZED;
        }
        try {
            String value = wsClient.find(new UserPropertyQuery("auth")).getValue();
            if (!Objects.equals(value, expectedValue)) {
                // exceptional case - update+retrieval were successful, but value doesn't match
                throw new IllegalStateException("Expected " + expectedValue + " , but got " + value);
            }
        } catch (ConnectionException e) {
            // exceptional case - update was successful, but not retrieval
            throw new IllegalStateException(e);
        }
        return AUTHORIZED;
    }

    /**
     * Updates information about users in security-plugin.
     */
    private static void updateUsersInExtAuth(Map<String, String> users) {
        setServerProperty(orchestrator, 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)));
    }

    @CheckForNull
    private static String format(Map<String, String> map) {
        if (map.isEmpty()) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            sb.append(entry.getKey()).append('=').append(entry.getValue()).append('\n');
        }
        return sb.toString();
    }

    private WsResponse checkAuthenticationWithWebService(String login, String password) {
        WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
                .url(orchestrator.getServer().getUrl()).credentials(login, password).build());
        // Call any WS
        return wsClient.wsConnector().call(new GetRequest("api/rules/search"));
    }

}