/*
* The MIT License (MIT)
*
* Copyright (c) 2014 Andreas Alanko, Emil Nilsson, Sony Mobile Communications AB.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.sonymobile.jenkins.plugins.gitlab.gitlabauth.security;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.sonymobile.jenkins.plugins.gitlab.gitlabapi.GitLabConfiguration;
import hudson.security.SecurityRealm;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.jvnet.hudson.test.JenkinsRule;
import java.util.concurrent.Callable;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.containing;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
/**
* Unit tests for {@link GitLabSecurityRealm}.
*
* Tests logging in on the system with GitLab authentication.
* The test cases uses a mock GitLab server to authenticate against.
*
* @author Emil Nilsson
*/
public class GitLabSecurityRealmTest {
/** The port to run the mocked GitLab server on. */
// fixme: allow setting port from command line to prevent collisions
private final static int GITLAB_PORT = 9090;
/** A rule for creating a Jenkins environment. */
@Rule
public JenkinsRule jenkinsRule = new JenkinsRule();
/** A rule for mocking the GitLab server API. */
@Rule
public WireMockRule wiremockRule = new WireMockRule(GITLAB_PORT);
/** A rule for catching expected exceptions. */
@Rule
public ExpectedException thrown = ExpectedException.none();
/** The GitLab security realm to use. */
private SecurityRealm securityRealm;
/** The Jenkins instance. */
private Jenkins jenkins;
/** The Jenkins web client. */
private JenkinsRule.WebClient webClient;
/**
* Set up the Jenkins environment and security realm.
*/
@Before
public void setUp() {
jenkins = jenkinsRule.jenkins;
webClient = jenkinsRule.createWebClient();
// configure the GitLab API plugin
configureApi();
// configure GitLab authentication
configureSecurityRealm();
}
/**
* Test authenticating a user logging in using valid credentials.
*/
@Test
public void authenticateWithValidCredentials() throws Exception {
// make GitLab respond with the current user for connection test
stubFor(get(urlEqualTo("/api/v3/user?private_token=private_token"))
.willReturn(aResponse()
.withStatus(200)
.withBodyFile("/api/v3/user.json")));
// make GitLab respond with a valid session
stubFor(post(urlEqualTo("/api/v3/session"))
.withRequestBody(containing("login=username"))
.withRequestBody(containing("password=password"))
.willReturn(aResponse()
.withStatus(201)
.withBodyFile("/api/v3/session.json")));
webClient.login("username", "password");
// get authentication from the web client
Authentication authentication = getAuthentication();
assertThat("User should be logged in", authentication, is(not(Jenkins.ANONYMOUS)));
assertThat(authentication.getPrincipal(), is(instanceOf(GitLabUserDetails.class)));
// get the authenticated user
GitLabUserDetails user = (GitLabUserDetails)authentication.getPrincipal();
assertThat("username", is(user.getUsername()));
assertThat(2, is(user.getId()));
assertThat("user@example.com", is(user.getEmail()));
assertThat("0123456789abcdef", is(user.getPrivateToken()));
}
/**
* Test authenticating a user logging in using invalid credentials.
*/
@Test
public void authenticateWithInvalidCredentials() throws Exception {
// make GitLab respond with the current user for connection test
stubFor(get(urlEqualTo("/api/v3/user?private_token=private_token"))
.willReturn(aResponse()
.withStatus(200)
.withBodyFile("/api/v3/user.json")));
// make GitLab respond with an HTTP 401 Unauthorized
stubFor(post(urlEqualTo("/api/v3/session"))
.withRequestBody(containing("login=invalidusername"))
.withRequestBody(containing("password=invalidpassword"))
.willReturn(aResponse()
.withStatus(401)));
// login should fail with HTTP 401 Unauthorized
thrown.expect(FailingHttpStatusCodeException.class);
thrown.expectMessage("401");
webClient.login("invalidusername", "invalidpassword");
}
/**
* Configures the GitLab API plugin of the Jenkins instance.
*/
private void configureApi() {
GitLabConfiguration config = jenkinsRule.get(GitLabConfiguration.class).getInstance();
config.setServerUrl("http://localhost:" + GITLAB_PORT);
// private token can be anything
config.setPrivateToken("private_token");
}
/**
* Configures the Jenkins instance to authenticate against GitLab.
*/
private void configureSecurityRealm() {
// use GitLab authentication
securityRealm = new GitLabSecurityRealm();
jenkins.setSecurityRealm(securityRealm);
}
/**
* Gets the authentication object from the web client.
*
* @return the authentication object
*/
private Authentication getAuthentication() {
try {
return webClient.executeOnServer(new Callable<Authentication>() {
public Authentication call() throws Exception {
return jenkins.getAuthentication();
}
});
} catch (Exception e) {
// safely ignore all exceptions, the method never throws anything
return null;
}
}
}