diff --git a/core/src/main/java/org/acegisecurity/acl/AclEntry.java b/core/src/main/java/org/acegisecurity/acl/AclEntry.java
new file mode 100644
index 0000000000..b6c2af521c
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/AclEntry.java
@@ -0,0 +1,25 @@
+/* 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;
+
+/**
+ * Marker interface representing an access control list entry associated with a
+ * specific domain object instance.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AclEntry {}
diff --git a/core/src/main/java/org/acegisecurity/acl/AclManager.java b/core/src/main/java/org/acegisecurity/acl/AclManager.java
new file mode 100644
index 0000000000..fe880ae31e
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/AclManager.java
@@ -0,0 +1,58 @@
+/* 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;
+
+import net.sf.acegisecurity.Authentication;
+
+
+/**
+ * Obtains the AclEntry instances that apply to a particular
+ * domain object instance.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AclManager {
+ //~ Methods ================================================================
+
+ /**
+ * Obtains the ACLs that apply to the specified domain instance.
+ *
+ * @param domainInstance the instance for which ACL information is required
+ * (never null)
+ *
+ * @return the ACLs that apply, or null if no ACLs apply to
+ * the specified domain instance
+ */
+ public AclEntry[] getAcls(Object domainInstance);
+
+ /**
+ * Obtains the ACLs that apply to the specified domain instance, but only
+ * including those ACLs which have been granted to the presented
+ * Authentication object
+ *
+ * @param domainInstance the instance for which ACL information is required
+ * (never null)
+ * @param authentication the prncipal for which ACL information should be
+ * filtered (never null)
+ *
+ * @return only those ACLs applying to the domain instance that have been
+ * granted to the principal (or null) if no such ACLs
+ * are found
+ */
+ public AclEntry[] getAcls(Object domainInstance,
+ Authentication authentication);
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/AclProvider.java b/core/src/main/java/org/acegisecurity/acl/AclProvider.java
new file mode 100644
index 0000000000..0b7f08ba13
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/AclProvider.java
@@ -0,0 +1,82 @@
+/* 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;
+
+import net.sf.acegisecurity.Authentication;
+
+/**
+ * Indicates a class can process a given domain object instance and
+ * authoritatively return the ACLs that apply.
+ *
+ *
+ * Implementations are typically called from the {@link AclProviderManager}. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public interface AclProvider { + //~ Methods ================================================================ + + /** + * Obtains the ACLs that apply to the specified domain instance. + * + *
+ * Will never be called unless the {@link #supports(Object)} method
+ * returned true.
+ *
null)
+ *
+ * @return the ACLs that apply, or null if no ACLs apply to
+ * the specified domain instance
+ */
+ public AclEntry[] getAcls(Object domainInstance);
+
+ /**
+ * Obtains the ACLs that apply to the specified domain instance
+ * and presented Authentication object.
+ *
+ *
+ * Will never be called unless the {@link #supports(Object)} method
+ * returned true.
+ *
null)
+ * @param authentication the prncipal for which ACL information should be
+ * filtered (never null)
+ *
+ * @return only those ACLs applying to the domain instance that have been
+ * granted to the principal (or null) if no such ACLs
+ * are found
+ */
+ public AclEntry[] getAcls(Object domainInstance,
+ Authentication authentication);
+
+ /**
+ * Indicates whether this AclProvider can authoritatively
+ * return ACL information for the specified domain object instance.
+ *
+ * @param domainInstance the instance for which ACL information is required
+ * (never null)
+ *
+ * @return true if this provider is authoritative for the
+ * specified domain object instance, false otherwise
+ */
+ public boolean supports(Object domainInstance);
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/AclProviderManager.java b/core/src/main/java/org/acegisecurity/acl/AclProviderManager.java
new file mode 100644
index 0000000000..97c9de8290
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/AclProviderManager.java
@@ -0,0 +1,159 @@
+/* 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;
+
+import net.sf.acegisecurity.Authentication;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Iterates through a list of {@link AclProvider}s to locate the ACLs that
+ * apply to a given domain object instance.
+ *
+ *
+ * If no compatible provider is found, it is assumed that no ACLs apply for the
+ * specified domain object instance and null is returned.
+ *
+ * Provides core bit mask handling methods. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public abstract class AbstractBasicAclEntry implements BasicAclEntry { + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(AbstractBasicAclEntry.class); + + //~ Instance fields ======================================================== + + private AclObjectIdentity aclObjectIdentity; + private AclObjectIdentity aclObjectParentIdentity; + private Object recipient; + private int[] validPermissions; + private int mask = 0; // default means no permissions + + //~ Constructors =========================================================== + + public AbstractBasicAclEntry(Object recipient, + AclObjectIdentity aclObjectIdentity, + AclObjectIdentity aclObjectParentIdentity, int mask) { + if (recipient == null) { + throw new IllegalArgumentException("recipient cannot be null"); + } + + if (aclObjectIdentity == null) { + throw new IllegalArgumentException( + "aclObjectIdentity cannot be null"); + } + + validPermissions = getValidPermissions(); + Arrays.sort(validPermissions); + + for (int i = 0; i < validPermissions.length; i++) { + if (logger.isDebugEnabled()) { + logger.debug("Valid permission: " + + printPermissionsBlock(validPermissions[i]) + " " + + printBinary(validPermissions[i]) + " (" + + validPermissions[i] + ")"); + } + } + + this.recipient = recipient; + this.aclObjectIdentity = aclObjectIdentity; + this.aclObjectParentIdentity = aclObjectParentIdentity; + this.mask = mask; + } + + protected AbstractBasicAclEntry() {} + + //~ Methods ================================================================ + + public void setAclObjectIdentity(AclObjectIdentity aclObjectIdentity) { + this.aclObjectIdentity = aclObjectIdentity; + } + + public AclObjectIdentity getAclObjectIdentity() { + return this.aclObjectIdentity; + } + + public void setAclObjectParentIdentity( + AclObjectIdentity aclObjectParentIdentity) { + this.aclObjectParentIdentity = aclObjectParentIdentity; + } + + public AclObjectIdentity getAclObjectParentIdentity() { + return this.aclObjectParentIdentity; + } + + /** + * Subclasses must indicate the permissions they support. Each base + * permission should be an integer with a base 2. ie: the first permission + * is 2^^0 (1), the second permission is 2^^1 (1), the third permission is + * 2^^2 (4) etc. Each base permission should be exposed by the subclass as + * apublic static final int. It is further recommended that
+ * valid combinations of permissions are also exposed as public
+ * static final ints.
+ *
+ * + * This method returns all permission integers that are allowed to be used + * together. This must include any combinations of valid + * permissions. So if the permissions indicated by 2^^2 (4) and 2^^1 + * (2) can be used together, one of the integers returned by this method + * must be 6 (4 + 2). Otherwise attempts to set the permission will be + * rejected, as the final resulting mask will be rejected. + *
+ * + *+ * Whilst it may seem unduly time onerous to return every valid permission + * combination, doing so delivers maximum flexibility in ensuring + * ACLs only reflect logical combinations. For example, it would be + * inappropriate to grant a "read" and "write" permission along with an + * "unrestricted" permission, as the latter implies the former + * permissions. + *
+ * + * @return every valid combination of permissions + */ + public abstract int[] getValidPermissions(); + + /** + * Outputs the permissions in a human-friendly format. For example, this + * method may return "CR-D" to indicate the passed integer permits create, + * permits read, does not permit update, and permits delete. + * + * @param i the integer containing the mask which should be printed + * + * @return the human-friend formatted block + */ + public abstract String printPermissionsBlock(int i); + + public void setMask(int mask) { + this.mask = mask; + } + + public int getMask() { + return this.mask; + } + + public boolean isPermitted(int permissionToCheck) { + return isPermitted(this.mask, permissionToCheck); + } + + public void setRecipient(Object recipient) { + this.recipient = recipient; + } + + public Object getRecipient() { + return this.recipient; + } + + public int addPermission(int permissionToAdd) { + return addPermissions(new int[] {permissionToAdd}); + } + + public int addPermissions(int[] permissionsToAdd) { + if (logger.isDebugEnabled()) { + logger.debug("BEFORE Permissions: " + printPermissionsBlock(mask) + + " " + printBinary(mask) + " (" + mask + ")"); + } + + for (int i = 0; i < permissionsToAdd.length; i++) { + if (logger.isDebugEnabled()) { + logger.debug("Add permission: " + + printPermissionsBlock(permissionsToAdd[i]) + " " + + printBinary(permissionsToAdd[i]) + " (" + + permissionsToAdd[i] + ")"); + } + + this.mask |= permissionsToAdd[i]; + } + + if (Arrays.binarySearch(validPermissions, this.mask) < 0) { + throw new IllegalArgumentException( + "Resulting permission set will be invalid."); + } else { + if (logger.isDebugEnabled()) { + logger.debug("AFTER Permissions: " + + printPermissionsBlock(mask) + " " + printBinary(mask) + + " (" + mask + ")"); + } + + return this.mask; + } + } + + public int deletePermission(int permissionToDelete) { + return deletePermissions(new int[] {permissionToDelete}); + } + + public int deletePermissions(int[] permissionsToDelete) { + if (logger.isDebugEnabled()) { + logger.debug("BEFORE Permissions: " + printPermissionsBlock(mask) + + " " + printBinary(mask) + " (" + mask + ")"); + } + + for (int i = 0; i < permissionsToDelete.length; i++) { + if (logger.isDebugEnabled()) { + logger.debug("Delete permission: " + + printPermissionsBlock(permissionsToDelete[i]) + " " + + printBinary(permissionsToDelete[i]) + " (" + + permissionsToDelete[i] + ")"); + } + + this.mask &= ~permissionsToDelete[i]; + } + + if (Arrays.binarySearch(validPermissions, this.mask) < 0) { + throw new IllegalArgumentException( + "Resulting permission set will be invalid."); + } else { + if (logger.isDebugEnabled()) { + logger.debug("AFTER Permissions: " + + printPermissionsBlock(mask) + " " + printBinary(mask) + + " (" + mask + ")"); + } + + return this.mask; + } + } + + /** + * Outputs the permissions in human-friendly format for the current + *AbstractBasicAclEntry's mask.
+ *
+ * @return the human-friendly formatted block for this instance
+ */
+ public String printPermissionsBlock() {
+ return printPermissionsBlock(this.mask);
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getName());
+ sb.append("[").append(aclObjectIdentity).append(",").append(recipient);
+ sb.append("=").append(printPermissionsBlock(mask)).append(" ");
+ sb.append(printBinary(mask)).append(" (");
+ sb.append(mask).append(")").append("]");
+
+ return sb.toString();
+ }
+
+ public int togglePermission(int permissionToToggle) {
+ this.mask ^= permissionToToggle;
+
+ if (Arrays.binarySearch(validPermissions, this.mask) < 0) {
+ throw new IllegalArgumentException(
+ "Resulting permission set will be invalid.");
+ } else {
+ return this.mask;
+ }
+ }
+
+ protected boolean isPermitted(int maskToCheck, int permissionToCheck) {
+ return ((maskToCheck & permissionToCheck) == permissionToCheck);
+ }
+
+ private String printBinary(int i) {
+ String s = Integer.toString(i, 2);
+
+ String pattern = "................................";
+
+ String temp1 = pattern.substring(0, pattern.length() - s.length());
+
+ String temp2 = temp1 + s;
+
+ return temp2.replace('0', '.');
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/AclObjectIdentity.java b/core/src/main/java/org/acegisecurity/acl/basic/AclObjectIdentity.java
new file mode 100644
index 0000000000..8b7385fb4c
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/AclObjectIdentity.java
@@ -0,0 +1,67 @@
+/* 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 java.io.Serializable;
+
+
+/**
+ * Interface representing the identity of an individual domain object instance.
+ *
+ *
+ * It should be noted that AclObjectIdentity instances are created
+ * in various locations throughout the package. As
+ * AclObjectIdentitys are used as the key for caching, it is
+ * essential that implementations provide methods so that object-equality
+ * rather than reference-equality can be relied upon by caches. In other
+ * words, a cache can consider two AclObjectIdentitys equal if
+ * identity1.equals(identity2), rather than reference-equality of
+ * identity1==identity2.
+ *
+ * In practical terms this means you must implement the standard
+ * java.lang.Object methods shown below. Depending on your
+ * cache's internal structure, you may also need to implement special
+ * interfaces such as java.util.Comparator or
+ * java.lang.Comparable.
+ *
java.lang.Object documentation for the
+ * interface contract.
+ *
+ * @param obj to be compared
+ *
+ * @return true if the objects are equal, false
+ * otherwise
+ */
+ public boolean equals(Object obj);
+
+ /**
+ * Refer to the java.lang.Object documentation for the
+ * interface contract.
+ *
+ * @return a hash code representation of this object
+ */
+ public int hashCode();
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/AclObjectIdentityAware.java b/core/src/main/java/org/acegisecurity/acl/basic/AclObjectIdentityAware.java
new file mode 100644
index 0000000000..e25c5506d0
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/AclObjectIdentityAware.java
@@ -0,0 +1,42 @@
+/* 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;
+
+/**
+ * Indicates a domain object instance is able to provide {@link
+ * AclObjectIdentity} information.
+ *
+ *
+ * Domain objects must implement this interface if they wish to provide an
+ * AclObjectIdentity rather than it being determined by relying
+ * classes. Specifically, the {@link BasicAclProvider} detects and uses this
+ * interface.
+ *
AclObjectIdentity for this instance.
+ *
+ * @return the ACL object identity for this instance (can never be
+ * null)
+ */
+ public AclObjectIdentity getAclObjectIdentity();
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/BasicAclDao.java b/core/src/main/java/org/acegisecurity/acl/basic/BasicAclDao.java
new file mode 100644
index 0000000000..e9f94b2ef6
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/BasicAclDao.java
@@ -0,0 +1,58 @@
+/* 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;
+
+/**
+ * Represents a data access object that can return the {@link BasicAclEntry}s
+ * applying to a given ACL object identity.
+ *
+ *
+ * BasicAclDao implementations are responsible for interpreting a
+ * given {@link AclObjectIdentity} and being able to lookup and return the
+ * corresponding {@link BasicAclEntry}[]s.
+ *
+ * BasicAclDaos many, but are not required to, allow the backend
+ * ACL repository to specify the class of BasicAclEntry
+ * implementations that should be returned.
+ *
+ * Does not perform caching, include ACLs from any inheritance + * hierarchy or filter returned objects based on effective permissions. + * Implementations are solely responsible for returning ACLs found in the + * ACL repository for the specified object identity. + *
+ * + * @param aclObjectIdentity the domain object instance that ACL information + * is being requested for (nevernull)
+ *
+ * @return the ACLs that apply (no nulls are permitted in the
+ * array), or null if no ACLs could be found for the
+ * specified ACL object identity
+ */
+ public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity);
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/BasicAclEntry.java b/core/src/main/java/org/acegisecurity/acl/basic/BasicAclEntry.java
new file mode 100644
index 0000000000..14e8edce3a
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/BasicAclEntry.java
@@ -0,0 +1,126 @@
+/* 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 net.sf.acegisecurity.acl.AclEntry;
+
+
+/**
+ * Represents an entry in an access control list.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface BasicAclEntry extends AclEntry {
+ //~ Methods ================================================================
+
+ /**
+ * This setter should only be used by DAO implementations.
+ *
+ * @param aclObjectIdentity an object which can be used to uniquely
+ * identify the domain object instance subject of this ACL entry
+ */
+ public void setAclObjectIdentity(AclObjectIdentity aclObjectIdentity);
+
+ /**
+ * Indicates the domain object instance that is subject of this
+ * BasicAclEntry. This information may be of interest to
+ * relying classes (voters and business methods) that wish to know the
+ * actual origination of the ACL entry (so as to distinguish individual
+ * ACL entries from others contributed by the inheritance hierarchy).
+ *
+ * @return the ACL object identity that is subject of this ACL entry (never
+ * null)
+ */
+ public AclObjectIdentity getAclObjectIdentity();
+
+ /**
+ * This setter should only be used by DAO implementations.
+ *
+ * @param aclObjectParentIdentity an object which represents the parent of
+ * the domain object instance subject of this ACL entry, or
+ * null if either the domain object instance has no
+ * parent or its parent should be not used to compute an
+ * inheritance hierarchy
+ */
+ public void setAclObjectParentIdentity(
+ AclObjectIdentity aclObjectParentIdentity);
+
+ /**
+ * Indicates any ACL parent of the domain object instance. This is used by
+ * BasicAclProvider to walk the inheritance hierarchy. An
+ * domain object instance need not have a parent.
+ *
+ * @return the ACL object identity that is the parent of this ACL entry
+ * (may be null if no parent should be consulted)
+ */
+ public AclObjectIdentity getAclObjectParentIdentity();
+
+ /**
+ * This setter should only be used by DAO implementations.
+ *
+ * @param mask the integer representing the permissions bit mask
+ */
+ public void setMask(int mask);
+
+ /**
+ * Access control lists in this package are based on bit masking. The
+ * integer value of the bit mask can be obtained from this method.
+ *
+ * @return the bit mask applicable to this ACL entry (zero indicates a bit
+ * mask where no permissions have been granted)
+ */
+ public int getMask();
+
+ /**
+ * This setter should only be used by DAO implementations.
+ *
+ * @param recipient a representation of the recipient of this ACL entry
+ * that makes sense to an EffectiveAclsResolver
+ * implementation
+ */
+ public void setRecipient(Object recipient);
+
+ /**
+ * A domain object instance will usually have multiple
+ * BasicAclEntrys. Each separate BasicAclEntry
+ * applies to a particular "recipient". Typical examples of recipients
+ * include (but do not necessarily have to include) usernames, role names,
+ * complex granted authorities etc.
+ *
+ *
+ * It is essential that only one BasicAclEntry exists for a
+ * given recipient. Otherwise conflicts as to the mask that should
+ * apply to a given recipient will occur.
+ *
+ * This method indicates which recipient this BasicAclEntry
+ * applies to. The returned object type will vary depending on the type of
+ * recipient. For instance, it might be a String containing a
+ * username, or a GrantedAuthorityImpl containing a complex
+ * granted authority that is being granted the permissions contained in
+ * this access control entry. The {@link EffectiveAclsResolver} and {@link
+ * BasicAclProvider#getAcls(Object, Authentication)} can process the
+ * different recipient types and return only those that apply to a
+ * specified Authentication object.
+ *
null)
+ */
+ public Object getRecipient();
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/BasicAclEntryCache.java b/core/src/main/java/org/acegisecurity/acl/basic/BasicAclEntryCache.java
new file mode 100644
index 0000000000..d143f2bdc4
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/BasicAclEntryCache.java
@@ -0,0 +1,61 @@
+/* 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;
+
+/**
+ * Provides a cache of {@link BasicAclEntry} objects.
+ *
+ *
+ * Implementations should provide appropriate methods to set their cache
+ * parameters (eg time-to-live) and/or force removal of entities before their
+ * normal expiration. These are not part of the
+ * BasicAclEntryCache interface contract because they vary
+ * depending on the type of caching system used (eg in-memory vs disk vs
+ * cluster vs hybrid).
+ *
BasicAclEntrys (no
+ * nulls are permitted in the returned array) or
+ * null if the object identity could not be found or
+ * if the cache entry has expired
+ */
+ public BasicAclEntry[] getEntriesFromCache(
+ AclObjectIdentity aclObjectIdentity);
+
+ /**
+ * Places an array of {@link BasicAclEntry}s in the cache.
+ *
+ *
+ * No nulls are allowed in the passed array. If any
+ * null is passed, the implementation may throw an exception.
+ *
+ * Retrieves access control lists (ACL) entries for domain object instances + * from a data access object (DAO). + *
+ * + *
+ * This implementation will provide ACL lookup services for any object that it
+ * can determine the {@link AclObjectIdentity} for by calling the {@link
+ * #obtainIdentity(Object)} method. Subclasses can override this method if
+ * they only want the BasicAclProvider responding to particular
+ * domain object instances.
+ *
+ * BasicAclProvider will walk an inheritance hierarchy if a
+ * BasicAclEntry returned by the DAO indicates it has a parent.
+ * NB: inheritance occurs at a domain instance object level. It does
+ * not occur at an ACL recipient level. This means
+ * allBasicAclEntrys for a given domain instance object
+ * must have the same parent identity, or
+ * allBasicAclEntrys must have null as their
+ * parent identity.
+ *
+ * A cache should be used. This is provided by the {@link BasicAclEntryCache}.
+ * BasicAclProvider by default is setup to use the {@link
+ * NullAclEntryCache}, which performs no caching.
+ *
+ * To implement the {@link #getAcls(Object, Authentication)} method,
+ * BasicAclProvider requires a {@link EffectiveAclsResolver} to
+ * be configured against it. By default the {@link
+ * GrantedAuthorityEffectiveAclsResolver} is used.
+ *
null.
+ */
+ private BasicAclDao basicAclDao;
+ private BasicAclEntryCache basicAclEntryCache = new NullAclEntryCache();
+ private Class defaultAclObjectIdentityClass = NamedEntityObjectIdentity.class;
+ private EffectiveAclsResolver effectiveAclsResolver = new GrantedAuthorityEffectiveAclsResolver();
+
+ //~ Methods ================================================================
+
+ public AclEntry[] getAcls(Object domainInstance) {
+ Map map = new HashMap();
+
+ AclObjectIdentity aclIdentity = obtainIdentity(domainInstance);
+
+ if (aclIdentity == null) {
+ throw new IllegalArgumentException(
+ "domainInstance is not supported by this provider");
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Looking up: " + aclIdentity.toString());
+ }
+
+ BasicAclEntry[] instanceAclEntries = lookup(aclIdentity);
+
+ // Exit if there is no ACL information or parent for this instance
+ if (instanceAclEntries == null) {
+ return null;
+ }
+
+ // Add the leaf objects to the Map, keyed on recipient
+ for (int i = 0; i < instanceAclEntries.length; i++) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Explicit add: "
+ + instanceAclEntries[i].toString());
+ }
+
+ map.put(instanceAclEntries[i].getRecipient(), instanceAclEntries[i]);
+ }
+
+ AclObjectIdentity parent = instanceAclEntries[0]
+ .getAclObjectParentIdentity();
+
+ while (parent != null) {
+ BasicAclEntry[] parentAclEntries = lookup(parent);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Parent lookup: " + parent.toString());
+ }
+
+ // Exit loop if parent couldn't be found (unexpected condition)
+ if (parentAclEntries == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Parent could not be found in ACL repository");
+ }
+
+ break;
+ }
+
+ // Now add each _NEW_ recipient to the list
+ for (int i = 0; i < parentAclEntries.length; i++) {
+ if (!map.containsKey(parentAclEntries[i].getRecipient())) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Added parent to map: "
+ + parentAclEntries[i].toString());
+ }
+
+ map.put(parentAclEntries[i].getRecipient(),
+ parentAclEntries[i]);
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Did NOT add parent to map: "
+ + parentAclEntries[i].toString());
+ }
+ }
+ }
+
+ // Prepare for next iteration of while loop
+ parent = parentAclEntries[0].getAclObjectParentIdentity();
+ }
+
+ Collection collection = map.values();
+
+ return (AclEntry[]) collection.toArray(new AclEntry[] {});
+ }
+
+ public AclEntry[] getAcls(Object domainInstance,
+ Authentication authentication) {
+ AclEntry[] allAcls = (AclEntry[]) this.getAcls(domainInstance);
+
+ return this.effectiveAclsResolver.resolveEffectiveAcls(allAcls,
+ authentication);
+ }
+
+ public void setBasicAclDao(BasicAclDao basicAclDao) {
+ this.basicAclDao = basicAclDao;
+ }
+
+ public BasicAclDao getBasicAclDao() {
+ return basicAclDao;
+ }
+
+ public void setBasicAclEntryCache(BasicAclEntryCache basicAclEntryCache) {
+ this.basicAclEntryCache = basicAclEntryCache;
+ }
+
+ public BasicAclEntryCache getBasicAclEntryCache() {
+ return basicAclEntryCache;
+ }
+
+ /**
+ * Allows selection of the AclObjectIdentity class that an
+ * attempt should be made to construct if the passed object does not
+ * implement AclObjectIdentityAware.
+ *
+ *
+ * NB: Any defaultAclObjectIdentityClassmust provide a
+ * public constructor that accepts an Object. Otherwise it is
+ * not possible for the BasicAclProvider to try to create the
+ * AclObjectIdentity instance at runtime.
+ *
AclObjectIdentity is returned by {@link
+ * #obtainIdentity(Object)}.
+ *
+ * @param domainInstance the instance to check
+ *
+ * @return true if this provider supports the passed object,
+ * false otherwise
+ */
+ public boolean supports(Object domainInstance) {
+ if (obtainIdentity(domainInstance) == null) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * This method looks up the AclObjectIdentity of a passed
+ * domain object instance.
+ *
+ *
+ * This implementation attempts to obtain the
+ * AclObjectIdentity via reflection inspection of the class
+ * for the {@link AclObjectIdentityAware} interface. If this fails, an
+ * attempt is made to construct a {@link
+ * #getDefaultAclObjectIdentityClass()} object by passing the domain
+ * instance object into its constructor.
+ *
null)
+ *
+ * @return an ACL object identity, or null if one could not be
+ * obtained
+ */
+ protected AclObjectIdentity obtainIdentity(Object domainInstance) {
+ if (domainInstance instanceof AclObjectIdentityAware) {
+ AclObjectIdentityAware aclObjectIdentityAware = (AclObjectIdentityAware) domainInstance;
+
+ return aclObjectIdentityAware.getAclObjectIdentity();
+ }
+
+ try {
+ Constructor constructor = defaultAclObjectIdentityClass
+ .getConstructor(new Class[] {Object.class});
+
+ return (AclObjectIdentity) constructor.newInstance(new Object[] {domainInstance});
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ private BasicAclEntry[] lookup(AclObjectIdentity aclObjectIdentity) {
+ BasicAclEntry[] result = basicAclEntryCache.getEntriesFromCache(aclObjectIdentity);
+
+ if (result != null) {
+ if (result[0].getRecipient().equals(RECIPIENT_FOR_CACHE_EMPTY)) {
+ return null;
+ } else {
+ return result;
+ }
+ }
+
+ result = basicAclDao.getAcls(aclObjectIdentity);
+
+ if (result == null) {
+ SimpleAclEntry[] emptyAclEntries = {new SimpleAclEntry(RECIPIENT_FOR_CACHE_EMPTY,
+ aclObjectIdentity, null, 0)};
+ basicAclEntryCache.putEntriesInCache(emptyAclEntries);
+
+ return null;
+ }
+
+ basicAclEntryCache.putEntriesInCache(result);
+
+ return result;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/EffectiveAclsResolver.java b/core/src/main/java/org/acegisecurity/acl/basic/EffectiveAclsResolver.java
new file mode 100644
index 0000000000..4d8e17efdd
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/EffectiveAclsResolver.java
@@ -0,0 +1,65 @@
+/* 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 net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.acl.AclEntry;
+
+
+/**
+ * Determines the ACLs that are effective for a given
+ * Authentication object.
+ *
+ *
+ * Implementations will vary depending on their ability to interpret the
+ * "recipient" object types contained in {@link BasicAclEntry} instances, and
+ * how those recipient object types correspond to
+ * Authentication-presented principals and granted authorities.
+ *
+ * Implementations should not filter the resulting ACL list from lower-order
+ * permissions. So if a resulting ACL list grants a "read" permission, an
+ * "unlimited" permission and a "zero" permission (due to the effective ACLs
+ * for different granted authorities held by the Authentication
+ * object), all three permissions would be returned as distinct
+ * BasicAclEntry instances. It is the responsibility of the
+ * relying classes (voters and business methods) to ignore or handle
+ * lower-order permissions in a business logic dependent manner.
+ *
Authentication object.
+ *
+ * @param allAcls every ACL assigned to a domain object instance
+ * @param filteredBy the principal (populated with
+ * GrantedAuthoritys along with any other members that
+ * relate to role or group membership) that effective ACLs should
+ * be returned for
+ *
+ * @return the ACLs that apply to the presented principal, or
+ * null if there are none after filtering
+ */
+ public AclEntry[] resolveEffectiveAcls(AclEntry[] allAcls,
+ Authentication filteredBy);
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/GrantedAuthorityEffectiveAclsResolver.java b/core/src/main/java/org/acegisecurity/acl/basic/GrantedAuthorityEffectiveAclsResolver.java
new file mode 100644
index 0000000000..d98e9760f9
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/GrantedAuthorityEffectiveAclsResolver.java
@@ -0,0 +1,102 @@
+/* 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 net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.GrantedAuthority;
+import net.sf.acegisecurity.acl.AclEntry;
+
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ * Simple implementation of {@link EffectiveAclsResolver}.
+ *
+ *
+ * This implementation does not need to understand the "recipient" types
+ * presented in a BasicAclEntry because it merely delegates to
+ * the detected {@link Authentication#getPrincipal()} or {@link
+ * Authentication#getAuthorities()}. The principal object or granted
+ * authorities object has its Object.equals(recipient) method
+ * called to make the decision as to whether the recipient in the
+ * BasicAclEntry is the same as the principal or granted
+ * authority.
+ *
+ * This class should prove an adequate ACLs resolver if you're using standard
+ * Acegi Security classes. This is because the typical
+ * Authentication token is
+ * UsernamePasswordAuthenticationToken, which for its
+ * principal is usually a String. The
+ * GrantedAuthorityImpl is typically used for granted
+ * authorities, which tests for equality based on a String. This
+ * means BasicAclDaos simply need to return a String
+ * to represent the recipient. If you use non-String objects, you
+ * will probably require an alternative EffectiveAclsResolver.
+ *
+ * Uses Strings to store the identity of the domain object
+ * instance. Also offers a constructor that uses reflection to build the
+ * identity information.
+ *
NamedEntityObjectIdentity based on the passed
+ * object instance. The passed object must provide a getId()
+ * method, otherwise an exception will be thrown.
+ *
+ * @param object the domain object instance to create an identity for
+ *
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ * @throws IllegalArgumentException
+ */
+ public NamedEntityObjectIdentity(Object object)
+ throws IllegalAccessException, InvocationTargetException {
+ if (object == null) {
+ throw new IllegalArgumentException("object cannot be null");
+ }
+
+ this.classname = object.getClass().getName();
+
+ Class clazz = object.getClass();
+
+ try {
+ Method id = clazz.getMethod("getId", null);
+ Object result = id.invoke(object, null);
+ this.id = result.toString();
+ } catch (NoSuchMethodException nsme) {
+ throw new IllegalArgumentException(
+ "object does not provide a getId() method");
+ }
+ }
+
+ protected NamedEntityObjectIdentity() {
+ throw new IllegalArgumentException("Cannot use default constructor");
+ }
+
+ //~ Methods ================================================================
+
+ /**
+ * Indicates the classname portion of the object identity.
+ *
+ * @return the classname (never null)
+ */
+ public String getClassname() {
+ return classname;
+ }
+
+ /**
+ * Indicates the instance identity portion of the object identity.
+ *
+ * @return the instance identity (never null)
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Important so caching operates properly.
+ *
+ *
+ * Considers an object of the same class equal if it has the same
+ * classname and id properties.
+ *
true if the presented object matches this object
+ */
+ public boolean equals(Object arg0) {
+ if (arg0 == null) {
+ return false;
+ }
+
+ if (!(arg0 instanceof NamedEntityObjectIdentity)) {
+ return false;
+ }
+
+ NamedEntityObjectIdentity other = (NamedEntityObjectIdentity) arg0;
+
+ if (this.getId().equals(other.getId())
+ && this.getClassname().equals(other.getClassname())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Important so caching operates properly.
+ *
+ * @return the hash of the classname and id
+ */
+ public int hashCode() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(this.classname).append(this.id);
+
+ return sb.toString().hashCode();
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(this.getClass().getName()).append("[");
+ sb.append("Classname: ").append(this.classname);
+ sb.append("; Identity: ").append(this.id).append("]");
+
+ return sb.toString();
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/SimpleAclEntry.java b/core/src/main/java/org/acegisecurity/acl/basic/SimpleAclEntry.java
new file mode 100644
index 0000000000..f6d4b94dc3
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/SimpleAclEntry.java
@@ -0,0 +1,112 @@
+/* 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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Stores some privileges typical of a domain object.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SimpleAclEntry extends AbstractBasicAclEntry {
+ //~ Static fields/initializers =============================================
+
+ private static final Log logger = LogFactory.getLog(SimpleAclEntry.class);
+
+ // Base permissions we permit
+ public static final int NOTHING = 0;
+ public static final int ADMINISTRATION = (int) Math.pow(2, 0);
+ public static final int READ = (int) Math.pow(2, 1);
+ public static final int WRITE = (int) Math.pow(2, 2);
+ public static final int CREATE = (int) Math.pow(2, 3);
+ public static final int DELETE = (int) Math.pow(2, 4);
+
+ // Combinations of base permissions we permit
+ public static final int READ_WRITE_CREATE_DELETE = READ | WRITE | CREATE
+ | DELETE;
+ public static final int READ_WRITE_CREATE = READ | WRITE | CREATE;
+ public static final int READ_WRITE = READ | WRITE;
+ public static final int READ_WRITE_DELETE = READ | WRITE | DELETE;
+
+ // Array required by the abstract superclass via getValidPermissions()
+ private static final int[] validPermissions = {NOTHING, ADMINISTRATION, READ, WRITE, CREATE, DELETE, READ_WRITE_CREATE_DELETE, READ_WRITE_CREATE, READ_WRITE, READ_WRITE_DELETE};
+
+ //~ Constructors ===========================================================
+
+ /**
+ * Allows {@link BasicAclDao} implementations to construct this object
+ * using newInstance().
+ *
+ * + * Normal classes should not use this default constructor. + *
+ */ + public SimpleAclEntry() { + super(); + } + + public SimpleAclEntry(Object recipient, + AclObjectIdentity aclObjectIdentity, + AclObjectIdentity aclObjectParentIdentity, int mask) { + super(recipient, aclObjectIdentity, aclObjectParentIdentity, mask); + } + + //~ Methods ================================================================ + + public int[] getValidPermissions() { + return validPermissions; + } + + public String printPermissionsBlock(int i) { + StringBuffer sb = new StringBuffer(); + + if (isPermitted(i, ADMINISTRATION)) { + sb.append('A'); + } else { + sb.append('-'); + } + + if (isPermitted(i, READ)) { + sb.append('R'); + } else { + sb.append('-'); + } + + if (isPermitted(i, WRITE)) { + sb.append('W'); + } else { + sb.append('-'); + } + + if (isPermitted(i, CREATE)) { + sb.append('C'); + } else { + sb.append('-'); + } + + if (isPermitted(i, DELETE)) { + sb.append('D'); + } else { + sb.append('-'); + } + + return sb.toString(); + } +} diff --git a/core/src/main/java/org/acegisecurity/acl/basic/cache/BasicAclEntryHolder.java b/core/src/main/java/org/acegisecurity/acl/basic/cache/BasicAclEntryHolder.java new file mode 100644 index 0000000000..f7038d8f32 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acl/basic/cache/BasicAclEntryHolder.java @@ -0,0 +1,78 @@ +/* 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.cache; + +import net.sf.acegisecurity.acl.basic.BasicAclEntry; + +import java.io.Serializable; + + +/** + * Used by {@link EhCacheBasedAclEntryCache} to store the array of + *BasicAclEntrys in the cache.
+ *
+ * + * This is necessary because caches store a single object per key, not an + * array. + *
+ * + *+ * This class uses value object semantics. ie: construction-based + * initialisation without any setters for the properties. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public class BasicAclEntryHolder implements Serializable { + //~ Instance fields ======================================================== + + private BasicAclEntry[] basicAclEntries; + + //~ Constructors =========================================================== + + /** + * Constructs theBasicAclEntryHolder.
+ *
+ * @param aclEntries to cache (any nulls will cause an
+ * exception, which should not be a problem as the contract for
+ * BasicAclEntryCache allows exceptions if
+ * nulls are presented)
+ *
+ * @throws IllegalArgumentException if a null exists anywhere
+ * in the aclEntries or if a null is
+ * passed to the constructor
+ */
+ public BasicAclEntryHolder(BasicAclEntry[] aclEntries) {
+ if (aclEntries == null) {
+ throw new IllegalArgumentException("aclEntries cannot be null");
+ }
+
+ for (int i = 0; i < aclEntries.length; i++) {
+ if (aclEntries[i] == null) {
+ throw new IllegalArgumentException("aclEntries cannot be null");
+ }
+ }
+
+ this.basicAclEntries = aclEntries;
+ }
+
+ //~ Methods ================================================================
+
+ public BasicAclEntry[] getBasicAclEntries() {
+ return basicAclEntries;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/cache/EhCacheBasedAclEntryCache.java b/core/src/main/java/org/acegisecurity/acl/basic/cache/EhCacheBasedAclEntryCache.java
new file mode 100644
index 0000000000..0d6e1b276d
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/cache/EhCacheBasedAclEntryCache.java
@@ -0,0 +1,135 @@
+/* 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.cache;
+
+import net.sf.acegisecurity.acl.basic.AclObjectIdentity;
+import net.sf.acegisecurity.acl.basic.BasicAclEntry;
+import net.sf.acegisecurity.acl.basic.BasicAclEntryCache;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.dao.DataRetrievalFailureException;
+
+
+/**
+ * Caches BasicAclEntrys using EHCACHE.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class EhCacheBasedAclEntryCache implements BasicAclEntryCache,
+ InitializingBean, DisposableBean {
+ //~ Static fields/initializers =============================================
+
+ private static final Log logger = LogFactory.getLog(EhCacheBasedAclEntryCache.class);
+ private static final String CACHE_NAME = "ehCacheBasedAclEntryCache";
+
+ //~ Instance fields ========================================================
+
+ private Cache cache;
+ private CacheManager manager;
+ private int minutesToIdle = 5;
+
+ //~ Methods ================================================================
+
+ public BasicAclEntry[] getEntriesFromCache(
+ AclObjectIdentity aclObjectIdentity) {
+ Element element = null;
+
+ try {
+ element = cache.get(aclObjectIdentity);
+ } catch (CacheException cacheException) {
+ throw new DataRetrievalFailureException("Cache failure: "
+ + cacheException.getMessage());
+ }
+
+ // Return null if cache element has expired or not found
+ if (element == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cache miss: " + aclObjectIdentity);
+ }
+
+ return null;
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cache hit: " + (element != null) + "; object: "
+ + aclObjectIdentity);
+ }
+
+ BasicAclEntryHolder holder = (BasicAclEntryHolder) element.getValue();
+
+ return holder.getBasicAclEntries();
+ }
+
+ public void setMinutesToIdle(int minutesToIdle) {
+ this.minutesToIdle = minutesToIdle;
+ }
+
+ /**
+ * Specifies how many minutes an entry will remain in the cache from when
+ * it was last accessed.
+ *
+ * + * Defaults to 5 minutes. + *
+ * + * @return Returns the minutes an element remains in the cache + */ + public int getMinutesToIdle() { + return minutesToIdle; + } + + public void afterPropertiesSet() throws Exception { + if (CacheManager.getInstance().cacheExists(CACHE_NAME)) { + // don’t remove the cache + } else { + manager = CacheManager.create(); + + // Cache name, max memory, overflowToDisk, eternal, timeToLive, timeToIdle + cache = new Cache(CACHE_NAME, Integer.MAX_VALUE, false, false, + minutesToIdle * 60, minutesToIdle * 60); + + manager.addCache(cache); + } + } + + public void destroy() throws Exception { + manager.removeCache(CACHE_NAME); + } + + public void putEntriesInCache(BasicAclEntry[] basicAclEntry) { + BasicAclEntryHolder holder = new BasicAclEntryHolder(basicAclEntry); + Element element = new Element(basicAclEntry[0].getAclObjectIdentity(), + holder); + + if (logger.isDebugEnabled()) { + logger.debug("Cache put: " + element.getKey()); + } + + cache.put(element); + } +} diff --git a/core/src/main/java/org/acegisecurity/acl/basic/cache/NullAclEntryCache.java b/core/src/main/java/org/acegisecurity/acl/basic/cache/NullAclEntryCache.java new file mode 100644 index 0000000000..d7d94f8464 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acl/basic/cache/NullAclEntryCache.java @@ -0,0 +1,56 @@ +/* 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.cache; + +import net.sf.acegisecurity.acl.basic.AclObjectIdentity; +import net.sf.acegisecurity.acl.basic.BasicAclEntry; +import net.sf.acegisecurity.acl.basic.BasicAclEntryCache; + + +/** + * Does not perform any caching. + * + *+ * Do not use in production settings, as ACL queries are likely to be + * extensive. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public class NullAclEntryCache implements BasicAclEntryCache { + //~ Methods ================================================================ + + /** + * As nothing ever stored in the cache, will always return + *null.
+ *
+ * @param aclObjectIdentity ignored
+ *
+ * @return always null
+ */
+ public BasicAclEntry[] getEntriesFromCache(
+ AclObjectIdentity aclObjectIdentity) {
+ return null;
+ }
+
+ /**
+ * Meets method signature but doesn't store in any cache.
+ *
+ * @param basicAclEntry ignored
+ */
+ public void putEntriesInCache(BasicAclEntry[] basicAclEntry) {}
+}
diff --git a/core/src/main/java/org/acegisecurity/acl/basic/cache/package.html b/core/src/main/java/org/acegisecurity/acl/basic/cache/package.html
new file mode 100644
index 0000000000..aff4f2f651
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/cache/package.html
@@ -0,0 +1,5 @@
+
+
+Caches ACL information for the BasicAclProvider.
+
+
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
new file mode 100644
index 0000000000..4c068cbf66
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImpl.java
@@ -0,0 +1,256 @@
+/* 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.BasicAclDao;
+import net.sf.acegisecurity.acl.basic.BasicAclEntry;
+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.jdbc.core.SqlParameter;
+import org.springframework.jdbc.core.support.JdbcDaoSupport;
+import org.springframework.jdbc.object.MappingSqlQuery;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.sql.DataSource;
+
+
+/**
+ * + * Retrieves ACL details from a JDBC location. + *
+ * + *+ * 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. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +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 = ?"; + private static final Log logger = LogFactory.getLog(JdbcDaoSupport.class); + + //~ Instance fields ======================================================== + + private MappingSqlQuery aclsByObjectIdentity; + private String aclsByObjectIdentityQuery; + + //~ Constructors =========================================================== + + public JdbcDaoImpl() { + aclsByObjectIdentityQuery = DEF_ACLS_BY_OBJECT_IDENTITY_QUERY; + } + + //~ Methods ================================================================ + + /** + * Returns the ACLs associated with the requested + *AclObjectIdentity.
+ *
+ *
+ * The {@link BasicAclEntry}s returned by this method will have
+ * String-based recipients. This will not be a problem if you
+ * are using the GrantedAuthorityEffectiveAclsResolver, which
+ * is the default configured against BasicAclProvider.
+ *
+ * This method will only return ACLs for requests where the
+ * AclObjectIdentity is of type {@link
+ * NamedEntityObjectIdentity}. Of course, you can subclass or replace this
+ * class and support your own custom AclObjectIdentity types.
+ *
null and must be an instance of
+ * NamedEntityObjectIdentity)
+ *
+ * @return the ACLs that apply (without any nulls inside the
+ * array), or null if not found or if an incompatible
+ * AclObjectIdentity was requested
+ */
+ public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity) {
+ // Ensure we can process this type of AclObjectIdentity
+ if (!(aclObjectIdentity instanceof NamedEntityObjectIdentity)) {
+ return null;
+ }
+
+ NamedEntityObjectIdentity neoi = (NamedEntityObjectIdentity) aclObjectIdentity;
+
+ // Compose the String we expect to find in the RDBMS
+ String aclObjectIdentityString = neoi.getClassname() + ":"
+ + neoi.getId();
+
+ // Lookup the BasicAclEntrys from RDBMS (may include null responses)
+ List acls = aclsByObjectIdentity.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);
+ }
+ }
+
+ // Return null if nothing of use found (to meet interface contract)
+ if (toReturnAcls.size() > 0) {
+ return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {});
+ } else {
+ return null;
+ }
+ }
+
+ public void setAclsByObjectIdentity(
+ MappingSqlQuery aclsByObjectIdentityQuery) {
+ this.aclsByObjectIdentity = aclsByObjectIdentityQuery;
+ }
+
+ public MappingSqlQuery getAclsByObjectIdentity() {
+ return aclsByObjectIdentity;
+ }
+
+ /**
+ * Allows the default query string used to retrieve ACLs based on object
+ * identity to be overriden, if default table or column names need to be
+ * changed. The default query is {@link
+ * #DEF_ACLS_BY_OBJECT_IDENTITY_QUERY}; when modifying this query, ensure
+ * that all returned columns are mapped back to the same column names as
+ * in the default query.
+ *
+ * @param queryString The query string to set
+ */
+ public void setAclsByObjectIdentityQuery(String queryString) {
+ aclsByObjectIdentityQuery = queryString;
+ }
+
+ public String getAclsByObjectIdentityQuery() {
+ return aclsByObjectIdentityQuery;
+ }
+
+ protected void initDao() throws ApplicationContextException {
+ initMappingSqlQueries();
+ }
+
+ /**
+ * Extension point to allow other MappingSqlQuery objects to be substituted
+ * in a subclass
+ */
+ protected void initMappingSqlQueries() {
+ setAclsByObjectIdentity(new AclsByObjectIdentityMapping(getDataSource()));
+ }
+
+ //~ Inner Classes ==========================================================
+
+ /**
+ * Query object to look up ACL entries.
+ *
+ * + * 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 + *
+ */ + protected class AclsByObjectIdentityMapping extends MappingSqlQuery { + protected AclsByObjectIdentityMapping(DataSource ds) { + super(ds, aclsByObjectIdentityQuery); + 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); + + // Try to create the indicated BasicAclEntry class + BasicAclEntry entry; + + try { + Class aclClazz = this.getClass().getClassLoader().loadClass(aclClass); + entry = (BasicAclEntry) aclClazz.newInstance(); + } catch (ClassNotFoundException cnf) { + logger.error(cnf); + + return null; + } catch (InstantiationException ie) { + logger.error(ie); + + return null; + } catch (IllegalAccessException iae) { + logger.error(iae); + + return null; + } + + // 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; + } + + private AclObjectIdentity buildIdentity(String identity) { + if (identity == null) { + // Must be an empty parent, so return null + return null; + } + + int delim = identity.lastIndexOf(":"); + String classname = identity.substring(0, delim); + String id = identity.substring(delim + 1); + + return new NamedEntityObjectIdentity(classname, id); + } + } +} diff --git a/core/src/main/java/org/acegisecurity/acl/basic/jdbc/package.html b/core/src/main/java/org/acegisecurity/acl/basic/jdbc/package.html new file mode 100644 index 0000000000..e98cd09475 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acl/basic/jdbc/package.html @@ -0,0 +1,5 @@ + + +JDBC-based data access object for ACL information. + + diff --git a/core/src/main/java/org/acegisecurity/acl/basic/package.html b/core/src/main/java/org/acegisecurity/acl/basic/package.html new file mode 100644 index 0000000000..239e2530e2 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acl/basic/package.html @@ -0,0 +1,5 @@ + + +Access control list implementation based on integer bit masks. + + diff --git a/core/src/main/java/org/acegisecurity/acl/package.html b/core/src/main/java/org/acegisecurity/acl/package.html new file mode 100644 index 0000000000..782b8df14f --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acl/package.html @@ -0,0 +1,15 @@ + + +Enables retrieval of access control lists (ACLs) for domain object instances. + +The goal of this package is to locate the AclEntrys
+that apply to a given domain object instance.
+
+An AclManager has ultimate resposibility for obtaining the
+AclEntrys instances, with a provider-based implementation
+available via the AclProviderManager class (and
+its AclProvider interface.
Objects of type String
+ *
+ * @param domainInstance DOCUMENT ME!
+ *
+ * @return DOCUMENT ME!
+ */
+ public boolean supports(Object domainInstance) {
+ return (domainInstance instanceof String);
+ }
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/BasicAclProviderTests.java b/core/src/test/java/org/acegisecurity/acl/basic/BasicAclProviderTests.java
new file mode 100644
index 0000000000..6683399345
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/BasicAclProviderTests.java
@@ -0,0 +1,365 @@
+/* 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 junit.framework.TestCase;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.PopulatedDatabase;
+import net.sf.acegisecurity.acl.AclEntry;
+import net.sf.acegisecurity.acl.basic.cache.BasicAclEntryHolder;
+import net.sf.acegisecurity.acl.basic.cache.NullAclEntryCache;
+import net.sf.acegisecurity.acl.basic.jdbc.JdbcDaoImpl;
+import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Tests {@link BasicAclProvider}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class BasicAclProviderTests extends TestCase {
+ //~ Static fields/initializers =============================================
+
+ public static final String OBJECT_IDENTITY = "net.sf.acegisecurity.acl.DomainObject";
+
+ //~ Constructors ===========================================================
+
+ public BasicAclProviderTests() {
+ super();
+ }
+
+ public BasicAclProviderTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(BasicAclProviderTests.class);
+ }
+
+ public void testCachingUsedProperly() throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ MockCache cache = new MockCache();
+ provider.setBasicAclEntryCache(cache);
+
+ assertEquals(0, cache.getGets());
+ assertEquals(0, cache.getGetsHits());
+ assertEquals(0, cache.getPuts());
+ assertEquals(0, cache.getBackingMap().size());
+
+ Object object = new MockDomain(1); // has no parents
+ provider.getAcls(object);
+
+ assertEquals(1, cache.getGets());
+ assertEquals(0, cache.getGetsHits());
+ assertEquals(1, cache.getPuts());
+ assertEquals(1, cache.getBackingMap().size());
+
+ provider.getAcls(object);
+
+ assertEquals(2, cache.getGets());
+ assertEquals(1, cache.getGetsHits());
+ assertEquals(1, cache.getPuts());
+ assertEquals(1, cache.getBackingMap().size());
+
+ object = new MockDomain(1000); // does not exist
+
+ provider.getAcls(object);
+
+ assertEquals(3, cache.getGets());
+ assertEquals(1, cache.getGetsHits());
+ assertEquals(2, cache.getPuts());
+ assertEquals(2, cache.getBackingMap().size());
+
+ provider.getAcls(object);
+
+ assertEquals(4, cache.getGets());
+ assertEquals(2, cache.getGetsHits());
+ assertEquals(2, cache.getPuts());
+ assertEquals(2, cache.getBackingMap().size());
+
+ provider.getAcls(object);
+
+ assertEquals(5, cache.getGets());
+ assertEquals(3, cache.getGetsHits());
+ assertEquals(2, cache.getPuts());
+ assertEquals(2, cache.getBackingMap().size());
+ }
+
+ public void testExceptionThrownIfUnsupportedObjectIsSubmitted()
+ throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ // this one should NOT be supported, as it has no getId() method
+ assertFalse(provider.supports(new Integer(34)));
+
+ // try anyway
+ try {
+ provider.getAcls(new Integer(34));
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testGetAclsForInstanceNotFound() throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ Object object = new MockDomain(546464646);
+ AclEntry[] acls = provider.getAcls(object);
+ assertNull(acls);
+ }
+
+ public void testGetAclsForInstanceWithParentLevels()
+ throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ Object object = new MockDomain(6);
+ AclEntry[] acls = provider.getAcls(object);
+ assertEquals(2, acls.length);
+
+ assertEquals("scott", ((BasicAclEntry) acls[0]).getRecipient());
+ assertEquals("ROLE_SUPERVISOR", ((BasicAclEntry) acls[1]).getRecipient());
+ }
+
+ public void testGetAclsForInstanceWithoutParent() throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ Object object = new MockDomain(7);
+ AclEntry[] acls = provider.getAcls(object);
+ assertEquals(1, acls.length);
+ }
+
+ public void testGetAclsWithAuthentication() throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ Authentication scott = new UsernamePasswordAuthenticationToken("scott",
+ "unused");
+
+ Object object = new MockDomain(6);
+ AclEntry[] acls = provider.getAcls(object, scott);
+
+ assertEquals(1, acls.length);
+ assertEquals("scott", ((BasicAclEntry) acls[0]).getRecipient());
+ }
+
+ public void testGettersSetters() {
+ BasicAclProvider provider = new BasicAclProvider();
+ assertEquals(NullAclEntryCache.class,
+ provider.getBasicAclEntryCache().getClass());
+ assertEquals(NamedEntityObjectIdentity.class,
+ provider.getDefaultAclObjectIdentityClass());
+ assertEquals(GrantedAuthorityEffectiveAclsResolver.class,
+ provider.getEffectiveAclsResolver().getClass());
+
+ provider.setBasicAclEntryCache(null);
+ assertNull(provider.getBasicAclEntryCache());
+
+ provider.setDefaultAclObjectIdentityClass(null);
+ assertNull(provider.getDefaultAclObjectIdentityClass());
+
+ provider.setEffectiveAclsResolver(null);
+ assertNull(provider.getEffectiveAclsResolver());
+
+ provider.setBasicAclDao(new MockDao());
+ assertNotNull(provider.getBasicAclDao());
+ }
+
+ public void testStartupFailsIfNullAclDao() throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+
+ try {
+ provider.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testStartupFailsIfNullEffectiveAclsResolver()
+ throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ provider.setEffectiveAclsResolver(null);
+
+ try {
+ provider.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testStartupFailsIfNullEntryCache() throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ provider.setBasicAclEntryCache(null);
+
+ try {
+ provider.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testStartupFailsIfProblemWithAclObjectIdentityClass()
+ throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ // check nulls rejected
+ provider.setDefaultAclObjectIdentityClass(null);
+
+ try {
+ provider.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+
+ // check non-AclObjectIdentity classes are also rejected
+ provider.setDefaultAclObjectIdentityClass(String.class);
+
+ try {
+ provider.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+
+ // check AclObjectIdentity class without constructor accepting a
+ // domain object is also rejected
+ provider.setDefaultAclObjectIdentityClass(MockAclObjectIdentity.class);
+
+ try {
+ provider.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertEquals("defaultAclObjectIdentityClass must provide a constructor that accepts the domain object instance!",
+ expected.getMessage());
+ }
+ }
+
+ public void testSupports() throws Exception {
+ BasicAclProvider provider = new BasicAclProvider();
+ provider.setBasicAclDao(makePopulatedJdbcDao());
+
+ // this one should NOT be supported, as it has no getId() method
+ assertFalse(provider.supports(new Integer(34)));
+
+ // this one SHOULD be supported, as it has a getId() method
+ assertTrue(provider.supports(new SomeDomain()));
+
+ // this one SHOULD be supported, as it implements AclObjectIdentityAware
+ assertTrue(provider.supports(new MockDomain(4)));
+ }
+
+ private JdbcDaoImpl makePopulatedJdbcDao() throws Exception {
+ JdbcDaoImpl dao = new JdbcDaoImpl();
+ dao.setDataSource(PopulatedDatabase.getDataSource());
+ dao.afterPropertiesSet();
+
+ return dao;
+ }
+
+ //~ Inner Classes ==========================================================
+
+ private class MockCache implements BasicAclEntryCache {
+ private Map map = new HashMap();
+ private int gets = 0;
+ private int getsHits = 0;
+ private int puts = 0;
+
+ public Map getBackingMap() {
+ return map;
+ }
+
+ public BasicAclEntry[] getEntriesFromCache(
+ AclObjectIdentity aclObjectIdentity) {
+ gets++;
+
+ Object result = map.get(aclObjectIdentity);
+
+ if (result == null) {
+ return null;
+ }
+
+ getsHits++;
+
+ BasicAclEntryHolder holder = (BasicAclEntryHolder) result;
+
+ return holder.getBasicAclEntries();
+ }
+
+ public int getGets() {
+ return gets;
+ }
+
+ public int getGetsHits() {
+ return getsHits;
+ }
+
+ public int getPuts() {
+ return puts;
+ }
+
+ public void putEntriesInCache(BasicAclEntry[] basicAclEntry) {
+ puts++;
+
+ BasicAclEntryHolder holder = new BasicAclEntryHolder(basicAclEntry);
+ map.put(basicAclEntry[0].getAclObjectIdentity(), holder);
+ }
+ }
+
+ private class MockDao implements BasicAclDao {
+ public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity) {
+ return null;
+ }
+ }
+
+ private class MockDomain implements AclObjectIdentityAware {
+ private int id;
+
+ public MockDomain(int id) {
+ this.id = id;
+ }
+
+ public AclObjectIdentity getAclObjectIdentity() {
+ return new NamedEntityObjectIdentity(OBJECT_IDENTITY,
+ new Integer(id).toString());
+ }
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/GrantedAuthorityEffectiveAclsResolverTests.java b/core/src/test/java/org/acegisecurity/acl/basic/GrantedAuthorityEffectiveAclsResolverTests.java
new file mode 100644
index 0000000000..1d2a9631df
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/GrantedAuthorityEffectiveAclsResolverTests.java
@@ -0,0 +1,118 @@
+/* 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 junit.framework.TestCase;
+
+import net.sf.acegisecurity.GrantedAuthority;
+import net.sf.acegisecurity.GrantedAuthorityImpl;
+import net.sf.acegisecurity.acl.AclEntry;
+import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
+
+/**
+ * Tests {@link GrantedAuthorityEffectiveAclsResolver}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class GrantedAuthorityEffectiveAclsResolverTests extends TestCase {
+ //~ Instance fields ========================================================
+
+ private SimpleAclEntry entry100RoleEverybody = new SimpleAclEntry("ROLE_EVERYBODY",
+ new NamedEntityObjectIdentity("OBJECT", "100"), null, 14);
+ private SimpleAclEntry entry100RoleOne = new SimpleAclEntry("ROLE_ONE",
+ new NamedEntityObjectIdentity("OBJECT", "100"), null, 0);
+ private SimpleAclEntry entry100RoleTwo = new SimpleAclEntry("ROLE_TWO",
+ new NamedEntityObjectIdentity("OBJECT", "100"), null, 2);
+ private UsernamePasswordAuthenticationToken scott = new UsernamePasswordAuthenticationToken("scott",
+ "not used",
+ new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_EVERYBODY"), new GrantedAuthorityImpl(
+ "ROLE_TWO")});
+ private SimpleAclEntry entry100Scott = new SimpleAclEntry(scott
+ .getPrincipal(), new NamedEntityObjectIdentity("OBJECT", "100"),
+ null, 4);
+ private UsernamePasswordAuthenticationToken dianne = new UsernamePasswordAuthenticationToken("dianne",
+ "not used");
+ private UsernamePasswordAuthenticationToken marissa = new UsernamePasswordAuthenticationToken("marissa",
+ "not used",
+ new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_EVERYBODY"), new GrantedAuthorityImpl("ROLE_ONE")});
+ private SimpleAclEntry entry100Marissa = new SimpleAclEntry(marissa
+ .getPrincipal(), new NamedEntityObjectIdentity("OBJECT", "100"),
+ null, 2);
+
+ // convenience group
+ private SimpleAclEntry[] acls = {entry100Marissa, entry100Scott, entry100RoleEverybody, entry100RoleOne, entry100RoleTwo};
+
+ //~ Constructors ===========================================================
+
+ public GrantedAuthorityEffectiveAclsResolverTests() {
+ super();
+ }
+
+ public GrantedAuthorityEffectiveAclsResolverTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(GrantedAuthorityEffectiveAclsResolverTests.class);
+ }
+
+ public void testResolveAclsForDianneWhoHasANullForAuthorities() {
+ GrantedAuthorityEffectiveAclsResolver resolver = new GrantedAuthorityEffectiveAclsResolver();
+ assertNull(resolver.resolveEffectiveAcls(acls, dianne));
+ }
+
+ public void testResolveAclsForMarissa() {
+ GrantedAuthorityEffectiveAclsResolver resolver = new GrantedAuthorityEffectiveAclsResolver();
+ assertEquals(3, resolver.resolveEffectiveAcls(acls, marissa).length);
+ assertEquals(entry100Marissa,
+ resolver.resolveEffectiveAcls(acls, marissa)[0]);
+ assertEquals(entry100RoleEverybody,
+ resolver.resolveEffectiveAcls(acls, marissa)[1]);
+ assertEquals(entry100RoleOne,
+ resolver.resolveEffectiveAcls(acls, marissa)[2]);
+ }
+
+ public void testResolveAclsForScott() {
+ GrantedAuthorityEffectiveAclsResolver resolver = new GrantedAuthorityEffectiveAclsResolver();
+ assertEquals(3, resolver.resolveEffectiveAcls(acls, scott).length);
+ assertEquals(entry100Scott,
+ resolver.resolveEffectiveAcls(acls, scott)[0]);
+ assertEquals(entry100RoleEverybody,
+ resolver.resolveEffectiveAcls(acls, scott)[1]);
+ assertEquals(entry100RoleTwo,
+ resolver.resolveEffectiveAcls(acls, scott)[2]);
+ }
+
+ public void testSkipsNonBasicAclEntryObjects() {
+ GrantedAuthorityEffectiveAclsResolver resolver = new GrantedAuthorityEffectiveAclsResolver();
+ AclEntry[] basicAcls = {entry100Marissa, entry100Scott, entry100RoleEverybody, entry100RoleOne, new MockAcl(), entry100RoleTwo};
+ assertEquals(3, resolver.resolveEffectiveAcls(basicAcls, marissa).length);
+ }
+
+ //~ Inner Classes ==========================================================
+
+ private class MockAcl implements AclEntry {
+ // does nothing
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/MockAclObjectIdentity.java b/core/src/test/java/org/acegisecurity/acl/basic/MockAclObjectIdentity.java
new file mode 100644
index 0000000000..3e57c705a6
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/MockAclObjectIdentity.java
@@ -0,0 +1,28 @@
+/* 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;
+
+/**
+ * Implements AclObjectIdentity but is incompatible with
+ * BasicAclProvider because it cannot be constructed by passing
+ * in a domain object instance.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class MockAclObjectIdentity implements AclObjectIdentity {
+ // has no "public MockAclObjectIdentity(Object object)" constructor!
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/NamedEntityObjectIdentityTests.java b/core/src/test/java/org/acegisecurity/acl/basic/NamedEntityObjectIdentityTests.java
new file mode 100644
index 0000000000..0d99e4c8e0
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/NamedEntityObjectIdentityTests.java
@@ -0,0 +1,135 @@
+/* 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 junit.framework.TestCase;
+
+
+/**
+ * Tests {@link NamedEntityObjectIdentity}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class NamedEntityObjectIdentityTests extends TestCase {
+ //~ Constructors ===========================================================
+
+ public NamedEntityObjectIdentityTests() {
+ super();
+ }
+
+ public NamedEntityObjectIdentityTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(NamedEntityObjectIdentityTests.class);
+ }
+
+ public void testConstructionViaReflection() throws Exception {
+ SomeDomain domainObject = new SomeDomain();
+ domainObject.setId(34);
+
+ NamedEntityObjectIdentity name = new NamedEntityObjectIdentity(domainObject);
+ assertEquals("34", name.getId());
+ assertEquals(domainObject.getClass().getName(), name.getClassname());
+ name.toString();
+ }
+
+ public void testConstructionViaReflectionFailsIfNoGetIdMethod()
+ throws Exception {
+ try {
+ new NamedEntityObjectIdentity(new Integer(45));
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testConstructionViaReflectionFailsIfNullPassed()
+ throws Exception {
+ try {
+ new NamedEntityObjectIdentity(null);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testDefaultConstructorRejected() {
+ try {
+ new NamedEntityObjectIdentity();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testEquality() {
+ NamedEntityObjectIdentity original = new NamedEntityObjectIdentity("foo",
+ "12");
+ assertFalse(original.equals(null));
+ assertFalse(original.equals(new Integer(354)));
+ assertFalse(original.equals(
+ new NamedEntityObjectIdentity("foo", "23232")));
+ assertTrue(original.equals(new NamedEntityObjectIdentity("foo", "12")));
+ assertTrue(original.equals(original));
+ }
+
+ public void testNormalConstructionRejectedIfInvalidArguments()
+ throws Exception {
+ try {
+ new NamedEntityObjectIdentity(null, "12");
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+
+ try {
+ new NamedEntityObjectIdentity("classname", null);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+
+ try {
+ new NamedEntityObjectIdentity("", "12");
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+
+ try {
+ new NamedEntityObjectIdentity("classname", "");
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testNormalOperation() {
+ NamedEntityObjectIdentity name = new NamedEntityObjectIdentity("domain",
+ "id");
+ assertEquals("domain", name.getClassname());
+ assertEquals("id", name.getId());
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/SimpleAclEntryTests.java b/core/src/test/java/org/acegisecurity/acl/basic/SimpleAclEntryTests.java
new file mode 100644
index 0000000000..3e9b01e8e6
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/SimpleAclEntryTests.java
@@ -0,0 +1,183 @@
+/* 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 junit.framework.TestCase;
+
+
+/**
+ * Tests {@link SimpleAclEntry}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SimpleAclEntryTests extends TestCase {
+ //~ Constructors ===========================================================
+
+ public SimpleAclEntryTests() {
+ super();
+ }
+
+ public SimpleAclEntryTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(SimpleAclEntryTests.class);
+ }
+
+ public void testCorrectOperation() {
+ String recipient = "marissa";
+ AclObjectIdentity objectIdentity = new NamedEntityObjectIdentity("domain",
+ "12");
+ SimpleAclEntry acl = new SimpleAclEntry(recipient, objectIdentity,
+ null, 0);
+
+ assertFalse(acl.isPermitted(SimpleAclEntry.ADMINISTRATION));
+ acl.addPermission(SimpleAclEntry.ADMINISTRATION);
+ assertTrue(acl.isPermitted(SimpleAclEntry.ADMINISTRATION));
+ assertFalse(acl.isPermitted(SimpleAclEntry.CREATE));
+ assertFalse(acl.isPermitted(SimpleAclEntry.DELETE));
+ assertFalse(acl.isPermitted(SimpleAclEntry.READ));
+ assertFalse(acl.isPermitted(SimpleAclEntry.WRITE));
+ assertEquals("A----", acl.printPermissionsBlock());
+ acl.deletePermission(SimpleAclEntry.ADMINISTRATION);
+ assertFalse(acl.isPermitted(SimpleAclEntry.ADMINISTRATION));
+ assertEquals("-----", acl.printPermissionsBlock());
+
+ acl.addPermissions(new int[] {SimpleAclEntry.READ, SimpleAclEntry.WRITE});
+ acl.addPermission(SimpleAclEntry.CREATE);
+ assertFalse(acl.isPermitted(SimpleAclEntry.ADMINISTRATION));
+ assertTrue(acl.isPermitted(SimpleAclEntry.CREATE));
+ assertFalse(acl.isPermitted(SimpleAclEntry.DELETE));
+ assertTrue(acl.isPermitted(SimpleAclEntry.READ));
+ assertTrue(acl.isPermitted(SimpleAclEntry.WRITE));
+ assertEquals("-RWC-", acl.printPermissionsBlock());
+
+ acl.deletePermission(SimpleAclEntry.CREATE);
+ acl.deletePermissions(new int[] {SimpleAclEntry.READ, SimpleAclEntry.WRITE});
+ assertEquals("-----", acl.printPermissionsBlock());
+
+ acl.togglePermission(SimpleAclEntry.CREATE);
+ assertTrue(acl.isPermitted(SimpleAclEntry.CREATE));
+ assertFalse(acl.isPermitted(SimpleAclEntry.ADMINISTRATION));
+ acl.togglePermission(SimpleAclEntry.CREATE);
+ assertFalse(acl.isPermitted(SimpleAclEntry.CREATE));
+ }
+
+ public void testDetectsNullOnMainConstructor() {
+ String recipient = "marissa";
+ AclObjectIdentity objectIdentity = new NamedEntityObjectIdentity("domain",
+ "12");
+
+ try {
+ new SimpleAclEntry(recipient, null, null, 2);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+
+ try {
+ new SimpleAclEntry(null, objectIdentity, null, 2);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testGettersSetters() {
+ SimpleAclEntry acl = new SimpleAclEntry();
+
+ AclObjectIdentity objectIdentity = new NamedEntityObjectIdentity("domain",
+ "693");
+ acl.setAclObjectIdentity(objectIdentity);
+ assertEquals(objectIdentity, acl.getAclObjectIdentity());
+
+ AclObjectIdentity parentObjectIdentity = new NamedEntityObjectIdentity("domain",
+ "13");
+ acl.setAclObjectParentIdentity(parentObjectIdentity);
+ assertEquals(parentObjectIdentity, acl.getAclObjectParentIdentity());
+
+ acl.setMask(2);
+ assertEquals(2, acl.getMask());
+
+ acl.setRecipient("scott");
+ assertEquals("scott", acl.getRecipient());
+ }
+
+ public void testRejectsInvalidMasksInAddMethod() {
+ String recipient = "marissa";
+ AclObjectIdentity objectIdentity = new NamedEntityObjectIdentity("domain",
+ "12");
+ SimpleAclEntry acl = new SimpleAclEntry(recipient, objectIdentity,
+ null, 4);
+
+ try {
+ acl.addPermission(Integer.MAX_VALUE);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testRejectsInvalidMasksInDeleteMethod() {
+ String recipient = "marissa";
+ AclObjectIdentity objectIdentity = new NamedEntityObjectIdentity("domain",
+ "12");
+ SimpleAclEntry acl = new SimpleAclEntry(recipient, objectIdentity,
+ null, 0);
+ acl.addPermissions(new int[] {SimpleAclEntry.READ, SimpleAclEntry.WRITE, SimpleAclEntry.CREATE});
+
+ try {
+ acl.deletePermission(SimpleAclEntry.READ); // can't write if we can't read
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testRejectsInvalidMasksInTogglePermissionMethod() {
+ String recipient = "marissa";
+ AclObjectIdentity objectIdentity = new NamedEntityObjectIdentity("domain",
+ "12");
+ SimpleAclEntry acl = new SimpleAclEntry(recipient, objectIdentity,
+ null, 0);
+ acl.addPermissions(new int[] {SimpleAclEntry.READ, SimpleAclEntry.WRITE, SimpleAclEntry.CREATE});
+
+ try {
+ acl.togglePermission(SimpleAclEntry.READ); // can't write if we can't read
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testToString() {
+ String recipient = "marissa";
+ AclObjectIdentity objectIdentity = new NamedEntityObjectIdentity("domain",
+ "12");
+ SimpleAclEntry acl = new SimpleAclEntry(recipient, objectIdentity,
+ null, 0);
+ acl.addPermissions(new int[] {SimpleAclEntry.READ, SimpleAclEntry.WRITE, SimpleAclEntry.CREATE});
+ assertTrue(acl.toString().endsWith("marissa=-RWC- ............................111. (14)]"));
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/SomeDomain.java b/core/src/test/java/org/acegisecurity/acl/basic/SomeDomain.java
new file mode 100644
index 0000000000..36c56f1888
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/SomeDomain.java
@@ -0,0 +1,38 @@
+/* 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;
+
+/**
+ * Simple object to use when testing NamedEntityObjectIdentity.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SomeDomain {
+ //~ Instance fields ========================================================
+
+ private int id;
+
+ //~ Methods ================================================================
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/cache/BasicAclEntryHolderTests.java b/core/src/test/java/org/acegisecurity/acl/basic/cache/BasicAclEntryHolderTests.java
new file mode 100644
index 0000000000..e75a562a62
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/cache/BasicAclEntryHolderTests.java
@@ -0,0 +1,66 @@
+/* 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.cache;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.acl.basic.BasicAclEntry;
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+
+/**
+ * Tests {@link BasicAclEntryHolder}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class BasicAclEntryHolderTests extends TestCase {
+ //~ Constructors ===========================================================
+
+ public BasicAclEntryHolderTests() {
+ super();
+ }
+
+ public BasicAclEntryHolderTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(BasicAclEntryHolderTests.class);
+ }
+
+ public void testRejectsNull() throws Exception {
+ try {
+ new BasicAclEntryHolder(null);
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+
+ try {
+ new BasicAclEntryHolder(new BasicAclEntry[] {new SimpleAclEntry(), null, new SimpleAclEntry()});
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/cache/EhCacheBasedAclEntryCacheTests.java b/core/src/test/java/org/acegisecurity/acl/basic/cache/EhCacheBasedAclEntryCacheTests.java
new file mode 100644
index 0000000000..b0834549af
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/cache/EhCacheBasedAclEntryCacheTests.java
@@ -0,0 +1,95 @@
+/* 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.cache;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.acl.basic.AclObjectIdentity;
+import net.sf.acegisecurity.acl.basic.BasicAclEntry;
+import net.sf.acegisecurity.acl.basic.NamedEntityObjectIdentity;
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+
+/**
+ * Tests {@link EhCacheBasedAclEntryCache}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class EhCacheBasedAclEntryCacheTests extends TestCase {
+ //~ Static fields/initializers =============================================
+
+ private static final AclObjectIdentity OBJECT_100 = new NamedEntityObjectIdentity("OBJECT",
+ "100");
+ private static final AclObjectIdentity OBJECT_200 = new NamedEntityObjectIdentity("OBJECT",
+ "200");
+ private static final BasicAclEntry OBJECT_100_MARISSA = new SimpleAclEntry("marissa",
+ OBJECT_100, null, 2);
+ private static final BasicAclEntry OBJECT_100_SCOTT = new SimpleAclEntry("scott",
+ OBJECT_100, null, 4);
+ private static final BasicAclEntry OBJECT_200_PETER = new SimpleAclEntry("peter",
+ OBJECT_200, null, 4);
+
+ //~ Constructors ===========================================================
+
+ public EhCacheBasedAclEntryCacheTests() {
+ super();
+ }
+
+ public EhCacheBasedAclEntryCacheTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(EhCacheBasedAclEntryCacheTests.class);
+ }
+
+ public void testCacheOperation() throws Exception {
+ EhCacheBasedAclEntryCache cache = new EhCacheBasedAclEntryCache();
+ cache.afterPropertiesSet();
+
+ // execute a second time to test detection of existing instance
+ cache.afterPropertiesSet();
+
+ cache.putEntriesInCache(new BasicAclEntry[] {OBJECT_100_SCOTT, OBJECT_100_MARISSA});
+ cache.putEntriesInCache(new BasicAclEntry[] {OBJECT_200_PETER});
+
+ // Check we can get them from cache again
+ assertEquals(OBJECT_100_SCOTT,
+ cache.getEntriesFromCache(
+ new NamedEntityObjectIdentity("OBJECT", "100"))[0]);
+ assertEquals(OBJECT_100_MARISSA,
+ cache.getEntriesFromCache(
+ new NamedEntityObjectIdentity("OBJECT", "100"))[1]);
+ assertEquals(OBJECT_200_PETER,
+ cache.getEntriesFromCache(
+ new NamedEntityObjectIdentity("OBJECT", "200"))[0]);
+
+ cache.destroy();
+ }
+
+ public void testGettersSetters() {
+ EhCacheBasedAclEntryCache cache = new EhCacheBasedAclEntryCache();
+ cache.setMinutesToIdle(15);
+ assertEquals(15, cache.getMinutesToIdle());
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/cache/NullAclEntryCacheTests.java b/core/src/test/java/org/acegisecurity/acl/basic/cache/NullAclEntryCacheTests.java
new file mode 100644
index 0000000000..8713d90e52
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/cache/NullAclEntryCacheTests.java
@@ -0,0 +1,58 @@
+/* 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.cache;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.acl.basic.BasicAclEntry;
+import net.sf.acegisecurity.acl.basic.NamedEntityObjectIdentity;
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+
+/**
+ * Tests {@link NullAclEntryCache}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class NullAclEntryCacheTests extends TestCase {
+ //~ Constructors ===========================================================
+
+ public NullAclEntryCacheTests() {
+ super();
+ }
+
+ public NullAclEntryCacheTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(NullAclEntryCacheTests.class);
+ }
+
+ public void testCacheOperation() throws Exception {
+ NullAclEntryCache cache = new NullAclEntryCache();
+ cache.putEntriesInCache(new BasicAclEntry[] {new SimpleAclEntry()});
+ cache.getEntriesFromCache(new NamedEntityObjectIdentity("not_used",
+ "not_used"));
+ }
+}
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
new file mode 100644
index 0000000000..f9fee5ed20
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java
@@ -0,0 +1,121 @@
+/* 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 junit.framework.TestCase;
+
+import net.sf.acegisecurity.PopulatedDatabase;
+import net.sf.acegisecurity.acl.basic.AclObjectIdentity;
+import net.sf.acegisecurity.acl.basic.BasicAclEntry;
+import net.sf.acegisecurity.acl.basic.NamedEntityObjectIdentity;
+
+import org.springframework.jdbc.object.MappingSqlQuery;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+/**
+ * Tests {@link JdbcDaoImpl}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class JdbcDaoImplTests extends TestCase {
+ //~ Static fields/initializers =============================================
+
+ public static final String OBJECT_IDENTITY = "net.sf.acegisecurity.acl.DomainObject";
+
+ //~ Constructors ===========================================================
+
+ public JdbcDaoImplTests() {
+ super();
+ }
+
+ public JdbcDaoImplTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(JdbcDaoImplTests.class);
+ }
+
+ public void testGetsAclsWhichExistInDatabase() throws Exception {
+ JdbcDaoImpl dao = makePopulatedJdbcDao();
+ AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY,
+ "2");
+ BasicAclEntry[] acls = dao.getAcls(identity);
+ assertEquals(2, acls.length);
+ }
+
+ public void testGettersSetters() throws Exception {
+ JdbcDaoImpl dao = makePopulatedJdbcDao();
+ dao.setAclsByObjectIdentity(new MockMappingSqlQuery());
+ assertNotNull(dao.getAclsByObjectIdentity());
+
+ 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);
+ }
+
+ public void testNullReturnedIfEntityNotFound() throws Exception {
+ JdbcDaoImpl dao = makePopulatedJdbcDao();
+ AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY,
+ "NOT_VALID_ID");
+ BasicAclEntry[] result = dao.getAcls(identity);
+ assertNull(result);
+ }
+
+ public void testRejectsNonNamedEntityObjectIdentity()
+ throws Exception {
+ JdbcDaoImpl dao = new JdbcDaoImpl();
+ AclObjectIdentity identity = new AclObjectIdentity() {}
+ ;
+
+ assertNull(dao.getAcls(identity));
+ }
+
+ private JdbcDaoImpl makePopulatedJdbcDao() throws Exception {
+ JdbcDaoImpl dao = new JdbcDaoImpl();
+ dao.setDataSource(PopulatedDatabase.getDataSource());
+ dao.afterPropertiesSet();
+
+ return dao;
+ }
+
+ //~ Inner Classes ==========================================================
+
+ private class MockMappingSqlQuery extends MappingSqlQuery {
+ protected Object mapRow(ResultSet arg0, int arg1)
+ throws SQLException {
+ return null;
+ }
+ }
+}
diff --git a/docs/reference/src/index.xml b/docs/reference/src/index.xml
index 80c2e9577d..d41591cb74 100644
--- a/docs/reference/src/index.xml
+++ b/docs/reference/src/index.xml
@@ -69,7 +69,7 @@