From bad58fe96a7dae5acb7c23a587174fed439c5566 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Fri, 11 Jan 2008 16:46:53 +0000 Subject: [PATCH] SEC-272: Partial group manager implementation. --- .../security/userdetails/GroupsManager.java | 32 +++++ .../jdbc/JdbcUserDetailsManager.java | 120 +++++++++++++++++- .../security/PopulatedDatabase.java | 15 ++- .../jdbc/JdbcUserDetailsManagerTests.java | 45 ++++++- 4 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/userdetails/GroupsManager.java diff --git a/core/src/main/java/org/springframework/security/userdetails/GroupsManager.java b/core/src/main/java/org/springframework/security/userdetails/GroupsManager.java new file mode 100644 index 0000000000..3ae6c6f13f --- /dev/null +++ b/core/src/main/java/org/springframework/security/userdetails/GroupsManager.java @@ -0,0 +1,32 @@ +package org.springframework.security.userdetails; + +import org.springframework.security.GrantedAuthority; + +import java.util.List; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public interface GroupsManager { + + List findAllGroups(); + + List findUsersInGroup(String groupName); + + void createGroup(String groupName, GrantedAuthority[] authorities); +// +// void deleteGroup(String groupName); +// +// void renameGroup(String oldName, String newName); +// +// void addUserToGroup(String username, String group); +// +// void removeUserFromGroup(String username, String groupName); +// +// GrantedAuthority[] findGroupAuthorities(String groupName); +// +// void removeGroupAuthority(String groupName, GrantedAuthority authority); +// +// void addGroupAuthority(String groupName, GrantedAuthority authority); +} diff --git a/core/src/main/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManager.java b/core/src/main/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManager.java index a5ed688b1f..5a42e69c77 100644 --- a/core/src/main/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManager.java @@ -4,12 +4,14 @@ import org.springframework.security.AccessDeniedException; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationException; import org.springframework.security.AuthenticationManager; +import org.springframework.security.GrantedAuthority; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.dao.UserCache; import org.springframework.security.providers.dao.cache.NullUserCache; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsManager; +import org.springframework.security.userdetails.GroupsManager; import org.springframework.context.ApplicationContextException; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.object.MappingSqlQuery; @@ -31,10 +33,12 @@ import java.util.List; * * @author Luke Taylor * @version $Id$ + * @since 2.0 */ -public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager { +public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager, GroupsManager { //~ Static fields/initializers ===================================================================================== + // UserDetailsManager SQL public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)"; public static final String DEF_DELETE_USER_SQL = @@ -50,6 +54,19 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa public static final String DEF_CHANGE_PASSWORD_SQL = "update users set password = ? where username = ?"; + // GroupsManager SQL + public static final String DEF_FIND_GROUPS_SQL = + "select group_name from groups"; + public static final String DEF_FIND_USERS_IN_GROUP_SQL = + "select username from group_members gm, groups g " + + "where gm.group_id = g.id" + + " and g.group_name = ?"; + public static final String DEF_INSERT_GROUP_SQL = + "insert into groups (group_name) values (?)"; + public static final String DEF_FIND_GROUP_ID_SQL = + "select id from groups where group_name = ?"; + public static final String DEF_INSERT_GROUP_AUTHORITY_SQL = + "insert into group_authorities (group_id, authority) values (?,?)"; //~ Instance fields ================================================================================================ @@ -63,6 +80,12 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa private String userExistsSql = DEF_USER_EXISTS_SQL; private String changePasswordSql = DEF_CHANGE_PASSWORD_SQL; + private String findAllGroupsSql = DEF_FIND_GROUPS_SQL; + private String findUsersInGroupSql = DEF_FIND_USERS_IN_GROUP_SQL; + private String insertGroupSql = DEF_INSERT_GROUP_SQL; + private String findGroupIdSql = DEF_FIND_GROUP_ID_SQL; + private String insertGroupAuthoritySql = DEF_INSERT_GROUP_AUTHORITY_SQL; + protected SqlUpdate insertUser; protected SqlUpdate deleteUser; protected SqlUpdate updateUser; @@ -71,6 +94,12 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa protected SqlQuery userExistsQuery; protected SqlUpdate changePassword; + protected SqlQuery findAllGroupsQuery; + protected SqlQuery findUsersInGroupQuery; + protected SqlUpdate insertGroup; + protected SqlQuery findGroupIdQuery; + protected SqlUpdate insertGroupAuthority; + private AuthenticationManager authenticationManager; private UserCache userCache = new NullUserCache(); @@ -90,9 +119,18 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa deleteUserAuthorities = new DeleteUserAuthorities(getDataSource()); userExistsQuery = new UserExistsQuery(getDataSource()); changePassword = new ChangePassword(getDataSource()); + + findAllGroupsQuery = new AllGroupsQuery(getDataSource()); + findUsersInGroupQuery = new GroupMembersQuery(getDataSource()); + insertGroup = new InsertGroup(getDataSource()); + findGroupIdQuery = new FindGroupIdQuery(getDataSource()); + insertGroupAuthority = new InsertGroupAuthority(getDataSource()); + super.initDao(); } + //~ UserDetailsManager implementation ============================================================================== + public void createUser(UserDetails user) { insertUser.update(new Object[] {user.getUsername(), user.getPassword(), Boolean.valueOf(user.isEnabled())}); @@ -167,6 +205,29 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa return users.size() == 1; } + //~ GroupManager implementation ==================================================================================== + + public List findAllGroups() { + return findAllGroupsQuery.execute(); + } + + public List findUsersInGroup(String groupName) { + Assert.hasText(groupName); + return findUsersInGroupQuery.execute(groupName); + } + + public void createGroup(String groupName, GrantedAuthority[] authorities) { + Assert.hasText(groupName); + Assert.notNull(authorities); + + insertGroup.update(groupName); + Integer key = (Integer) findGroupIdQuery.findObject(groupName); + + for (int i=0; i < authorities.length; i++) { + insertGroupAuthority.update( new Object[] {key, authorities[i].getAuthority()}); + } + } + public void setAuthenticationManager(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @@ -206,6 +267,10 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa this.changePasswordSql = changePasswordSql; } + public void setFindAllGroupsSql(String findAllGroupsSql) { + this.findAllGroupsSql = findAllGroupsSql; + } + /** * Optionally sets the UserCache if one is in use in the application. * This allows the user to be removed from the cache after updates have taken place to avoid stale data. @@ -276,7 +341,6 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa protected class UserExistsQuery extends MappingSqlQuery { - public UserExistsQuery(DataSource ds) { super(ds, userExistsSql); declareParameter(new SqlParameter(Types.VARCHAR)); @@ -287,4 +351,56 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa return rs.getString(1); } } + + protected class AllGroupsQuery extends MappingSqlQuery { + public AllGroupsQuery(DataSource ds) { + super(ds, findAllGroupsSql); + compile(); + } + + protected Object mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getString(1); + } + } + + protected class GroupMembersQuery extends MappingSqlQuery { + public GroupMembersQuery(DataSource ds) { + super(ds, findUsersInGroupSql); + declareParameter(new SqlParameter(Types.VARCHAR)); + compile(); + } + + protected Object mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getString(1); + } + } + + protected class InsertGroup extends SqlUpdate { + public InsertGroup(DataSource ds) { + super(ds, insertGroupSql); + declareParameter(new SqlParameter(Types.VARCHAR)); + compile(); + } + } + + private class FindGroupIdQuery extends MappingSqlQuery { + public FindGroupIdQuery(DataSource ds) { + super(ds, findGroupIdSql); + declareParameter(new SqlParameter(Types.INTEGER)); + compile(); + } + + protected Object mapRow(ResultSet rs, int rowNum) throws SQLException { + return Integer.valueOf(rs.getInt(1)); + } + } + + protected class InsertGroupAuthority extends SqlUpdate { + public InsertGroupAuthority(DataSource ds) { + super(ds, insertGroupAuthoritySql); + declareParameter(new SqlParameter(Types.INTEGER)); + declareParameter(new SqlParameter(Types.VARCHAR)); + compile(); + } + } } diff --git a/core/src/test/java/org/springframework/security/PopulatedDatabase.java b/core/src/test/java/org/springframework/security/PopulatedDatabase.java index db5dccc3ce..1ddaaf836a 100644 --- a/core/src/test/java/org/springframework/security/PopulatedDatabase.java +++ b/core/src/test/java/org/springframework/security/PopulatedDatabase.java @@ -99,6 +99,11 @@ public class PopulatedDatabase { template.execute("INSERT INTO acl_permission VALUES (null, 3, 'scott', 14);"); template.execute("INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);"); + createGroupTables(template); + insertGroupData(template); + } + + public static void createGroupTables(JdbcTemplate template) { // Group tables and data template.execute( "CREATE TABLE GROUPS(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY, GROUP_NAME VARCHAR_IGNORECASE(50) NOT NULL)"); @@ -106,15 +111,17 @@ public class PopulatedDatabase { "CREATE TABLE GROUP_AUTHORITIES(GROUP_ID BIGINT NOT NULL, AUTHORITY VARCHAR(50) NOT NULL, CONSTRAINT FK_GROUP_AUTHORITIES_GROUP FOREIGN KEY(GROUP_ID) REFERENCES GROUPS(ID))"); template.execute( "CREATE TABLE GROUP_MEMBERS(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY, USERNAME VARCHAR(50) NOT NULL, GROUP_ID BIGINT NOT NULL, CONSTRAINT FK_GROUP_MEMBERS_GROUP FOREIGN KEY(GROUP_ID) REFERENCES GROUPS(ID))"); + } + public static void insertGroupData(JdbcTemplate template) { template.execute("INSERT INTO USERS VALUES('jerry','password',TRUE)"); template.execute("INSERT INTO USERS VALUES('tom','password',TRUE)"); - template.execute("INSERT INTO GROUPS VALUES (0, 'GROUP_ZERO')"); - template.execute("INSERT INTO GROUPS VALUES (1, 'GROUP_ONE')"); - template.execute("INSERT INTO GROUPS VALUES (2, 'GROUP_TWO')"); + template.execute("INSERT INTO GROUPS VALUES (0, 'GROUP_0')"); + template.execute("INSERT INTO GROUPS VALUES (1, 'GROUP_1')"); + template.execute("INSERT INTO GROUPS VALUES (2, 'GROUP_2')"); // Group 3 isn't used - template.execute("INSERT INTO GROUPS VALUES (3, 'GROUP_THREE')"); + template.execute("INSERT INTO GROUPS VALUES (3, 'GROUP_3')"); template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (0, 'ROLE_A')"); template.execute("INSERT INTO GROUP_AUTHORITIES VALUES (1, 'ROLE_B')"); diff --git a/core/src/test/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManagerTests.java b/core/src/test/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManagerTests.java index f691254cfb..38d36e4cdf 100644 --- a/core/src/test/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManagerTests.java +++ b/core/src/test/java/org/springframework/security/userdetails/jdbc/JdbcUserDetailsManagerTests.java @@ -4,6 +4,9 @@ import org.springframework.security.AccessDeniedException; import org.springframework.security.Authentication; import org.springframework.security.BadCredentialsException; import org.springframework.security.MockAuthenticationManager; +import org.springframework.security.PopulatedDatabase; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.dao.UserCache; @@ -22,6 +25,8 @@ import org.junit.Test; import java.util.Map; import java.util.HashMap; +import java.util.List; +import java.util.Collections; /** * Tests for {@link JdbcUserDetailsManager} @@ -43,7 +48,7 @@ public class JdbcUserDetailsManagerTests { @BeforeClass public static void createDataSource() { - dataSource = new DriverManagerDataSource("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:tokenrepotest", "sa", ""); + dataSource = new DriverManagerDataSource("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:jdbcusermgrtest", "sa", ""); } @AfterClass @@ -71,12 +76,17 @@ public class JdbcUserDetailsManagerTests { "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(); } @@ -177,6 +187,39 @@ public class JdbcUserDetailsManagerTests { assertTrue(cache.getUserMap().containsKey("joe")); } + @Test + public void findAllGroupsReturnsExpectedGroupNames() { + List groups = manager.findAllGroups(); + assertEquals(4, groups.size()); + + Collections.sort(groups); + assertEquals("GROUP_0", groups.get(0)); + assertEquals("GROUP_1", groups.get(1)); + assertEquals("GROUP_2", groups.get(2)); + assertEquals("GROUP_3", groups.get(3)); + } + + @Test + public void findGroupMembersReturnsCorrectData() { + List groupMembers = manager.findUsersInGroup("GROUP_0"); + assertEquals(1, groupMembers.size()); + assertEquals("jerry", groupMembers.get(0)); + groupMembers = manager.findUsersInGroup("GROUP_1"); + assertEquals(2, groupMembers.size()); + } + + @Test + public void createGroupInsertsCorrectData() { + manager.createGroup("TEST_GROUP", AuthorityUtils.stringArrayToAuthorityArray(new String[] {"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'"); + + assertEquals(2, roles.size()); + } + private Authentication authenticateJoe() { UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("joe","password", joe.getAuthorities());