/*
* 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.server.security.providers;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.PluginRoleConfig;
import com.thoughtworks.go.config.SecurityAuthConfig;
import com.thoughtworks.go.config.SecurityConfig;
import com.thoughtworks.go.domain.packagerepository.ConfigurationPropertyMother;
import com.thoughtworks.go.plugin.access.authentication.AuthenticationExtension;
import com.thoughtworks.go.plugin.access.authentication.AuthenticationPluginRegistry;
import com.thoughtworks.go.plugin.access.authentication.models.User;
import com.thoughtworks.go.plugin.access.authorization.AuthorizationExtension;
import com.thoughtworks.go.plugin.access.authorization.AuthorizationMetadataStore;
import com.thoughtworks.go.plugin.access.authorization.models.AuthenticationResponse;
import com.thoughtworks.go.plugin.domain.authorization.AuthorizationPluginInfo;
import com.thoughtworks.go.plugin.domain.authorization.Capabilities;
import com.thoughtworks.go.plugin.domain.authorization.SupportedAuthType;
import com.thoughtworks.go.plugin.infra.plugininfo.GoPluginDescriptor;
import com.thoughtworks.go.server.security.AuthorityGranter;
import com.thoughtworks.go.server.security.GoAuthority;
import com.thoughtworks.go.server.security.userdetail.GoUserPrinciple;
import com.thoughtworks.go.server.service.GoConfigService;
import com.thoughtworks.go.server.service.PluginRoleService;
import com.thoughtworks.go.server.service.UserService;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
public class PluginAuthenticationProviderTest {
private static final AuthenticationResponse NULL_AUTH_RESPONSE = new AuthenticationResponse(null, null);
@Mock
private AuthorizationExtension authorizationExtension;
@Mock
private AuthenticationExtension authenticationExtension;
@Mock
private AuthenticationPluginRegistry authenticationPluginRegistry;
@Mock
private AuthorityGranter authorityGranter;
@Mock
private UsernamePasswordAuthenticationToken authenticationToken;
@Mock
private GoConfigService goConfigService;
@Mock
private PluginRoleService pluginRoleService;
@Mock
private UserService userService;
private GrantedAuthority userAuthority;
private PluginAuthenticationProvider provider;
@Rule
public ExpectedException exception = ExpectedException.none();
private SecurityConfig securityConfig;
@Before
public void setUp() {
initMocks(this);
when(authenticationToken.getCredentials()).thenReturn("password");
userAuthority = GoAuthority.ROLE_USER.asAuthority();
when(authorityGranter.authorities("username")).thenReturn(new GrantedAuthority[]{userAuthority});
provider = new PluginAuthenticationProvider(authenticationPluginRegistry, authenticationExtension, authorizationExtension, authorityGranter,
goConfigService, pluginRoleService, userService);
securityConfig = new SecurityConfig();
when(goConfigService.security()).thenReturn(securityConfig);
}
@After
public void tearDown() throws Exception {
AuthorizationMetadataStore.instance().clear();
}
@Test
public void shouldThrowUpWhenNoPluginCouldAuthenticateUser() throws Exception {
exception.expect(UsernameNotFoundException.class);
exception.expectMessage("Unable to authenticate user: bob");
addPluginSupportingPasswordBasedAuthentication("ldap");
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>(Arrays.asList("password")));
when(authorizationExtension.authenticateUser("ldap", "bob", "password", securityConfig.securityAuthConfigs().findByPluginId(null), null)).thenReturn(NULL_AUTH_RESPONSE);
when(authenticationExtension.authenticateUser("password", "bob", "password")).thenReturn(null);
provider.retrieveUser("bob", authenticationToken);
}
@Test
public void shouldAskAuthenticationPluginsWhenAuthorizationPluginIsUnableToAuthenticateUser() {
String pluginId = "plugin-id-1";
addPluginSupportingPasswordBasedAuthentication(pluginId);
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>(Arrays.asList(pluginId)));
when(authorizationExtension.authenticateUser(pluginId, "username", "password", securityConfig.securityAuthConfigs().findByPluginId(pluginId), null)).thenReturn(NULL_AUTH_RESPONSE);
try {
provider.retrieveUser("username", authenticationToken);
fail("should have thrown up");
} catch (Exception e) {
assertThat(e, is(instanceOf(UsernameNotFoundException.class)));
assertThat(e.getMessage(), is("Unable to authenticate user: username"));
}
}
@Test
public void shouldAddUserIfDoesNotExistOnSuccessfulAuthenticationUsingTheAuthorizationPlugin() {
String pluginId = "plugin-id-1";
addPluginSupportingPasswordBasedAuthentication(pluginId);
securityConfig.securityAuthConfigs().add(new SecurityAuthConfig("github", pluginId));
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>(Arrays.asList()));
AuthenticationResponse response = new AuthenticationResponse(new User("username", "display-name", "username@example.com"), Collections.emptyList());
when(authorizationExtension.authenticateUser(pluginId, "username", "password", securityConfig.securityAuthConfigs().findByPluginId(pluginId), securityConfig.getPluginRoles(pluginId))).thenReturn(response);
provider.retrieveUser("username", authenticationToken);
verify(userService).addUserIfDoesNotExist(new com.thoughtworks.go.domain.User("username", "display-name", "username@example.com"));
}
@Test
public void shouldAddUserIfDoesNotExistOnSuccessfulAuthenticationUsingTheAuthenticationPlugin() {
String pluginId = "plugin-id-1";
securityConfig.securityAuthConfigs().add(new SecurityAuthConfig("github", pluginId));
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>(Arrays.asList(pluginId)));
when(authenticationExtension.authenticateUser(pluginId, "username", "password")).thenReturn(new User("username", "display-name", "username@example.com"));
provider.retrieveUser("username", authenticationToken);
verify(userService).addUserIfDoesNotExist(new com.thoughtworks.go.domain.User("username", "display-name", "username@example.com"));
}
@Test(expected = UsernameNotFoundException.class)
public void shouldErrorOutIfUnableToAuthenticateUsingAnyOfThePlugins() {
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>(Arrays.asList()));
try {
provider.retrieveUser("username", authenticationToken);
fail("should have thrown up");
} finally {
verify(userService, never()).addUserIfDoesNotExist(any(com.thoughtworks.go.domain.User.class));
}
}
@Test
public void shouldCreateGoUserPrincipalWhenAnAuthorizationPluginIsAbleToAuthenticateUser() {
String pluginId1 = "plugin-id-1";
String pluginId2 = "plugin-id-2";
addPluginSupportingPasswordBasedAuthentication(pluginId1);
addPluginSupportingPasswordBasedAuthentication(pluginId2);
securityConfig.securityAuthConfigs().add(new SecurityAuthConfig("github", pluginId2));
securityConfig.addRole(new PluginRoleConfig("admin", "github", ConfigurationPropertyMother.create("foo")));
when(authorizationExtension.authenticateUser(pluginId1, "username", "password", securityConfig.securityAuthConfigs().findByPluginId(pluginId1), null)).thenReturn(NULL_AUTH_RESPONSE);
AuthenticationResponse response = new AuthenticationResponse(new User("username", "display-name", "test@test.com"), Collections.emptyList());
when(authorizationExtension.authenticateUser(pluginId2, "username", "password", securityConfig.securityAuthConfigs().findByPluginId(pluginId2), securityConfig.getPluginRoles(pluginId2))).thenReturn(response);
UserDetails userDetails = provider.retrieveUser("username", authenticationToken);
assertThat(userDetails, is(instanceOf(GoUserPrinciple.class)));
GoUserPrinciple goUserPrincipal = (GoUserPrinciple) userDetails;
assertThat(goUserPrincipal.getUsername(), is("username"));
assertThat(goUserPrincipal.getDisplayName(), is("display-name"));
assertThat(goUserPrincipal.getAuthorities().length, is(1));
assertThat(goUserPrincipal.getAuthorities()[0], is(userAuthority));
}
@Test
public void shouldCreateGoUserPrincipalWhenAnAuthenticationPluginIsAbleToAuthenticateUser() {
String pluginId1 = "plugin-id-1";
String pluginId2 = "plugin-id-2";
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>(Arrays.asList(pluginId1, pluginId2)));
when(authenticationExtension.authenticateUser(pluginId1, "username", "password")).thenReturn(null);
when(authenticationExtension.authenticateUser(pluginId2, "username", "password")).thenReturn(new User("username", null, null));
UserDetails userDetails = provider.retrieveUser("username", authenticationToken);
assertThat(userDetails, is(instanceOf(GoUserPrinciple.class)));
GoUserPrinciple goUserPrincipal = (GoUserPrinciple) userDetails;
assertThat(goUserPrincipal.getUsername(), is("username"));
assertThat(goUserPrincipal.getDisplayName(), is("username"));
assertThat(goUserPrincipal.getAuthorities().length, is(1));
assertThat(goUserPrincipal.getAuthorities()[0], is(userAuthority));
}
@Test
public void shouldAnswerSupportsBasedOnPluginAvailability() {
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>());
assertThat(provider.supports(UsernamePasswordAuthenticationToken.class), is(false));
addPluginSupportingPasswordBasedAuthentication("plugin-id-1");
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>());
assertThat(provider.supports(UsernamePasswordAuthenticationToken.class), is(true));
AuthorizationMetadataStore.instance().clear();
when(authenticationPluginRegistry.getPluginsThatSupportsPasswordBasedAuthentication()).thenReturn(new HashSet<>(Arrays.asList("plugin-id-1")));
assertThat(provider.supports(UsernamePasswordAuthenticationToken.class), is(true));
}
@Test
public void shouldUpdatePluginRolesForAUserPostAuthentication() throws Exception {
securityConfig.securityAuthConfigs().add(new SecurityAuthConfig("ldap", "cd.go.ldap"));
securityConfig.securityAuthConfigs().add(new SecurityAuthConfig("github", "cd.go.github"));
String pluginId1 = "cd.go.ldap";
String pluginId2 = "cd.go.github";
addPluginSupportingPasswordBasedAuthentication(pluginId1);
addPluginSupportingPasswordBasedAuthentication(pluginId2);
when(authorizationExtension.authenticateUser(pluginId1, "username", "password", securityConfig.securityAuthConfigs().findByPluginId(pluginId1), securityConfig.getPluginRoles(pluginId1))).thenReturn(
new AuthenticationResponse(
new User("username", "bob", "bob@example.com"),
Arrays.asList("blackbird", "admins")
)
);
when(authorizationExtension.authenticateUser(pluginId2, "username", "password", securityConfig.securityAuthConfigs().findByPluginId(pluginId2), securityConfig.getPluginRoles(pluginId2))).thenReturn(NULL_AUTH_RESPONSE);
UserDetails userDetails = provider.retrieveUser("username", new UsernamePasswordAuthenticationToken(null, "password"));
assertNotNull(userDetails);
verify(pluginRoleService).updatePluginRoles("cd.go.ldap", "username", CaseInsensitiveString.caseInsensitiveStrings(Arrays.asList("blackbird", "admins")));
}
private void addPluginSupportingPasswordBasedAuthentication(String pluginId) {
AuthorizationPluginInfo pluginInfo = new AuthorizationPluginInfo(
new GoPluginDescriptor(pluginId, null, null, null, null, false), null, null, null,
new Capabilities(SupportedAuthType.Password, true, false));
AuthorizationMetadataStore.instance().setPluginInfo(pluginInfo);
}
}