/* * Copyright 2017 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.thoughtworks.go.plugin.access.authorization; import com.thoughtworks.go.config.PluginRoleConfig; import com.thoughtworks.go.config.SecurityAuthConfig; import com.thoughtworks.go.config.SecurityAuthConfigs; import com.thoughtworks.go.domain.packagerepository.ConfigurationPropertyMother; import com.thoughtworks.go.plugin.access.authentication.models.User; import com.thoughtworks.go.plugin.access.authorization.models.AuthenticationResponse; import com.thoughtworks.go.plugin.access.authorization.models.SupportedAuthType; import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; import com.thoughtworks.go.plugin.api.response.validation.ValidationError; import com.thoughtworks.go.plugin.api.response.validation.ValidationResult; import com.thoughtworks.go.plugin.domain.common.Metadata; import com.thoughtworks.go.plugin.domain.common.PluginConfiguration; import com.thoughtworks.go.plugin.infra.PluginManager; import org.hamcrest.core.Is; import org.json.JSONException; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.skyscreamer.jsonassert.JSONAssert; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import static com.thoughtworks.go.plugin.access.authorization.AuthorizationPluginConstants.*; import static com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse.SUCCESS_RESPONSE_CODE; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasSize; import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; public class AuthorizationExtensionTest { public static final String PLUGIN_ID = "plugin-id"; @Mock private PluginManager pluginManager; private ArgumentCaptor<GoPluginApiRequest> requestArgumentCaptor; private AuthorizationExtension authorizationExtension; @Rule public ExpectedException thrown = ExpectedException.none(); @Before public void setUp() throws Exception { initMocks(this); when(pluginManager.resolveExtensionVersion(PLUGIN_ID, Arrays.asList("1.0"))).thenReturn("1.0"); when(pluginManager.isPluginOfType(AuthorizationPluginConstants.EXTENSION_NAME, PLUGIN_ID)).thenReturn(true); authorizationExtension = new AuthorizationExtension(pluginManager); requestArgumentCaptor = ArgumentCaptor.forClass(GoPluginApiRequest.class); } @Test public void shouldTalkToPlugin_To_GetCapabilities() throws Exception { String responseBody = "{\"supported_auth_type\":\"password\",\"can_search\":true}"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); com.thoughtworks.go.plugin.domain.authorization.Capabilities capabilities = authorizationExtension.getCapabilities(PLUGIN_ID); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_GET_CAPABILITIES, null); assertThat(capabilities.getSupportedAuthType().toString(), is(SupportedAuthType.Password.toString())); assertThat(capabilities.canSearch(), is(true)); } @Test public void shouldTalkToPlugin_To_GetPluginConfigurationMetadata() throws Exception { String responseBody = "[{\"key\":\"username\",\"metadata\":{\"required\":true,\"secure\":false}},{\"key\":\"password\",\"metadata\":{\"required\":true,\"secure\":true}}]"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); List<PluginConfiguration> authConfigMetadata = authorizationExtension.getAuthConfigMetadata(PLUGIN_ID); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_GET_AUTH_CONFIG_METADATA, null); assertThat(authConfigMetadata.size(), is(2)); assertThat(authConfigMetadata, containsInAnyOrder( new PluginConfiguration("username", new Metadata(true, false)), new PluginConfiguration("password", new Metadata(true, true)) )); } @Test public void shouldTalkToPlugin_To_GetAuthConfigView() throws Exception { String responseBody = "{ \"template\": \"<div>This is view snippet</div>\" }"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); String pluginConfigurationView = authorizationExtension.getAuthConfigView(PLUGIN_ID); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_GET_AUTH_CONFIG_VIEW, null); assertThat(pluginConfigurationView, is("<div>This is view snippet</div>")); } @Test public void shouldTalkToPlugin_To_ValidateAuthConfig() throws Exception { String responseBody = "[{\"message\":\"Url must not be blank.\",\"key\":\"Url\"},{\"message\":\"SearchBase must not be blank.\",\"key\":\"SearchBase\"}]"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); ValidationResult validationResult = authorizationExtension.validateAuthConfig(PLUGIN_ID, Collections.emptyMap()); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_VALIDATE_AUTH_CONFIG, "{}"); assertThat(validationResult.isSuccessful(), is(false)); assertThat(validationResult.getErrors(), containsInAnyOrder( new ValidationError("Url", "Url must not be blank."), new ValidationError("SearchBase", "SearchBase must not be blank.") )); } @Test public void shouldTalkToPlugin_To_VerifyConnection() throws Exception { String responseBody = "{\"status\":\"success\"}"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); AuthorizationExtension authorizationExtensionSpy = spy(authorizationExtension); authorizationExtensionSpy.verifyConnection(PLUGIN_ID, Collections.emptyMap()); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_VERIFY_CONNECTION, "{}"); verify(authorizationExtensionSpy).verifyConnection(PLUGIN_ID, Collections.emptyMap()); } @Test public void shouldTalkToPlugin_To_GetRoleConfigurationMetadata() throws Exception { String responseBody = "[{\"key\":\"memberOf\",\"metadata\":{\"required\":true,\"secure\":false}}]"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); List<PluginConfiguration> roleConfigurationMetadata = authorizationExtension.getRoleConfigurationMetadata(PLUGIN_ID); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_GET_ROLE_CONFIG_METADATA, null); assertThat(roleConfigurationMetadata.size(), is(1)); assertThat(roleConfigurationMetadata, containsInAnyOrder( new PluginConfiguration("memberOf", new Metadata(true, false)) )); } @Test public void shouldTalkToPlugin_To_GetRoleConfigurationView() throws Exception { String responseBody = "{ \"template\": \"<div>This is view snippet</div>\" }"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); String pluginConfigurationView = authorizationExtension.getRoleConfigurationView(PLUGIN_ID); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_GET_ROLE_CONFIG_VIEW, null); assertThat(pluginConfigurationView, is("<div>This is view snippet</div>")); } @Test public void shouldTalkToPlugin_To_ValidateRoleConfiguration() throws Exception { String responseBody = "[{\"message\":\"memberOf must not be blank.\",\"key\":\"memberOf\"}]"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); ValidationResult validationResult = authorizationExtension.validateRoleConfiguration(PLUGIN_ID, Collections.emptyMap()); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_VALIDATE_ROLE_CONFIG, "{}"); assertThat(validationResult.isSuccessful(), is(false)); assertThat(validationResult.getErrors(), containsInAnyOrder( new ValidationError("memberOf", "memberOf must not be blank.") )); } @Test public void shouldTalkToPlugin_To_AuthenticateUser() throws Exception { String requestBody = "{\n" + " \"credentials\": {\n" + " \"username\": \"bob\",\n" + " \"password\": \"secret\"\n" + " },\n" + " \"auth_configs\": [\n" + " {\n" + " \"id\": \"ldap\",\n" + " \"configuration\": {\n" + " \"url\": \"some-url\"\n" + " }\n" + " }\n" + " ],\n" + " \"role_configs\": [\n" + " {\n" + " \"name\": \"foo\",\n" + " \"auth_config_id\": \"ldap\",\n" + " \"configuration\": {\n" + " \"memberOf\": \"ou=some-value\"\n" + " }\n" + " }\n" + " ]\n" + "}"; String responseBody = "{\"user\":{\"username\":\"bob\",\"display_name\":\"Bob\",\"email\":\"bob@example.com\"},\"roles\":[\"blackbird\"]}"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); final PluginRoleConfig roleConfig = new PluginRoleConfig("foo", "ldap", ConfigurationPropertyMother.create("memberOf", false, "ou=some-value")); final List<PluginRoleConfig> pluginRoleConfigs = Collections.singletonList(roleConfig); final SecurityAuthConfigs authConfigs = new SecurityAuthConfigs(); authConfigs.add(new SecurityAuthConfig("ldap", "cd.go.ldap", ConfigurationPropertyMother.create("url", false, "some-url"))); AuthenticationResponse authenticationResponse = authorizationExtension.authenticateUser(PLUGIN_ID, "bob", "secret", authConfigs, pluginRoleConfigs); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_AUTHENTICATE_USER, requestBody); assertThat(authenticationResponse.getUser(), is(new User("bob", "Bob", "bob@example.com"))); assertThat(authenticationResponse.getRoles().get(0), is("blackbird")); } @Test public void shouldTalkToPlugin_To_AuthenticateUserWithEmptyListIfRoleConfigsAreNotProvided() throws Exception { String requestBody = "{\n" + " \"credentials\": {\n" + " \"username\": \"bob\",\n" + " \"password\": \"secret\"\n" + " },\n" + " \"auth_configs\": [\n" + " {\n" + " \"id\": \"ldap\",\n" + " \"configuration\": {\n" + " \"url\": \"some-url\"\n" + " }\n" + " }\n" + " ],\n" + " \"role_configs\": []\n" + "}"; String responseBody = "{\"user\":{\"username\":\"bob\",\"display_name\":\"Bob\",\"email\":\"bob@example.com\"},\"roles\":[\"blackbird\"]}"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); final SecurityAuthConfigs authConfigs = new SecurityAuthConfigs(); authConfigs.add(new SecurityAuthConfig("ldap", "cd.go.ldap", ConfigurationPropertyMother.create("url", false, "some-url"))); AuthenticationResponse authenticationResponse = authorizationExtension.authenticateUser(PLUGIN_ID, "bob", "secret", authConfigs, null); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_AUTHENTICATE_USER, requestBody); assertThat(authenticationResponse.getUser(), is(new User("bob", "Bob", "bob@example.com"))); assertThat(authenticationResponse.getRoles().get(0), is("blackbird")); } @Test public void authenticateUser_shouldErrorOutInAbsenceOfSecurityAuthConfigs() throws Exception { thrown.expect(MissingAuthConfigsException.class); thrown.expectMessage("No AuthConfigs configured for plugin: plugin-id, Plugin would need at-least one auth_config to authenticate user."); authorizationExtension.authenticateUser(PLUGIN_ID, "bob", "secret", null, null); verifyNoMoreInteractions(pluginManager); } @Test public void shouldTalkToPlugin_To_SearchUsers() throws Exception { String requestBody = "{\n" + " \"search_term\": \"bob\",\n" + " \"auth_configs\": [\n" + " {\n" + " \"id\": \"ldap\",\n" + " \"configuration\": {\n" + " \"foo\": \"bar\"\n" + " }\n" + " }\n" + " ]\n" + "}"; String responseBody = "[{\"username\":\"bob\",\"display_name\":\"Bob\",\"email\":\"bob@example.com\"}]"; when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); List<User> users = authorizationExtension.searchUsers(PLUGIN_ID, "bob", Collections.singletonList(new SecurityAuthConfig("ldap", "cd.go.ldap", ConfigurationPropertyMother.create("foo", false, "bar")))); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_SEARCH_USERS, requestBody); assertThat(users, hasSize(1)); assertThat(users, hasItem(new User("bob", "Bob", "bob@example.com"))); } @Test public void shouldTalkToPlugin_To_GetAuthorizationServerRedirectUrl() throws JSONException { String requestBody = "{\n" + " \"auth_configs\": [\n" + " {\n" + " \"id\": \"github\",\n" + " \"configuration\": {\n" + " \"url\": \"some-url\"\n" + " }\n" + " }\n" + " ],\n" + " \"authorization_server_callback_url\": \"http://go.site.url/go/plugin/plugin-id/authenticate\"\n"+ "}"; String responseBody = "{\"authorization_server_url\":\"url_to_authorization_server\"}"; SecurityAuthConfig authConfig = new SecurityAuthConfig("github", "cd.go.github", ConfigurationPropertyMother.create("url", false, "some-url")); when(pluginManager.submitTo(eq(PLUGIN_ID), requestArgumentCaptor.capture())).thenReturn(new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, responseBody)); String authorizationServerRedirectUrl = authorizationExtension.getAuthorizationServerRedirectUrl(PLUGIN_ID, Collections.singletonList(authConfig), "http://go.site.url"); assertRequest(requestArgumentCaptor.getValue(), AuthorizationPluginConstants.EXTENSION_NAME, "1.0", REQUEST_AUTHORIZATION_SERVER_REDIRECT_URL, requestBody); assertThat(authorizationServerRedirectUrl, is("url_to_authorization_server")); } private void assertRequest(GoPluginApiRequest goPluginApiRequest, String extensionName, String version, String requestName, String requestBody) throws JSONException { Assert.assertThat(goPluginApiRequest.extension(), Is.is(extensionName)); Assert.assertThat(goPluginApiRequest.extensionVersion(), Is.is(version)); Assert.assertThat(goPluginApiRequest.requestName(), Is.is(requestName)); JSONAssert.assertEquals(requestBody, goPluginApiRequest.requestBody(), true); } }