SEC-415: Add document management system ACL sample.
This commit is contained in:
parent
93509dc999
commit
17cc70a3cd
|
@ -4,6 +4,10 @@
|
|||
<classpathentry kind="src" path="core/src/main/resources"/>
|
||||
<classpathentry kind="src" path="core/src/test/java"/>
|
||||
<classpathentry kind="src" path="core/src/test/resources"/>
|
||||
<classpathentry kind="src" path="samples/dms/src/test/java"/>
|
||||
<classpathentry kind="src" path="samples/dms/src/main/resources"/>
|
||||
<classpathentry kind="src" path="samples/dms/src/main/java"/>
|
||||
<classpathentry kind="src" path="samples/dms/src/test/resources"/>
|
||||
<classpathentry kind="src" path="sandbox/other/src/main/java"/>
|
||||
<classpathentry kind="src" path="sandbox/other/src/test/java"/>
|
||||
<classpathentry kind="src" path="samples/contacts/src/main/java"/>
|
||||
|
@ -52,7 +56,7 @@
|
|||
<classpathentry kind="var" path="MAVEN_REPO/ehcache/jars/ehcache-1.1.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/javax.servlet/jars/jsp-api-2.0.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/hibernate/jars/hibernate-3.0.3.jar"/>
|
||||
<classpathentry sourcepath="DIST_BASE/commons-beanutils-1.6.1-src/src/java" kind="var" path="MAVEN_REPO/commons-beanutils/jars/commons-beanutils-1.6.1.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/commons-beanutils/jars/commons-beanutils-1.6.1.jar" sourcepath="DIST_BASE/commons-beanutils-1.6.1-src/src/java"/>
|
||||
<classpathentry kind="src" path="samples/contacts-tiger/src/main/java"/>
|
||||
<classpathentry kind="src" path="core-tiger/src/main/java"/>
|
||||
<classpathentry kind="src" path="core-tiger/src/main/resources"/>
|
||||
|
@ -64,7 +68,7 @@
|
|||
<classpathentry kind="var" path="MAVEN_REPO/org.samba.jcifs/jars/jcifs-1.2.6.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/dom4j/jars/dom4j-1.6.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/xerces/jars/xercesImpl-2.6.2.jar"/>
|
||||
<classpathentry sourcepath="MAVEN_REPO/jmock/distributions/jmock-1.0.1-src.jar" kind="var" path="MAVEN_REPO/jmock/jars/jmock-1.0.1.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/jmock/jars/jmock-1.0.1.jar" sourcepath="MAVEN_REPO/jmock/distributions/jmock-1.0.1-src.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/jdbm/jars/jdbm-1.0.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/regexp/jars/regexp-1.2.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/org.slf4j/jars/slf4j-log4j12-1.0-rc5.jar"/>
|
||||
|
@ -79,5 +83,6 @@
|
|||
<classpathentry kind="var" path="MAVEN_REPO/taglibs/jars/standard-1.0.6.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/commons-attributes/jars/commons-attributes-api-2.1.jar"/>
|
||||
<classpathentry kind="var" path="MAVEN_REPO/log4j/jars/log4j-1.2.9.jar"/>
|
||||
<classpathentry kind="var" path="M2_REPO/postgresql/postgresql/8.1-407.jdbc3/postgresql-8.1-407.jdbc3.jar"/>
|
||||
<classpathentry kind="output" path="target/eclipseclasses"/>
|
||||
</classpath>
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package sample.dms;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractElement {
|
||||
/** The name of this token (ie filename or directory segment name */
|
||||
private String name;
|
||||
|
||||
/** The parent of this token (ie directory, or null if referring to root) */
|
||||
private AbstractElement parent;
|
||||
|
||||
/** The database identifier for this object (null if not persisted) */
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* Constructor to use to represent a root element. A root element has an id of -1.
|
||||
*/
|
||||
protected AbstractElement() {
|
||||
this.name = "/";
|
||||
this.parent = null;
|
||||
this.id = new Long(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to use to represent a non-root element.
|
||||
*
|
||||
* @param name name for this element (required, cannot be "/")
|
||||
* @param parent for this element (required, cannot be null)
|
||||
*/
|
||||
protected AbstractElement(String name, AbstractElement parent) {
|
||||
Assert.hasText(name, "Name required");
|
||||
Assert.notNull(parent, "Parent required");
|
||||
Assert.notNull(parent.getId(), "The parent must have been saved in order to create a child");
|
||||
this.name = name;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of this token (never null, although will be "/" if root, otherwise it won't include separators)
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public AbstractElement getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fully-qualified name of this element, including any parents
|
||||
*/
|
||||
public String getFullName() {
|
||||
List strings = new ArrayList();
|
||||
AbstractElement currentElement = this;
|
||||
while (currentElement != null) {
|
||||
strings.add(0, currentElement.getName());
|
||||
currentElement = currentElement.getParent();
|
||||
}
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
String lastCharacter = null;
|
||||
for (Iterator i = strings.iterator(); i.hasNext();) {
|
||||
String token = (String) i.next();
|
||||
if (!"/".equals(lastCharacter) && lastCharacter != null) {
|
||||
sb.append("/");
|
||||
}
|
||||
sb.append(token);
|
||||
lastCharacter = token.substring(token.length()-1);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package sample.dms;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.acegisecurity.Authentication;
|
||||
import org.acegisecurity.GrantedAuthority;
|
||||
import org.acegisecurity.GrantedAuthorityImpl;
|
||||
import org.acegisecurity.context.SecurityContextHolder;
|
||||
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* Populates the DMS in-memory database with document and ACL information.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DataSourcePopulator implements InitializingBean {
|
||||
protected static final int LEVEL_NEGATE_READ = 0;
|
||||
protected static final int LEVEL_GRANT_READ = 1;
|
||||
protected static final int LEVEL_GRANT_WRITE = 2;
|
||||
protected static final int LEVEL_GRANT_ADMIN = 3;
|
||||
protected JdbcTemplate template;
|
||||
protected DocumentDao documentDao;
|
||||
protected TransactionTemplate tt;
|
||||
|
||||
public DataSourcePopulator(DataSource dataSource, DocumentDao documentDao, PlatformTransactionManager platformTransactionManager) {
|
||||
Assert.notNull(dataSource, "DataSource required");
|
||||
Assert.notNull(documentDao, "DocumentDao required");
|
||||
Assert.notNull(platformTransactionManager, "PlatformTransactionManager required");
|
||||
this.template = new JdbcTemplate(dataSource);
|
||||
this.documentDao = documentDao;
|
||||
this.tt = new TransactionTemplate(platformTransactionManager);
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// ACL tables
|
||||
template.execute("CREATE TABLE ACL_SID(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,PRINCIPAL BOOLEAN NOT NULL,SID VARCHAR_IGNORECASE(100) NOT NULL,CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));");
|
||||
template.execute("CREATE TABLE ACL_CLASS(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,CLASS VARCHAR_IGNORECASE(100) NOT NULL,CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));");
|
||||
template.execute("CREATE TABLE ACL_OBJECT_IDENTITY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,OBJECT_ID_CLASS BIGINT NOT NULL,OBJECT_ID_IDENTITY BIGINT NOT NULL,PARENT_OBJECT BIGINT,OWNER_SID BIGINT,ENTRIES_INHERITING BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));");
|
||||
template.execute("CREATE TABLE ACL_ENTRY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY BIGINT NOT NULL,ACE_ORDER INT NOT NULL,SID BIGINT NOT NULL,MASK INTEGER NOT NULL,GRANTING BOOLEAN NOT NULL,AUDIT_SUCCESS BOOLEAN NOT NULL,AUDIT_FAILURE BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));");
|
||||
|
||||
// Normal authentication tables
|
||||
template.execute("CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,ENABLED BOOLEAN NOT NULL);");
|
||||
template.execute("CREATE TABLE AUTHORITIES(USERNAME VARCHAR_IGNORECASE(50) NOT NULL,AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME));");
|
||||
template.execute("CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY);");
|
||||
|
||||
// Document management system business tables
|
||||
template.execute("CREATE TABLE DIRECTORY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY, DIRECTORY_NAME VARCHAR_IGNORECASE(50) NOT NULL, PARENT_DIRECTORY_ID BIGINT)");
|
||||
template.execute("CREATE TABLE FILE(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY, FILE_NAME VARCHAR_IGNORECASE(50) NOT NULL, CONTENT VARCHAR_IGNORECASE(1024), PARENT_DIRECTORY_ID BIGINT)");
|
||||
|
||||
// Populate the authentication and role tables
|
||||
template.execute("INSERT INTO USERS VALUES('marissa','a564de63c2d0da68cf47586ee05984d7',TRUE);");
|
||||
template.execute("INSERT INTO USERS VALUES('dianne','65d15fe9156f9c4bbffd98085992a44e',TRUE);");
|
||||
template.execute("INSERT INTO USERS VALUES('scott','2b58af6dddbd072ed27ffc86725d7d3a',TRUE);");
|
||||
template.execute("INSERT INTO USERS VALUES('peter','22b5c9accc6e1ba628cedc63a72d57f8',FALSE);");
|
||||
template.execute("INSERT INTO USERS VALUES('bill','2b58af6dddbd072ed27ffc86725d7d3a',TRUE);");
|
||||
template.execute("INSERT INTO USERS VALUES('bob','2b58af6dddbd072ed27ffc86725d7d3a',TRUE);");
|
||||
template.execute("INSERT INTO USERS VALUES('jane','2b58af6dddbd072ed27ffc86725d7d3a',TRUE);");
|
||||
template.execute("INSERT INTO AUTHORITIES VALUES('marissa','ROLE_USER');");
|
||||
template.execute("INSERT INTO AUTHORITIES VALUES('marissa','ROLE_SUPERVISOR');");
|
||||
template.execute("INSERT INTO AUTHORITIES VALUES('dianne','ROLE_USER');");
|
||||
template.execute("INSERT INTO AUTHORITIES VALUES('scott','ROLE_USER');");
|
||||
template.execute("INSERT INTO AUTHORITIES VALUES('peter','ROLE_USER');");
|
||||
template.execute("INSERT INTO AUTHORITIES VALUES('bill','ROLE_USER');");
|
||||
template.execute("INSERT INTO AUTHORITIES VALUES('bob','ROLE_USER');");
|
||||
template.execute("INSERT INTO AUTHORITIES VALUES('jane','ROLE_USER');");
|
||||
|
||||
// Now create an ACL entry for the root directory
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("marissa", "ignored", new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_IGNORED")}));
|
||||
tt.execute(new TransactionCallback() {
|
||||
public Object doInTransaction(TransactionStatus arg0) {
|
||||
addPermission(documentDao, Directory.ROOT_DIRECTORY, "ROLE_USER", LEVEL_GRANT_WRITE);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Now go off and create some directories and files for our users
|
||||
createSampleData("marissa", "koala");
|
||||
createSampleData("dianne", "emu");
|
||||
createSampleData("scott", "wombat");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory for the user, and a series of sub-directories. The root directory is the parent for the user directory. The sub-directories
|
||||
* are "confidential" and "shared". The ROLE_USER will be given read and write access to "shared".
|
||||
*/
|
||||
private void createSampleData(String username, String password) {
|
||||
Assert.notNull(documentDao, "DocumentDao required");
|
||||
Assert.hasText(username, "Username required");
|
||||
|
||||
Authentication auth = new UsernamePasswordAuthenticationToken(username, password);
|
||||
|
||||
try {
|
||||
// Set the SecurityContextHolder ThreadLocal so any subclasses automatically know which user is operating
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
|
||||
// Create the home directory first
|
||||
Directory home = new Directory(username, Directory.ROOT_DIRECTORY);
|
||||
documentDao.create(home);
|
||||
addPermission(documentDao, home, username, LEVEL_GRANT_ADMIN);
|
||||
addPermission(documentDao, home, "ROLE_USER", LEVEL_GRANT_READ);
|
||||
createFiles(documentDao, home);
|
||||
|
||||
// Now create the confidential directory
|
||||
Directory confid = new Directory("confidential", home);
|
||||
documentDao.create(confid);
|
||||
addPermission(documentDao, confid, "ROLE_USER", LEVEL_NEGATE_READ);
|
||||
createFiles(documentDao, confid);
|
||||
|
||||
// Now create the shared directory
|
||||
Directory shared = new Directory("shared", home);
|
||||
documentDao.create(shared);
|
||||
addPermission(documentDao, shared, "ROLE_USER", LEVEL_GRANT_READ);
|
||||
addPermission(documentDao, shared, "ROLE_USER", LEVEL_GRANT_WRITE);
|
||||
createFiles(documentDao, shared);
|
||||
} finally {
|
||||
// Clear the SecurityContextHolder ThreadLocal so future calls are guaranteed to be clean
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
private void createFiles(DocumentDao documentDao, Directory parent) {
|
||||
Assert.notNull(documentDao, "DocumentDao required");
|
||||
Assert.notNull(parent, "Parent required");
|
||||
int countBeforeInsert = documentDao.findElements(parent).length;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
File file = new File("file_" + i + ".txt", parent);
|
||||
documentDao.create(file);
|
||||
}
|
||||
Assert.isTrue(countBeforeInsert + 10 == documentDao.findElements(parent).length, "Failed to increase count by 10");
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows subclass to add permissions.
|
||||
*
|
||||
* @param documentDao that will presumably offer methods to enable the operation to be completed
|
||||
* @param element to the subject of the new permissions
|
||||
* @param recipient to receive permission (if it starts with ROLE_ it is assumed to be a GrantedAuthority, else it is a username)
|
||||
* @param level based on the static final integer fields on this class
|
||||
*/
|
||||
protected void addPermission(DocumentDao documentDao, AbstractElement element, String recipient, int level) {}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package sample.dms;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*
|
||||
*/
|
||||
public class Directory extends AbstractElement {
|
||||
public static final Directory ROOT_DIRECTORY = new Directory();
|
||||
|
||||
private Directory() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Directory(String name, Directory parent) {
|
||||
super(name, parent);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Directory[fullName='" + getFullName() + "'; name='" + getName() + "'; id='" + getId() + "'; parent='" + getParent() + "']";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package sample.dms;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*
|
||||
*/
|
||||
public interface DocumentDao {
|
||||
/**
|
||||
* Creates an entry in the database for the element.
|
||||
*
|
||||
* @param element an unsaved element (the "id" will be updated after method is invoked)
|
||||
*/
|
||||
public void create(AbstractElement element);
|
||||
|
||||
/**
|
||||
* Removes a file from the database for the specified element.
|
||||
*
|
||||
* @param file the file to remove (cannot be null)
|
||||
*/
|
||||
public void delete(File file);
|
||||
|
||||
/**
|
||||
* Modifies a file in the database.
|
||||
*
|
||||
* @param file the file to update (cannot be null)
|
||||
*/
|
||||
public void update(File file);
|
||||
|
||||
/**
|
||||
* Locates elements in the database which appear under the presented directory
|
||||
*
|
||||
* @param directory the directory (cannot be null - use {@link Directory#ROOT_DIRECTORY} for root)
|
||||
* @return zero or more elements in the directory (an empty array may be returned - never null)
|
||||
*/
|
||||
public AbstractElement[] findElements(Directory directory);
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package sample.dms;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.acegisecurity.util.FieldUtils;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.support.JdbcDaoSupport;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Basic JDBC implementation of {@link DocumentDao}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DocumentDaoImpl extends JdbcDaoSupport implements DocumentDao {
|
||||
|
||||
private static final String INSERT_INTO_DIRECTORY = "insert into directory(directory_name, parent_directory_id) values (?,?)";
|
||||
private static final String INSERT_INTO_FILE = "insert into file(file_name, content, parent_directory_id) values (?,?,?)";
|
||||
private static final String SELECT_FROM_DIRECTORY = "select id from directory where parent_directory_id = ?";
|
||||
private static final String SELECT_FROM_DIRECTORY_NULL = "select id from directory where parent_directory_id is null";
|
||||
private static final String SELECT_FROM_FILE = "select id, file_name, content, parent_directory_id from file where parent_directory_id = ?";
|
||||
private static final String SELECT_FROM_DIRECTORY_SINGLE = "select id, directory_name, parent_directory_id from directory where id = ?";
|
||||
private static final String DELETE_FROM_FILE = "delete from file where id = ?";
|
||||
private static final String UPDATE_FILE = "update file set content = ? where id = ?";
|
||||
private static final String SELECT_IDENTITY = "call identity()";
|
||||
|
||||
private Long obtainPrimaryKey() {
|
||||
Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running");
|
||||
return new Long(getJdbcTemplate().queryForLong(SELECT_IDENTITY));
|
||||
}
|
||||
|
||||
public void create(AbstractElement element) {
|
||||
Assert.notNull(element, "Element required");
|
||||
Assert.isNull(element.getId(), "Element has previously been saved");
|
||||
if (element instanceof Directory) {
|
||||
Directory directory = (Directory) element;
|
||||
Long parentId = directory.getParent() == null ? null : directory.getParent().getId();
|
||||
getJdbcTemplate().update(INSERT_INTO_DIRECTORY, new Object[] {directory.getName(), parentId});
|
||||
FieldUtils.setProtectedFieldValue("id", directory, obtainPrimaryKey());
|
||||
} else if (element instanceof File) {
|
||||
File file = (File) element;
|
||||
Long parentId = file.getParent() == null ? null : file.getParent().getId();
|
||||
getJdbcTemplate().update(INSERT_INTO_FILE, new Object[] {file.getName(), file.getContent(), parentId});
|
||||
FieldUtils.setProtectedFieldValue("id", file, obtainPrimaryKey());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported AbstractElement");
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(File file) {
|
||||
Assert.notNull(file, "File required");
|
||||
Assert.notNull(file.getId(), "File ID required");
|
||||
getJdbcTemplate().update(DELETE_FROM_FILE, new Object[] {file.getId()});
|
||||
}
|
||||
|
||||
/** Executes recursive SQL as needed to build a full Directory hierarchy of objects */
|
||||
private Directory getDirectoryWithImmediateParentPopulated(final Long id) {
|
||||
return (Directory) getJdbcTemplate().queryForObject(SELECT_FROM_DIRECTORY_SINGLE, new Object[] {id}, new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
|
||||
Long parentDirectoryId = new Long(rs.getLong("parent_directory_id"));
|
||||
Directory parentDirectory = Directory.ROOT_DIRECTORY;
|
||||
if (parentDirectoryId != null && !parentDirectoryId.equals(new Long(-1))) {
|
||||
// Need to go and lookup the parent, so do that first
|
||||
parentDirectory = getDirectoryWithImmediateParentPopulated(parentDirectoryId);
|
||||
}
|
||||
Directory directory = new Directory(rs.getString("directory_name"), parentDirectory);
|
||||
FieldUtils.setProtectedFieldValue("id", directory, new Long(rs.getLong("id")));
|
||||
return directory;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AbstractElement[] findElements(Directory directory) {
|
||||
Assert.notNull(directory, "Directory required (the ID can be null to refer to root)");
|
||||
if (directory.getId() == null) {
|
||||
List directories = getJdbcTemplate().query(SELECT_FROM_DIRECTORY_NULL, new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
|
||||
return getDirectoryWithImmediateParentPopulated(new Long(rs.getLong("id")));
|
||||
}
|
||||
});
|
||||
return (AbstractElement[]) directories.toArray(new AbstractElement[] {});
|
||||
}
|
||||
List directories = getJdbcTemplate().query(SELECT_FROM_DIRECTORY, new Object[] {directory.getId()}, new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
|
||||
return getDirectoryWithImmediateParentPopulated(new Long(rs.getLong("id")));
|
||||
}
|
||||
});
|
||||
List files = getJdbcTemplate().query(SELECT_FROM_FILE, new Object[] {directory.getId()}, new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
|
||||
Long parentDirectoryId = new Long(rs.getLong("parent_directory_id"));
|
||||
Directory parentDirectory = null;
|
||||
if (parentDirectoryId != null) {
|
||||
parentDirectory = getDirectoryWithImmediateParentPopulated(parentDirectoryId);
|
||||
}
|
||||
File file = new File(rs.getString("file_name"), parentDirectory);
|
||||
FieldUtils.setProtectedFieldValue("id", file, new Long(rs.getLong("id")));
|
||||
return file;
|
||||
}
|
||||
});
|
||||
// Add the File elements after the Directory elements
|
||||
directories.addAll(files);
|
||||
return (AbstractElement[]) directories.toArray(new AbstractElement[] {});
|
||||
}
|
||||
|
||||
public void update(File file) {
|
||||
Assert.notNull(file, "File required");
|
||||
Assert.notNull(file.getId(), "File ID required");
|
||||
getJdbcTemplate().update(UPDATE_FILE, new Object[] {file.getContent(), file.getId()});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package sample.dms;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*/
|
||||
public class File extends AbstractElement {
|
||||
/** Content of the file, which can be null */
|
||||
private String content;
|
||||
|
||||
public File(String name, Directory parent) {
|
||||
super(name, parent);
|
||||
Assert.isTrue(!parent.equals(Directory.ROOT_DIRECTORY), "Cannot insert File into root directory");
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "File[fullName='" + getFullName() + "'; name='" + getName() + "'; id='" + getId() + "'; content=" + getContent() + "'; parent='" + getParent() + "']";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package sample.dms.secured;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.acegisecurity.acls.MutableAcl;
|
||||
import org.acegisecurity.acls.MutableAclService;
|
||||
import org.acegisecurity.acls.NotFoundException;
|
||||
import org.acegisecurity.acls.Permission;
|
||||
import org.acegisecurity.acls.domain.BasePermission;
|
||||
import org.acegisecurity.acls.objectidentity.ObjectIdentity;
|
||||
import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
|
||||
import org.acegisecurity.acls.sid.GrantedAuthoritySid;
|
||||
import org.acegisecurity.acls.sid.PrincipalSid;
|
||||
import org.acegisecurity.acls.sid.Sid;
|
||||
import org.acegisecurity.context.SecurityContextHolder;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import sample.dms.AbstractElement;
|
||||
import sample.dms.DataSourcePopulator;
|
||||
import sample.dms.DocumentDao;
|
||||
|
||||
public class SecureDataSourcePopulator extends DataSourcePopulator {
|
||||
|
||||
private MutableAclService aclService;
|
||||
|
||||
public SecureDataSourcePopulator(DataSource dataSource, SecureDocumentDao documentDao, PlatformTransactionManager platformTransactionManager, MutableAclService aclService) {
|
||||
super(dataSource, documentDao, platformTransactionManager);
|
||||
Assert.notNull(aclService, "MutableAclService required");
|
||||
this.aclService = aclService;
|
||||
}
|
||||
|
||||
protected void addPermission(DocumentDao documentDao, AbstractElement element, String recipient, int level) {
|
||||
Assert.notNull(documentDao, "DocumentDao required");
|
||||
Assert.isInstanceOf(SecureDocumentDao.class, documentDao, "DocumentDao should have been a SecureDocumentDao");
|
||||
Assert.notNull(element, "Element required");
|
||||
Assert.hasText(recipient, "Recipient required");
|
||||
Assert.notNull(SecurityContextHolder.getContext().getAuthentication(), "SecurityContextHolder must contain an Authentication");
|
||||
|
||||
// We need SecureDocumentDao to assign different permissions
|
||||
SecureDocumentDao dao = (SecureDocumentDao) documentDao;
|
||||
|
||||
// We need to construct an ACL-specific Sid. Note the prefix contract is defined on the superclass method's JavaDocs
|
||||
Sid sid = null;
|
||||
if (recipient.startsWith("ROLE_")) {
|
||||
sid = new GrantedAuthoritySid(recipient);
|
||||
} else {
|
||||
sid = new PrincipalSid(recipient);
|
||||
}
|
||||
|
||||
// We need to identify the target domain object and create an ObjectIdentity for it
|
||||
// This works because AbstractElement has a "getId()" method
|
||||
ObjectIdentity identity = new ObjectIdentityImpl(element);
|
||||
// ObjectIdentity identity = new ObjectIdentityImpl(element.getClass(), element.getId()); // equivalent
|
||||
|
||||
// Next we need to create a Permission
|
||||
Permission permission = null;
|
||||
if (level == LEVEL_NEGATE_READ || level == LEVEL_GRANT_READ) {
|
||||
permission = BasePermission.READ;
|
||||
} else if (level == LEVEL_GRANT_WRITE) {
|
||||
permission = BasePermission.WRITE;
|
||||
} else if (level == LEVEL_GRANT_ADMIN) {
|
||||
permission = BasePermission.ADMINISTRATION;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported LEVEL_");
|
||||
}
|
||||
|
||||
// Attempt to retrieve the existing ACL, creating an ACL if it doesn't already exist for this ObjectIdentity
|
||||
MutableAcl acl = null;
|
||||
try {
|
||||
acl = (MutableAcl) aclService.readAclById(identity);
|
||||
} catch (NotFoundException nfe) {
|
||||
acl = aclService.createAcl(identity);
|
||||
Assert.notNull(acl, "Acl could not be retrieved or created");
|
||||
}
|
||||
|
||||
// Now we have an ACL, add another ACE to it
|
||||
if (level == LEVEL_NEGATE_READ) {
|
||||
acl.insertAce(null, permission, sid, false); // not granting
|
||||
} else {
|
||||
acl.insertAce(null, permission, sid, true); // granting
|
||||
}
|
||||
|
||||
// Finally, persist the modified ACL
|
||||
aclService.updateAcl(acl);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package sample.dms.secured;
|
||||
|
||||
import sample.dms.DocumentDao;
|
||||
|
||||
/**
|
||||
* Extends the {@link DocumentDao} and introduces ACL-related methods.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*
|
||||
*/
|
||||
public interface SecureDocumentDao extends DocumentDao {
|
||||
/**
|
||||
* @return all the usernames existing in the system.
|
||||
*/
|
||||
public String[] getUsers();
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package sample.dms.secured;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.acegisecurity.acls.MutableAcl;
|
||||
import org.acegisecurity.acls.MutableAclService;
|
||||
import org.acegisecurity.acls.domain.BasePermission;
|
||||
import org.acegisecurity.acls.objectidentity.ObjectIdentity;
|
||||
import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
|
||||
import org.acegisecurity.acls.sid.PrincipalSid;
|
||||
import org.acegisecurity.context.SecurityContextHolder;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import sample.dms.AbstractElement;
|
||||
import sample.dms.DocumentDaoImpl;
|
||||
|
||||
/**
|
||||
* Adds extra {@link SecureDocumentDao} methods.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*
|
||||
*/
|
||||
public class SecureDocumentDaoImpl extends DocumentDaoImpl implements SecureDocumentDao {
|
||||
|
||||
private static final String SELECT_FROM_USERS = "SELECT USERNAME FROM USERS ORDER BY USERNAME";
|
||||
private MutableAclService mutableAclService;
|
||||
|
||||
public SecureDocumentDaoImpl(MutableAclService mutableAclService) {
|
||||
Assert.notNull(mutableAclService, "MutableAclService required");
|
||||
this.mutableAclService = mutableAclService;
|
||||
}
|
||||
|
||||
public String[] getUsers() {
|
||||
return (String[]) getJdbcTemplate().query(SELECT_FROM_USERS, new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
|
||||
return rs.getString("USERNAME");
|
||||
}
|
||||
}).toArray(new String[] {});
|
||||
}
|
||||
|
||||
public void create(AbstractElement element) {
|
||||
super.create(element);
|
||||
|
||||
// Create an ACL identity for this element
|
||||
ObjectIdentity identity = new ObjectIdentityImpl(element);
|
||||
MutableAcl acl = mutableAclService.createAcl(identity);
|
||||
|
||||
// If the AbstractElement has a parent, go and retrieve its identity (it should already exist)
|
||||
if (element.getParent() != null) {
|
||||
ObjectIdentity parentIdentity = new ObjectIdentityImpl(element.getParent());
|
||||
MutableAcl aclParent = (MutableAcl) mutableAclService.readAclById(parentIdentity);
|
||||
acl.setParent(aclParent);
|
||||
}
|
||||
acl.insertAce(null, BasePermission.ADMINISTRATION, new PrincipalSid(SecurityContextHolder.getContext().getAuthentication()), true);
|
||||
|
||||
mutableAclService.updateAcl(acl);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
|
||||
|
||||
<!--
|
||||
- Application context representing the application without any security services.
|
||||
-
|
||||
- $Id$
|
||||
-->
|
||||
|
||||
<beans>
|
||||
|
||||
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
|
||||
<property name="transactionAttributeSource">
|
||||
<value>
|
||||
sample.dms.DocumentDao.*=PROPAGATION_REQUIRED
|
||||
</value>
|
||||
</property>
|
||||
<property name="transactionManager" ref="transactionManager" />
|
||||
</bean>
|
||||
|
||||
<bean id="documentDao" class="sample.dms.DocumentDaoImpl">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSourcePopulator" class="sample.dms.DataSourcePopulator">
|
||||
<constructor-arg ref="dataSource"/>
|
||||
<constructor-arg ref="documentDao"/>
|
||||
<constructor-arg ref="transactionManager"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
|
@ -0,0 +1,233 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
|
||||
|
||||
<!--
|
||||
- Application context representing the application WITH security services.
|
||||
-
|
||||
- $Id$
|
||||
-->
|
||||
|
||||
<beans>
|
||||
|
||||
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
|
||||
<property name="transactionAttributeSource">
|
||||
<value>
|
||||
sample.dms.secured.SecureDocumentDao.*=PROPAGATION_REQUIRED
|
||||
sample.dms.DocumentDao.*=PROPAGATION_REQUIRED
|
||||
org.acegisecurity.acls.AclService.*=PROPAGATION_REQUIRED
|
||||
org.acegisecurity.acls.MutableAclService.*=PROPAGATION_REQUIRED
|
||||
org.acegisecurity.acls.jdbc.JdbcMutableAclService.*=PROPAGATION_REQUIRED
|
||||
org.acegisecurity.acls.jdbc.JdbcAclService.*=PROPAGATION_REQUIRED
|
||||
</value>
|
||||
</property>
|
||||
<property name="transactionManager" ref="transactionManager" />
|
||||
</bean>
|
||||
|
||||
<bean id="documentDao" class="sample.dms.secured.SecureDocumentDaoImpl">
|
||||
<constructor-arg ref="aclService"/>
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSourcePopulator" class="sample.dms.secured.SecureDataSourcePopulator">
|
||||
<constructor-arg ref="dataSource"/>
|
||||
<constructor-arg ref="documentDao"/>
|
||||
<constructor-arg ref="transactionManager"/>
|
||||
<constructor-arg ref="aclService"/>
|
||||
</bean>
|
||||
|
||||
<!-- =================================== SECURITY DEFINITION BEANS ======================================== -->
|
||||
|
||||
<!-- ======================== AUTHENTICATION (note there is no UI and this is for integration tests only) ======================= -->
|
||||
|
||||
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
|
||||
<property name="providers">
|
||||
<list>
|
||||
<ref local="daoAuthenticationProvider"/>
|
||||
<ref local="anonymousAuthenticationProvider"/>
|
||||
<ref local="rememberMeAuthenticationProvider"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
|
||||
<property name="userDetailsService" ref="jdbcDaoImpl"/>
|
||||
<property name="userCache" ref="userCache"/>
|
||||
<property name="passwordEncoder">
|
||||
<bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
|
||||
|
||||
<bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
|
||||
<property name="cacheManager" ref="cacheManager"/>
|
||||
<property name="cacheName" value="userCache"/>
|
||||
</bean>
|
||||
|
||||
<bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
|
||||
<property name="cache" ref="userCacheBackend"/>
|
||||
</bean>
|
||||
|
||||
<!-- Automatically receives AuthenticationEvent messages -->
|
||||
<bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>
|
||||
|
||||
<bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
|
||||
<property name="key" value="foobar"/>
|
||||
</bean>
|
||||
|
||||
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
|
||||
|
||||
<bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
|
||||
<property name="userDetailsService" ref="jdbcDaoImpl"/>
|
||||
<property name="key" value="springRocks"/>
|
||||
</bean>
|
||||
|
||||
<bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
|
||||
<property name="key" value="springRocks"/>
|
||||
</bean>
|
||||
|
||||
<!-- ========================= "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ============================== -->
|
||||
|
||||
<!-- ACL permission masks used by this application -->
|
||||
<bean id="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
|
||||
<property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION</value></property>
|
||||
</bean>
|
||||
<bean id="org.acegisecurity.acls.domain.BasePermission.READ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
|
||||
<property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.READ</value></property>
|
||||
</bean>
|
||||
<bean id="org.acegisecurity.acls.domain.BasePermission.WRITE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
|
||||
<property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.WRITE</value></property>
|
||||
</bean>
|
||||
|
||||
|
||||
<!-- An access decision voter that reads ROLE_* configuration settings -->
|
||||
<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>
|
||||
|
||||
<!-- An access decision voter that reads ACL_ABSTRACT_ELEMENT_WRITE_PARENT configuration settings -->
|
||||
<bean id="aclAbstractElementWriteParentVoter" class="org.acegisecurity.vote.AclEntryVoter">
|
||||
<constructor-arg ref="aclService"/>
|
||||
<constructor-arg value="ACL_ABSTRACT_ELEMENT_WRITE_PARENT"/>
|
||||
<constructor-arg>
|
||||
<list>
|
||||
<ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
|
||||
<ref local="org.acegisecurity.acls.domain.BasePermission.WRITE"/>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<property name="processDomainObjectClass"><value>sample.dms.AbstractElement</value></property>
|
||||
<property name="internalMethod" value="getParent"/>
|
||||
</bean>
|
||||
|
||||
<!-- An access decision voter that reads ACL_ABSTRACT_ELEMENT_WRITE configuration settings -->
|
||||
<bean id="aclAbstractElementWriteVoter" class="org.acegisecurity.vote.AclEntryVoter">
|
||||
<constructor-arg ref="aclService"/>
|
||||
<constructor-arg value="ACL_ABSTRACT_ELEMENT_WRITE"/>
|
||||
<constructor-arg>
|
||||
<list>
|
||||
<ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
|
||||
<ref local="org.acegisecurity.acls.domain.BasePermission.WRITE"/>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<property name="processDomainObjectClass"><value>sample.dms.AbstractElement</value></property>
|
||||
</bean>
|
||||
|
||||
<!-- An access decision manager used by the business objects -->
|
||||
<bean id="businessAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
|
||||
<property name="allowIfAllAbstainDecisions" value="true"/>
|
||||
<property name="decisionVoters">
|
||||
<list>
|
||||
<ref local="roleVoter"/>
|
||||
<ref local="aclAbstractElementWriteParentVoter"/>
|
||||
<ref local="aclAbstractElementWriteVoter"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->
|
||||
|
||||
<bean id="aclCache" class="org.acegisecurity.acls.jdbc.EhCacheBasedAclCache">
|
||||
<constructor-arg>
|
||||
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
|
||||
<property name="cacheManager">
|
||||
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
|
||||
</property>
|
||||
<property name="cacheName" value="aclCache"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="lookupStrategy" class="org.acegisecurity.acls.jdbc.BasicLookupStrategy">
|
||||
<constructor-arg ref="dataSource"/>
|
||||
<constructor-arg ref="aclCache"/>
|
||||
<constructor-arg ref="aclAuthorizationStrategy"/>
|
||||
<constructor-arg>
|
||||
<bean class="org.acegisecurity.acls.domain.ConsoleAuditLogger"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="aclAuthorizationStrategy" class="org.acegisecurity.acls.domain.AclAuthorizationStrategyImpl">
|
||||
<constructor-arg>
|
||||
<list>
|
||||
<bean class="org.acegisecurity.GrantedAuthorityImpl">
|
||||
<constructor-arg value="ROLE_ADMINISTRATOR"/>
|
||||
</bean>
|
||||
<bean class="org.acegisecurity.GrantedAuthorityImpl">
|
||||
<constructor-arg value="ROLE_ADMINISTRATOR"/>
|
||||
</bean>
|
||||
<bean class="org.acegisecurity.GrantedAuthorityImpl">
|
||||
<constructor-arg value="ROLE_ADMINISTRATOR"/>
|
||||
</bean>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="aclService" class="org.acegisecurity.acls.jdbc.JdbcMutableAclService">
|
||||
<constructor-arg ref="dataSource"/>
|
||||
<constructor-arg ref="lookupStrategy"/>
|
||||
<constructor-arg ref="aclCache"/>
|
||||
</bean>
|
||||
|
||||
<!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->
|
||||
|
||||
<bean id="afterInvocationManager" class="org.acegisecurity.afterinvocation.AfterInvocationProviderManager">
|
||||
<property name="providers">
|
||||
<list>
|
||||
<ref local="afterAclCollectionRead"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
|
||||
<bean id="afterAclCollectionRead" class="org.acegisecurity.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
|
||||
<constructor-arg ref="aclService"/>
|
||||
<constructor-arg>
|
||||
<list>
|
||||
<ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
|
||||
<ref local="org.acegisecurity.acls.domain.BasePermission.READ"/>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
|
||||
|
||||
<bean id="methodSecurityAdvisor" class="org.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor" autowire="constructor"/>
|
||||
|
||||
<bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
|
||||
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
|
||||
<property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
|
||||
<property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
|
||||
<property name="objectDefinitionSource">
|
||||
<value>
|
||||
sample.dms.DocumentDao.create=ACL_ABSTRACT_ELEMENT_WRITE_PARENT
|
||||
sample.dms.DocumentDao.delete=ACL_ABSTRACT_ELEMENT_WRITE
|
||||
sample.dms.DocumentDao.update=ACL_ABSTRACT_ELEMENT_WRITE
|
||||
sample.dms.DocumentDao.findElements=AFTER_ACL_COLLECTION_READ
|
||||
sample.dms.secured.SecureDocumentDao.getUsers=ROLE_USER
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
|
||||
|
||||
<!--
|
||||
- Application context representing the transaction, auto proxy and data source beans.
|
||||
-
|
||||
- $Id$
|
||||
-->
|
||||
|
||||
<beans>
|
||||
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
|
||||
<property name="url" value="jdbc:hsqldb:mem:test"/>
|
||||
<property name="username" value="sa"/>
|
||||
<property name="password" value=""/>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource"><ref local="dataSource"/></property>
|
||||
</bean>
|
||||
|
||||
<bean id="autoproxy" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
|
||||
|
||||
<bean id="transactionAdvisor" class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor" autowire="constructor" />
|
||||
|
||||
</beans>
|
|
@ -0,0 +1,87 @@
|
|||
import org.acegisecurity.context.SecurityContextHolder;
|
||||
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
|
||||
|
||||
import sample.dms.AbstractElement;
|
||||
import sample.dms.Directory;
|
||||
import sample.dms.DocumentDao;
|
||||
|
||||
/**
|
||||
* Basic integration test for DMS sample.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*
|
||||
*/
|
||||
public class DmsIntegrationTests extends AbstractTransactionalDataSourceSpringContextTests {
|
||||
protected DocumentDao documentDao;
|
||||
|
||||
protected String[] getConfigLocations() {
|
||||
return new String[] {"classpath:applicationContext-dms-shared.xml", "classpath:applicationContext-dms-insecure.xml"};
|
||||
}
|
||||
|
||||
public void setDocumentDao(DocumentDao documentDao) {
|
||||
this.documentDao = documentDao;
|
||||
}
|
||||
|
||||
public void testBasePopulation() {
|
||||
assertEquals(9, jdbcTemplate.queryForInt("select count(id) from DIRECTORY"));
|
||||
assertEquals(90, jdbcTemplate.queryForInt("select count(id) from FILE"));
|
||||
assertEquals(3, documentDao.findElements(Directory.ROOT_DIRECTORY).length);
|
||||
}
|
||||
|
||||
public void testMarissaRetrieval() {
|
||||
process("marissa", "koala", false);
|
||||
}
|
||||
|
||||
public void testScottRetrieval() {
|
||||
process("scott", "wombat", false);
|
||||
}
|
||||
|
||||
public void testDianneRetrieval() {
|
||||
process("dianne", "emu", false);
|
||||
}
|
||||
|
||||
protected void process(String username, String password, boolean shouldBeFiltered) {
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(username, password));
|
||||
System.out.println("------ Test for username: " + username + " ------");
|
||||
AbstractElement[] rootElements = documentDao.findElements(Directory.ROOT_DIRECTORY);
|
||||
assertEquals(3, rootElements.length);
|
||||
Directory homeDir = null;
|
||||
Directory nonHomeDir = null;
|
||||
for (int i = 0; i < rootElements.length; i++) {
|
||||
if (rootElements[i].getName().equals(username)) {
|
||||
homeDir = (Directory) rootElements[i];
|
||||
} else {
|
||||
nonHomeDir = (Directory) rootElements[i];
|
||||
}
|
||||
}
|
||||
System.out.println("Home directory......: " + homeDir.getFullName());
|
||||
System.out.println("Non-home directory..: " + nonHomeDir.getFullName());
|
||||
|
||||
AbstractElement[] homeElements = documentDao.findElements(homeDir);
|
||||
assertEquals(12, homeElements.length); // confidential and shared directories, plus 10 files
|
||||
|
||||
AbstractElement[] nonHomeElements = documentDao.findElements(nonHomeDir);
|
||||
assertEquals(shouldBeFiltered ? 11 : 12, nonHomeElements.length); // cannot see the user's "confidential" sub-directory when filtering
|
||||
|
||||
// Attempt to read the other user's confidential directory from the returned results
|
||||
// Of course, we shouldn't find a "confidential" directory in the results if we're filtering
|
||||
Directory nonHomeConfidentialDir = null;
|
||||
for (int i = 0; i < nonHomeElements.length; i++) {
|
||||
if (nonHomeElements[i].getName().equals("confidential")) {
|
||||
nonHomeConfidentialDir = (Directory) nonHomeElements[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldBeFiltered) {
|
||||
assertNull("Found confidential directory when we should not have", nonHomeConfidentialDir);
|
||||
} else {
|
||||
System.out.println("Inaccessible dir....: " + nonHomeConfidentialDir.getFullName());
|
||||
assertEquals(10, documentDao.findElements(nonHomeConfidentialDir).length); // 10 files (no sub-directories)
|
||||
}
|
||||
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import org.acegisecurity.GrantedAuthority;
|
||||
import org.acegisecurity.GrantedAuthorityImpl;
|
||||
import org.acegisecurity.acls.Acl;
|
||||
import org.acegisecurity.acls.AclService;
|
||||
import org.acegisecurity.acls.objectidentity.ObjectIdentity;
|
||||
import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
|
||||
import org.acegisecurity.context.SecurityContextHolder;
|
||||
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||
|
||||
import sample.dms.AbstractElement;
|
||||
import sample.dms.Directory;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Basic integration test for DMS sample when security has been added.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*
|
||||
*/
|
||||
public class SecureDmsIntegrationTests extends DmsIntegrationTests {
|
||||
|
||||
private AclService aclService;
|
||||
|
||||
public void setAclService(AclService aclService) {
|
||||
this.aclService = aclService;
|
||||
}
|
||||
|
||||
protected String[] getConfigLocations() {
|
||||
return new String[] {"classpath:applicationContext-dms-shared.xml", "classpath:applicationContext-dms-secure.xml"};
|
||||
}
|
||||
|
||||
public void testBasePopulation() {
|
||||
assertEquals(9, jdbcTemplate.queryForInt("select count(id) from DIRECTORY"));
|
||||
assertEquals(90, jdbcTemplate.queryForInt("select count(id) from FILE"));
|
||||
assertEquals(4, jdbcTemplate.queryForInt("select count(id) from ACL_SID")); // 3 users + 1 role
|
||||
assertEquals(2, jdbcTemplate.queryForInt("select count(id) from ACL_CLASS")); // Directory and File
|
||||
assertEquals(100, jdbcTemplate.queryForInt("select count(id) from ACL_OBJECT_IDENTITY"));
|
||||
assertEquals(115, jdbcTemplate.queryForInt("select count(id) from ACL_ENTRY"));
|
||||
}
|
||||
/*
|
||||
public void testItOut() {
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("marissa", "password", new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_SUPERVISOR")}));
|
||||
|
||||
|
||||
AbstractElement[] elements = documentDao.findElements(Directory.ROOT_DIRECTORY);
|
||||
ObjectIdentity oid = new ObjectIdentityImpl(elements[0]);
|
||||
//ObjectIdentity oid = new ObjectIdentityImpl(Directory.class, new Long(3));
|
||||
Acl acl = aclService.readAclById(oid);
|
||||
System.out.println(acl);
|
||||
|
||||
}*/
|
||||
|
||||
public void testMarissaRetrieval() {
|
||||
process("marissa", "koala", true);
|
||||
}
|
||||
|
||||
|
||||
public void testScottRetrieval() {
|
||||
process("scott", "wombat", true);
|
||||
}
|
||||
|
||||
public void testDianneRetrieval() {
|
||||
process("dianne", "emu", true);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue