/*
* Copyright 2002-2016 the original author or authors.
*
* 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 org.springframework.security.provisioning;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.PopulatedDatabase;
import org.springframework.security.TestDataSource;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
/**
* Tests for {@link JdbcUserDetailsManager}
*
* @author Luke Taylor
*/
public class JdbcUserDetailsManagerTests {
private static final String SELECT_JOE_SQL = "select * from users where username = 'joe'";
private static final String SELECT_JOE_AUTHORITIES_SQL = "select * from authorities where username = 'joe'";
private static final UserDetails joe = new User("joe", "password", true, true, true,
true, AuthorityUtils.createAuthorityList("A", "C", "B"));
private static TestDataSource dataSource;
private JdbcUserDetailsManager manager;
private MockUserCache cache;
private JdbcTemplate template;
@BeforeClass
public static void createDataSource() {
dataSource = new TestDataSource("jdbcusermgrtest");
}
@AfterClass
public static void clearDataSource() throws Exception {
dataSource.destroy();
dataSource = null;
}
@Before
public void initializeManagerAndCreateTables() {
manager = new JdbcUserDetailsManager();
cache = new MockUserCache();
manager.setUserCache(cache);
manager.setDataSource(dataSource);
manager.setCreateUserSql(JdbcUserDetailsManager.DEF_CREATE_USER_SQL);
manager.setUpdateUserSql(JdbcUserDetailsManager.DEF_UPDATE_USER_SQL);
manager.setUserExistsSql(JdbcUserDetailsManager.DEF_USER_EXISTS_SQL);
manager.setCreateAuthoritySql(JdbcUserDetailsManager.DEF_INSERT_AUTHORITY_SQL);
manager.setDeleteUserAuthoritiesSql(JdbcUserDetailsManager.DEF_DELETE_USER_AUTHORITIES_SQL);
manager.setDeleteUserSql(JdbcUserDetailsManager.DEF_DELETE_USER_SQL);
manager.setChangePasswordSql(JdbcUserDetailsManager.DEF_CHANGE_PASSWORD_SQL);
manager.initDao();
template = manager.getJdbcTemplate();
template.execute("create table users(username varchar(20) not null primary key,"
+ "password varchar(20) not null, enabled boolean not null)");
template.execute("create table authorities (username varchar(20) not null, authority varchar(20) not null, "
+ "constraint fk_authorities_users foreign key(username) references users(username))");
PopulatedDatabase.createGroupTables(template);
PopulatedDatabase.insertGroupData(template);
}
@After
public void dropTablesAndClearContext() {
template.execute("drop table authorities");
template.execute("drop table users");
template.execute("drop table group_authorities");
template.execute("drop table group_members");
template.execute("drop table groups");
SecurityContextHolder.clearContext();
}
@Test
public void createUserInsertsCorrectData() {
manager.createUser(joe);
UserDetails joe2 = manager.loadUserByUsername("joe");
assertThat(joe2).isEqualTo(joe);
}
@Test
public void deleteUserRemovesUserDataAndAuthoritiesAndClearsCache() {
insertJoe();
manager.deleteUser("joe");
assertThat(template.queryForList(SELECT_JOE_SQL)).isEmpty();
assertThat(template.queryForList(SELECT_JOE_AUTHORITIES_SQL)).isEmpty();
assertThat(cache.getUserMap().containsKey("joe")).isFalse();
}
@Test
public void updateUserChangesDataCorrectlyAndClearsCache() {
insertJoe();
User newJoe = new User("joe", "newpassword", false, true, true, true,
AuthorityUtils.createAuthorityList(new String[] { "D", "F", "E" }));
manager.updateUser(newJoe);
UserDetails joe = manager.loadUserByUsername("joe");
assertThat(joe).isEqualTo(newJoe);
assertThat(cache.getUserMap().containsKey("joe")).isFalse();
}
@Test
public void userExistsReturnsFalseForNonExistentUsername() {
assertThat(manager.userExists("joe")).isFalse();
}
@Test
public void userExistsReturnsTrueForExistingUsername() {
insertJoe();
assertThat(manager.userExists("joe")).isTrue();
assertThat(cache.getUserMap().containsKey("joe")).isTrue();
}
@Test(expected = AccessDeniedException.class)
public void changePasswordFailsForUnauthenticatedUser() {
manager.changePassword("password", "newPassword");
}
@Test
public void changePasswordSucceedsWithAuthenticatedUserAndNoAuthenticationManagerSet() {
insertJoe();
authenticateJoe();
manager.changePassword("wrongpassword", "newPassword");
UserDetails newJoe = manager.loadUserByUsername("joe");
assertThat(newJoe.getPassword()).isEqualTo("newPassword");
assertThat(cache.getUserMap().containsKey("joe")).isFalse();
}
@Test
public void changePasswordSucceedsWithIfReAuthenticationSucceeds() {
insertJoe();
Authentication currentAuth = authenticateJoe();
AuthenticationManager am = mock(AuthenticationManager.class);
when(am.authenticate(currentAuth)).thenReturn(currentAuth);
manager.setAuthenticationManager(am);
manager.changePassword("password", "newPassword");
UserDetails newJoe = manager.loadUserByUsername("joe");
assertThat(newJoe.getPassword()).isEqualTo("newPassword");
// The password in the context should also be altered
Authentication newAuth = SecurityContextHolder.getContext().getAuthentication();
assertThat(newAuth.getName()).isEqualTo("joe");
assertThat(newAuth.getDetails()).isEqualTo(currentAuth.getDetails());
assertThat(newAuth.getCredentials()).isNull();
assertThat(cache.getUserMap().containsKey("joe")).isFalse();
}
@Test
public void changePasswordFailsIfReAuthenticationFails() {
insertJoe();
authenticateJoe();
AuthenticationManager am = mock(AuthenticationManager.class);
when(am.authenticate(any(Authentication.class))).thenThrow(
new BadCredentialsException(""));
manager.setAuthenticationManager(am);
try {
manager.changePassword("password", "newPassword");
fail("Expected BadCredentialsException");
}
catch (BadCredentialsException expected) {
}
// Check password hasn't changed.
UserDetails newJoe = manager.loadUserByUsername("joe");
assertThat(newJoe.getPassword()).isEqualTo("password");
assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).isEqualTo("password");
assertThat(cache.getUserMap().containsKey("joe")).isTrue();
}
@Test
public void findAllGroupsReturnsExpectedGroupNames() {
List<String> groups = manager.findAllGroups();
assertThat(groups).hasSize(4);
Collections.sort(groups);
assertThat(groups.get(0)).isEqualTo("GROUP_0");
assertThat(groups.get(1)).isEqualTo("GROUP_1");
assertThat(groups.get(2)).isEqualTo("GROUP_2");
assertThat(groups.get(3)).isEqualTo("GROUP_3");
}
@Test
public void findGroupMembersReturnsCorrectData() {
List<String> groupMembers = manager.findUsersInGroup("GROUP_0");
assertThat(groupMembers).hasSize(1);
assertThat(groupMembers.get(0)).isEqualTo("jerry");
groupMembers = manager.findUsersInGroup("GROUP_1");
assertThat(groupMembers).hasSize(2);
}
@Test
@SuppressWarnings("unchecked")
public void createGroupInsertsCorrectData() {
manager.createGroup("TEST_GROUP",
AuthorityUtils.createAuthorityList("ROLE_X", "ROLE_Y"));
List roles = template
.queryForList("select ga.authority from groups g, group_authorities ga "
+ "where ga.group_id = g.id " + "and g.group_name = 'TEST_GROUP'");
assertThat(roles).hasSize(2);
}
@Test
public void deleteGroupRemovesData() throws Exception {
manager.deleteGroup("GROUP_0");
manager.deleteGroup("GROUP_1");
manager.deleteGroup("GROUP_2");
manager.deleteGroup("GROUP_3");
assertThat(template.queryForList("select * from group_authorities")).isEmpty();
assertThat(template.queryForList("select * from group_members")).isEmpty();
assertThat(template.queryForList("select id from groups")).isEmpty();
}
@Test
public void renameGroupIsSuccessful() throws Exception {
manager.renameGroup("GROUP_0", "GROUP_X");
assertThat(template.queryForObject("select id from groups where group_name = 'GROUP_X'",
Integer.class)).isEqualTo(0);
}
@Test
public void addingGroupUserSetsCorrectData() throws Exception {
manager.addUserToGroup("tom", "GROUP_0");
assertThat(
template.queryForList(
"select username from group_members where group_id = 0")).hasSize(2);
}
@Test
public void removeUserFromGroupDeletesGroupMemberRow() throws Exception {
manager.removeUserFromGroup("jerry", "GROUP_1");
assertThat(
template.queryForList(
"select group_id from group_members where username = 'jerry'")).hasSize(1);
}
@Test
public void findGroupAuthoritiesReturnsCorrectAuthorities() throws Exception {
assertThat(AuthorityUtils.createAuthorityList("ROLE_A")).isEqualTo(manager.findGroupAuthorities("GROUP_0"));
}
@Test
public void addGroupAuthorityInsertsCorrectGroupAuthorityRow() throws Exception {
GrantedAuthority auth = new SimpleGrantedAuthority("ROLE_X");
manager.addGroupAuthority("GROUP_0", auth);
template.queryForObject(
"select authority from group_authorities where authority = 'ROLE_X' and group_id = 0",
String.class);
}
@Test
public void deleteGroupAuthorityRemovesCorrectRows() throws Exception {
GrantedAuthority auth = new SimpleGrantedAuthority("ROLE_A");
manager.removeGroupAuthority("GROUP_0", auth);
assertThat(
template.queryForList(
"select authority from group_authorities where group_id = 0")).isEmpty();
manager.removeGroupAuthority("GROUP_2", auth);
assertThat(
template.queryForList(
"select authority from group_authorities where group_id = 2")).hasSize(2);
}
// SEC-1156
@Test
public void createUserDoesNotSaveAuthoritiesIfEnableAuthoritiesIsFalse()
throws Exception {
manager.setEnableAuthorities(false);
manager.createUser(joe);
assertThat(template.queryForList(SELECT_JOE_AUTHORITIES_SQL)).isEmpty();
}
// SEC-1156
@Test
public void updateUserDoesNotSaveAuthoritiesIfEnableAuthoritiesIsFalse()
throws Exception {
manager.setEnableAuthorities(false);
insertJoe();
template.execute("delete from authorities where username='joe'");
manager.updateUser(joe);
assertThat(template.queryForList(SELECT_JOE_AUTHORITIES_SQL)).isEmpty();
}
// SEC-2166
@Test
public void createNewAuthenticationUsesNullPasswordToKeepPassordsSave() {
insertJoe();
UsernamePasswordAuthenticationToken currentAuth = new UsernamePasswordAuthenticationToken(
"joe", null, AuthorityUtils.createAuthorityList("ROLE_USER"));
Authentication updatedAuth = manager.createNewAuthentication(currentAuth, "new");
assertThat(updatedAuth.getCredentials()).isNull();
}
private Authentication authenticateJoe() {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
"joe", "password", joe.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
return auth;
}
private void insertJoe() {
template.execute("insert into users (username, password, enabled) values ('joe','password','true')");
template.execute("insert into authorities (username, authority) values ('joe','A')");
template.execute("insert into authorities (username, authority) values ('joe','B')");
template.execute("insert into authorities (username, authority) values ('joe','C')");
cache.putUserInCache(joe);
}
private class MockUserCache implements UserCache {
private Map<String, UserDetails> cache = new HashMap<String, UserDetails>();
public UserDetails getUserFromCache(String username) {
return (User) cache.get(username);
}
public void putUserInCache(UserDetails user) {
cache.put(user.getUsername(), user);
}
public void removeUserFromCache(String username) {
cache.remove(username);
}
Map<String, UserDetails> getUserMap() {
return cache;
}
}
}