From 13d5a2dbca624555ef0b876041ee4d5f00c0990d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 31 Jul 2004 03:42:18 +0000 Subject: [PATCH] Refactor ACL database tables. --- .../acl/basic/jdbc/JdbcDaoImpl.java | 316 ++++++++++++++---- .../org/acegisecurity/PopulatedDatabase.java | 30 +- .../acl/basic/jdbc/JdbcDaoImplTests.java | 49 ++- docs/reference/src/index.xml | 51 ++- hsqldb/acegisecurity.properties | 2 +- hsqldb/acegisecurity.script | 22 +- hsqldb/dbinit.txt | 46 ++- 7 files changed, 390 insertions(+), 126 deletions(-) diff --git a/core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImpl.java b/core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImpl.java index 4c068cbf66..1fe10e7a71 100644 --- a/core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImpl.java +++ b/core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImpl.java @@ -33,7 +33,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import java.util.Iterator; import java.util.List; import java.util.Vector; @@ -46,12 +45,11 @@ import javax.sql.DataSource; *

* *

- * A default database structure is assumed (see {@link - * #DEF_ACLS_BY_OBJECT_IDENTITY_QUERY}). This may be overridden by setting the - * default query strings to use. If this does not provide enough flexibility, - * another strategy would be to subclass this class and override the {@link - * MappingSqlQuery} instance used, via the {@link #initMappingSqlQueries()} - * extension point. + * A default database structure is assumed. This may be overridden by setting + * the default query strings to use. If this does not provide enough + * flexibility, another strategy would be to subclass this class and override + * the {@link MappingSqlQuery} instance used, via the {@link + * #initMappingSqlQueries()} extension point. *

* * @author Ben Alex @@ -60,18 +58,23 @@ import javax.sql.DataSource; public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao { //~ Static fields/initializers ============================================= - public static final String DEF_ACLS_BY_OBJECT_IDENTITY_QUERY = "SELECT OBJECT_IDENTITY, RECIPIENT, PARENT_OBJECT_IDENTITY, MASK, ACL_CLASS FROM acls WHERE object_identity = ?"; + public static final String RECIPIENT_USED_FOR_INHERITENCE_MARKER = "___INHERITENCE_MARKER_ONLY___"; + public static final String DEF_ACLS_BY_OBJECT_IDENTITY_QUERY = "SELECT RECIPIENT, MASK FROM acl_permission WHERE acl_object_identity = ?"; + public static final String DEF_OBJECT_PROPERTIES_QUERY = "SELECT ID, OBJECT_IDENTITY, ACL_CLASS, PARENT.OBJECT_IDENTITY as PARENT_OBJECT_IDENTITY FROM acl_object_identity LEFT OUTER JOIN acl_object_identity as PARENT ON acl_object_identity.parent_object=parent.id WHERE parent.id=acl_object_identity.parent_object and object_identity = ?"; private static final Log logger = LogFactory.getLog(JdbcDaoSupport.class); //~ Instance fields ======================================================== private MappingSqlQuery aclsByObjectIdentity; + private MappingSqlQuery objectProperties; private String aclsByObjectIdentityQuery; + private String objectPropertiesQuery; //~ Constructors =========================================================== public JdbcDaoImpl() { aclsByObjectIdentityQuery = DEF_ACLS_BY_OBJECT_IDENTITY_QUERY; + objectPropertiesQuery = DEF_OBJECT_PROPERTIES_QUERY; } //~ Methods ================================================================ @@ -114,26 +117,36 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao { String aclObjectIdentityString = neoi.getClassname() + ":" + neoi.getId(); - // Lookup the BasicAclEntrys from RDBMS (may include null responses) - List acls = aclsByObjectIdentity.execute(aclObjectIdentityString); + // Lookup the object's main properties from the RDBMS (guaranteed no nulls) + List objects = objectProperties.execute(aclObjectIdentityString); - // Now prune list of null responses (to meet interface contract) - List toReturnAcls = new Vector(); - Iterator iter = acls.iterator(); - - while (iter.hasNext()) { - Object object = iter.next(); - - if (object != null) { - toReturnAcls.add(object); - } + if (objects.size() == 0) { + // this is an unknown object identity string + return null; } - // Return null if nothing of use found (to meet interface contract) - if (toReturnAcls.size() > 0) { - return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {}); + // Cast to an object properties holder (there should only be one record) + AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects.get(0); + + // Lookup the object's ACLs from RDBMS (guaranteed no nulls) + List acls = aclsByObjectIdentity.execute(propertiesInformation + .getForeignKeyId()); + + if (acls.size() == 0) { + // return merely an inheritence marker (as we know about the object but it has no related ACLs) + return new BasicAclEntry[] {createBasicAclEntry(propertiesInformation, + null)}; } else { - return null; + // return the individual ACL instances + AclDetailsHolder[] aclHolders = (AclDetailsHolder[]) acls.toArray(new AclDetailsHolder[] {}); + List toReturnAcls = new Vector(); + + for (int i = 0; i < aclHolders.length; i++) { + toReturnAcls.add(createBasicAclEntry(propertiesInformation, + aclHolders[i])); + } + + return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {}); } } @@ -164,6 +177,18 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao { return aclsByObjectIdentityQuery; } + public void setObjectProperties(MappingSqlQuery objectPropertiesQuery) { + this.objectProperties = objectPropertiesQuery; + } + + public void setObjectPropertiesQuery(String queryString) { + objectPropertiesQuery = queryString; + } + + public String getObjectPropertiesQuery() { + return objectPropertiesQuery; + } + protected void initDao() throws ApplicationContextException { initMappingSqlQueries(); } @@ -174,70 +199,151 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao { */ protected void initMappingSqlQueries() { setAclsByObjectIdentity(new AclsByObjectIdentityMapping(getDataSource())); + setObjectProperties(new ObjectPropertiesMapping(getDataSource())); + } + + /** + * Constructs an individual BasicAclEntry from the passed + * AclDetailsHolders. + * + *

+ * Guarantees to never return null (exceptions are thrown in + * the event of any issues). + *

+ * + * @param propertiesInformation mandatory information about which instance + * to create, the object identity, and the parent object identity + * (null or empty Strings prohibited for + * aclClass and aclObjectIdentity + * @param aclInformation optional information about the individual ACL + * record (if null only an "inheritence marker" + * instance is returned; if not null, it is prohibited + * to present null or an empty String for + * recipient) + * + * @return a fully populated instance suitable for use by external objects + * + * @throws IllegalArgumentException if the indicated ACL class could not be + * created + */ + private BasicAclEntry createBasicAclEntry( + AclDetailsHolder propertiesInformation, AclDetailsHolder aclInformation) { + BasicAclEntry entry; + + try { + entry = (BasicAclEntry) propertiesInformation.getAclClass() + .newInstance(); + } catch (InstantiationException ie) { + throw new IllegalArgumentException(ie.getMessage()); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException(iae.getMessage()); + } + + entry.setAclObjectIdentity(propertiesInformation.getAclObjectIdentity()); + entry.setAclObjectParentIdentity(propertiesInformation + .getAclObjectParentIdentity()); + + if (aclInformation == null) { + // this is an inheritence marker instance only + entry.setMask(0); + entry.setRecipient(RECIPIENT_USED_FOR_INHERITENCE_MARKER); + } else { + // this is an individual ACL entry + entry.setMask(aclInformation.getMask()); + entry.setRecipient(aclInformation.getRecipient()); + } + + return entry; } //~ Inner Classes ========================================================== /** - * Query object to look up ACL entries. + * Query object to look up individual ACL entries. + * + *

+ * Returns the generic AclDetailsHolder object. + *

+ * + *

+ * Guarantees to never return null (exceptions are thrown in + * the event of any issues). + *

* *

* The executed SQL requires the following information be made available - * from the indicated placeholders: 1. OBJECT_IDENTITY, 2. RECIPIENT, 3. - * PARENT_OBJECT_IDENTITY, 4. MASK, and 5. ACL_CLASS + * from the indicated placeholders: 1. RECIPIENT, 2. MASK. *

*/ protected class AclsByObjectIdentityMapping extends MappingSqlQuery { protected AclsByObjectIdentityMapping(DataSource ds) { super(ds, aclsByObjectIdentityQuery); + declareParameter(new SqlParameter(Types.INTEGER)); + compile(); + } + + protected Object mapRow(ResultSet rs, int rownum) + throws SQLException { + String recipient = rs.getString(1); + int mask = rs.getInt(2); + + if ((recipient == null) || "".equals(recipient)) { + throw new IllegalArgumentException("recipient required"); + } + + return new AclDetailsHolder(recipient, mask); + } + } + + /** + * Query object to look up properties for an object identity. + * + *

+ * Returns the generic AclDetailsHolder object. + *

+ * + *

+ * Guarantees to never return null (exceptions are thrown in + * the event of any issues). + *

+ * + *

+ * The executed SQL requires the following information be made available + * from the indicated placeholders: 1. ID, 2. OBJECT_IDENTITY, 3. + * ACL_CLASS and 4. PARENT_OBJECT_IDENTITY. + *

+ */ + protected class ObjectPropertiesMapping extends MappingSqlQuery { + protected ObjectPropertiesMapping(DataSource ds) { + super(ds, objectPropertiesQuery); declareParameter(new SqlParameter(Types.VARCHAR)); compile(); } protected Object mapRow(ResultSet rs, int rownum) throws SQLException { - String objectIdentity = rs.getString(1); - String recipient = rs.getString(2); - String parentObjectIdentity = rs.getString(3); - int mask = rs.getInt(4); - String aclClass = rs.getString(5); + int id = rs.getInt(1); // required + String objectIdentity = rs.getString(2); // required + String aclClass = rs.getString(3); // required + String parentObjectIdentity = rs.getString(4); // optional - // Try to create the indicated BasicAclEntry class - BasicAclEntry entry; + if ((objectIdentity == null) || "".equals(objectIdentity) + || (aclClass == null) || "".equals(aclClass)) { + // shouldn't happen if DB schema defined NOT NULL columns + throw new IllegalArgumentException( + "required DEF_OBJECT_PROPERTIES_QUERY value returned null or empty"); + } + + Class aclClazz; try { - Class aclClazz = this.getClass().getClassLoader().loadClass(aclClass); - entry = (BasicAclEntry) aclClazz.newInstance(); + aclClazz = this.getClass().getClassLoader().loadClass(aclClass); } catch (ClassNotFoundException cnf) { - logger.error(cnf); - - return null; - } catch (InstantiationException ie) { - logger.error(ie); - - return null; - } catch (IllegalAccessException iae) { - logger.error(iae); - - return null; + throw new IllegalArgumentException(cnf.getMessage()); } - // Now set each of the ACL's properties - entry.setAclObjectIdentity(buildIdentity(objectIdentity)); - entry.setAclObjectParentIdentity(buildIdentity(parentObjectIdentity)); - entry.setRecipient(recipient); - entry.setMask(mask); - - if ((entry.getRecipient() == null) - || (entry.getAclObjectIdentity() == null)) { - // Problem with retrieval of ACL - // (shouldn't happen if DB schema defined NOT NULL columns) - logger.error("recipient or aclObjectIdentity is null"); - - return null; - } - - return entry; + return new AclDetailsHolder(id, buildIdentity(objectIdentity), + buildIdentity(parentObjectIdentity), aclClazz); } private AclObjectIdentity buildIdentity(String identity) { @@ -253,4 +359,88 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao { return new NamedEntityObjectIdentity(classname, id); } } + + /** + * Used to hold details of a domain object instance's properties, or an + * individual ACL entry. + * + *

+ * Not all properties will be set. The actual properties set will depend on + * which MappingSqlQuery creates the object. + *

+ * + *

+ * Does not enforce nulls or empty Strings as + * this is performed by the MappingSqlQuery objects (or + * preferably the backend RDBMS via schema constraints). + *

+ */ + private class AclDetailsHolder { + private AclObjectIdentity aclObjectIdentity; + private AclObjectIdentity aclObjectParentIdentity; + private Class aclClass; + private Object recipient; + private int foreignKeyId; + private int mask; + + /** + * Record details of an individual ACL entry (usually from the + * ACL_PERMISSION table) + * + * @param recipient the recipient + * @param mask the integer to be masked + */ + public AclDetailsHolder(Object recipient, int mask) { + this.recipient = recipient; + this.mask = mask; + } + + /** + * Record details of a domain object instance's properties (usually + * from the ACL_OBJECT_IDENTITY table) + * + * @param foreignKeyId used by the + * AclsByObjectIdentityMapping to locate the + * individual ACL entries + * @param aclObjectIdentity the object identity of the domain object + * instance + * @param aclObjectParentIdentity the object identity of the domain + * object instance's parent + * @param aclClass the class of which a new instance which should be + * created for each individual ACL entry (or an inheritence + * "holder" class if there are no ACL entries) + */ + public AclDetailsHolder(int foreignKeyId, + AclObjectIdentity aclObjectIdentity, + AclObjectIdentity aclObjectParentIdentity, Class aclClass) { + this.foreignKeyId = foreignKeyId; + this.aclObjectIdentity = aclObjectIdentity; + this.aclObjectParentIdentity = aclObjectParentIdentity; + this.aclClass = aclClass; + } + + public Class getAclClass() { + return aclClass; + } + + public AclObjectIdentity getAclObjectIdentity() { + return aclObjectIdentity; + } + + public AclObjectIdentity getAclObjectParentIdentity() { + return aclObjectParentIdentity; + } + + public int getForeignKeyId() { + return foreignKeyId; + } + + public int getMask() { + return mask; + } + + public Object getRecipient() { + return recipient; + } + } } diff --git a/core/src/test/java/org/acegisecurity/PopulatedDatabase.java b/core/src/test/java/org/acegisecurity/PopulatedDatabase.java index 00f606e2e4..b6a0ae9be9 100644 --- a/core/src/test/java/org/acegisecurity/PopulatedDatabase.java +++ b/core/src/test/java/org/acegisecurity/PopulatedDatabase.java @@ -63,7 +63,9 @@ public class PopulatedDatabase { template.execute( "CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY)"); template.execute( - "CREATE TABLE ACLS(OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,PARENT_OBJECT_IDENTITY VARCHAR_IGNORECASE(250),MASK INTEGER NOT NULL,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT PK_ACLS PRIMARY KEY(OBJECT_IDENTITY,RECIPIENT))"); + "CREATE TABLE ACL_OBJECT_IDENTITY(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,PARENT_OBJECT INTEGER,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT UNIQUE_OBJECT_IDENTITY UNIQUE(OBJECT_IDENTITY),CONSTRAINT SYS_FK_3 FOREIGN KEY(PARENT_OBJECT) REFERENCES ACL_OBJECT_IDENTITY(ID))"); + template.execute( + "CREATE TABLE ACL_PERMISSION(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY INTEGER NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,MASK INTEGER NOT NULL,CONSTRAINT UNIQUE_RECIPIENT UNIQUE(ACL_OBJECT_IDENTITY,RECIPIENT),CONSTRAINT SYS_FK_7 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID))"); template.execute("SET IGNORECASE TRUE"); template.execute("INSERT INTO USERS VALUES('dianne','emu',TRUE)"); template.execute("INSERT INTO USERS VALUES('marissa','koala',TRUE)"); @@ -81,22 +83,30 @@ public class PopulatedDatabase { template.execute( "INSERT INTO AUTHORITIES VALUES('peter','ROLE_TELLER')"); template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:1','ROLE_SUPERVISOR',NULL,1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')"); + "INSERT INTO acl_object_identity VALUES (1, 'net.sf.acegisecurity.acl.DomainObject:1', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');"); template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:2','marissa','net.sf.acegisecurity.acl.DomainObject:1',2,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')"); + "INSERT INTO acl_object_identity VALUES (2, 'net.sf.acegisecurity.acl.DomainObject:2', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');"); template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:2','ROLE_SUPERVISOR','net.sf.acegisecurity.acl.DomainObject:1',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')"); + "INSERT INTO acl_object_identity VALUES (3, 'net.sf.acegisecurity.acl.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');"); template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:3','scott','net.sf.acegisecurity.acl.DomainObject:1',14,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')"); + "INSERT INTO acl_object_identity VALUES (4, 'net.sf.acegisecurity.acl.DomainObject:4', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');"); template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:4','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:1',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')"); + "INSERT INTO acl_object_identity VALUES (5, 'net.sf.acegisecurity.acl.DomainObject:5', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');"); template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:5','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:3',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')"); + "INSERT INTO acl_object_identity VALUES (6, 'net.sf.acegisecurity.acl.DomainObject:6', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');"); + // ----- BEGIN deviation from normal sample data load script ----- template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:6','scott','net.sf.acegisecurity.acl.DomainObject:3',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')"); + "INSERT INTO acl_object_identity VALUES (7, 'net.sf.acegisecurity.acl.DomainObject:7', 3, 'some.invalid.acl.entry.class');"); + // ----- FINISH deviation from normal sample data load script ----- template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:7','scott','some.invalid.parent:1',2,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')"); + "INSERT INTO acl_permission VALUES (null, 1, 'ROLE_SUPERVISOR', 1);"); template.execute( - "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:8','scott','net.sf.acegisecurity.acl.DomainObject:3',1,'some.invalid.basic.acl.entry.class.name')"); + "INSERT INTO acl_permission VALUES (null, 2, 'ROLE_SUPERVISOR', 0);"); + template.execute( + "INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2);"); + template.execute( + "INSERT INTO acl_permission VALUES (null, 3, 'scott', 14);"); + template.execute( + "INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);"); } } diff --git a/core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java b/core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java index f9fee5ed20..2610e96ecd 100644 --- a/core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java +++ b/core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java @@ -59,7 +59,22 @@ public class JdbcDaoImplTests extends TestCase { junit.textui.TestRunner.run(JdbcDaoImplTests.class); } - public void testGetsAclsWhichExistInDatabase() throws Exception { + public void testExceptionThrownIfBasicAclEntryClassNotFound() + throws Exception { + JdbcDaoImpl dao = makePopulatedJdbcDao(); + AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY, + "7"); + + try { + dao.getAcls(identity); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testGetsEntriesWhichExistInDatabaseAndHaveAcls() + throws Exception { JdbcDaoImpl dao = makePopulatedJdbcDao(); AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY, "2"); @@ -67,6 +82,26 @@ public class JdbcDaoImplTests extends TestCase { assertEquals(2, acls.length); } + public void testGetsEntriesWhichExistInDatabaseButHaveNoAcls() + throws Exception { + JdbcDaoImpl dao = makePopulatedJdbcDao(); + AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY, + "5"); + BasicAclEntry[] acls = dao.getAcls(identity); + assertEquals(1, acls.length); + assertEquals(JdbcDaoImpl.RECIPIENT_USED_FOR_INHERITENCE_MARKER, + acls[0].getRecipient()); + } + + public void testGetsEntriesWhichHaveNoParent() throws Exception { + JdbcDaoImpl dao = makePopulatedJdbcDao(); + AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY, + "1"); + BasicAclEntry[] acls = dao.getAcls(identity); + assertEquals(1, acls.length); + assertNull(acls[0].getAclObjectParentIdentity()); + } + public void testGettersSetters() throws Exception { JdbcDaoImpl dao = makePopulatedJdbcDao(); dao.setAclsByObjectIdentity(new MockMappingSqlQuery()); @@ -74,15 +109,9 @@ public class JdbcDaoImplTests extends TestCase { dao.setAclsByObjectIdentityQuery("foo"); assertEquals("foo", dao.getAclsByObjectIdentityQuery()); - } - public void testNullReturnedIfBasicAclEntryClassNotFound() - throws Exception { - JdbcDaoImpl dao = makePopulatedJdbcDao(); - AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY, - "8"); - BasicAclEntry[] result = dao.getAcls(identity); - assertNull(result); + dao.setObjectPropertiesQuery("foobar"); + assertEquals("foobar", dao.getObjectPropertiesQuery()); } public void testNullReturnedIfEntityNotFound() throws Exception { @@ -93,7 +122,7 @@ public class JdbcDaoImplTests extends TestCase { assertNull(result); } - public void testRejectsNonNamedEntityObjectIdentity() + public void testReturnsNullForUnNamedEntityObjectIdentity() throws Exception { JdbcDaoImpl dao = new JdbcDaoImpl(); AclObjectIdentity identity = new AclObjectIdentity() {} diff --git a/docs/reference/src/index.xml b/docs/reference/src/index.xml index d41591cb74..69f1b771f7 100644 --- a/docs/reference/src/index.xml +++ b/docs/reference/src/index.xml @@ -3208,23 +3208,41 @@ public java.lang.Object getRecipient(); default database schema and some sample data will aid in understanding its function: - CREATE TABLE acls ( - object_identity VARCHAR_IGNORECASE(250) NOT NULL, - recipient VARCHAR_IGNORECASE(100) NOT NULL, - parent_object_identity VARCHAR_IGNORECASE(250), - mask INTEGER NOT NULL, - acl_class VARCHAR_IGNORECASE(250) NOT NULL, - CONSTRAINT pk_acls PRIMARY KEY(object_identity, recipient) + CREATE TABLE acl_object_identity ( + id IDENTITY NOT NULL, + object_identity VARCHAR_IGNORECASE(250) NOT NULL, + parent_object INTEGER, + acl_class VARCHAR_IGNORECASE(250) NOT NULL, + CONSTRAINT unique_object_identity UNIQUE(object_identity), + FOREIGN KEY (parent_object) REFERENCES acl_object_identity(id) ); -INSERT INTO acls VALUES ('corp.DomainObject:1', 'ROLE_SUPERVISOR', null, 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('corp.DomainObject:2', 'ROLE_SUPERVISOR', 'corp.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('corp.DomainObject:2', 'marissa', 'corp.DomainObject:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('corp.DomainObject:3', 'scott', 'corp.DomainObject:1', 14, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('corp.DomainObject:4', 'inheritance_marker_only', 'corp.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('corp.DomainObject:5', 'inheritance_marker_only', 'corp.DomainObject:3', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('corp.DomainObject:6', 'scott', 'corp.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('corp.DomainObject:7', 'scott', 'some.invalid.parent:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +CREATE TABLE acl_permission ( + id IDENTITY NOT NULL, + acl_object_identity INTEGER NOT NULL, + recipient VARCHAR_IGNORECASE(100) NOT NULL, + mask INTEGER NOT NULL, + CONSTRAINT unique_recipient UNIQUE(acl_object_identity, recipient), + FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id) +); + +INSERT INTO acl_object_identity VALUES (1, 'corp.DomainObject:1', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (2, 'corp.DomainObject:2', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (3, 'corp.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (4, 'corp.DomainObject:4', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (5, 'corp.DomainObject:5', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (6, 'corp.DomainObject:6', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); + +INSERT INTO acl_permission VALUES (null, 1, 'ROLE_SUPERVISOR', 1); +INSERT INTO acl_permission VALUES (null, 2, 'ROLE_SUPERVISOR', 0); +INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2); +INSERT INTO acl_permission VALUES (null, 3, 'scott', 14); +INSERT INTO acl_permission VALUES (null, 6, 'scott', 1); + + As can be seen, database-specific constraints are used + extensively to ensure the integrity of the ACL information. If you + need to use a different database (Hypersonic SQL statements are shown + above), you should try to implement equivalent constraints. The JdbcDaoImpl will only respond to requests for NamedEntityObjectIdentitys. It converts such @@ -3312,8 +3330,7 @@ INSERT INTO acls VALUES ('corp.DomainObject:7', 'scott', 'some.invalid.parent:1' --- 5 ROLE_SUPERVISOR Administer (from parent #3) --- scott Read, Write, Create (from parent #3) --- 6 ROLE_SUPERVISOR Administer (from parent #3) ---- scott Administer (overrides parent #3) ---- 7 scott Read (invalid parent ignored) +--- scott Administer (overrides parent #3) So the above explains how a domain object instance has its AclObjectIdentity discovered, and the diff --git a/hsqldb/acegisecurity.properties b/hsqldb/acegisecurity.properties index 0ad6ced730..f75943192f 100644 --- a/hsqldb/acegisecurity.properties +++ b/hsqldb/acegisecurity.properties @@ -1,5 +1,5 @@ #HSQL database -#Wed Jul 28 02:20:39 GMT 2004 +#Sat Jul 31 02:59:13 GMT 2004 hsqldb.script_format=0 runtime.gc_interval=0 sql.enforce_strict_size=false diff --git a/hsqldb/acegisecurity.script b/hsqldb/acegisecurity.script index 75cbb48275..07e895e6d1 100644 --- a/hsqldb/acegisecurity.script +++ b/hsqldb/acegisecurity.script @@ -1,7 +1,8 @@ CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,ENABLED BOOLEAN NOT NULL) 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)) CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY) -CREATE TABLE ACLS(OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,PARENT_OBJECT_IDENTITY VARCHAR_IGNORECASE(250),MASK INTEGER NOT NULL,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT PK_ACLS PRIMARY KEY(OBJECT_IDENTITY,RECIPIENT)) +CREATE TABLE ACL_OBJECT_IDENTITY(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,PARENT_OBJECT INTEGER,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT UNIQUE_OBJECT_IDENTITY UNIQUE(OBJECT_IDENTITY),CONSTRAINT SYS_FK_3 FOREIGN KEY(PARENT_OBJECT) REFERENCES ACL_OBJECT_IDENTITY(ID)) +CREATE TABLE ACL_PERMISSION(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY INTEGER NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,MASK INTEGER NOT NULL,CONSTRAINT UNIQUE_RECIPIENT UNIQUE(ACL_OBJECT_IDENTITY,RECIPIENT),CONSTRAINT SYS_FK_7 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID)) SET IGNORECASE TRUE CREATE USER SA PASSWORD "" ADMIN INSERT INTO USERS VALUES('dianne','emu',TRUE) @@ -13,11 +14,14 @@ INSERT INTO AUTHORITIES VALUES('marissa','ROLE_SUPERVISOR') INSERT INTO AUTHORITIES VALUES('dianne','ROLE_TELLER') INSERT INTO AUTHORITIES VALUES('scott','ROLE_TELLER') INSERT INTO AUTHORITIES VALUES('peter','ROLE_TELLER') -INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:1','ROLE_SUPERVISOR',NULL,1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') -INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:2','marissa','net.sf.acegisecurity.acl.DomainObject:1',2,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') -INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:2','ROLE_SUPERVISOR','net.sf.acegisecurity.acl.DomainObject:1',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') -INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:3','scott','net.sf.acegisecurity.acl.DomainObject:1',14,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') -INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:4','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:1',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') -INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:5','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:3',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') -INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:6','scott','net.sf.acegisecurity.acl.DomainObject:3',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') -INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:7','scott','some.invalid.parent:1',2,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') +INSERT INTO ACL_OBJECT_IDENTITY VALUES(1,'net.sf.acegisecurity.acl.DomainObject:1',NULL,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') +INSERT INTO ACL_OBJECT_IDENTITY VALUES(2,'net.sf.acegisecurity.acl.DomainObject:2',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') +INSERT INTO ACL_OBJECT_IDENTITY VALUES(3,'net.sf.acegisecurity.acl.DomainObject:3',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') +INSERT INTO ACL_OBJECT_IDENTITY VALUES(4,'net.sf.acegisecurity.acl.DomainObject:4',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') +INSERT INTO ACL_OBJECT_IDENTITY VALUES(5,'net.sf.acegisecurity.acl.DomainObject:5',3,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') +INSERT INTO ACL_OBJECT_IDENTITY VALUES(6,'net.sf.acegisecurity.acl.DomainObject:6',3,'net.sf.acegisecurity.acl.basic.SimpleAclEntry') +INSERT INTO ACL_PERMISSION VALUES(0,1,'ROLE_SUPERVISOR',1) +INSERT INTO ACL_PERMISSION VALUES(1,2,'ROLE_SUPERVISOR',0) +INSERT INTO ACL_PERMISSION VALUES(2,2,'marissa',2) +INSERT INTO ACL_PERMISSION VALUES(3,3,'scott',14) +INSERT INTO ACL_PERMISSION VALUES(4,6,'scott',1) diff --git a/hsqldb/dbinit.txt b/hsqldb/dbinit.txt index b71ace7114..2648b5c5f4 100644 --- a/hsqldb/dbinit.txt +++ b/hsqldb/dbinit.txt @@ -27,13 +27,24 @@ INSERT INTO authorities VALUES ('dianne', 'ROLE_TELLER'); INSERT INTO authorities VALUES ('scott', 'ROLE_TELLER'); INSERT INTO authorities VALUES ('peter', 'ROLE_TELLER'); -CREATE TABLE acls ( - object_identity VARCHAR_IGNORECASE(250) NOT NULL, - recipient VARCHAR_IGNORECASE(100) NOT NULL, - parent_object_identity VARCHAR_IGNORECASE(250), - mask INTEGER NOT NULL, - acl_class VARCHAR_IGNORECASE(250) NOT NULL, - CONSTRAINT pk_acls PRIMARY KEY(object_identity, recipient) +--- Indexes auto created in HSQLDB for primary keys and unique columns + +CREATE TABLE acl_object_identity ( + id IDENTITY NOT NULL, + object_identity VARCHAR_IGNORECASE(250) NOT NULL, + parent_object INTEGER, + acl_class VARCHAR_IGNORECASE(250) NOT NULL, + CONSTRAINT unique_object_identity UNIQUE(object_identity), + FOREIGN KEY (parent_object) REFERENCES acl_object_identity(id) +); + +CREATE TABLE acl_permission ( + id IDENTITY NOT NULL, + acl_object_identity INTEGER NOT NULL, + recipient VARCHAR_IGNORECASE(100) NOT NULL, + mask INTEGER NOT NULL, + CONSTRAINT unique_recipient UNIQUE(acl_object_identity, recipient), + FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id) ); --- Mask integer 0 = no permissions @@ -56,16 +67,19 @@ CREATE TABLE acls ( --- scott Read, Write, Create (from parent #3) --- 6 ROLE_SUPERVISOR Administer (from parent #3) --- scott Administer (overrides parent #3) ---- 7 scott Read (invalid parent ignored) --------------------------------------------------------------------- -INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:1', 'ROLE_SUPERVISOR', null, 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:2', 'ROLE_SUPERVISOR', 'net.sf.acegisecurity.acl.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:2', 'marissa', 'net.sf.acegisecurity.acl.DomainObject:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:3', 'scott', 'net.sf.acegisecurity.acl.DomainObject:1', 14, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:4', 'inheritance_marker_only', 'net.sf.acegisecurity.acl.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:5', 'inheritance_marker_only', 'net.sf.acegisecurity.acl.DomainObject:3', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:6', 'scott', 'net.sf.acegisecurity.acl.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); -INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:7', 'scott', 'some.invalid.parent:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (1, 'net.sf.acegisecurity.acl.DomainObject:1', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (2, 'net.sf.acegisecurity.acl.DomainObject:2', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (3, 'net.sf.acegisecurity.acl.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (4, 'net.sf.acegisecurity.acl.DomainObject:4', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (5, 'net.sf.acegisecurity.acl.DomainObject:5', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); +INSERT INTO acl_object_identity VALUES (6, 'net.sf.acegisecurity.acl.DomainObject:6', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry'); + +INSERT INTO acl_permission VALUES (null, 1, 'ROLE_SUPERVISOR', 1); +INSERT INTO acl_permission VALUES (null, 2, 'ROLE_SUPERVISOR', 0); +INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2); +INSERT INTO acl_permission VALUES (null, 3, 'scott', 14); +INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);