Initial commit of ACL capabilities.

This commit is contained in:
Ben Alex 2004-07-29 07:51:22 +00:00
parent b749e83b1d
commit 56829872b6
35 changed files with 4084 additions and 3 deletions

View File

@ -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 {}

View File

@ -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 <code>AclEntry</code> 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 <code>null</code>)
*
* @return the ACLs that apply, or <code>null</code> 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
* <code>Authentication</code> object
*
* @param domainInstance the instance for which ACL information is required
* (never <code>null</code>)
* @param authentication the prncipal for which ACL information should be
* filtered (never <code>null</code>)
*
* @return only those ACLs applying to the domain instance that have been
* granted to the principal (or <code>null</code>) if no such ACLs
* are found
*/
public AclEntry[] getAcls(Object domainInstance,
Authentication authentication);
}

View File

@ -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.
*
* <P>
* Implementations are typically called from the {@link AclProviderManager}.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface AclProvider {
//~ Methods ================================================================
/**
* Obtains the ACLs that apply to the specified domain instance.
*
* <P>
* Will never be called unless the {@link #supports(Object)} method
* returned <code>true</code>.
* </p>
*
* @param domainInstance the instance for which ACL information is required
* (never <code>null</code>)
*
* @return the ACLs that apply, or <code>null</code> 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 <code>Authentication</code> object.
*
* <P>
* Will never be called unless the {@link #supports(Object)} method
* returned <code>true</code>.
* </p>
*
* @param domainInstance the instance for which ACL information is required
* (never <code>null</code>)
* @param authentication the prncipal for which ACL information should be
* filtered (never <code>null</code>)
*
* @return only those ACLs applying to the domain instance that have been
* granted to the principal (or <code>null</code>) if no such ACLs
* are found
*/
public AclEntry[] getAcls(Object domainInstance,
Authentication authentication);
/**
* Indicates whether this <code>AclProvider</code> can authoritatively
* return ACL information for the specified domain object instance.
*
* @param domainInstance the instance for which ACL information is required
* (never <code>null</code>)
*
* @return <code>true</code> if this provider is authoritative for the
* specified domain object instance, <code>false</code> otherwise
*/
public boolean supports(Object domainInstance);
}

View File

@ -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.
*
* <P>
* If no compatible provider is found, it is assumed that no ACLs apply for the
* specified domain object instance and <code>null</code> is returned.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class AclProviderManager implements AclManager, InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(AclProviderManager.class);
//~ Instance fields ========================================================
private List providers;
//~ Methods ================================================================
public AclEntry[] getAcls(Object domainInstance) {
if (domainInstance == null) {
throw new IllegalArgumentException(
"domainInstance is null - violating interface contract");
}
Iterator iter = providers.iterator();
while (iter.hasNext()) {
AclProvider provider = (AclProvider) iter.next();
if (provider.supports(domainInstance)) {
if (logger.isDebugEnabled()) {
logger.debug("ACL lookup using "
+ provider.getClass().getName());
}
return provider.getAcls(domainInstance);
}
}
if (logger.isDebugEnabled()) {
logger.debug("No AclProvider found for "
+ domainInstance.toString());
}
return null;
}
public AclEntry[] getAcls(Object domainInstance,
Authentication authentication) {
if (domainInstance == null) {
throw new IllegalArgumentException(
"domainInstance is null - violating interface contract");
}
if (authentication == null) {
throw new IllegalArgumentException(
"authentication is null - violating interface contract");
}
Iterator iter = providers.iterator();
while (iter.hasNext()) {
AclProvider provider = (AclProvider) iter.next();
if (provider.supports(domainInstance)) {
if (logger.isDebugEnabled()) {
logger.debug("ACL lookup using "
+ provider.getClass().getName());
}
return provider.getAcls(domainInstance, authentication);
}
}
if (logger.isDebugEnabled()) {
logger.debug("No AclProvider found for "
+ domainInstance.toString());
}
return null;
}
/**
* Sets the {@link AclProvider} objects to be used for ACL determinations.
*
* @param newList that should be used for ACL determinations
*
* @throws IllegalArgumentException if an invalid provider was included in
* the list
*/
public void setProviders(List newList) {
checkIfValidList(newList);
Iterator iter = newList.iterator();
while (iter.hasNext()) {
Object currentObject = null;
try {
currentObject = iter.next();
AclProvider attemptToCast = (AclProvider) currentObject;
} catch (ClassCastException cce) {
throw new IllegalArgumentException("AclProvider "
+ currentObject.getClass().getName()
+ " must implement AclProvider");
}
}
this.providers = newList;
}
public List getProviders() {
return this.providers;
}
public void afterPropertiesSet() throws Exception {
checkIfValidList(this.providers);
}
private void checkIfValidList(List listToCheck) {
if ((listToCheck == null) || (listToCheck.size() == 0)) {
throw new IllegalArgumentException(
"A list of AclManagers is required");
}
}
}

View File

@ -0,0 +1,279 @@
/* 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;
import java.util.Arrays;
/**
* Abstract implementation of {@link BasicAclEntry}.
*
* <P>
* Provides core bit mask handling methods.
* </p>
*
* @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
* a <code>public static final int</code>. It is further recommended that
* valid combinations of permissions are also exposed as <code>public
* static final int</code>s.
*
* <P>
* This method returns all permission integers that are allowed to be used
* together. <B>This must include any combinations of valid
* permissions</b>. 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.
* </p>
*
* <P>
* Whilst it may seem unduly time onerous to return every valid permission
* <B>combination</B>, 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.
* </p>
*
* @return <b>every</b> 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
* <code>AbstractBasicAclEntry</code>'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', '.');
}
}

View File

@ -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.
*
* <P>
* It should be noted that <code>AclObjectIdentity</code> instances are created
* in various locations throughout the package. As
* <code>AclObjectIdentity</code>s 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 <code>AclObjectIdentity</code>s equal if
* <code>identity1.equals(identity2)</code>, rather than reference-equality of
* <code>identity1==identity2</code>.
* </p>
*
* <P>
* In practical terms this means you must implement the standard
* <code>java.lang.Object</code> methods shown below. Depending on your
* cache's internal structure, you may also need to implement special
* interfaces such as <code>java.util.Comparator</code> or
* <code>java.lang.Comparable</code>.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface AclObjectIdentity extends Serializable {
//~ Methods ================================================================
/**
* Refer to the <code>java.lang.Object</code> documentation for the
* interface contract.
*
* @param obj to be compared
*
* @return <code>true</code> if the objects are equal, <code>false</code>
* otherwise
*/
public boolean equals(Object obj);
/**
* Refer to the <code>java.lang.Object</code> documentation for the
* interface contract.
*
* @return a hash code representation of this object
*/
public int hashCode();
}

View File

@ -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.
*
* <P>
* Domain objects must implement this interface if they wish to provide an
* <code>AclObjectIdentity</code> rather than it being determined by relying
* classes. Specifically, the {@link BasicAclProvider} detects and uses this
* interface.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface AclObjectIdentityAware {
//~ Methods ================================================================
/**
* Retrieves the <code>AclObjectIdentity</code> for this instance.
*
* @return the ACL object identity for this instance (can never be
* <code>null</code>)
*/
public AclObjectIdentity getAclObjectIdentity();
}

View File

@ -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.
*
* <P>
* <code>BasicAclDao</code> implementations are responsible for interpreting a
* given {@link AclObjectIdentity} and being able to lookup and return the
* corresponding {@link BasicAclEntry}[]s.
* </p>
*
* <P>
* <code>BasicAclDao</code>s many, but are not required to, allow the backend
* ACL repository to specify the class of <code>BasicAclEntry</code>
* implementations that should be returned.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface BasicAclDao {
//~ Methods ================================================================
/**
* Obtains the ACLs that apply to the specified domain instance.
*
* <P>
* Does <b>not</b> 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.
* </p>
*
* @param aclObjectIdentity the domain object instance that ACL information
* is being requested for (never <code>null</code>)
*
* @return the ACLs that apply (no <code>null</code>s are permitted in the
* array), or <code>null</code> if no ACLs could be found for the
* specified ACL object identity
*/
public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity);
}

View File

@ -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 <B>only</B> 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
* <code>BasicAclEntry</code>. 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
* <code>null</code>)
*/
public AclObjectIdentity getAclObjectIdentity();
/**
* This setter should <B>only</B> be used by DAO implementations.
*
* @param aclObjectParentIdentity an object which represents the parent of
* the domain object instance subject of this ACL entry, or
* <code>null</code> 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
* <code>BasicAclProvider</code> to walk the inheritance hierarchy. An
* domain object instance need <b>not</b> have a parent.
*
* @return the ACL object identity that is the parent of this ACL entry
* (may be <code>null</code> if no parent should be consulted)
*/
public AclObjectIdentity getAclObjectParentIdentity();
/**
* This setter should <B>only</B> 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 <B>only</B> be used by DAO implementations.
*
* @param recipient a representation of the recipient of this ACL entry
* that makes sense to an <code>EffectiveAclsResolver</code>
* implementation
*/
public void setRecipient(Object recipient);
/**
* A domain object instance will usually have multiple
* <code>BasicAclEntry</code>s. Each separate <code>BasicAclEntry</code>
* applies to a particular "recipient". Typical examples of recipients
* include (but do not necessarily have to include) usernames, role names,
* complex granted authorities etc.
*
* <P>
* <B>It is essential that only one <code>BasicAclEntry</code> exists for a
* given recipient</B>. Otherwise conflicts as to the mask that should
* apply to a given recipient will occur.
* </p>
*
* <P>
* This method indicates which recipient this <code>BasicAclEntry</code>
* applies to. The returned object type will vary depending on the type of
* recipient. For instance, it might be a <code>String</code> containing a
* username, or a <code>GrantedAuthorityImpl</code> 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 <code>Authentication</code> object.
* </p>
*
* @return the recipient of this access control list entry (never
* <code>null</code>)
*/
public Object getRecipient();
}

View File

@ -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.
*
* <P>
* 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
* <code>BasicAclEntryCache</code> interface contract because they vary
* depending on the type of caching system used (eg in-memory vs disk vs
* cluster vs hybrid).
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface BasicAclEntryCache {
//~ Methods ================================================================
/**
* Obtains an array of {@link BasicAclEntry}s from the cache.
*
* @param aclObjectIdentity which should be obtained from the cache
*
* @return any applicable <code>BasicAclEntry</code>s (no
* <code>null</code>s are permitted in the returned array) or
* <code>null</code> 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.
*
* <P>
* No <code>null</code>s are allowed in the passed array. If any
* <code>null</code> is passed, the implementation may throw an exception.
* </p>
*
* @param basicAclEntry the ACL entries to cache (the key will be extracted
* from the {@link BasicAclEntry#getAclObjectIdentity()} method
*/
public void putEntriesInCache(BasicAclEntry[] basicAclEntry);
}

View File

@ -0,0 +1,341 @@
/* 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;
import net.sf.acegisecurity.acl.AclProvider;
import net.sf.acegisecurity.acl.basic.cache.NullAclEntryCache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* <P>
* Retrieves access control lists (ACL) entries for domain object instances
* from a data access object (DAO).
* </p>
*
* <P>
* 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 <code>BasicAclProvider</code> responding to particular
* domain object instances.
* </p>
*
* <P>
* <code>BasicAclProvider</code> will walk an inheritance hierarchy if a
* <code>BasicAclEntry</code> returned by the DAO indicates it has a parent.
* NB: inheritance occurs at a <I>domain instance object</I> level. It does
* not occur at an ACL recipient level. This means
* <B>all</B><code>BasicAclEntry</code>s for a given domain instance object
* <B>must</B> have the <B>same</B> parent identity, or
* <B>all</B><code>BasicAclEntry</code>s must have <code>null</code> as their
* parent identity.
* </p>
*
* <P>
* A cache should be used. This is provided by the {@link BasicAclEntryCache}.
* <code>BasicAclProvider</code> by default is setup to use the {@link
* NullAclEntryCache}, which performs no caching.
* </p>
*
* <P>
* To implement the {@link #getAcls(Object, Authentication)} method,
* <code>BasicAclProvider</code> requires a {@link EffectiveAclsResolver} to
* be configured against it. By default the {@link
* GrantedAuthorityEffectiveAclsResolver} is used.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class BasicAclProvider implements AclProvider, InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(BasicAclProvider.class);
/**
* Marker added to the cache to indicate an AclObjectIdentity has no
* corresponding BasicAclEntry[]s
*/
private static String RECIPIENT_FOR_CACHE_EMPTY = "RESERVED_RECIPIENT_NOBODY";
//~ Instance fields ========================================================
/**
* Must be set to an appropriate data access object. Defaults to
* <code>null</code>.
*/
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 <code>AclObjectIdentity</code> class that an
* attempt should be made to construct if the passed object does not
* implement <code>AclObjectIdentityAware</code>.
*
* <P>
* NB: Any <code>defaultAclObjectIdentityClass</code><b>must</b> provide a
* public constructor that accepts an <code>Object</code>. Otherwise it is
* not possible for the <code>BasicAclProvider</code> to try to create the
* <code>AclObjectIdentity</code> instance at runtime.
* </p>
*
* @param defaultAclObjectIdentityClass
*/
public void setDefaultAclObjectIdentityClass(
Class defaultAclObjectIdentityClass) {
this.defaultAclObjectIdentityClass = defaultAclObjectIdentityClass;
}
public Class getDefaultAclObjectIdentityClass() {
return defaultAclObjectIdentityClass;
}
public void setEffectiveAclsResolver(
EffectiveAclsResolver effectiveAclsResolver) {
this.effectiveAclsResolver = effectiveAclsResolver;
}
public EffectiveAclsResolver getEffectiveAclsResolver() {
return effectiveAclsResolver;
}
public void afterPropertiesSet() {
if (basicAclDao == null) {
throw new IllegalArgumentException("basicAclDao required");
}
if (basicAclEntryCache == null) {
throw new IllegalArgumentException("basicAclEntryCache required");
}
if (effectiveAclsResolver == null) {
throw new IllegalArgumentException("effectiveAclsResolver required");
}
if ((defaultAclObjectIdentityClass == null)
|| (!AclObjectIdentity.class.isAssignableFrom(
this.defaultAclObjectIdentityClass))) {
throw new IllegalArgumentException(
"defaultAclObjectIdentityClass that implements AclObjectIdentity is required");
}
try {
Constructor constructor = defaultAclObjectIdentityClass
.getConstructor(new Class[] {Object.class});
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException(
"defaultAclObjectIdentityClass must provide a constructor that accepts the domain object instance!");
}
}
/**
* Indicates support for the passed object if it an
* <code>AclObjectIdentity</code> is returned by {@link
* #obtainIdentity(Object)}.
*
* @param domainInstance the instance to check
*
* @return <code>true</code> if this provider supports the passed object,
* <code>false</code> otherwise
*/
public boolean supports(Object domainInstance) {
if (obtainIdentity(domainInstance) == null) {
return false;
} else {
return true;
}
}
/**
* This method looks up the <code>AclObjectIdentity</code> of a passed
* domain object instance.
*
* <P>
* This implementation attempts to obtain the
* <code>AclObjectIdentity</code> 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.
* </p>
*
* @param domainInstance the domain object instance (never
* <code>null</code>)
*
* @return an ACL object identity, or <code>null</code> 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;
}
}

View File

@ -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
* <code>Authentication</code> object.
*
* <P>
* 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
* <code>Authentication</code>-presented principals and granted authorities.
* </p>
*
* <P>
* 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 <code>Authentication</code>
* object), all three permissions would be returned as distinct
* <code>BasicAclEntry</code> 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.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface EffectiveAclsResolver {
//~ Methods ================================================================
/**
* Determines the ACLs that apply to the presented
* <code>Authentication</code> object.
*
* @param allAcls every ACL assigned to a domain object instance
* @param filteredBy the principal (populated with
* <code>GrantedAuthority</code>s 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
* <code>null</code> if there are none after filtering
*/
public AclEntry[] resolveEffectiveAcls(AclEntry[] allAcls,
Authentication filteredBy);
}

View File

@ -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}.
*
* <P>
* This implementation does not need to understand the "recipient" types
* presented in a <code>BasicAclEntry</code> because it merely delegates to
* the detected {@link Authentication#getPrincipal()} or {@link
* Authentication#getAuthorities()}. The principal object or granted
* authorities object has its <code>Object.equals(recipient)</code> method
* called to make the decision as to whether the recipient in the
* <code>BasicAclEntry</code> is the same as the principal or granted
* authority.
* </p>
*
* <P>
* This class should prove an adequate ACLs resolver if you're using standard
* Acegi Security classes. This is because the typical
* <code>Authentication</code> token is
* <code>UsernamePasswordAuthenticationToken</code>, which for its
* <code>principal</code> is usually a <code>String</code>. The
* <code>GrantedAuthorityImpl</code> is typically used for granted
* authorities, which tests for equality based on a <code>String</code>. This
* means <code>BasicAclDao</code>s simply need to return a <code>String</code>
* to represent the recipient. If you use non-<code>String</code> objects, you
* will probably require an alternative <code>EffectiveAclsResolver</code>.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class GrantedAuthorityEffectiveAclsResolver
implements EffectiveAclsResolver {
//~ Methods ================================================================
public AclEntry[] resolveEffectiveAcls(AclEntry[] allAcls,
Authentication filteredBy) {
List list = new Vector();
for (int i = 0; i < allAcls.length; i++) {
if (!(allAcls[i] instanceof BasicAclEntry)) {
continue;
}
Object recipient = ((BasicAclEntry) allAcls[i]).getRecipient();
// Allow the Authentication's getPrincipal to decide whether
// the presented recipient is "equal" (allows BasicAclDaos to
// return Strings rather than proper objects in simple cases)
if (filteredBy.getPrincipal().equals(recipient)) {
list.add(allAcls[i]);
} else {
// No direct match against principal; try each authority.
// As with the principal, allow each of the Authentication's
// granted authorities to decide whether the presented
// recipient is "equal"
GrantedAuthority[] authorities = filteredBy.getAuthorities();
if ((authorities == null) || (authorities.length == 0)) {
continue;
}
for (int k = 0; k < authorities.length; k++) {
if (authorities[k].equals(recipient)) {
list.add(allAcls[i]);
}
}
}
}
// return null if appropriate (as per interface contract)
if (list.size() > 0) {
return (BasicAclEntry[]) list.toArray(new BasicAclEntry[] {});
} else {
return null;
}
}
}

View File

@ -0,0 +1,161 @@
/* 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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Simple implementation of {@link AclObjectIdentity}.
*
* <P>
* Uses <code>String</code>s to store the identity of the domain object
* instance. Also offers a constructor that uses reflection to build the
* identity information.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class NamedEntityObjectIdentity implements AclObjectIdentity {
//~ Instance fields ========================================================
private String classname;
private String id;
//~ Constructors ===========================================================
public NamedEntityObjectIdentity(String classname, String id) {
if ((classname == null) || "".equals(classname)) {
throw new IllegalArgumentException("classname required");
}
if ((id == null) || "".equals(id)) {
throw new IllegalArgumentException("id required");
}
this.classname = classname;
this.id = id;
}
/**
* Creates the <code>NamedEntityObjectIdentity</code> based on the passed
* object instance. The passed object must provide a <code>getId()</code>
* 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 <code>null</code>)
*/
public String getClassname() {
return classname;
}
/**
* Indicates the instance identity portion of the object identity.
*
* @return the instance identity (never <code>null</code>)
*/
public String getId() {
return id;
}
/**
* Important so caching operates properly.
*
* <P>
* Considers an object of the same class equal if it has the same
* <code>classname</code> and <code>id</code> properties.
* </p>
*
* @param arg0 object to compare
*
* @return <code>true</code> 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();
}
}

View File

@ -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 <code>newInstance()</code>.
*
* <P>
* Normal classes should <B>not</B> use this default constructor.
* </p>
*/
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();
}
}

View File

@ -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
* <code>BasicAclEntry</code>s in the cache.
*
* <P>
* This is necessary because caches store a single object per key, not an
* array.
* </p>
*
* <P>
* This class uses value object semantics. ie: construction-based
* initialisation without any setters for the properties.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class BasicAclEntryHolder implements Serializable {
//~ Instance fields ========================================================
private BasicAclEntry[] basicAclEntries;
//~ Constructors ===========================================================
/**
* Constructs the <code>BasicAclEntryHolder</code>.
*
* @param aclEntries to cache (any <code>null</code>s will cause an
* exception, which should not be a problem as the contract for
* <code>BasicAclEntryCache</code> allows exceptions if
* <code>null</code>s are presented)
*
* @throws IllegalArgumentException if a <code>null</code> exists anywhere
* in the <code>aclEntries</code> or if a <code>null</code> 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;
}
}

View File

@ -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 <code>BasicAclEntry</code>s using <A
* HREF="http://ehcache.sourceforge.net">EHCACHE</a>.
*
* @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.
*
* <P>
* Defaults to 5 minutes.
* </p>
*
* @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)) {
// dont 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);
}
}

View File

@ -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.
*
* <P>
* <B>Do not use in production settings</B>, as ACL queries are likely to be
* extensive.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class NullAclEntryCache implements BasicAclEntryCache {
//~ Methods ================================================================
/**
* As nothing ever stored in the cache, will always return
* <code>null</code>.
*
* @param aclObjectIdentity ignored
*
* @return always <code>null</code>
*/
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) {}
}

View File

@ -0,0 +1,5 @@
<html>
<body>
Caches ACL information for the <code>BasicAclProvider</code>.
</body>
</html>

View File

@ -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;
/**
* <p>
* Retrieves ACL details from a JDBC location.
* </p>
*
* <p>
* 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.
* </p>
*
* @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
* <code>AclObjectIdentity</code>.
*
* <P>
* The {@link BasicAclEntry}s returned by this method will have
* <code>String</code>-based recipients. This will not be a problem if you
* are using the <code>GrantedAuthorityEffectiveAclsResolver</code>, which
* is the default configured against <code>BasicAclProvider</code>.
* </p>
*
* <P>
* This method will only return ACLs for requests where the
* <code>AclObjectIdentity</code> is of type {@link
* NamedEntityObjectIdentity}. Of course, you can subclass or replace this
* class and support your own custom <code>AclObjectIdentity</code> types.
* </p>
*
* @param aclObjectIdentity for which ACL information is required (cannot
* be <code>null</code> and must be an instance of
* <code>NamedEntityObjectIdentity</code>)
*
* @return the ACLs that apply (without any <code>null</code>s inside the
* array), or <code>null</code> if not found or if an incompatible
* <code>AclObjectIdentity</code> 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.
*
* <P>
* 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
* </p>
*/
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);
}
}
}

View File

@ -0,0 +1,5 @@
<html>
<body>
JDBC-based data access object for ACL information.
</body>
</html>

View File

@ -0,0 +1,5 @@
<html>
<body>
Access control list implementation based on integer bit masks.
</body>
</html>

View File

@ -0,0 +1,15 @@
<html>
<body>
Enables retrieval of access control lists (ACLs) for domain object instances.
<P>The goal of this package is to locate the <code>AclEntry</code>s
that apply to a given domain object instance.
</P>
<P>
An <code>AclManager</code> has ultimate resposibility for obtaining the
<code>AclEntry</code>s instances, with a provider-based implementation
available via the <code>AclProviderManager</code> class (and
its <code>AclProvider</code> interface.</P>
</body>
</html>

View File

@ -0,0 +1,207 @@
/* 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 junit.framework.TestCase;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.acl.basic.NamedEntityObjectIdentity;
import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import java.util.List;
import java.util.Vector;
/**
* Tests {@link AclProviderManager}.
*
* @author Ben Alex
* @version $Id$
*/
public class AclProviderManagerTests extends TestCase {
//~ Constructors ===========================================================
public AclProviderManagerTests() {
super();
}
public AclProviderManagerTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(AclProviderManagerTests.class);
}
public void testAclLookupFails() {
AclProviderManager mgr = makeProviderManager();
assertNull(mgr.getAcls(new Integer(5)));
}
public void testAclLookupForGivenAuthenticationSuccess() {
AclProviderManager mgr = makeProviderManager();
assertNotNull(mgr.getAcls("STRING",
new UsernamePasswordAuthenticationToken("marissa", "not used")));
}
public void testAclLookupSuccess() {
AclProviderManager mgr = makeProviderManager();
assertNotNull(mgr.getAcls("STRING"));
}
public void testRejectsNulls() {
AclProviderManager mgr = new AclProviderManager();
try {
mgr.getAcls(null);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
mgr.getAcls(null,
new UsernamePasswordAuthenticationToken("marissa", "not used"));
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
mgr.getAcls("SOME_DOMAIN_INSTANCE", null);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testReturnsNullIfNoSupportingProvider() {
AclProviderManager mgr = makeProviderManager();
assertNull(mgr.getAcls(new Integer(4),
new UsernamePasswordAuthenticationToken("marissa", "not used")));
assertNull(mgr.getAcls(new Integer(4)));
}
public void testStartupFailsIfProviderListNotContainingProviders()
throws Exception {
List providers = new Vector();
providers.add("THIS_IS_NOT_A_PROVIDER");
AclProviderManager mgr = new AclProviderManager();
try {
mgr.setProviders(providers);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testStartupFailsIfProviderListNotSet()
throws Exception {
AclProviderManager mgr = new AclProviderManager();
try {
mgr.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testStartupFailsIfProviderListNull() throws Exception {
AclProviderManager mgr = new AclProviderManager();
try {
mgr.setProviders(null);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testSuccessfulStartup() throws Exception {
AclProviderManager mgr = makeProviderManager();
mgr.afterPropertiesSet();
assertTrue(true);
assertEquals(1, mgr.getProviders().size());
}
private AclProviderManager makeProviderManager() {
MockProvider provider1 = new MockProvider();
List providers = new Vector();
providers.add(provider1);
AclProviderManager mgr = new AclProviderManager();
mgr.setProviders(providers);
return mgr;
}
//~ Inner Classes ==========================================================
private class MockProvider implements AclProvider {
private UsernamePasswordAuthenticationToken marissa = new UsernamePasswordAuthenticationToken("marissa",
"not used",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_FOO"), new GrantedAuthorityImpl("ROLE_BAR")});
private SimpleAclEntry entry100Marissa = new SimpleAclEntry(marissa
.getPrincipal(),
new NamedEntityObjectIdentity("OBJECT", "100"), null, 2);
private UsernamePasswordAuthenticationToken scott = new UsernamePasswordAuthenticationToken("scott",
"not used",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_FOO"), new GrantedAuthorityImpl("ROLE_MANAGER")});
private SimpleAclEntry entry100Scott = new SimpleAclEntry(scott
.getPrincipal(),
new NamedEntityObjectIdentity("OBJECT", "100"), null, 4);
public AclEntry[] getAcls(Object domainInstance,
Authentication authentication) {
if (authentication.getPrincipal().equals(scott.getPrincipal())) {
return new AclEntry[] {entry100Scott};
}
if (authentication.getPrincipal().equals(marissa.getPrincipal())) {
return new AclEntry[] {entry100Marissa};
}
return null;
}
public AclEntry[] getAcls(Object domainInstance) {
return new AclEntry[] {entry100Marissa, entry100Scott};
}
/**
* Only supports <code>Object</code>s of type <code>String</code>
*
* @param domainInstance DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public boolean supports(Object domainInstance) {
return (domainInstance instanceof String);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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
}
}

View File

@ -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 <code>AclObjectIdentity</code> but is incompatible with
* <code>BasicAclProvider</code> 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!
}

View File

@ -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());
}
}

View File

@ -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)]"));
}
}

View File

@ -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 <code>NamedEntityObjectIdentity</code>.
*
* @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;
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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"));
}
}

View File

@ -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;
}
}
}

View File

@ -69,7 +69,7 @@
<sect2 id="security-high-level-design-key-components">
<title>Key Components</title>
<para>The Acegi Security System for Spring essentially comprises six
<para>The Acegi Security System for Spring essentially comprises seven
key functional parts:</para>
<itemizedlist spacing="compact">
@ -109,6 +109,11 @@
authentication, authorization, run-as replacement and execution of
a given operation.</para>
</listitem>
<listitem>
<para>An acess control list (ACL) management package, which can be
used to obtain ACLs for domain object instances.</para>
</listitem>
</itemizedlist>
<para>Secure objects refer to any type of object that can have
@ -134,8 +139,8 @@
<literal>FilterInterceptor</literal>) with complete
transparency.</para>
<para>Each of the six key parts is discussed in detail throughout this
document.</para>
<para>Each of the seven key parts is discussed in detail throughout
this document.</para>
</sect2>
<sect2 id="security-high-level-design-supported-secure-objects">
@ -2985,6 +2990,370 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
</sect2>
</sect1>
<sect1 id="acls">
<title>Instance-Based Access Control List (ACL) Security</title>
<sect2 id="acls-overview">
<title>Overview</title>
<para>THIS FEATURE WAS ADDED IN VERSION 0.6. WE WELCOME YOUR COMMENTS
AND IMPROVEMENTS.</para>
<para>Complex applications often will find the need to define access
permissions not simply at a web request or method invocation level.
Instead, security decisions need to comprise both who
(<literal>Authentication</literal>), where
(<literal>MethodInvocation</literal>) and what
(<literal>SomeDomainObject</literal>). In other words, authorization
decisions also need to consider the actual domain object instance
subject of a method invocation.</para>
<para>Imagine you're designing an application for a pet clinic. There
will be two main groups of users of your Spring-based application:
staff of the pet clinic, as well as the pet clinic's customers. The
staff will have access to all of the data, whilst your customers will
only be able to see their own customer records. To make it a little
more interesting, your customers can allow other users to see their
customer records, such as their "puppy preschool "mentor or president
of their local "Pony Club". Using Acegi Security System for Spring as
the foundation, you have several approaches that can be
used:<orderedlist>
<listitem>
<para>Write your business methods to enforce the security. You
could consult a collection within the
<literal>Customer</literal> domain object instance to determine
which users have access. By using the
<literal>ContextHolder.getContext()</literal> and casting it to
<literal>SecureContext</literal>, you'll be able to access the
<literal>Authentication</literal> object.</para>
</listitem>
<listitem>
<para>Write an <literal>AccessDecisionVoter</literal> to enforce
the security from the <literal>GrantedAuthority[]</literal>s
stored in the <literal>Authentication</literal> object. This
would mean your <literal>AuthenticationManager</literal> would
need to populate the <literal>Authentication</literal> with
custom <literal>GrantedAuthority</literal>[]s representing each
of the <literal>Customer</literal> domain object instances the
principal has access to.</para>
</listitem>
<listitem>
<para>Write an <literal>AccessDecisionVoter</literal> to enforce
the security and open the target <literal>Customer</literal>
domain object directly. This would mean your voter needs access
to a DAO that allows it to retrieve the
<literal>Customer</literal> object. It would then access the
<literal>Customer</literal> object's collection of approved
users and make the appropriate decision.</para>
</listitem>
</orderedlist></para>
<para>Each one of these approaches is perfectly legitimate. However,
the first couples your authorization checking to your business code.
The main problems with this include the enhanced difficulty of unit
testing and the fact it would be more difficult to reuse the
<literal>Customer</literal> authorization logic elsewhere. Obtaining
the <literal>GrantedAuthority[]</literal>s from the
<literal>Authentication</literal> object is also fine, but will not
scale to large numbers of <literal>Customer</literal>s. If a user
might be able to access 5,000 <literal>Customer</literal>s (unlikely
in this case, but imagine if it were a popular vet for a large Pony
Club!) the amount of memory consumed and time required to construct
the <literal>Authentication</literal> object would be undesirable. The
final method, opening the <literal>Customer</literal> directly from
external code, is probably the best of the three. It achieves
separation of concerns, and doesn't misuse memory or CPU cycles, but
it is still inefficient in that both the
<literal>AccessDecisionVoter</literal> and the eventual business
method itself will perform a call to the DAO responsible for
retrieving the <literal>Customer</literal> object. Two accesses per
method invocation is clearly undesirable. In addition, with every
approach listed you'll need to write your own access control list
(ACL) persistence and business logic from scratch.</para>
<para>Fortunately, there is another alternative, which we'll talk
about below.</para>
</sect2>
<sect2 id="acls-acl-package">
<title>The net.sf.acegisecurity.acl Package</title>
<para>The <literal>net.sf.acegisecurity.acl</literal> package is very
simple, comprising only a handful of interfaces and a single class. It
provides the basic foundation for access control list (ACL) lookups.
The central interface is <literal>AclManager</literal>, which is
defined by two methods:</para>
<para><programlisting>public AclEntry[] getAcls(java.lang.Object domainInstance);
public AclEntry[] getAcls(java.lang.Object domainInstance, Authentication authentication);</programlisting></para>
<para><literal>AclManager</literal> is intended to be used as a
collaborator against your business objects, or, more desirably,
<literal>AccessDecisionVoter</literal>s. This means you use Spring's
normal <literal>ApplicationContext</literal> features to wire up your
<literal>AccessDecisionVoter</literal> (or business method) with an
<literal>AclManager</literal>. Consideration was given to placing the
ACL information in the <literal>ContextHolder</literal>, but it was
felt this would be inefficient both in terms of memory usage as well
as the time spent loading potentially unused ACL information. The
trade-off of needing to wire up a collaborator for those objects
requiring ACL information is rather minor, particularly in a
Spring-managed application.</para>
<para>The first method of the <literal>AclManager</literal> will
return all ACLs applying to the domain object instance passed to it.
The second method does the same, but only returns those ACLs which
apply to the passed <literal>Authentication</literal> object.</para>
<para>The <literal>AclEntry</literal> interface returned by
<literal>AclManager</literal> is merely a marker interface. You will
need to provide an implementation that reflects that ACL permissions
for your application.</para>
<para>Rounding out the <literal>net.sf.acegisecurity.acl</literal>
package is an <literal>AclProviderManager</literal> class, with a
corresponding <literal>AclProvider</literal> interface.
<literal>AclProviderManager</literal> is a concrete implementation of
<literal>AclManager</literal>, which iterates through registered
<literal>AclProvider</literal>s. The first
<literal>AclProvider</literal> that indicates it can authoritatively
provide ACL information for the presented domain object instance will
be used. This is very similar to the
<literal>AuthenticationProvider</literal> interface used for
authentication.</para>
<para>With this background, let's now look at a usable ACL
implementation.</para>
</sect2>
<sect2 id="acls-masking">
<title>Integer Masked ACLs</title>
<para>Acegi Security System for Spring includes a production-quality
ACL provider implementation. The implementation is based on integer
masking, which is commonly used for ACL permissions given its
flexibility and speed. Anyone who has used Unix's
<literal>chmod</literal> command will know all about this type of
permission masking (eg <literal>chmod 777</literal>). You'll find the
classes and interfaces for the integer masking ACL package under
<literal>net.sf.acegisecurity.acl.basic</literal>.</para>
<para>Extending the <literal>AclEntry</literal> interface is a
<literal>BasicAclEntry</literal> interface, with the main methods
shown below:</para>
<para><programlisting>public AclObjectIdentity getAclObjectIdentity();
public AclObjectIdentity getAclObjectParentIdentity();
public int getMask();
public java.lang.Object getRecipient();</programlisting></para>
<para>As shown, each <literal>BasicAclEntry</literal> has four main
properties. The <literal>mask</literal> is the integer that represents
the permissions granted to the <literal>recipient</literal>. The
<literal>aclObjectIdentity</literal> is able to identify the domain
object instance for which the ACL applies, and the
<literal>aclObjectParentIdentity</literal> optionally specifies the
parent of the domain object instance. Multiple
<literal>BasicAclEntry</literal>s usually exist against a single
domain object instance, and as suggested by the parent identity
property, permissions granted higher in the object hierarchy will
trickle down and be inherited (unless blocked by integer zero).</para>
<para><literal>BasicAclEntry</literal> implementations typically
provide convenience methods, such as
<literal>isReadAllowed()</literal>, to avoid application classes
needing to perform bit masking themselves. The
<literal>SimpleAclEntry</literal> and
<literal>AbstractBasicAclEntry</literal> demonstrate and provide much
of this bit masking logic.</para>
<para>The <literal>AclObjectIdentity</literal> itself is merely a
marker interface, so you need to provide implementations for your
domain objects. However, the package does include a
<literal>NamedEntityObjectIdentity</literal> implementation which will
suit many needs. The <literal>NamedEntityObjectIdentity</literal>
identifies a given domain object instance by the classname of the
instance and the identity of the instance. A
<literal>NamedEntityObjectIdentity</literal> can be constructed
manually (by calling the constructor and providing the classname and
identity <literal>String</literal>s), or by passing in any domain
object that contains a <literal>getId()</literal> method.</para>
<para>The actual <literal>AclProvider</literal> implementation is
named <literal>BasicAclProvider</literal>. It has adopted a similar
design to that used by the authentication-related
<literal>DaoAuthenticationProvder</literal>. Specifically, you define
a <literal>BasicAclDao</literal> against the provider, so different
ACL repository types can be accessed in a pluggable manner. The
<literal>BasicAclProvider</literal> also supports pluggable cache
providers (with Acegi Security System for Spring including an
implementation that fronts EH-CACHE).</para>
<para>The <literal>BasicAclDao</literal> interface is very simple to
implement:</para>
<para><programlisting>public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity);</programlisting></para>
<para>A <literal>BasicAclDao</literal> implementation needs to
understand the presented <literal>AclObjectIdentity</literal> and how
it maps to a storage repository, find the relevant records, and create
appropriate <literal>BasicAclEntry</literal> objects and return
them.</para>
<para>Acegi Security includes a single <literal>BasicAclDao</literal>
implementation called <literal>JdbcDaoImpl</literal>. As implied by
the name, it accesses ACL information from a JDBC database. The
default database schema and some sample data will aid in understanding
its function:</para>
<para><programlisting>CREATE TABLE acls (
object_identity VARCHAR_IGNORECASE(250) NOT NULL,
recipient VARCHAR_IGNORECASE(100) NOT NULL,
parent_object_identity VARCHAR_IGNORECASE(250),
mask INTEGER NOT NULL,
acl_class VARCHAR_IGNORECASE(250) NOT NULL,
CONSTRAINT pk_acls PRIMARY KEY(object_identity, recipient)
);
INSERT INTO acls VALUES ('corp.DomainObject:1', 'ROLE_SUPERVISOR', null, 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
INSERT INTO acls VALUES ('corp.DomainObject:2', 'ROLE_SUPERVISOR', 'corp.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
INSERT INTO acls VALUES ('corp.DomainObject:2', 'marissa', 'corp.DomainObject:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
INSERT INTO acls VALUES ('corp.DomainObject:3', 'scott', 'corp.DomainObject:1', 14, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
INSERT INTO acls VALUES ('corp.DomainObject:4', 'inheritance_marker_only', 'corp.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
INSERT INTO acls VALUES ('corp.DomainObject:5', 'inheritance_marker_only', 'corp.DomainObject:3', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
INSERT INTO acls VALUES ('corp.DomainObject:6', 'scott', 'corp.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
INSERT INTO acls VALUES ('corp.DomainObject:7', 'scott', 'some.invalid.parent:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');</programlisting></para>
<para>The <literal>JdbcDaoImpl</literal> will only respond to requests
for <literal>NamedEntityObjectIdentity</literal>s. It converts such
identities into a single <literal>String</literal>, comprising
the<literal> NamedEntityObjectIdentity.getClassname()</literal> +
<literal>":"</literal> +
<literal>NamedEntityObjectIdentity.getId()</literal>. This yields the
type of <literal>object_identity</literal> values shown above. As
indicated by the sample data, each database row corresponds to a
single <literal>BasicAclEntry</literal>. As stated earlier and
demonstrated by <literal>corp.DomainObject:2</literal> in the above
sample data, each domain object instance will often have multiple
<literal>BasicAclEntry</literal>[]s.</para>
<para>As <literal>JdbcDaoImpl</literal> is required to return concrete
<literal>BasicAclEntry</literal> classes, it needs to know which
<literal>BasicAclEntry</literal> implementation it is to create and
populate. This is the role of the <literal>acl_class</literal> column.
<literal>JdbcDaoImpl</literal> will create the indicated class and set
its <literal>mask</literal>, <literal>recipient</literal>,
<literal>aclObjectIdentity</literal> and
<literal>aclObjectParentIdentity</literal> properties.</para>
<para>As you can probably tell from the sample data, the
<literal>parent_object_identity</literal> value can either be null or
in the same format as the <literal>object_identity</literal>. If
non-null, <literal>JdbcDaoImpl</literal> will create a
<literal>NamedEntityObjectIdentity</literal> to place inside the
returned <literal>BasicAclEntry</literal> class.</para>
<para>Returning to the <literal>BasicAclProvider</literal>, before it
can poll the <literal>BasicAclDao</literal> implementation it needs to
convert the domain object instance it was passed into an
<literal>AclObjectIdentity</literal>.
<literal>BasicAclProvider</literal> has a <literal>protected
AclObjectIdentity obtainIdentity(Object domainInstance)</literal>
method that is responsible for this. As a protected method, it enables
subclasses to easily override. The normal implementation checks
whether the passed domain object instance implements the
<literal>AclObjectIdentityAware</literal> interface, which is merely a
getter for an <literal>AclObjectIdentity</literal>. If the domain
object does implement this interface, that is the identity returned.
If the domain object does not implement this interface, the method
will attempt to create an <literal>AclObjectIdentity</literal> by
passing the domain object instance to the constructor of a class
defined by the
<literal>BasicAclProvider.getDefaultAclObjectIdentity()</literal>
method. By default the defined class is
<literal>NamedEntityObjectIdentity</literal>, which was described in
more detail above. Therefore, you will need to either (i) provide a
<literal>getId()</literal> method on your domain objects, (ii)
implement <literal>AclObjectIdentityAware</literal> on your domain
objects, (iii) provide an alternative
<literal>AclObjectIdentity</literal> implementation that will accept
your domain object in its constructor, or (iv) override the
<literal>obtainIdentity(Object)</literal> method.</para>
<para>Once the <literal>AclObjectIdentity</literal> of the domain
object instance is determined, the <literal>BasicAclProvider</literal>
will poll the DAO to obtain its <literal>BasicAclEntry</literal>[]s.
If any of the entries returned by the DAO indicate there is a parent,
that parent will be polled, and the process will repeat until there is
no further parent. The permissions assigned to a
<literal>recipient</literal> closest to the domain object instance
will always take priority and override any inherited permissions. From
the sample data above, the following inherited permissions would
apply:</para>
<para><programlisting>--- Mask integer 0 = no permissions
--- Mask integer 1 = administer
--- Mask integer 2 = read
--- Mask integer 6 = read and write permissions
--- Mask integer 14 = read and write and create permissions
---------------------------------------------------------------------
--- *** INHERITED RIGHTS FOR DIFFERENT INSTANCES AND RECIPIENTS ***
--- INSTANCE RECIPIENT PERMISSION(S) (COMMENT #INSTANCE)
---------------------------------------------------------------------
--- 1 ROLE_SUPERVISOR Administer
--- 2 ROLE_SUPERVISOR None (overrides parent #1)
--- marissa Read
--- 3 ROLE_SUPERVISOR Administer (from parent #1)
--- scott Read, Write, Create
--- 4 ROLE_SUPERVISOR Administer (from parent #1)
--- 5 ROLE_SUPERVISOR Administer (from parent #3)
--- scott Read, Write, Create (from parent #3)
--- 6 ROLE_SUPERVISOR Administer (from parent #3)
--- scott Administer (overrides parent #3)
--- 7 scott Read (invalid parent ignored)</programlisting></para>
<para>So the above explains how a domain object instance has its
<literal>AclObjectIdentity</literal> discovered, and the
<literal>BasicAclDao</literal> will be polled successively until an
array of inherited permissions is constructed for the domain object
instance. The final step is to determine the
<literal>BasicAclEntry</literal>[]s that are actually applicable to a
given <literal>Authentication</literal> object.</para>
<para>As you would recall, the <literal>AclManager</literal> (and all
delegates, up to and including <literal>BasicAclProvider</literal>)
provides a method which returns only those
<literal>BasicAclEntry</literal>[]s applying to a passed
<literal>Authentication</literal> object.
<literal>BasicAclProvider</literal> delivers this functionality by
delegating the filtering operation to an
<literal>EffectiveAclsResolver</literal> implementation. The default
implementation,
<literal>GrantedAuthorityEffectiveAclsResolver</literal>, will iterate
through the <literal>BasicAclEntry</literal>[]s and include only those
where the <literal>recipient</literal> is equal to either the
<literal>Authentication</literal>'s <literal>principal</literal> or
any of the <literal>Authentication</literal>'s
<literal>GrantedAuthority</literal>[]s. Please refer to the JavaDocs
for more information.</para>
</sect2>
<sect2 id="acls-conclusion">
<title>Conclusion</title>
<para>Acegi Security's instance-specific ACL packages shield you from
much of the complexity of developing your own ACL approach. The
interfaces and classes detailed above provide a scalable, customisable
ACL solution that is decoupled from your application code. Whilst the
reference documentation may suggest complexity, the basic
implementation is able to support most typical applications
out-of-the-box.</para>
</sect2>
</sect1>
<sect1 id="security-filters">
<title>Filters</title>
@ -3081,6 +3450,11 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
<para>All of the above filters use
<literal>FilterToBeanProxy</literal>, which is discussed in the
previous section.</para>
<para>If you're using SiteMesh, ensure the Acegi Security filters
execute before the SiteMesh filters are called. This enables the
<literal>ContextHolder</literal> to be populated in time for use by
SiteMesh decorators.</para>
</sect2>
</sect1>