diff --git a/sandbox/src/main/java/org/acegisecurity/acls/AccessControlEntry.java b/sandbox/src/main/java/org/acegisecurity/acls/AccessControlEntry.java new file mode 100644 index 0000000000..12cc1184d5 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/acls/AccessControlEntry.java @@ -0,0 +1,30 @@ +package org.acegisecurity.acls; + +import java.io.Serializable; + +import org.acegisecurity.acls.sid.Sid; + +/** + * Represents an individual permission assignment within an {@link Acl}. + * + *
+ * Instances MUST be immutable, as they are returned by Acl
+ * and should not allow client modification.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface AccessControlEntry {
+ /**
+ * Obtains an identifier that represents this ACE.
+ *
+ * @return the identifier, or null if unsaved
+ */
+ public Serializable getId();
+
+ public Acl getAcl();
+ public Sid getSid();
+ public Permission getPermission();
+ public boolean isGranting();
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/Acl.java b/sandbox/src/main/java/org/acegisecurity/acls/Acl.java
new file mode 100644
index 0000000000..30c4b1ef84
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/Acl.java
@@ -0,0 +1,206 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import java.io.Serializable;
+
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.sid.Sid;
+
+
+/**
+ * Represents an access control list (ACL) for a domain object.
+ *
+ *
+ * An Acl represents all ACL entries for a given domain object. In
+ * order to avoid needing references to the domain object itself, this
+ * interface handles indirection between a domain object and an ACL object
+ * identity via the {@link
+ * org.acegisecurity.acls.objectidentity.ObjectIdentity} interface.
+ *
+ * An implementation represents the {@link org.acegisecurity.acls.Permission} + * list applicable for some or all {@link org.acegisecurity.acls.sid.Sid} + * instances. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public interface Acl extends Serializable { + //~ Methods ================================================================ + + + /** + * Returns all of the entries represented by the presentAcl
+ * (not parents).
+ *
+ * + * This method is typically used for administrative purposes. + *
+ * + *+ * The order that entries appear in the array is unspecified. However, if + * implementations use particular ordering logic in authorization + * decisions, the entries returned by this method MUST be ordered + * in that manner. + *
+ * + *+ * Do NOT use this method for making authorization decisions. + * Instead use {@link #isGranted(Permission[], Sid[])}. + *
+ * + *
+ * This method must operate correctly even if the Acl only
+ * represents a subset of Sids. The caller is responsible for
+ * correctly handling the result if only a subset of Sids is
+ * represented.
+ *
Acl
+ */
+ public AccessControlEntry[] getEntries();
+
+ /**
+ * Obtains the domain object this Acl provides entries for.
+ * This is immutable once an Acl is created.
+ *
+ * @return the object identity
+ */
+ public ObjectIdentity getObjectIdentity();
+
+ /**
+ * A domain object may have a parent for the purpose of ACL inheritance. If
+ * there is a parent, its ACL can be accessed via this method. In turn,
+ * the parent's parent (grandparent) can be accessed and so on.
+ *
+ *
+ * This method solely represents the presence of a navigation hierarchy
+ * between the parent Acl and this Acl. For
+ * actual inheritance to take place, the {@link #isEntriesInheriting()}
+ * must also be true.
+ *
+ * This method must operate correctly even if the Acl only
+ * represents a subset of Sids. The caller is responsible for
+ * correctly handling the result if only a subset of Sids is
+ * represented.
+ *
Acl
+ */
+ public Acl getParentAcl();
+
+ /**
+ * Indicates whether the ACL entries from the {@link #getParentAcl()}
+ * should flow down into the current Acl.
+ *
+ *
+ * The mere link between an Acl and a parent Acl
+ * on its own is insufficient to cause ACL entries to inherit down. This
+ * is because a domain object may wish to have entirely independent
+ * entries, but maintain the link with the parent for navigation purposes.
+ * Thus, this method denotes whether or not the navigation relationship
+ * also extends to the actual inheritence of entries.
+ *
true if parent ACL entries inherit into the current
+ * Acl
+ */
+ public boolean isEntriesInheriting();
+
+ /**
+ * This is the actual authorization logic method, and must be used whenever
+ * ACL authorization decisions are required.
+ *
+ *
+ * An array of Sids are presented, representing security
+ * identifies of the current principal. In addition, an array of
+ * Permissions is presented which will have one or more bits
+ * set in order to indicate the permissions needed for an affirmative
+ * authorization decision. An array is presented because holding
+ * any of the Permissions inside the array will be
+ * sufficient for an affirmative authorization.
+ *
+ * The actual approach used to make authorization decisions is left to the
+ * implementation and is not specified by this interface. For example, an
+ * implementation MAY search the current ACL in the order the ACL
+ * entries have been stored. If a single entry is found that has the same
+ * active bits as are shown in a passed Permission, that
+ * entry's grant or deny state may determine the authorization decision.
+ * If the case of a deny state, the deny decision will only be relevant if
+ * all other Permissions passed in the array have also been
+ * unsuccessfully searched. If no entry is found that match the bits in
+ * the current ACL, provided that {@link #isEntriesInheriting()} is
+ * true, the authorization decision may be passed to the
+ * parent ACL. If there is no matching entry, the implementation MAY throw
+ * an exception, or make a predefined authorization decision.
+ *
+ * This method must operate correctly even if the Acl only
+ * represents a subset of Sids. The caller is responsible for
+ * correctly handling the result if only a subset of Sids is
+ * represented.
+ *
true denotes the query is for
+ * administrative purposes and no logger or auditing (if supported
+ * by the implementation) should be undertaken
+ *
+ * @return true is authorization is granted
+ *
+ * @throws NotFoundException MAY be thrown if an implementation cannot make
+ * an authoritative authorization decision
+ * @throws UnloadedSidException thrown if the Acl does not
+ * have details for one or more of the Sids passed as
+ * arguments
+ */
+ public boolean isGranted(Permission[] permission, Sid[] sids,
+ boolean administrativeMode)
+ throws NotFoundException, UnloadedSidException;
+
+ /**
+ * For efficiency reasons an Acl may be loaded and
+ * not contain entries for every Sid in the system.
+ * If an Acl has been loaded and does not represent every
+ * Sid, all methods of the Sid can only be used
+ * within the limited scope of the Sid instances it actually
+ * represents.
+ *
+ *
+ * It is normal to load an Acl for only particular
+ * Sids if read-only authorization decisions are being made.
+ * However, if user interface reporting or modification of
+ * Acls are desired, an Acl should be loaded
+ * with all Sids. This method denotes whether or not the
+ * specified Sids have been loaded or not.
+ *
true if every passed Sid is
+ * represented by this Acl instance
+ */
+ public boolean isSidLoaded(Sid[] sids);
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/AclFormattingUtils.java b/sandbox/src/main/java/org/acegisecurity/acls/AclFormattingUtils.java
new file mode 100644
index 0000000000..a3aaf09816
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/AclFormattingUtils.java
@@ -0,0 +1,118 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Utility methods for displaying ACL information.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AclFormattingUtils {
+ //~ Methods ================================================================
+
+ public static String demergePatterns(String original, String removeBits) {
+ Assert.notNull(original, "Original string required");
+ Assert.notNull(removeBits, "Bits To Remove string required");
+ Assert.isTrue(original.length() == removeBits.length(),
+ "Original and Bits To Remove strings must be identical length");
+
+ char[] replacement = new char[original.length()];
+
+ for (int i = 0; i < original.length(); i++) {
+ if (removeBits.charAt(i) == Permission.RESERVED_OFF) {
+ replacement[i] = original.charAt(i);
+ } else {
+ replacement[i] = Permission.RESERVED_OFF;
+ }
+ }
+
+ return new String(replacement);
+ }
+
+ public static String mergePatterns(String original, String extraBits) {
+ Assert.notNull(original, "Original string required");
+ Assert.notNull(extraBits, "Extra Bits string required");
+ Assert.isTrue(original.length() == extraBits.length(),
+ "Original and Extra Bits strings must be identical length");
+
+ char[] replacement = new char[extraBits.length()];
+
+ for (int i = 0; i < extraBits.length(); i++) {
+ if (extraBits.charAt(i) == Permission.RESERVED_OFF) {
+ replacement[i] = original.charAt(i);
+ } else {
+ replacement[i] = extraBits.charAt(i);
+ }
+ }
+
+ return new String(replacement);
+ }
+
+ private static String printBinary(int i, char on, char off) {
+ String s = Integer.toString(i, 2);
+ String pattern = Permission.THIRTY_TWO_RESERVED_OFF;
+ String temp2 = pattern.substring(0, pattern.length() - s.length()) + s;
+
+ return temp2.replace('0', off).replace('1', on);
+ }
+
+ /**
+ * Returns a representation of the active bits in the presented mask, with
+ * each active bit being denoted by character "".
+ *
+ * + * Inactive bits will be denoted by character {@link + * Permission#RESERVED_OFF}. + *
+ * + * @param i the integer bit mask to print the active bits for + * + * @return a 32-character representation of the bit mask + */ + public static String printBinary(int i) { + return AclFormattingUtils.printBinary(i, '*', Permission.RESERVED_OFF); + } + + /** + * Returns a representation of the active bits in the presented mask, with + * each active bit being denoted by the passed character. + * + *+ * Inactive bits will be denoted by character {@link + * Permission#RESERVED_OFF}. + *
+ * + * @param mask the integer bit mask to print the active bits for + * @param code the character to print when an active bit is detected + * + * @return a 32-character representation of the bit mask + */ + public static String printBinary(int mask, char code) { + Assert.doesNotContain(new Character(code).toString(), + new Character(Permission.RESERVED_ON).toString(), + Permission.RESERVED_ON + " is a reserved character code"); + Assert.doesNotContain(new Character(code).toString(), + new Character(Permission.RESERVED_OFF).toString(), + Permission.RESERVED_OFF + " is a reserved character code"); + + return AclFormattingUtils.printBinary(mask, Permission.RESERVED_ON, + Permission.RESERVED_OFF).replace(Permission.RESERVED_ON, code); + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/acls/AclService.java b/sandbox/src/main/java/org/acegisecurity/acls/AclService.java new file mode 100644 index 0000000000..1761f443a9 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/acls/AclService.java @@ -0,0 +1,75 @@ +/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls; + +import org.acegisecurity.acls.objectidentity.ObjectIdentity; +import org.acegisecurity.acls.sid.Sid; + +import java.util.Map; + + +/** + * Provides retrieval of {@link Acl} instances. + * + * @author Ben Alex + * @version $Id$ + */ +public interface AclService { + //~ Methods ================================================================ + + /** + * Obtains all theAcls that apply for the passed
+ * Objects.
+ *
+ *
+ * The returned map is keyed on the passed objects, with the values being
+ * the Acl instances. Any unknown objects will not have a map
+ * key.
+ *
null)
+ */
+ public Map readAclsById(ObjectIdentity[] objects) throws NotFoundException;
+
+ /**
+ * Obtains all the Acls that apply for the passed
+ * Objects, but only for the security identifies passed.
+ *
+ * + * Implementations MAY provide a subset of the ACLs via this + * method although this is NOT a requirement. This is intended to allow + * performance optimisations within implementations. Callers should + * therefore use this method in preference to the alternative overloaded + * version which does not have performance optimisation opportunities. + *
+ * + *
+ * The returned map is keyed on the passed objects, with the values being
+ * the Acl instances. Any unknown objects (or objects for
+ * which the interested Sids do not have entries) will not
+ * have a map key.
+ *
null to denote all entries)
+ *
+ * @return a map with zero or more elements (never null)
+ */
+ public Map readAclsById(ObjectIdentity[] objects, Sid[] sids) throws NotFoundException;
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/AlreadyExistsException.java b/sandbox/src/main/java/org/acegisecurity/acls/AlreadyExistsException.java
new file mode 100644
index 0000000000..dcfde5d7a5
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/AlreadyExistsException.java
@@ -0,0 +1,49 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an Acl entry already exists for the object.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AlreadyExistsException extends AcegiSecurityException {
+ //~ Constructors ===========================================================
+
+ /**
+ * Constructs an AlreadyExistsException with the specified message.
+ *
+ * @param msg the detail message
+ */
+ public AlreadyExistsException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an AlreadyExistsException with the specified message
+ * and root cause.
+ *
+ * @param msg the detail message
+ * @param t root cause
+ */
+ public AlreadyExistsException(String msg, Throwable t) {
+ super(msg, t);
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/AuditableAccessControlEntry.java b/sandbox/src/main/java/org/acegisecurity/acls/AuditableAccessControlEntry.java
new file mode 100644
index 0000000000..a37f1463a5
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/AuditableAccessControlEntry.java
@@ -0,0 +1,13 @@
+package org.acegisecurity.acls;
+
+/**
+ * Represents an ACE that provides auditing information.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface AuditableAccessControlEntry extends AccessControlEntry {
+ public boolean isAuditSuccess();
+ public boolean isAuditFailure();
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/AuditableAcl.java b/sandbox/src/main/java/org/acegisecurity/acls/AuditableAcl.java
new file mode 100644
index 0000000000..78dd668434
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/AuditableAcl.java
@@ -0,0 +1,12 @@
+package org.acegisecurity.acls;
+
+/**
+ * A mutable ACL that provides audit capabilities.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface AuditableAcl extends MutableAcl {
+ public void updateAuditing(Long aceId, boolean auditSuccess, boolean auditFailure);
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/ChildrenExistException.java b/sandbox/src/main/java/org/acegisecurity/acls/ChildrenExistException.java
new file mode 100644
index 0000000000..e64366ce55
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/ChildrenExistException.java
@@ -0,0 +1,51 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an {@link Acl} cannot be deleted because children
+ * Acls exist.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ChildrenExistException extends AcegiSecurityException {
+ //~ Constructors ===========================================================
+
+ /**
+ * Constructs an ChildrenExistException with the specified
+ * message.
+ *
+ * @param msg the detail message
+ */
+ public ChildrenExistException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an ChildrenExistException with the specified
+ * message and root cause.
+ *
+ * @param msg the detail message
+ * @param t root cause
+ */
+ public ChildrenExistException(String msg, Throwable t) {
+ super(msg, t);
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/IdentityUnavailableException.java b/sandbox/src/main/java/org/acegisecurity/acls/IdentityUnavailableException.java
new file mode 100644
index 0000000000..3256ccd68e
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/IdentityUnavailableException.java
@@ -0,0 +1,49 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an ACL identity could not be extracted from an object.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class IdentityUnavailableException extends AcegiSecurityException {
+ //~ Constructors ===========================================================
+
+ /**
+ * Constructs an IdentityUnavailableException with the specified message.
+ *
+ * @param msg the detail message
+ */
+ public IdentityUnavailableException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an IdentityUnavailableException with the specified message
+ * and root cause.
+ *
+ * @param msg the detail message
+ * @param t root cause
+ */
+ public IdentityUnavailableException(String msg, Throwable t) {
+ super(msg, t);
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/MutableAcl.java b/sandbox/src/main/java/org/acegisecurity/acls/MutableAcl.java
new file mode 100644
index 0000000000..773ecbc232
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/MutableAcl.java
@@ -0,0 +1,61 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import java.io.Serializable;
+
+import org.acegisecurity.acls.sid.Sid;
+
+
+/**
+ * A mutable Acl.
+ *
+ * + * A mutable ACL must ensure that appropriate security checks are performed + * before allowing access to its methods. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public interface MutableAcl extends Acl { + //~ Methods ================================================================ + + + /** + * Obtains an identifier that represents thisMutableAcl.
+ *
+ * @return the identifier, or null if unsaved
+ */
+ public Serializable getId();
+
+
+ public void deleteAce(Long aceId) throws NotFoundException ;
+
+ public void insertAce(Long afterAceId, Permission permission, Sid sid,
+ boolean granting) throws NotFoundException;
+
+ /**
+ * Changes the parent of this ACL.
+ *
+ * @param newParent the new parent
+ */
+ public void setParent(MutableAcl newParent);
+
+ public void updateAce(Long aceId, Permission permission) throws NotFoundException;
+
+ public void setEntriesInheriting(boolean entriesInheriting);
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/MutableAclService.java b/sandbox/src/main/java/org/acegisecurity/acls/MutableAclService.java
new file mode 100644
index 0000000000..64676cbc2f
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/MutableAclService.java
@@ -0,0 +1,77 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+
+/**
+ * Provides support for creating and storing Acl instances.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface MutableAclService extends AclService {
+ //~ Methods ================================================================
+
+ /**
+ * Creates an empty Acl object in the database. It will have
+ * no entries. The returned object will then be used to add entries.
+ *
+ * @param object the object identity to create
+ *
+ * @return an ACL object with its ID set
+ *
+ * @throws AlreadyExistsException if the passed object identity already has
+ * a record
+ */
+ public MutableAcl createAcl(ObjectIdentity object)
+ throws AlreadyExistsException;
+
+ /**
+ * Removes the specified entry from the database.
+ *
+ * @param object the object identity to remove
+ * @param deleteChildren whether to cascade the delete to children
+ *
+ * @throws ChildrenExistException if the deleteChildren argument was
+ * false but children exist
+ */
+ public void deleteAcl(ObjectIdentity object, boolean deleteChildren)
+ throws ChildrenExistException;
+
+ /**
+ * Locates all object identities that use the specified parent. This is
+ * useful for administration tools, and before issuing a {@link
+ * #deleteAcl(ObjectIdentity, boolean)}.
+ *
+ * @param parentIdentity to locate children of
+ *
+ * @return the children (or null if none were found)
+ */
+ public ObjectIdentity[] findChildren(ObjectIdentity parentIdentity);
+
+ /**
+ * Changes an existing Acl in the database.
+ *
+ * @param acl to modify
+ *
+ * @throws NotFoundException if the relevant record could not be found (did
+ * you remember to use {@link #createAcl(ObjectIdentity)} to
+ * create the object, rather than creating it with the
+ * new keyword?)
+ */
+ public void updateAcl(MutableAcl acl) throws NotFoundException;
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/NotFoundException.java b/sandbox/src/main/java/org/acegisecurity/acls/NotFoundException.java
new file mode 100644
index 0000000000..6309d284cf
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/NotFoundException.java
@@ -0,0 +1,49 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an ACL-related object cannot be found.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class NotFoundException extends AcegiSecurityException {
+ //~ Constructors ===========================================================
+
+ /**
+ * Constructs an NotFoundException with the specified message.
+ *
+ * @param msg the detail message
+ */
+ public NotFoundException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an NotFoundException with the specified message
+ * and root cause.
+ *
+ * @param msg the detail message
+ * @param t root cause
+ */
+ public NotFoundException(String msg, Throwable t) {
+ super(msg, t);
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/OwnershipAcl.java b/sandbox/src/main/java/org/acegisecurity/acls/OwnershipAcl.java
new file mode 100644
index 0000000000..239d02f1d0
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/OwnershipAcl.java
@@ -0,0 +1,37 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls;
+
+import org.acegisecurity.acls.sid.Sid;
+
+/**
+ * A mutable ACL that provides ownership capabilities.
+ *
+ * + * Generally the owner of an ACL is able to call any ACL mutator method, as + * well as assign a new owner. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public interface OwnershipAcl extends MutableAcl { + //~ Methods ================================================================ + + public Sid getOwner(); + + public void setOwner(Sid newOwner); +} diff --git a/sandbox/src/main/java/org/acegisecurity/acls/Permission.java b/sandbox/src/main/java/org/acegisecurity/acls/Permission.java new file mode 100644 index 0000000000..f4d84511f0 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/acls/Permission.java @@ -0,0 +1,70 @@ +/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls; + +import org.acegisecurity.acls.sid.Sid; + + +/** + * Represents a permission granted to a {@link Sid} for a given domain object. + * + * @author Ben Alex + * @version $Id$ + */ +public interface Permission { + //~ Static fields/initializers ============================================= + + public static final char RESERVED_ON = '~'; + public static final char RESERVED_OFF = '.'; + public static final String THIRTY_TWO_RESERVED_OFF = "................................"; + + //~ Methods ================================================================ + + /** + * Returns the bits that represents the permission. + * + * @return the bits that represent the permission + */ + public int getMask(); + + /** + * Returns a 32-character long bit patternString representing
+ * this permission.
+ *
+ *
+ * Implementations are free to format the pattern as they see fit, although
+ * under no circumstances may {@link #RESERVED_OFF} or {@link
+ * #RESERVED_ON} be used within the pattern. An exemption is in the case
+ * of {@link #RESERVED_OFF} which is used to denote a bit that is off
+ * (clear). Implementations may also elect to use {@link #RESERVED_ON}
+ * internally for computation purposes, although this method may not
+ * return any String containing {@link #RESERVED_ON}.
+ *
+ * The returned String must be 32 characters in length. + *
+ * + *+ * This method is only used for user interface and logging purposes. It is + * not used in any permission calculations. Therefore, duplication of + * characters within the output is permitted. + *
+ * + * @return a 32-character bit pattern + */ + public String getPattern(); +} diff --git a/sandbox/src/main/java/org/acegisecurity/acls/UnloadedSidException.java b/sandbox/src/main/java/org/acegisecurity/acls/UnloadedSidException.java new file mode 100644 index 0000000000..1228cb98f6 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/acls/UnloadedSidException.java @@ -0,0 +1,51 @@ +/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls; + +import org.acegisecurity.AcegiSecurityException; + + +/** + * Thrown if an {@link Acl} cannot perform an operation because it only + * loaded a subset ofSids and the caller has requested details
+ * for an unloaded Sid.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class UnloadedSidException extends AcegiSecurityException {
+ //~ Constructors ===========================================================
+
+ /**
+ * Constructs an NotFoundException with the specified message.
+ *
+ * @param msg the detail message
+ */
+ public UnloadedSidException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an NotFoundException with the specified message
+ * and root cause.
+ *
+ * @param msg the detail message
+ * @param t root cause
+ */
+ public UnloadedSidException(String msg, Throwable t) {
+ super(msg, t);
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/domain/AccessControlEntryImpl.java b/sandbox/src/main/java/org/acegisecurity/acls/domain/AccessControlEntryImpl.java
new file mode 100644
index 0000000000..4f1c89f0ad
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/domain/AccessControlEntryImpl.java
@@ -0,0 +1,124 @@
+package org.acegisecurity.acls.domain;
+
+import java.io.Serializable;
+
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AuditableAccessControlEntry;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.sid.Sid;
+import org.springframework.util.Assert;
+
+/**
+ * An immutable default implementation of AccessControlEntry.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AccessControlEntryImpl implements AccessControlEntry, AuditableAccessControlEntry {
+ private Serializable id;
+ private Acl acl;
+ private Sid sid;
+ private Permission permission;
+ private boolean granting;
+ private boolean auditSuccess = false;
+ private boolean auditFailure = false;
+ private boolean aceDirty = false;
+
+ public void clearDirtyFlags() {
+ this.aceDirty = false;
+ }
+
+ public boolean equals(Object arg0) {
+ if (!(arg0 instanceof AccessControlEntryImpl)) {
+ return false;
+ }
+ AccessControlEntryImpl rhs = (AccessControlEntryImpl) arg0;
+ if (this.aceDirty != rhs.isAceDirty() ||
+ this.auditFailure != rhs.isAuditFailure() ||
+ this.auditSuccess != rhs.isAuditSuccess() ||
+ this.granting != rhs.isGranting() ||
+ !this.acl.equals(rhs.getAcl()) ||
+ !this.id.equals(rhs.getId()) ||
+ !this.permission.equals(rhs.getPermission()) ||
+ !this.sid.equals(rhs.getSid()) ) {
+ return false;
+ }
+ return true;
+ }
+
+
+
+ public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid, Permission permission, boolean granting, boolean auditSuccess, boolean auditFailure) {
+ Assert.notNull(acl, "Acl required");
+ Assert.notNull(sid, "Sid required");
+ Assert.notNull(permission, "Permission required");
+ this.id = id;
+ this.acl = acl; // can be null
+ this.sid = sid;
+ this.permission = permission;
+ this.granting = granting;
+ this.auditSuccess = auditSuccess;
+ this.auditFailure = auditFailure;
+ }
+
+ public Acl getAcl() {
+ return acl;
+ }
+ public boolean isGranting() {
+ return granting;
+ }
+ public Serializable getId() {
+ return id;
+ }
+ public Permission getPermission() {
+ return permission;
+ }
+ public Sid getSid() {
+ return sid;
+ }
+
+ void setPermission(Permission permission) {
+ Assert.notNull(permission, "Permission required");
+ this.permission = permission;
+ this.aceDirty = true;
+ }
+
+ void setId(Serializable id) {
+ this.id = id;
+ }
+
+ public boolean isAuditFailure() {
+ return auditFailure;
+ }
+
+ void setAuditFailure(boolean auditFailure) {
+ this.auditFailure = auditFailure;
+ this.aceDirty = true;
+ }
+
+ public boolean isAuditSuccess() {
+ return auditSuccess;
+ }
+
+ void setAuditSuccess(boolean auditSuccess) {
+ this.auditSuccess = auditSuccess;
+ this.aceDirty = true;
+ }
+
+ public boolean isAceDirty() {
+ return aceDirty;
+ }
+
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("AccessControlEntryImpl[");
+ sb.append("id: ").append(this.id).append("; ");
+ sb.append("granting: ").append(this.granting).append("; ");
+ sb.append("sid: ").append(this.sid).append("; ");
+ sb.append("permission: ").append(this.permission);
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java b/sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java
new file mode 100644
index 0000000000..bde9b465d0
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java
@@ -0,0 +1,500 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls.domain;
+
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import org.acegisecurity.AccessDeniedException;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AuditableAcl;
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.OwnershipAcl;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.UnloadedSidException;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.acls.sid.Sid;
+import org.acegisecurity.context.SecurityContextHolder;
+import org.springframework.util.Assert;
+
+
+/**
+ * Base implementation of Acl.
+ *
+ * @author Ben Alex
+ * @version $Id
+ */
+public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
+ //~ Instance fields ========================================================
+ private static final int CHANGE_OWNERSHIP = 0;
+ private static final int CHANGE_AUDITING = 1;
+ private static final int CHANGE_GENERAL = 2;
+
+ private GrantedAuthority gaTakeOwnership;
+ private GrantedAuthority gaModifyAuditing;
+ private GrantedAuthority gaGeneralChanges;
+
+ private Acl parentAcl;
+ private AuditLogger auditLogger = new ConsoleAuditLogger(); // AuditableAcl
+ private List aces = new Vector();
+ private List deletedAces = new Vector();
+ private Long id;
+ private ObjectIdentity objectIdentity;
+ private Sid owner; // OwnershipAcl
+ private boolean entriesInheriting = false;
+ private Sid[] loadedSids = null; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
+ private boolean aclDirty = false; // for snapshot detection
+ private boolean addedAces = false; // for snapshot detection
+ private boolean updatedAces = false; // for snapshot detection
+
+ //~ Constructors ===========================================================
+
+ /**
+ * Minimal constructor, which should be used {@link
+ * org.acegisecurity.acls.MutableAclService#createAcl(ObjectIdentity)}.
+ *
+ * @param objectIdentity the object identity this ACL relates to (required)
+ * @param id the primary key assigned to this ACL (required)
+ * @param auths an array of GrantedAuthoritys that have
+ * special permissions (index 0 is the authority needed to change
+ * ownership, index 1 is the authority needed to modify auditing details,
+ * index 2 is the authority needed to change other ACL and ACE details) (required)
+ */
+ public AclImpl(ObjectIdentity objectIdentity, Long id, GrantedAuthority[] auths) {
+ Assert.notNull(objectIdentity, "Object Identity required");
+ Assert.notNull(id, "Id required");
+ this.objectIdentity = objectIdentity;
+ this.id = id;
+ this.setAuthorities(auths);
+ }
+
+ /**
+ * Change the special adminstrative permissions honoured by this
+ * object.
+ *
+ *
+ * Normally a principal must be the owner of the ACL in order to
+ * make most changes. The authorities passed to this method provide
+ * a way for non-owners to modify the ACL (and indeed modify audit
+ * parameters, which are not available to ACL owners).
+ *
+ * @param auths an array of GrantedAuthoritys that have
+ * administrative permissions (index 0 is the authority needed to change
+ * ownership, index 1 is the authority needed to modify auditing details,
+ * index 2 is the authority needed to change other ACL and ACE details)
+ */
+ private void setAuthorities(GrantedAuthority[] auths) {
+ Assert.notEmpty(auths, "GrantedAuthority[] with three elements required");
+ Assert.isTrue(auths.length == 3, "GrantedAuthority[] with three elements required");
+ this.gaTakeOwnership = auths[0];
+ this.gaModifyAuditing = auths[1];
+ this.gaGeneralChanges = auths[2];
+ }
+
+ /**
+ * Full constructor, which should be used by persistence tools that do not
+ * provide field-level access features.
+ *
+ * @param objectIdentity the object identity this ACL relates to (required)
+ * @param id the primary key assigned to this ACL (required)
+ * @param auths an array of GrantedAuthoritys that have
+ * special permissions (index 0 is the authority needed to change
+ * ownership, index 1 is the authority needed to modify auditing details,
+ * index 2 is the authority needed to change other ACL and ACE details) (required)
+ * @param parentAcl the parent (may be null)
+ * @param loadedSids the loaded SIDs if only a subset were loaded (may be
+ * null)
+ * @param entriesInheriting if ACEs from the parent should inherit into
+ * this ACL
+ * @param owner the owner (required)
+ */
+ public AclImpl(ObjectIdentity objectIdentity, Long id, Acl parentAcl, GrantedAuthority[] auths,
+ Sid[] loadedSids, boolean entriesInheriting, Sid owner) {
+ Assert.notNull(objectIdentity, "Object Identity required");
+ Assert.notNull(id, "Id required");
+ Assert.notNull(owner, "Owner required");
+ this.objectIdentity = objectIdentity;
+ this.id = id;
+ setAuthorities(auths);
+ this.parentAcl = parentAcl; // may be null
+ this.loadedSids = loadedSids; // may be null
+ this.entriesInheriting = entriesInheriting;
+ this.owner = owner;
+ }
+
+ /**
+ * Private no-argument constructor for use by reflection-based persistence
+ * tools along with field-level access.
+ */
+ private AclImpl() {}
+
+ //~ Methods ================================================================
+
+ protected void securityCheck(int changeType) {
+ if (SecurityContextHolder.getContext() == null || SecurityContextHolder.getContext().getAuthentication() == null || !SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {
+ throw new AccessDeniedException("Authenticated principal required to operate with ACLs");
+ }
+
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ // Check if authorized by virtue of ACL ownership
+ Sid currentUser = new PrincipalSid(authentication);
+ if (currentUser.equals(this.owner) && (changeType == CHANGE_GENERAL || changeType == CHANGE_OWNERSHIP)) {
+ return;
+ }
+
+ // Not authorized by ACL ownership; try via adminstrative permissions
+ GrantedAuthority requiredAuthority = null;
+ if (changeType == CHANGE_AUDITING) {
+ requiredAuthority = this.gaModifyAuditing;
+ } else if (changeType == CHANGE_GENERAL) {
+ requiredAuthority = this.gaGeneralChanges;
+ } else if (changeType == CHANGE_OWNERSHIP) {
+ requiredAuthority = this.gaTakeOwnership;
+ } else {
+ throw new IllegalArgumentException("Unknown change type");
+ }
+
+ // Iterate this principal's authorities to determine right
+ GrantedAuthority[] auths = authentication.getAuthorities();
+ for (int i = 0; i < auths.length; i++) {
+ if (requiredAuthority.equals(auths[i])) {
+ return;
+ }
+ }
+
+ throw new AccessDeniedException("Principal does not have required ACL permissions to perform requested operation");
+ }
+
+ public void deleteAce(Long aceId) throws NotFoundException {
+ securityCheck(CHANGE_GENERAL);
+
+ synchronized (aces) {
+ int offset = findAceOffset(aceId);
+
+ if (offset == 1) {
+ throw new NotFoundException("Requested ACE ID not found");
+ }
+
+ aces.remove(offset);
+ deletedAces.add(aceId);
+ }
+ }
+
+ private int findAceOffset(Long aceId) {
+ Assert.notNull(aceId, "ACE ID is required");
+
+ synchronized (aces) {
+ for (int i = 0; i < aces.size(); i++) {
+ AccessControlEntry ace = (AccessControlEntry) aces.get(i);
+
+ if (ace.getId().equals(aceId)) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ public AccessControlEntry[] getEntries() {
+ // Can safely return AccessControlEntry directly, as they're immutable
+ return (AccessControlEntry[]) aces.toArray(new AccessControlEntry[] {});
+ }
+
+ public void setEntriesInheriting(boolean entriesInheriting) {
+ securityCheck(CHANGE_GENERAL);
+ this.entriesInheriting = entriesInheriting;
+ this.aclDirty = true;
+ }
+
+ public Serializable getId() {
+ return this.id;
+ }
+
+ public ObjectIdentity getObjectIdentity() {
+ return objectIdentity;
+ }
+
+ public Sid getOwner() {
+ return this.owner;
+ }
+
+ public Acl getParentAcl() {
+ return parentAcl;
+ }
+
+ public void insertAce(Long afterAceId, Permission permission, Sid sid,
+ boolean granting) throws NotFoundException {
+ securityCheck(CHANGE_GENERAL);
+ Assert.notNull(permission, "Permission required");
+ Assert.notNull(sid, "Sid required");
+
+ AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this,
+ sid, permission, granting, false, false);
+
+ synchronized (aces) {
+ if (afterAceId != null) {
+ int offset = findAceOffset(afterAceId);
+
+ if (offset == -1) {
+ throw new NotFoundException("Requested ACE ID not found");
+ }
+
+ aces.add(offset + 1, ace);
+ } else {
+ aces.add(ace);
+ }
+
+ }
+
+ this.addedAces = true;
+ }
+
+ public boolean isSidLoaded(Sid[] sids) {
+ // If loadedSides is null, this indicates all SIDs were loaded
+ // Also return true if the caller didn't specify a SID to find
+ if (this.loadedSids == null || sids == null || sids.length == 0) {
+ return true;
+ }
+
+ // This ACL applies to a SID subset. Iterate to check it applies
+ for (int i = 0; i < sids.length; i++) {
+ boolean found = false;
+ for (int y = 0; y < this.loadedSids.length; y++) {
+ if (sids[i].equals(this.loadedSids[y])) {
+ // this SID is OK
+ found = true;
+ break; // out of loadedSids for loop
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean isEntriesInheriting() {
+ return entriesInheriting;
+ }
+
+ /**
+ * Determines authorization. The order of the permission and
+ * sid arguments is extremely important! The method
+ * will iterate through each of the permissions in the order
+ * specified. For each iteration, all of the sids will be
+ * considered, again in the order they are presented. The iteration of
+ * each permission:sid combination will then inspect the ACEs
+ * in the order they appear in the ACL. When the first full match
+ * is found (ie an ACE that has the SID currently being searched for and
+ * the exact permission bit mask being search for), the grant or deny flag
+ * for that ACE will prevail. If the ACE specifies to grant access, the
+ * method will return true. If the ACE specifies to deny
+ * access, the loop will stop and the next permission
+ * iteration will be performed. If each permission indicates to deny
+ * access, the first deny ACE found will be considered the reason for the
+ * failure (as it was the first match found, and is therefore the one most
+ * logically requiring changes - although not always). If absolutely no
+ * matching ACE was found at all for any permission, the parent ACL will
+ * be tried (provided that there is a parent and {@link
+ * #isEntriesInheriting()} is true. The parent ACL will also
+ * scan its parent and so on. If ultimately no matching ACE is found, a
+ * NotFoundException will be thrown and the caller will need
+ * to decide how to handle the permission check. Similarly, if any of the
+ * passed SIDs were not loaded by the ACL, the
+ * UnloadedSidException will be thrown.
+ *
+ * @param permission the exact permissions to scan for (order is important)
+ * @param sids the exact SIDs to scan for (order is important)
+ * @param administrativeMode if true denotes the query is for
+ * administrative purposes and no auditing will be undertaken
+ *
+ * @return true if one of the permissions has been granted,
+ * false if one of the permissions has been
+ * specifically revoked
+ *
+ * @throws NotFoundException if an exact ACE for one of the permission bit
+ * masks and SID combination could not be found
+ * @throws UnloadedSidException if the passed SIDs are unknown to this ACL
+ * because the ACL was only loaded for a subset of SIDs
+ */
+ public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
+ throws NotFoundException, UnloadedSidException {
+ Assert.notEmpty(permission, "Permissions required");
+ Assert.notEmpty(sids, "SIDs required");
+
+ if (!this.isSidLoaded(sids)) {
+ throw new UnloadedSidException("ACL was not loaded for one or more SID");
+ }
+
+ AccessControlEntry firstRejection = null;
+
+ for (int i = 0; i < permission.length; i++) {
+ for (int x = 0; x < sids.length; x++) {
+ // Attempt to find exact match for this permission mask and SID
+ Iterator acesIterator = aces.iterator();
+ boolean scanNextSid = true;
+
+ while (acesIterator.hasNext()) {
+ AccessControlEntry ace = (AccessControlEntry) acesIterator
+ .next();
+
+ if ((ace.getPermission().getMask() == permission[i].getMask())
+ && ace.getSid().equals(sids[x])) {
+ // Found a matching ACE, so its authorization decision will prevail
+ if (ace.isGranting()) {
+ // Success
+ if (!administrativeMode) {
+ auditLogger.logIfNeeded(true, ace);
+ }
+
+ return true;
+ } else {
+ // Failure for this permission, so stop search
+ // We will see if they have a different permission
+ // (this permission is 100% rejected for this SID)
+ if (firstRejection == null) {
+ // Store first rejection for auditing reasons
+ firstRejection = ace;
+ }
+
+ scanNextSid = false; // helps break the loop
+
+ break; // exit "aceIterator" while loop
+ }
+ }
+ }
+
+ if (!scanNextSid) {
+ break; // exit SID for loop (now try next permission)
+ }
+ }
+ }
+
+ if (firstRejection != null) {
+ // We found an ACE to reject the request at this point, as no
+ // other ACEs were found that granted a different permission
+
+ if (!administrativeMode) {
+ auditLogger.logIfNeeded(false, firstRejection);
+ }
+
+ return false;
+ }
+
+ // No matches have been found so far
+ if (isEntriesInheriting() && (parentAcl != null)) {
+ // We have a parent, so let them try to find a matching ACE
+ return parentAcl.isGranted(permission, sids, false);
+ } else {
+ // We either have no parent, or we're the uppermost parent
+ throw new NotFoundException(
+ "Unable to locate a matching ACE for passed permissions and SIDs");
+ }
+ }
+
+ public void setOwner(Sid newOwner) {
+ securityCheck(CHANGE_OWNERSHIP);
+ Assert.notNull(newOwner, "Owner required");
+ this.owner = newOwner;
+ this.aclDirty = true;
+ }
+
+ public void setParent(MutableAcl newParent) {
+ securityCheck(CHANGE_GENERAL);
+ Assert.notNull(newParent, "New Parent required");
+ this.parentAcl = newParent;
+ this.aclDirty = true;
+ }
+
+ public void updateAce(Long aceId, Permission permission)
+ throws NotFoundException {
+ securityCheck(CHANGE_GENERAL);
+ synchronized (aces) {
+ int offset = findAceOffset(aceId);
+
+ if (offset == 1) {
+ throw new NotFoundException("Requested ACE ID not found");
+ }
+
+ AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
+ ace.setPermission(permission);
+ }
+
+ this.updatedAces = true;
+ }
+
+ public void updateAuditing(Long aceId, boolean auditSuccess,
+ boolean auditFailure) {
+ securityCheck(CHANGE_AUDITING);
+
+ synchronized (aces) {
+ int offset = findAceOffset(aceId);
+
+ if (offset == 1) {
+ throw new NotFoundException("Requested ACE ID not found");
+ }
+
+ AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
+ ace.setAuditSuccess(auditSuccess);
+ ace.setAuditFailure(auditFailure);
+ }
+ this.updatedAces = true;
+ }
+
+ /**
+ * Clears the dirty flags on the Acl, but not any
+ * associated ACEs.
+ */
+ public void clearDirtyFlags() {
+ this.aclDirty = false;
+ this.addedAces = false;
+ this.updatedAces = false;
+ }
+
+ public boolean isAclDirty() {
+ return aclDirty;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("AclImpl[");
+ sb.append("id: ").append(this.id).append("; ");
+ sb.append("objectIdentity: ").append(this.objectIdentity).append("; ");
+ sb.append("owner: ").append(this.owner).append("; ");
+ Iterator iterator = this.aces.iterator();
+ int count = 0;
+ while (iterator.hasNext()) {
+ count++;
+ if (count == 1) {
+ sb.append("\r\n");
+ }
+ sb.append(iterator.next().toString()).append("\r\n");
+ }
+ sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
+ sb.append("parent: ").append(this.parentAcl == null ? "Null" : this.parentAcl.getObjectIdentity());
+ sb.append("]");
+ return sb.toString();
+ }
+
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/domain/AuditLogger.java b/sandbox/src/main/java/org/acegisecurity/acls/domain/AuditLogger.java
new file mode 100644
index 0000000000..538816fc5c
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/domain/AuditLogger.java
@@ -0,0 +1,14 @@
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AccessControlEntry;
+
+/**
+ * Used by AclImpl to log audit events.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface AuditLogger {
+ public void logIfNeeded(boolean granted, AccessControlEntry ace);
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/domain/BasePermission.java b/sandbox/src/main/java/org/acegisecurity/acls/domain/BasePermission.java
new file mode 100644
index 0000000000..143a513e98
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/domain/BasePermission.java
@@ -0,0 +1,66 @@
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AclFormattingUtils;
+import org.acegisecurity.acls.Permission;
+
+public class BasePermission implements Permission {
+ public static final Permission READ = new BasePermission(1<<0, 'R'); // 1
+ public static final Permission WRITE = new BasePermission(1<<1, 'W'); // 2
+ public static final Permission CREATE = new BasePermission(1<<2, 'C'); // 4
+ public static final Permission ADMINISTRATION = new BasePermission(1<<3, 'A'); // 8
+
+ private int mask;
+ private char code;
+
+ private BasePermission(int mask, char code) {
+ this.mask = mask;
+ this.code = code;
+ }
+
+ public boolean equals(Object arg0) {
+ if (!(arg0 instanceof BasePermission)) {
+ return false;
+ }
+ BasePermission rhs = (BasePermission) arg0;
+ return (this.mask == rhs.getMask());
+ }
+
+ /**
+ * Dynamically creates a CumulativePermission
+ * representing the active bits in the passed mask.
+ * NB: Only uses BasePermission!
+ *
+ * @param mask to review
+ */
+ public static Permission buildFromMask(int mask) {
+ CumulativePermission permission = new CumulativePermission();
+
+ // TODO: Write the rest of it to iterate through the 32 bits and instantiate BasePermissions
+ if (mask == 1) {
+ permission.set(READ);
+ }
+ if (mask == 2) {
+ permission.set(WRITE);
+ }
+ if (mask == 4) {
+ permission.set(CREATE);
+ }
+ if (mask == 8) {
+ permission.set(ADMINISTRATION);
+ }
+ return permission;
+ }
+
+ public int getMask() {
+ return mask;
+ }
+
+ public String toString() {
+ return "BasePermission[" + getPattern() + "=" + mask + "]";
+ }
+
+ public String getPattern() {
+ return AclFormattingUtils.printBinary(mask, code);
+ }
+
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/domain/ConsoleAuditLogger.java b/sandbox/src/main/java/org/acegisecurity/acls/domain/ConsoleAuditLogger.java
new file mode 100644
index 0000000000..fe44081fc6
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/domain/ConsoleAuditLogger.java
@@ -0,0 +1,19 @@
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.AuditableAccessControlEntry;
+import org.springframework.util.Assert;
+
+public class ConsoleAuditLogger implements AuditLogger {
+ public void logIfNeeded(boolean granted, AccessControlEntry ace) {
+ Assert.notNull(ace, "AccessControlEntry required");
+ if (ace instanceof AuditableAccessControlEntry) {
+ AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace;
+ if (granted && auditableAce.isAuditSuccess()) {
+ System.out.println("GRANTED due to ACE: " + ace);
+ } else if (!granted && auditableAce.isAuditFailure()) {
+ System.out.println("DENIED due to ACE: " + ace);
+ }
+ }
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/domain/CumulativePermission.java b/sandbox/src/main/java/org/acegisecurity/acls/domain/CumulativePermission.java
new file mode 100644
index 0000000000..bcdb0f7b96
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/domain/CumulativePermission.java
@@ -0,0 +1,84 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AclFormattingUtils;
+import org.acegisecurity.acls.Permission;
+
+
+/**
+ * Represents a Permission that is constructed at runtime from
+ * other permissions.
+ *
+ *
+ * Methods return this, in order to facilitate method chaining.
+ *
+ * NB: This implementation does attempt to provide reasonably optimised lookups
+ * - within the constraints of a normalised database and standard ANSI SQL
+ * features. If you are willing to sacrifice either of these constraints (eg
+ * use a particular database feature such as hierarchical queries or
+ * materalized views, or reduce normalisation) you are likely to achieve better
+ * performance. In such situations you will need to provide your own custom
+ * LookupStrategy. This class does not support subclassing, as
+ * it is likely to change in future releases and therefore subclassing is
+ * unsupported.
+ *
+ * WARNING: This implementation completely disregards the "sids" argument! + * Every item in the cache is expected to contain all SIDs. + * + *
+ * The implementation works in batch sizes specfied by {@link #batchSize}.
+ *
+ */
+ public Map readAclsById(ObjectIdentity[] objects, Sid[] sids)
+ throws NotFoundException {
+ Assert.isTrue(batchSize >= 1, "BatchSize must be >= 1");
+ Assert.notEmpty(objects, "Objects to lookup required");
+
+ Map result = new HashMap(); // contains FULLY loaded Acl objects
+
+ Set currentBatchToLoad = new HashSet(); // contains ObjectIdentitys
+
+ for (int i = 0; i < objects.length; i++) {
+ // Check we don't already have this ACL in the results
+ if (result.containsKey(objects[i])) {
+ continue; // already in results, so move to next element
+ }
+
+ // Check cache for the present ACL entry
+ Acl acl = aclCache.getFromCache(objects[i]);
+
+ // Ensure any cached element supports all the requested SIDs
+ // (they should always, as our base impl doesn't filter on SID)
+ if (acl != null) {
+ if (acl.isSidLoaded(sids)) {
+ result.put(acl.getObjectIdentity(), acl);
+ continue; // now in results, so move to next element
+ } else {
+ throw new IllegalStateException(
+ "Error: SID-filtered element detected when implementation does not perform SID filtering - have you added something to the cache manually?");
+ }
+ }
+
+ // To get this far, we have no choice but to retrieve it via JDBC
+ // (although we don't do it until we get a batch of them to load)
+ currentBatchToLoad.add(objects[i]);
+
+ // Is it time to load from JDBC the currentBatchToLoad?
+ if ((currentBatchToLoad.size() == this.batchSize) || (i+1 == objects.length)) {
+ Map loadedBatch = lookupObjectIdentities((ObjectIdentity[]) currentBatchToLoad
+ .toArray(new ObjectIdentity[] {}));
+
+ // Add loaded batch (all elements 100% initialized) to results
+ result.putAll(loadedBatch);
+
+ // Add the loaded batch to the cache
+ Iterator loadedAclIterator = loadedBatch.values().iterator();
+
+ while (loadedAclIterator.hasNext()) {
+ aclCache.putInCache((AclImpl) loadedAclIterator.next());
+ }
+
+ currentBatchToLoad.clear();
+ }
+ }
+
+ // TODO: Now we're done, check every requested object identity was found (throw NotFoundException if needed)
+
+ return result;
+ }
+
+ /**
+ * Looks up a batch of ObjectIdentitys directly from the
+ * database.
+ *
+ *
+ * The caller is responsible for optimization issues, such as selecting the + * identities to lookup, ensuring the cache doesn't contain them already, + * and adding the returned elements to the cache etc. + *
+ * + *
+ * This subclass is required to return fully valid Acls,
+ * including properly-configured parent ACLs.
+ *
ResultSet row, and converts it into
+ * an AclImpl that contains a StubAclParent
+ *
+ * @param acls the Map we should add the converted Acl to
+ * @param rs the ResultSet focused on a current row
+ * @throws SQLException if something goes wrong converting values
+ */
+ private void convertCurrentResultIntoObject(Map acls, ResultSet rs) throws SQLException {
+ Long id = new Long(rs.getLong("ACL_ID"));
+
+ // If we already have an ACL for this ID, just create the ACE
+ AclImpl acl = (AclImpl) acls.get(id);
+
+ if (acl == null) {
+ // Make an AclImpl and pop it into the Map
+ ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString(
+ "CLASS"), new Long(rs.getLong("OBJECT_ID_IDENTITY")));
+
+ Acl parentAcl = null;
+ long parentAclId = rs.getLong("PARENT_OBJECT");
+
+ if (parentAclId != 0) {
+ parentAcl = new StubAclParent(new Long(parentAclId));
+ }
+
+ boolean entriesInheriting = rs.getBoolean("ENTRIES_INHERITING");
+ Sid owner;
+
+ if (rs.getBoolean("ACL_PRINCIPAL")) {
+ owner = new PrincipalSid(rs.getString("ACL_SID"));
+ } else {
+ owner = new GrantedAuthoritySid(rs.getString("ACL_SID"));
+ }
+
+ acl = new AclImpl(objectIdentity, id, parentAcl, auths, null,
+ entriesInheriting, owner);
+ acls.put(id, acl);
+ }
+
+ // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order)
+ Long aceId = new Long(rs.getLong("ACE_ID"));
+ Sid recipient;
+
+ if (rs.getBoolean("ACE_PRINCIPAL")) {
+ recipient = new PrincipalSid(rs.getString("ACE_SID"));
+ } else {
+ recipient = new GrantedAuthoritySid(rs.getString("ACE_SID"));
+ }
+
+ Permission permission = BasePermission.buildFromMask(rs.getInt("MASK"));
+ boolean granting = rs.getBoolean("GRANTING");
+ boolean auditSuccess = rs.getBoolean("AUDIT_SUCCESS");
+ boolean auditFailure = rs.getBoolean("AUDIT_FAILURE");
+
+ AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl,
+ recipient, permission, granting, auditSuccess, auditFailure);
+
+
+ Field acesField = getAccessibleField(AclImpl.class, "aces");
+ List aces;
+
+ try {
+ aces = (List) acesField.get(acl);
+ } catch (IllegalAccessException ex) {
+ throw new IllegalStateException(
+ "Could not obtain AclImpl.ace field", ex);
+ }
+
+ // Add the ACE if it doesn't already exist in the ACL.aces field
+ if (!aces.contains(ace)) {
+ aces.add(ace);
+ }
+ }
+
+ /**
+ * The final phase of converting the Map of AclImpl instances
+ * which contain StubAclParents into proper, valid AclImpls with
+ * correct ACL parents.
+ *
+ * @param inputMap the unconverted AclImpls
+ * @param inputAcl the current Acl that we wish to convert (this may be
+ * @return
+ */
+ private AclImpl convert(Map inputMap, Long currentIdentity) {
+ Assert.notEmpty(inputMap, "InputMap required");
+ Assert.notNull(currentIdentity, "CurrentIdentity required");
+
+ // Retrieve this Acl from the InputMap
+ Acl uncastAcl = (Acl) inputMap.get(currentIdentity);
+ Assert.isInstanceOf(AclImpl.class, uncastAcl, "The inputMap contained a non-AclImpl");
+ AclImpl inputAcl = (AclImpl) uncastAcl;
+
+ Acl parent = inputAcl.getParentAcl();
+ if (parent != null && parent instanceof StubAclParent) {
+ // Lookup the parent
+ StubAclParent stubAclParent = (StubAclParent) parent;
+ parent = convert(inputMap, stubAclParent.getId());
+ }
+
+ // Now we have the parent (if there is one), create the true AclImpl
+ AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), (Long)inputAcl.getId(), parent, auths, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner());
+
+ // Copy the "aces" from the input to the destination
+ Field field = getAccessibleField(AclImpl.class, "aces");
+ try {
+ field.set(result, field.get(inputAcl));
+ } catch (IllegalAccessException ex) {
+ throw new IllegalStateException("Could not obtain or set AclImpl.ace field");
+ }
+
+ return result;
+ }
+
+ private static String computeRepeatingSql(String repeatingSql, int requiredRepetitions) {
+ Assert.isTrue(requiredRepetitions >= 1, "Must be => 1");
+
+ String startSql = "select ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY, ACL_ENTRY.ACE_ORDER, "
+ + "ACL_OBJECT_IDENTITY.ID as ACL_ID, "
+ + "ACL_OBJECT_IDENTITY.PARENT_OBJECT, "
+ + "ACL_OBJECT_IDENTITY,ENTRIES_INHERITING, "
+ + "ACL_ENTRY.ID as ACE_ID, ACL_ENTRY.MASK, ACL_ENTRY.GRANTING, ACL_ENTRY.AUDIT_SUCCESS, ACL_ENTRY.AUDIT_FAILURE, "
+ + "ACE_SID.PRINCIPAL as ACE_PRINCIPAL, ACE_SID.SID as ACE_SID, "
+ + "ACL_SID.PRINCIPAL as ACL_PRINCIPAL, ACL_SID.SID as ACL_SID, "
+ + "ACL_CLASS.CLASS "
+ + "from ACL_OBJECT_IDENTITY, ACL_ENTRY, ACL_SID ACE_SID, ACL_SID ACL_SID, ACL_CLASS "
+ + "where ACL_ENTRY.ACL_OBJECT_IDENTITY = ACL_OBJECT_IDENTITY.ID "
+ + "and ACE_SID.ID = ACL_ENTRY.SID "
+ + "and ACL_SID.ID = ACL_OBJECT_IDENTITY.OWNER_SID "
+ + "and ACL_CLASS.ID = ACL_OBJECT_IDENTITY.OBJECT_ID_CLASS "
+ + "and ( ";
+
+ String endSql = ") order by ACL_ENTRY.ACL_OBJECT_IDENTITY asc, ACL_ENTRY.ACE_ORDER asc";
+
+ StringBuffer sqlStringBuffer = new StringBuffer();
+ sqlStringBuffer.append(startSql);
+
+ for (int i = 1; i <= requiredRepetitions; i++) {
+ sqlStringBuffer.append(repeatingSql);
+
+ if (i != requiredRepetitions) {
+ sqlStringBuffer.append(" or ");
+ }
+ }
+
+ sqlStringBuffer.append(endSql);
+
+ return sqlStringBuffer.toString();
+ }
+
+ private static Field getAccessibleField(Class clazz, String protectedField) {
+ Field field = null;
+
+ try {
+ field = clazz.getDeclaredField(protectedField);
+ } catch (NoSuchFieldException nsf) {}
+
+ if (field == null) {
+ // Unable to locate, so try the superclass (if there is one)
+ if (clazz.getSuperclass() != null) {
+ getAccessibleField(clazz.getSuperclass(), protectedField);
+ } else {
+ throw new IllegalArgumentException("Couldn't find '"
+ + protectedField + "' field");
+ }
+ }
+
+ // We have a field, so process
+ field.setAccessible(true);
+
+ return field;
+ }
+
+ //~ Inner Classes ==========================================================
+
+ private class StubAclParent implements Acl {
+ private Long id;
+
+ public StubAclParent(Long id) {
+ this.id = id;
+ }
+
+ public AccessControlEntry[] getEntries() {
+ throw new UnsupportedOperationException("Stub only");
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public ObjectIdentity getObjectIdentity() {
+ throw new UnsupportedOperationException("Stub only");
+ }
+
+ public Acl getParentAcl() {
+ throw new UnsupportedOperationException("Stub only");
+ }
+
+ public boolean isEntriesInheriting() {
+ throw new UnsupportedOperationException("Stub only");
+ }
+
+ public boolean isGranted(Permission[] permission, Sid[] sids,
+ boolean administrativeMode)
+ throws NotFoundException, UnloadedSidException {
+ throw new UnsupportedOperationException("Stub only");
+ }
+
+ public boolean isSidLoaded(Sid[] sids) {
+ throw new UnsupportedOperationException("Stub only");
+ }
+ }
+
+ private class ProcessResultSet implements ResultSetExtractor {
+ private Map acls;
+
+ public ProcessResultSet(Map acls) {
+ Assert.notNull(acls, "ACLs cannot be null");
+ this.acls = acls;
+ }
+
+ public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
+ Set parentIdsToLookup = new HashSet(); // Set of parent_id Longs
+
+ while (rs.next()) {
+ // Convert current row into an Acl (albeit with a StubAclParent)
+ convertCurrentResultIntoObject(acls, rs);
+
+ // Figure out if this row means we need to lookup another parent
+ long parentId = rs.getLong("PARENT_OBJECT");
+
+ if (parentId != 0) {
+ // See if its already in the "acls"
+ if (acls.containsKey(new Long(parentId))) {
+ continue; // skip this while element
+ }
+
+ // Now try to find it in the cache
+ Acl cached = aclCache.getFromCache(new Long(parentId));
+
+ if (cached == null) {
+ parentIdsToLookup.add(new Long(parentId));
+ } else {
+ // Pop into the acls map, so our convert method doesn't
+ // need to deal with an unsynchronized AclCache
+ Assert.isInstanceOf(AclImpl.class, cached, "Cached ACL must be an AclImpl");
+ acls.put(((AclImpl)cached).getId(), cached);
+ }
+ }
+ }
+
+ // Lookup parents, adding Acls (with StubAclParents) to "acl" map
+ if (parentIdsToLookup.size() > 0) {
+ lookupPrimaryKeys(acls, parentIdsToLookup);
+ }
+
+ // Return null to meet ResultSetExtractor method contract
+ return null;
+ }
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/jdbc/EhCacheBasedAclCache.java b/sandbox/src/main/java/org/acegisecurity/acls/jdbc/EhCacheBasedAclCache.java
new file mode 100644
index 0000000000..f50913b6e3
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/jdbc/EhCacheBasedAclCache.java
@@ -0,0 +1,58 @@
+package org.acegisecurity.acls.jdbc;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.Element;
+
+import org.acegisecurity.acls.domain.AclImpl;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.springframework.util.Assert;
+
+public class EhCacheBasedAclCache implements AclCache {
+
+ private Cache cache;
+
+ public EhCacheBasedAclCache(Cache cache) {
+ Assert.notNull(cache, "Cache required");
+ this.cache = cache;
+ }
+
+ public AclImpl getFromCache(ObjectIdentity objectIdentity) {
+ Element element = null;
+ try {
+ element = cache.get(objectIdentity);
+ } catch (CacheException ignored) {}
+ if (element == null) {
+ return null;
+ }
+ return (AclImpl) element.getValue();
+ }
+
+ public AclImpl getFromCache(Long pk) {
+ Element element = null;
+ try {
+ element = cache.get(pk);
+ } catch (CacheException ignored) {}
+ if (element == null) {
+ return null;
+ }
+ return (AclImpl) element.getValue();
+ }
+
+ public void putInCache(AclImpl acl) {
+ if (acl.getParentAcl() != null && acl.getParentAcl() instanceof AclImpl) {
+ putInCache((AclImpl)acl.getParentAcl());
+ }
+ cache.put(new Element(acl.getObjectIdentity(), acl));
+ cache.put(new Element(acl.getId(), acl));
+ }
+
+ public void evictFromCache(Long pk) {
+ AclImpl acl = getFromCache(pk);
+ if (acl != null) {
+ cache.remove(pk);
+ cache.remove(acl.getObjectIdentity());
+ }
+ }
+
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/jdbc/JdbcAclService.java b/sandbox/src/main/java/org/acegisecurity/acls/jdbc/JdbcAclService.java
new file mode 100644
index 0000000000..e0b61ff2b5
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/jdbc/JdbcAclService.java
@@ -0,0 +1,51 @@
+package org.acegisecurity.acls.jdbc;
+
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.sid.Sid;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.util.Assert;
+
+/**
+ * Simple JDBC-based implementation of AclService.
+ *
+ *
+ * Requires the "dirty" flags in {@link org.acegisecurity.acls.domain.AclImpl} and {@link org.acegisecurity.acls.domain.AccessControlEntryImpl}
+ * to be set, so that the implementation can detect changed parameters easily.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class JdbcAclService implements AclService/*, MutableAclService */ {
+
+ private AclCache aclCache;
+ private JdbcTemplate template;
+ private LookupStrategy lookupStrategy;
+
+ public JdbcAclService(DataSource dataSource, AclCache aclCache, LookupStrategy lookupStrategy) {
+ Assert.notNull(dataSource, "DataSource required");
+ Assert.notNull(aclCache, "AclCache required");
+ Assert.notNull(lookupStrategy, "LookupStrategy required");
+ this.template = new JdbcTemplate(dataSource);
+ this.aclCache = aclCache;
+ this.lookupStrategy = lookupStrategy;
+ }
+
+ public Map readAclsById(ObjectIdentity[] objects) {
+ return readAclsById(objects, null);
+ }
+
+ /**
+ * Method required by interface.
+ */
+ public Map readAclsById(ObjectIdentity[] objects, Sid[] sids) throws NotFoundException {
+ return lookupStrategy.readAclsById(objects, sids);
+ }
+
+
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/jdbc/LookupStrategy.java b/sandbox/src/main/java/org/acegisecurity/acls/jdbc/LookupStrategy.java
new file mode 100644
index 0000000000..eb5bec6e90
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/jdbc/LookupStrategy.java
@@ -0,0 +1,47 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls.jdbc;
+
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.sid.Sid;
+
+import java.util.Map;
+
+
+/**
+ * Performs optimised lookups for {@link JdbcAclService}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface LookupStrategy {
+ //~ Methods ================================================================
+
+ /**
+ * Perform database-specific optimized lookup.
+ *
+ * @param objects the identities to lookup (required)
+ * @param sids the SIDs for which identities are required (may be
+ * null - implementations may elect not to provide SID
+ * optimisations)
+ *
+ * @return the Map pursuant to the interface contract for
+ * {@link
+ * org.acegisecurity.acls.AclService#readAclsById(ObjectIdentity[],
+ * Sid[])}
+ */
+ public Map readAclsById(ObjectIdentity[] objects, Sid[] sids);
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentity.java b/sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentity.java
new file mode 100644
index 0000000000..7721ebd40f
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentity.java
@@ -0,0 +1,78 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls.objectidentity;
+
+import java.io.Serializable;
+
+/**
+ * Interface representing the identity of an individual domain object instance.
+ *
+ *
+ * As implementations are used as the key for caching and lookup, 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 ObjectIdentitys equal if
+ * identity1.equals(identity2), rather than reference-equality of
+ * identity1==identity2.
+ *
java.lang.Object documentation for the
+ * interface contract.
+ *
+ * @param obj to be compared
+ *
+ * @return true if the objects are equal, false
+ * otherwise
+ */
+ public boolean equals(Object obj);
+
+ /**
+ * Obtains the actual identifier. This identifier must not be reused to
+ * represent other domain objects with the same javaType.
+ *
+ * + * Because ACLs are largely immutable, it is strongly recommended to use a + * synthetic identifier (such as a database sequence number for the + * primary key). Do not use an identifier with business meaning, as that + * business meaning may change. + *
+ * + * @return the identifier (unique within thisjavaType
+ */
+ public Serializable getIdentifier();
+
+ /**
+ * Obtains the Java type represented by the domain object.
+ *
+ * @return the Java type of the domain object
+ */
+ public Class getJavaType();
+
+ /**
+ * Refer to the java.lang.Object documentation for the
+ * interface contract.
+ *
+ * @return a hash code representation of this object
+ */
+ public int hashCode();
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityImpl.java b/sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityImpl.java
new file mode 100644
index 0000000000..29425d5ad6
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityImpl.java
@@ -0,0 +1,157 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls.objectidentity;
+
+import org.acegisecurity.acl.basic.AclObjectIdentity;
+
+import org.acegisecurity.acls.IdentityUnavailableException;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+import java.io.Serializable;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * Simple implementation of {@link AclObjectIdentity}.
+ *
+ *
+ * Uses Strings to store the identity of the domain object
+ * instance. Also offers a constructor that uses reflection to build the
+ * identity information.
+ *
NamedEntityObjectIdentity based on the passed
+ * object instance. The passed object must provide a getId()
+ * method, otherwise an exception will be thrown.
+ *
+ * @param object the domain object instance to create an identity for
+ *
+ * @throws IdentityUnavailableException if identity could not be extracted
+ */
+ public ObjectIdentityImpl(Object object)
+ throws IdentityUnavailableException {
+ Assert.notNull(object, "object cannot be null");
+
+ this.javaType = object.getClass();
+
+ Object result;
+
+ try {
+ Method method = this.javaType.getMethod("getId", new Class[] {});
+ result = method.invoke(object, new Object[] {});
+ } catch (Exception e) {
+ throw new IdentityUnavailableException(
+ "Could not extract identity from object " + object, e);
+ }
+
+ Assert.isInstanceOf(Serializable.class, result,
+ "Getter must provide a return value of type Serializable");
+ this.identifier = (Serializable) result;
+ }
+
+ //~ Methods ================================================================
+
+ /**
+ * Important so caching operates properly.
+ *
+ *
+ * Considers an object of the same class equal if it has the same
+ * classname and id properties.
+ *
true if the presented object matches this object
+ */
+ public boolean equals(Object arg0) {
+ if (arg0 == null) {
+ return false;
+ }
+
+ if (!(arg0 instanceof ObjectIdentityImpl)) {
+ return false;
+ }
+
+ ObjectIdentityImpl other = (ObjectIdentityImpl) arg0;
+
+ if (this.getIdentifier().equals(other.getIdentifier())
+ && this.getJavaType().equals(other.getJavaType())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public Serializable getIdentifier() {
+ return identifier;
+ }
+
+ public Class getJavaType() {
+ return javaType;
+ }
+
+ /**
+ * Important so caching operates properly.
+ *
+ * @return the hash
+ */
+ public int hashCode() {
+ int code = 31;
+ code ^= this.javaType.hashCode();
+ code ^= this.identifier.hashCode();
+
+ return code;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(this.getClass().getName()).append("[");
+ sb.append("Java Type: ").append(this.javaType);
+ sb.append("; Identifier: ").append(this.identifier).append("]");
+
+ return sb.toString();
+ }
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/package.html b/sandbox/src/main/java/org/acegisecurity/acls/package.html
new file mode 100644
index 0000000000..692d065470
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/package.html
@@ -0,0 +1,5 @@
+
+
+Enables retrieval of access control lists (ACLs) for domain object instances.
+
+
diff --git a/sandbox/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java b/sandbox/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java
new file mode 100644
index 0000000000..5794ebc953
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java
@@ -0,0 +1,74 @@
+/* Copyright 2004, 2005, 2006 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 org.acegisecurity.acls.sid;
+
+import org.acegisecurity.GrantedAuthority;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Represents a GrantedAuthority as a Sid.
+ *
+ *
+ * This is a basic implementation that simply uses the
+ * String-based principal for Sid comparison. More
+ * complex principal objects may wish to provide an alternative
+ * Sid implementation that uses some other identifier.
+ *
Authentication.getPrincipal() as a
+ * Sid.
+ *
+ *
+ * This is a basic implementation that simply uses the
+ * String-based principal for Sid comparison. More
+ * complex principal objects may wish to provide an alternative
+ * Sid implementation that uses some other identifier.
+ *
+ * This interface provides indirection between actual security objects (eg
+ * principals, roles, groups etc) and what is stored inside an
+ * Acl. This is because an Acl will not store an
+ * entire security object, but only an abstraction of it. This interface
+ * therefore provides a simple way to compare these abstracted security
+ * identities with other security identities and actual security objects.
+ *
java.lang.Object documentation for the
+ * interface contract.
+ *
+ * @param obj to be compared
+ *
+ * @return true if the objects are equal, false
+ * otherwise
+ */
+ public boolean equals(Object obj);
+
+ /**
+ * Refer to the java.lang.Object documentation for the
+ * interface contract.
+ *
+ * @return a hash code representation of this object
+ */
+ public int hashCode();
+}
diff --git a/sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java b/sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java
new file mode 100644
index 0000000000..450f625f6b
--- /dev/null
+++ b/sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java
@@ -0,0 +1,41 @@
+package org.acegisecurity.acls.domain;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests BasePermission and CumulativePermission.
+ *
+ * @author Ben Alex
+ * @version $Id${date}
+ *
+ */
+public class PermissionTests extends TestCase {
+ public void testStringConversion() {
+ System.out.println("R = " + BasePermission.READ.toString());
+ assertEquals("BasePermission[...............................R=1]", BasePermission.READ.toString());
+
+ System.out.println("A = " + BasePermission.ADMINISTRATION.toString());
+ assertEquals("BasePermission[............................A...=8]", BasePermission.ADMINISTRATION.toString());
+
+ System.out.println("R = " + new CumulativePermission().set(BasePermission.READ).toString());
+ assertEquals("CumulativePermission[...............................R=1]", new CumulativePermission().set(BasePermission.READ).toString());
+
+ System.out.println("A = " + new CumulativePermission().set(BasePermission.ADMINISTRATION).toString());
+ assertEquals("CumulativePermission[............................A...=8]", new CumulativePermission().set(BasePermission.ADMINISTRATION).toString());
+
+ System.out.println("RA = " + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString());
+ assertEquals("CumulativePermission[............................A..R=9]", new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString());
+
+ System.out.println("R = " + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).clear(BasePermission.ADMINISTRATION).toString());
+ assertEquals("CumulativePermission[...............................R=1]", new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).clear(BasePermission.ADMINISTRATION).toString());
+
+ System.out.println("0 = " + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).clear(BasePermission.ADMINISTRATION).clear(BasePermission.READ).toString());
+ assertEquals("CumulativePermission[................................=0]", new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).clear(BasePermission.ADMINISTRATION).clear(BasePermission.READ).toString());
+ }
+
+ public void testExpectedIntegerValues() {
+ assertEquals(1, BasePermission.READ.getMask());
+ assertEquals(8, BasePermission.ADMINISTRATION.getMask());
+ assertEquals(9, new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask());
+ }
+}
diff --git a/sandbox/src/test/java/org/acegisecurity/acls/jdbc/DatabaseSeeder.java b/sandbox/src/test/java/org/acegisecurity/acls/jdbc/DatabaseSeeder.java
new file mode 100644
index 0000000000..d173bc08a4
--- /dev/null
+++ b/sandbox/src/test/java/org/acegisecurity/acls/jdbc/DatabaseSeeder.java
@@ -0,0 +1,23 @@
+package org.acegisecurity.acls.jdbc;
+
+import java.io.IOException;
+
+import javax.sql.DataSource;
+
+import org.springframework.core.io.Resource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
+
+public class DatabaseSeeder {
+
+ public DatabaseSeeder(DataSource dataSource, Resource resource) throws IOException {
+ Assert.notNull(dataSource, "dataSource required");
+ Assert.notNull(resource, "resource required");
+
+ JdbcTemplate template = new JdbcTemplate(dataSource);
+ String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
+ template.execute(sql);
+ }
+
+}
diff --git a/sandbox/src/test/java/org/acegisecurity/acls/jdbc/JdbcAclServiceTests.java b/sandbox/src/test/java/org/acegisecurity/acls/jdbc/JdbcAclServiceTests.java
new file mode 100644
index 0000000000..f537192674
--- /dev/null
+++ b/sandbox/src/test/java/org/acegisecurity/acls/jdbc/JdbcAclServiceTests.java
@@ -0,0 +1,40 @@
+package org.acegisecurity.acls.jdbc;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
+
+public class JdbcAclServiceTests extends AbstractDependencyInjectionSpringContextTests {
+
+ protected String[] getConfigLocations() {
+ return new String[] {"classpath:org/acegisecurity/acls/jdbc/applicationContext-test.xml"};
+ }
+
+ private JdbcAclService jdbcAclService;
+
+ public void testStub() {
+ ObjectIdentity id1 = new ObjectIdentityImpl("sample.contact.Contact", new Long(1));
+ ObjectIdentity id2 = new ObjectIdentityImpl("sample.contact.Contact", new Long(2));
+ ObjectIdentity id3 = new ObjectIdentityImpl("sample.contact.Contact", new Long(3));
+ ObjectIdentity id4 = new ObjectIdentityImpl("sample.contact.Contact", new Long(4));
+ ObjectIdentity id5 = new ObjectIdentityImpl("sample.contact.Contact", new Long(5));
+ ObjectIdentity id6 = new ObjectIdentityImpl("sample.contact.Contact", new Long(6));
+ Map map = jdbcAclService.readAclsById(new ObjectIdentity[] {id1, id2, id3, id4, id5, id6});
+ Iterator iterator = map.keySet().iterator();
+ while (iterator.hasNext()) {
+ ObjectIdentity identity = (ObjectIdentity) iterator.next();
+ assertEquals(identity, ((Acl)map.get(identity)).getObjectIdentity());
+ System.out.println(map.get(identity));
+ }
+ }
+
+ public void setJdbcAclService(JdbcAclService jdbcAclService) {
+ this.jdbcAclService = jdbcAclService;
+ }
+
+
+}
diff --git a/sandbox/src/test/java/org/acegisecurity/acls/jdbc/applicationContext-test.xml b/sandbox/src/test/java/org/acegisecurity/acls/jdbc/applicationContext-test.xml
new file mode 100644
index 0000000000..50436f7ae0
--- /dev/null
+++ b/sandbox/src/test/java/org/acegisecurity/acls/jdbc/applicationContext-test.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+