From af5917b6855ffe90a263723413fabc6192983567 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 15 Nov 2004 13:04:12 +0000 Subject: [PATCH] Added BasicAclExtendedDao interface and JdbcExtendedDaoImpl for ACL CRUD. --- changelog.txt | 1 + .../acl/basic/BasicAclExtendedDao.java | 64 +++ .../acl/basic/jdbc/JdbcDaoImpl.java | 8 +- .../acl/basic/jdbc/JdbcExtendedDaoImpl.java | 492 ++++++++++++++++++ 4 files changed, 561 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/org/acegisecurity/acl/basic/BasicAclExtendedDao.java create mode 100644 core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcExtendedDaoImpl.java diff --git a/changelog.txt b/changelog.txt index e0c9434c67..68a97942b5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9,6 +9,7 @@ Changes in version 0.7 (2004-xx-xx) * Added AuthenticationProcessingFilter.setDetails for use by subclasses * Added 403-causing exception to HttpSession via SecurityEnforcementFilter * Added net.sf.acegisecurity.intercept.event package +* Added BasicAclExtendedDao interface and JdbcExtendedDaoImpl for ACL CRUD * Improved BasicAclProvider to only respond to specified ACL object requests * Refactored MethodDefinitionSource to work with Method, not MethodInvocation * Refactored AbstractSecurityInterceptor to better support other AOP libraries diff --git a/core/src/main/java/org/acegisecurity/acl/basic/BasicAclExtendedDao.java b/core/src/main/java/org/acegisecurity/acl/basic/BasicAclExtendedDao.java new file mode 100644 index 0000000000..aee42cfed7 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acl/basic/BasicAclExtendedDao.java @@ -0,0 +1,64 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.acl.basic; + +import org.springframework.dao.DataAccessException; + +/** + * Represents a more extensive data access object + * for {@link BasicAclEntry}s. + * + *

+ * BasicAclExtendedDao implementations are responsible for interpreting a + * a given {@link AclObjectIdentity}. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public interface BasicAclExtendedDao extends BasicAclDao { + //~ Methods ================================================================ + + public void create(BasicAclEntry basicAclEntry) throws DataAccessException; + + /** + * Deletes all entries associated with the + * specified AclObjectIdentity. + * + * @param aclObjectIdentity to delete, including any BasicAclEntrys + */ + public void delete(AclObjectIdentity aclObjectIdentity) throws DataAccessException; + + /** + * Deletes the BasicAclEntry associated with the specified + * AclObjectIdentity and recipient Object. + * + * @param aclObjectIdentity to delete + * @param recipient to delete + */ + public void delete(AclObjectIdentity aclObjectIdentity, Object recipient) throws DataAccessException; + + /** + * Changes the permission mask assigned to the BasicAclEntry + * associated with the specified + * AclObjectIdentity and recipient Object. + * + * @param aclObjectIdentity to locate the relevant BasicAclEntry + * @param recipient to locate the relevant BasicAclEntry + * @param newMask indicating the new permission + */ + public void changeMask(AclObjectIdentity aclObjectIdentity, Object recipient, Integer newMask) throws DataAccessException; +} 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 1fe10e7a71..44298a6eb2 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 @@ -61,12 +61,12 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao { 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); + private static final Log logger = LogFactory.getLog(JdbcDaoImpl.class); //~ Instance fields ======================================================== - private MappingSqlQuery aclsByObjectIdentity; - private MappingSqlQuery objectProperties; + protected MappingSqlQuery aclsByObjectIdentity; + protected MappingSqlQuery objectProperties; private String aclsByObjectIdentityQuery; private String objectPropertiesQuery; @@ -375,7 +375,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao { * preferably the backend RDBMS via schema constraints). *

*/ - private class AclDetailsHolder { + protected final class AclDetailsHolder { private AclObjectIdentity aclObjectIdentity; private AclObjectIdentity aclObjectParentIdentity; private Class aclClass; diff --git a/core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcExtendedDaoImpl.java b/core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcExtendedDaoImpl.java new file mode 100644 index 0000000000..e702e3c5da --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcExtendedDaoImpl.java @@ -0,0 +1,492 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.acl.basic.jdbc; + +import net.sf.acegisecurity.acl.basic.AclObjectIdentity; +import net.sf.acegisecurity.acl.basic.BasicAclEntry; +import net.sf.acegisecurity.acl.basic.BasicAclExtendedDao; +import net.sf.acegisecurity.acl.basic.NamedEntityObjectIdentity; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationContextException; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DataRetrievalFailureException; + +import org.springframework.jdbc.core.SqlParameter; +import org.springframework.jdbc.object.MappingSqlQuery; +import org.springframework.jdbc.object.SqlUpdate; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import java.util.Iterator; +import java.util.List; + +import javax.sql.DataSource; + + +/** + *

+ * Extension of the base {@link JdbcDaoImpl}, which implements {@link + * BasicAclExtendedDao}. + *

+ * + *

+ * A default database structure is assumed. This may be overridden by setting + * the default query strings to use. + *

+ * + *

+ * This implementation works with String based recipients and + * {@link net.sf.acegisecurity.acl.basic.NamedEntityObjectIdentity} only. The + * latter can be changed by overriding {@link + * #convertAclObjectIdentityToString(AclObjectIdentity)}. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class JdbcExtendedDaoImpl extends JdbcDaoImpl + implements BasicAclExtendedDao { + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(JdbcExtendedDaoImpl.class); + public static final String DEF_ACL_OBJECT_IDENTITY_DELETE_STATEMENT = "DELETE FROM acl_object_identity WHERE id = ?"; + public static final String DEF_ACL_OBJECT_IDENTITY_INSERT_STATEMENT = "INSERT INTO acl_object_identity (id, object_identity, parent_object, acl_class) VALUES (?, ?, ?, ?)"; + public static final String DEF_ACL_PERMISSION_DELETE_STATEMENT = "DELETE FROM acl_permission WHERE acl_object_identity = ? AND recipient = ?"; + public static final String DEF_ACL_PERMISSION_INSERT_STATEMENT = "INSERT INTO acl_permission (id, acl_object_identity, recipient, mask) VALUES (?, ?, ?, ?)"; + public static final String DEF_ACL_PERMISSION_UPDATE_STATEMENT = "UPDATE acl_permission SET mask = ? WHERE id = ?"; + public static final String DEF_LOOKUP_PERMISSION_ID_QUERY = "SELECT id FROM acl_permission WHERE acl_object_identity = ? AND recipient = ?"; + + //~ Instance fields ======================================================== + + private AclObjectIdentityDelete aclObjectIdentityDelete; + private AclObjectIdentityInsert aclObjectIdentityInsert; + private AclPermissionDelete aclPermissionDelete; + private AclPermissionInsert aclPermissionInsert; + private AclPermissionUpdate aclPermissionUpdate; + private MappingSqlQuery lookupPermissionIdMapping; + private String aclObjectIdentityDeleteStatement; + private String aclObjectIdentityInsertStatement; + private String aclPermissionDeleteStatement; + private String aclPermissionInsertStatement; + private String aclPermissionUpdateStatement; + private String lookupPermissionIdQuery; + + //~ Constructors =========================================================== + + public JdbcExtendedDaoImpl() { + aclObjectIdentityDeleteStatement = DEF_ACL_OBJECT_IDENTITY_DELETE_STATEMENT; + aclObjectIdentityInsertStatement = DEF_ACL_OBJECT_IDENTITY_INSERT_STATEMENT; + aclPermissionDeleteStatement = DEF_ACL_PERMISSION_DELETE_STATEMENT; + aclPermissionInsertStatement = DEF_ACL_PERMISSION_INSERT_STATEMENT; + aclPermissionUpdateStatement = DEF_ACL_PERMISSION_UPDATE_STATEMENT; + lookupPermissionIdQuery = DEF_LOOKUP_PERMISSION_ID_QUERY; + } + + //~ Methods ================================================================ + + public void setAclObjectIdentityDelete( + AclObjectIdentityDelete aclObjectIdentityDelete) { + this.aclObjectIdentityDelete = aclObjectIdentityDelete; + } + + public AclObjectIdentityDelete getAclObjectIdentityDelete() { + return aclObjectIdentityDelete; + } + + public void setAclObjectIdentityDeleteStatement( + String aclObjectIdentityDeleteStatement) { + this.aclObjectIdentityDeleteStatement = aclObjectIdentityDeleteStatement; + } + + public String getAclObjectIdentityDeleteStatement() { + return aclObjectIdentityDeleteStatement; + } + + public void setAclObjectIdentityInsert( + AclObjectIdentityInsert aclObjectIdentityInsert) { + this.aclObjectIdentityInsert = aclObjectIdentityInsert; + } + + public AclObjectIdentityInsert getAclObjectIdentityInsert() { + return aclObjectIdentityInsert; + } + + public void setAclObjectIdentityInsertStatement( + String aclObjectIdentityInsertStatement) { + this.aclObjectIdentityInsertStatement = aclObjectIdentityInsertStatement; + } + + public String getAclObjectIdentityInsertStatement() { + return aclObjectIdentityInsertStatement; + } + + public void setAclPermissionDelete(AclPermissionDelete aclPermissionDelete) { + this.aclPermissionDelete = aclPermissionDelete; + } + + public AclPermissionDelete getAclPermissionDelete() { + return aclPermissionDelete; + } + + public void setAclPermissionDeleteStatement( + String aclPermissionDeleteStatement) { + this.aclPermissionDeleteStatement = aclPermissionDeleteStatement; + } + + public String getAclPermissionDeleteStatement() { + return aclPermissionDeleteStatement; + } + + public void setAclPermissionInsert(AclPermissionInsert aclPermissionInsert) { + this.aclPermissionInsert = aclPermissionInsert; + } + + public AclPermissionInsert getAclPermissionInsert() { + return aclPermissionInsert; + } + + public void setAclPermissionInsertStatement( + String aclPermissionInsertStatement) { + this.aclPermissionInsertStatement = aclPermissionInsertStatement; + } + + public String getAclPermissionInsertStatement() { + return aclPermissionInsertStatement; + } + + public void setAclPermissionUpdate(AclPermissionUpdate aclPermissionUpdate) { + this.aclPermissionUpdate = aclPermissionUpdate; + } + + public AclPermissionUpdate getAclPermissionUpdate() { + return aclPermissionUpdate; + } + + public void setAclPermissionUpdateStatement( + String aclPermissionUpdateStatement) { + this.aclPermissionUpdateStatement = aclPermissionUpdateStatement; + } + + public String getAclPermissionUpdateStatement() { + return aclPermissionUpdateStatement; + } + + public void setLookupPermissionIdMapping( + MappingSqlQuery lookupPermissionIdMapping) { + this.lookupPermissionIdMapping = lookupPermissionIdMapping; + } + + public MappingSqlQuery getLookupPermissionIdMapping() { + return lookupPermissionIdMapping; + } + + public void setLookupPermissionIdQuery(String lookupPermissionIdQuery) { + this.lookupPermissionIdQuery = lookupPermissionIdQuery; + } + + public String getLookupPermissionIdQuery() { + return lookupPermissionIdQuery; + } + + public void changeMask(AclObjectIdentity aclObjectIdentity, + Object recipient, Integer newMask) throws DataAccessException { + // Retrieve acl_object_identity record details + AclDetailsHolder aclDetailsHolder = lookupAclDetailsHolder(aclObjectIdentity); + + // Retrieve applicable acl_permission.id + int permissionId = lookupPermissionId(aclDetailsHolder.getForeignKeyId(), + recipient.toString()); + + if (permissionId == -1) { + throw new DataRetrievalFailureException( + "Could not locate existing acl_permission for aclObjectIdentity: " + + aclObjectIdentity + ", recipient: " + recipient.toString()); + } + + // Change permission + aclPermissionUpdate.update(new Integer( + aclDetailsHolder.getForeignKeyId()), newMask); + } + + public void create(BasicAclEntry basicAclEntry) throws DataAccessException { + // Create acl_object_identity record if required + createAclObjectIdentityIfRequired(basicAclEntry); + + // Retrieve acl_object_identity record details + AclDetailsHolder aclDetailsHolder = lookupAclDetailsHolder(basicAclEntry + .getAclObjectIdentity()); + + // Ensure there isn't an existing record for this recipient + if (lookupPermissionId(aclDetailsHolder.getForeignKeyId(), + basicAclEntry.getRecipient()) != -1) { + throw new DataIntegrityViolationException( + "This recipient already exists for this aclObjectIdentity"); + } + + // Create acl_permission + aclPermissionInsert.insert(new Integer( + aclDetailsHolder.getForeignKeyId()), + basicAclEntry.getRecipient().toString(), + new Integer(basicAclEntry.getMask())); + } + + public void delete(AclObjectIdentity aclObjectIdentity) + throws DataAccessException { + // Retrieve acl_object_identity record details + AclDetailsHolder aclDetailsHolder = lookupAclDetailsHolder(aclObjectIdentity); + + // Retrieve all acl_permissions applying to this acl_object_identity + Iterator acls = aclsByObjectIdentity.execute(aclDetailsHolder + .getForeignKeyId()).iterator(); + + // Delete all existing acl_permissions applying to this acl_object_identity + while (acls.hasNext()) { + AclDetailsHolder permission = (AclDetailsHolder) acls.next(); + delete(aclObjectIdentity, permission.getRecipient()); + } + + // Delete acl_object_identity + aclObjectIdentityDelete.delete(new Integer( + aclDetailsHolder.getForeignKeyId())); + } + + public void delete(AclObjectIdentity aclObjectIdentity, Object recipient) + throws DataAccessException { + // Retrieve acl_object_identity record details + AclDetailsHolder aclDetailsHolder = lookupAclDetailsHolder(aclObjectIdentity); + + // Delete acl_permission + aclPermissionDelete.delete(new Integer( + aclDetailsHolder.getForeignKeyId()), recipient.toString()); + } + + /** + * Responsible for covering a AclObjectIdentity to a + * String that can be located in the RDBMS. + * + * @param aclObjectIdentity to locate + * + * @return the object identity as a String + * + * @throws IllegalArgumentException DOCUMENT ME! + */ + protected String convertAclObjectIdentityToString( + AclObjectIdentity aclObjectIdentity) { + // Ensure we can process this type of AclObjectIdentity + if (!(aclObjectIdentity instanceof NamedEntityObjectIdentity)) { + throw new IllegalArgumentException( + "Only aclObjectIdentity of type NamedEntityObjectIdentity supported (was passed: " + + aclObjectIdentity + ")"); + } + + NamedEntityObjectIdentity neoi = (NamedEntityObjectIdentity) aclObjectIdentity; + + // Compose the String we expect to find in the RDBMS + return neoi.getClassname() + ":" + neoi.getId(); + } + + protected void initDao() throws ApplicationContextException { + super.initDao(); + lookupPermissionIdMapping = new LookupPermissionIdMapping(getDataSource()); + aclPermissionInsert = new AclPermissionInsert(getDataSource()); + aclObjectIdentityInsert = new AclObjectIdentityInsert(getDataSource()); + aclPermissionDelete = new AclPermissionDelete(getDataSource()); + aclObjectIdentityDelete = new AclObjectIdentityDelete(getDataSource()); + aclPermissionUpdate = new AclPermissionUpdate(getDataSource()); + } + + /** + * Convenience method that creates an acl_object_identity record if + * required. + * + * @param basicAclEntry containing the AclObjectIdentity to + * create + * + * @throws DataAccessException + */ + private void createAclObjectIdentityIfRequired(BasicAclEntry basicAclEntry) + throws DataAccessException { + String aclObjectIdentityString = convertAclObjectIdentityToString(basicAclEntry + .getAclObjectIdentity()); + + // Lookup the object's main properties from the RDBMS (guaranteed no nulls) + List objects = objectProperties.execute(aclObjectIdentityString); + + if (objects.size() == 0) { + if (basicAclEntry.getAclObjectParentIdentity() != null) { + AclDetailsHolder parentDetails = lookupAclDetailsHolder(basicAclEntry + .getAclObjectParentIdentity()); + + // Must create the acl_object_identity record + aclObjectIdentityInsert.insert(aclObjectIdentityString, + new Integer(parentDetails.getForeignKeyId()), + basicAclEntry.getClass().getName()); + } else { + // Must create the acl_object_identity record + aclObjectIdentityInsert.insert(aclObjectIdentityString, null, + basicAclEntry.getClass().getName()); + } + } + } + + /** + * Convenience method that obtains a given acl_object_identity record. + * + * @param aclObjectIdentity to lookup + * + * @return details of the record + * + * @throws DataRetrievalFailureException if record could not be found + */ + private AclDetailsHolder lookupAclDetailsHolder( + AclObjectIdentity aclObjectIdentity) + throws DataRetrievalFailureException { + String aclObjectIdentityString = convertAclObjectIdentityToString(aclObjectIdentity); + + // Lookup the object's main properties from the RDBMS (guaranteed no nulls) + List objects = objectProperties.execute(aclObjectIdentityString); + + if (objects.size() == 0) { + throw new DataRetrievalFailureException( + "aclObjectIdentity not found: " + aclObjectIdentityString); + } + + // Should only be one record + return (AclDetailsHolder) objects.get(0); + } + + /** + * Convenience method to lookup the acl_permission applying to a given + * acl_object_identity.id and acl_permission.recipient. + * + * @param aclObjectIdentityId to locate + * @param recipient to locate + * + * @return the acl_permission.id of the record, or -1 if not found + * + * @throws DataAccessException DOCUMENT ME! + */ + private int lookupPermissionId(int aclObjectIdentityId, Object recipient) + throws DataAccessException { + List list = lookupPermissionIdMapping.execute(new Object[] {new Integer( + aclObjectIdentityId), recipient}); + + if (list.size() == 0) { + return -1; + } + + return ((Integer) list.get(0)).intValue(); + } + + //~ Inner Classes ========================================================== + + protected class AclObjectIdentityDelete extends SqlUpdate { + protected AclObjectIdentityDelete(DataSource ds) { + super(ds, aclObjectIdentityDeleteStatement); + declareParameter(new SqlParameter(Types.INTEGER)); + compile(); + } + + protected void delete(Integer aclObjectIdentity) + throws DataAccessException { + super.update(aclObjectIdentity.intValue()); + } + } + + protected class AclObjectIdentityInsert extends SqlUpdate { + protected AclObjectIdentityInsert(DataSource ds) { + super(ds, aclObjectIdentityInsertStatement); + declareParameter(new SqlParameter(Types.INTEGER)); + declareParameter(new SqlParameter(Types.VARCHAR)); + declareParameter(new SqlParameter(Types.INTEGER)); + declareParameter(new SqlParameter(Types.VARCHAR)); + compile(); + } + + protected void insert(String objectIdentity, + Integer parentAclObjectIdentity, String aclClass) + throws DataAccessException { + Object[] objs = new Object[] {null, objectIdentity, parentAclObjectIdentity, aclClass}; + super.update(objs); + } + } + + protected class AclPermissionDelete extends SqlUpdate { + protected AclPermissionDelete(DataSource ds) { + super(ds, aclPermissionDeleteStatement); + declareParameter(new SqlParameter(Types.INTEGER)); + declareParameter(new SqlParameter(Types.VARCHAR)); + compile(); + } + + protected void delete(Integer aclObjectIdentity, String recipient) + throws DataAccessException { + super.update(new Object[] {aclObjectIdentity, recipient}); + } + } + + protected class AclPermissionInsert extends SqlUpdate { + protected AclPermissionInsert(DataSource ds) { + super(ds, aclPermissionInsertStatement); + declareParameter(new SqlParameter(Types.INTEGER)); + declareParameter(new SqlParameter(Types.INTEGER)); + declareParameter(new SqlParameter(Types.VARCHAR)); + declareParameter(new SqlParameter(Types.INTEGER)); + compile(); + } + + protected void insert(Integer aclObjectIdentity, String recipient, + Integer mask) throws DataAccessException { + Object[] objs = new Object[] {null, aclObjectIdentity, recipient, mask}; + super.update(objs); + } + } + + protected class AclPermissionUpdate extends SqlUpdate { + protected AclPermissionUpdate(DataSource ds) { + super(ds, aclPermissionUpdateStatement); + declareParameter(new SqlParameter(Types.INTEGER)); + declareParameter(new SqlParameter(Types.INTEGER)); + compile(); + } + + protected void update(Integer aclPermissionId, Integer newMask) + throws DataAccessException { + super.update(aclPermissionId.intValue(), newMask.intValue()); + } + } + + protected class LookupPermissionIdMapping extends MappingSqlQuery { + protected LookupPermissionIdMapping(DataSource ds) { + super(ds, lookupPermissionIdQuery); + declareParameter(new SqlParameter(Types.INTEGER)); + declareParameter(new SqlParameter(Types.VARCHAR)); + compile(); + } + + protected Object mapRow(ResultSet rs, int rownum) + throws SQLException { + return new Integer(rs.getInt(1)); + } + } +}