SEC-239: Initial commit. Work-in-progress only.

This commit is contained in:
Ben Alex 2006-05-23 08:48:19 +00:00
parent 4d24c88d1e
commit 5ba40705e8
38 changed files with 3163 additions and 0 deletions

View File

@ -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}.
*
* <p>
* Instances MUST be immutable, as they are returned by <code>Acl</code>
* 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 <code>null</code> if unsaved
*/
public Serializable getId();
public Acl getAcl();
public Sid getSid();
public Permission getPermission();
public boolean isGranting();
}

View File

@ -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.
*
* <p>
* An <code>Acl</code> 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.
* </p>
*
* <p>
* An implementation represents the {@link org.acegisecurity.acls.Permission}
* list applicable for some or all {@link org.acegisecurity.acls.sid.Sid}
* instances.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface Acl extends Serializable {
//~ Methods ================================================================
/**
* Returns all of the entries represented by the present <code>Acl</code>
* (not parents).
*
* <p>
* This method is typically used for administrative purposes.
* </p>
*
* <p>
* 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 <em>MUST</em> be ordered
* in that manner.
* </p>
*
* <p>
* Do <em>NOT</em> use this method for making authorization decisions.
* Instead use {@link #isGranted(Permission[], Sid[])}.
* </p>
*
* <p>
* This method must operate correctly even if the <code>Acl</code> only
* represents a subset of <code>Sid</code>s. The caller is responsible for
* correctly handling the result if only a subset of <code>Sid</code>s is
* represented.
* </p>
*
* @return the list of entries represented by the <code>Acl</code>
*/
public AccessControlEntry[] getEntries();
/**
* Obtains the domain object this <code>Acl</code> provides entries for.
* This is immutable once an <code>Acl</code> 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.
*
* <p>
* This method solely represents the presence of a navigation hierarchy
* between the parent <code>Acl</code> and this <code>Acl</code>. For
* actual inheritance to take place, the {@link #isEntriesInheriting()}
* must also be <code>true</code>.
* </p>
*
* <p>
* This method must operate correctly even if the <code>Acl</code> only
* represents a subset of <code>Sid</code>s. The caller is responsible for
* correctly handling the result if only a subset of <code>Sid</code>s is
* represented.
* </p>
*
* @return the parent <code>Acl</code>
*/
public Acl getParentAcl();
/**
* Indicates whether the ACL entries from the {@link #getParentAcl()}
* should flow down into the current <code>Acl</code>.
*
* <p>
* The mere link between an <code>Acl</code> and a parent <code>Acl</code>
* 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.
* </p>
*
* @return <code>true</code> if parent ACL entries inherit into the current
* <code>Acl</code>
*/
public boolean isEntriesInheriting();
/**
* This is the actual authorization logic method, and must be used whenever
* ACL authorization decisions are required.
*
* <p>
* An array of <code>Sid</code>s are presented, representing security
* identifies of the current principal. In addition, an array of
* <code>Permission</code>s 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
* <em>any</em> of the <code>Permission</code>s inside the array will be
* sufficient for an affirmative authorization.
* </p>
*
* <p>
* The actual approach used to make authorization decisions is left to the
* implementation and is not specified by this interface. For example, an
* implementation <em>MAY</em> 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 <code>Permission</code>, 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 <code>Permission</code>s 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
* <code>true</code>, 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.
* </p>
*
* <p>
* This method must operate correctly even if the <code>Acl</code> only
* represents a subset of <code>Sid</code>s. The caller is responsible for
* correctly handling the result if only a subset of <code>Sid</code>s is
* represented.
* </p>
*
* @param permission the permission or permissions required
* @param sids the security identities held by the principal
* @param administrativeMode if <code>true</code> denotes the query is for
* administrative purposes and no logger or auditing (if supported
* by the implementation) should be undertaken
*
* @return <code>true</code> is authorization is granted
*
* @throws NotFoundException MAY be thrown if an implementation cannot make
* an authoritative authorization decision
* @throws UnloadedSidException thrown if the <code>Acl</code> does not
* have details for one or more of the <code>Sid</code>s passed as
* arguments
*/
public boolean isGranted(Permission[] permission, Sid[] sids,
boolean administrativeMode)
throws NotFoundException, UnloadedSidException;
/**
* For efficiency reasons an <code>Acl</code> may be loaded and
* <em>not</em> contain entries for every <code>Sid</code> in the system.
* If an <code>Acl</code> has been loaded and does not represent every
* <code>Sid</code>, all methods of the <code>Sid</code> can only be used
* within the limited scope of the <code>Sid</code> instances it actually
* represents.
*
* <p>
* It is normal to load an <code>Acl</code> for only particular
* <code>Sid</code>s if read-only authorization decisions are being made.
* However, if user interface reporting or modification of
* <code>Acl</code>s are desired, an <code>Acl</code> should be loaded
* with all <code>Sid</code>s. This method denotes whether or not the
* specified <code>Sid</code>s have been loaded or not.
* </p>
*
* @param sids DOCUMENT ME!
*
* @return <code>true</code> if every passed <code>Sid</code> is
* represented by this <code>Acl</code> instance
*/
public boolean isSidLoaded(Sid[] sids);
}

View File

@ -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 "".
*
* <p>
* Inactive bits will be denoted by character {@link
* Permission#RESERVED_OFF}.
* </p>
*
* @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.
*
* <p>
* Inactive bits will be denoted by character {@link
* Permission#RESERVED_OFF}.
* </p>
*
* @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);
}
}

View File

@ -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 the <code>Acl</code>s that apply for the passed
* <code>Object</code>s.
*
* <p>
* The returned map is keyed on the passed objects, with the values being
* the <code>Acl</code> instances. Any unknown objects will not have a map
* key.
* </p>
*
* @param objects the objects to find ACL information for
*
* @return a map with zero or more elements (never <code>null</code>)
*/
public Map readAclsById(ObjectIdentity[] objects) throws NotFoundException;
/**
* Obtains all the <code>Acl</code>s that apply for the passed
* <code>Object</code>s, but only for the security identifies passed.
*
* <p>
* Implementations <em>MAY</em> 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.
* </p>
*
* <p>
* The returned map is keyed on the passed objects, with the values being
* the <code>Acl</code> instances. Any unknown objects (or objects for
* which the interested <code>Sid</code>s do not have entries) will not
* have a map key.
* </p>
*
* @param objects the objects to find ACL information for
* @param sids the security identities for which ACL information is
* required (may be <code>null</code> to denote all entries)
*
* @return a map with zero or more elements (never <code>null</code>)
*/
public Map readAclsById(ObjectIdentity[] objects, Sid[] sids) throws NotFoundException;
}

View File

@ -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 <code>Acl</code> entry already exists for the object.
*
* @author Ben Alex
* @version $Id$
*/
public class AlreadyExistsException extends AcegiSecurityException {
//~ Constructors ===========================================================
/**
* Constructs an <code>AlreadyExistsException</code> with the specified message.
*
* @param msg the detail message
*/
public AlreadyExistsException(String msg) {
super(msg);
}
/**
* Constructs an <code>AlreadyExistsException</code> 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);
}
}

View File

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

View File

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

View File

@ -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
* <code>Acl</code>s exist.
*
* @author Ben Alex
* @version $Id$
*/
public class ChildrenExistException extends AcegiSecurityException {
//~ Constructors ===========================================================
/**
* Constructs an <code>ChildrenExistException</code> with the specified
* message.
*
* @param msg the detail message
*/
public ChildrenExistException(String msg) {
super(msg);
}
/**
* Constructs an <code>ChildrenExistException</code> 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);
}
}

View File

@ -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 <code>IdentityUnavailableException</code> with the specified message.
*
* @param msg the detail message
*/
public IdentityUnavailableException(String msg) {
super(msg);
}
/**
* Constructs an <code>IdentityUnavailableException</code> 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);
}
}

View File

@ -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 <code>Acl</code>.
*
* <p>
* A mutable ACL must ensure that appropriate security checks are performed
* before allowing access to its methods.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface MutableAcl extends Acl {
//~ Methods ================================================================
/**
* Obtains an identifier that represents this <code>MutableAcl</code>.
*
* @return the identifier, or <code>null</code> 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);
}

View File

@ -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 <code>Acl</code> instances.
*
* @author Ben Alex
* @version $Id$
*/
public interface MutableAclService extends AclService {
//~ Methods ================================================================
/**
* Creates an empty <code>Acl</code> 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
* <code>false</code> 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 <code>null</code> if none were found)
*/
public ObjectIdentity[] findChildren(ObjectIdentity parentIdentity);
/**
* Changes an existing <code>Acl</code> 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
* <code>new</code> keyword?)
*/
public void updateAcl(MutableAcl acl) throws NotFoundException;
}

View File

@ -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 <code>NotFoundException</code> with the specified message.
*
* @param msg the detail message
*/
public NotFoundException(String msg) {
super(msg);
}
/**
* Constructs an <code>NotFoundException</code> 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);
}
}

View File

@ -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.
*
* <p>
* Generally the owner of an ACL is able to call any ACL mutator method, as
* well as assign a new owner.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface OwnershipAcl extends MutableAcl {
//~ Methods ================================================================
public Sid getOwner();
public void setOwner(Sid newOwner);
}

View File

@ -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 pattern <code>String</code> representing
* this permission.
*
* <p>
* 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 <code>String</code> containing {@link #RESERVED_ON}.
* </p>
*
* <p>
* The returned String must be 32 characters in length.
* </p>
*
* <p>
* 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.
* </p>
*
* @return a 32-character bit pattern
*/
public String getPattern();
}

View File

@ -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 of <code>Sid</code>s and the caller has requested details
* for an unloaded <code>Sid</code>.
*
* @author Ben Alex
* @version $Id$
*/
public class UnloadedSidException extends AcegiSecurityException {
//~ Constructors ===========================================================
/**
* Constructs an <code>NotFoundException</code> with the specified message.
*
* @param msg the detail message
*/
public UnloadedSidException(String msg) {
super(msg);
}
/**
* Constructs an <code>NotFoundException</code> 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);
}
}

View File

@ -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 <code>AccessControlEntry</code>.
*
* @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();
}
}

View File

@ -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 <code>Acl</code>.
*
* @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 <code>GrantedAuthority</code>s 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.
*
* <p>
* 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 <code>GrantedAuthority</code>s 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 <code>GrantedAuthority</code>s 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 <code>null</code>)
* @param loadedSids the loaded SIDs if only a subset were loaded (may be
* <code>null</code>)
* @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 <code>permission</code> and
* <code>sid</code> arguments is <em>extremely important</em>! The method
* will iterate through each of the <code>permission</code>s in the order
* specified. For each iteration, all of the <code>sid</code>s will be
* considered, again in the order they are presented. The iteration of
* each <code>permission:sid</code> combination will then inspect the ACEs
* in the order they appear in the ACL. When the <em>first full match</em>
* 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 <code>true</code>. If the ACE specifies to deny
* access, the loop will stop and the next <code>permission</code>
* 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 <code>true</code>. The parent ACL will also
* scan its parent and so on. If ultimately no matching ACE is found, a
* <code>NotFoundException</code> 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
* <code>UnloadedSidException</code> 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 <code>true</code> denotes the query is for
* administrative purposes and no auditing will be undertaken
*
* @return <code>true</code> if one of the permissions has been granted,
* <code>false</code> 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 <code>Acl</code>, 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();
}
}

View File

@ -0,0 +1,14 @@
package org.acegisecurity.acls.domain;
import org.acegisecurity.acls.AccessControlEntry;
/**
* Used by <code>AclImpl</code> to log audit events.
*
* @author Ben Alex
* @version $Id$
*
*/
public interface AuditLogger {
public void logIfNeeded(boolean granted, AccessControlEntry ace);
}

View File

@ -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 <code>CumulativePermission</code>
* representing the active bits in the passed mask.
* NB: Only uses <code>BasePermission</code>!
*
* @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);
}
}

View File

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

View File

@ -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 <code>Permission</code> that is constructed at runtime from
* other permissions.
*
* <p>
* Methods return <code>this</code>, in order to facilitate method chaining.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class CumulativePermission implements Permission {
//~ Instance fields ========================================================
private String pattern = THIRTY_TWO_RESERVED_OFF;
private int mask = 0;
//~ Methods ================================================================
public CumulativePermission clear(Permission permission) {
this.mask &= ~permission.getMask();
this.pattern = AclFormattingUtils.demergePatterns(this.pattern,
permission.getPattern());
return this;
}
public boolean equals(Object arg0) {
if (!(arg0 instanceof CumulativePermission)) {
return false;
}
CumulativePermission rhs = (CumulativePermission) arg0;
return (this.mask == rhs.getMask());
}
public CumulativePermission clear() {
this.mask = 0;
this.pattern = THIRTY_TWO_RESERVED_OFF;
return this;
}
public int getMask() {
return this.mask;
}
public String getPattern() {
return this.pattern;
}
public CumulativePermission set(Permission permission) {
this.mask |= permission.getMask();
this.pattern = AclFormattingUtils.mergePatterns(this.pattern,
permission.getPattern());
return this;
}
public String toString() {
return "CumulativePermission[" + pattern + "=" + this.mask + "]";
}
}

View File

@ -0,0 +1,18 @@
package org.acegisecurity.acls.jdbc;
import org.acegisecurity.acls.domain.AclImpl;
import org.acegisecurity.acls.objectidentity.ObjectIdentity;
/**
* A caching layer for {@link JdbcAclService}.
*
* @author Ben Alex
* @version $Id$
*
*/
public interface AclCache {
public AclImpl getFromCache(ObjectIdentity objectIdentity);
public AclImpl getFromCache(Long pk);
public void putInCache(AclImpl acl); // should walk tree as well!
public void evictFromCache(Long pk);
}

View File

@ -0,0 +1,517 @@
/* 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 java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.acls.AccessControlEntry;
import org.acegisecurity.acls.Acl;
import org.acegisecurity.acls.NotFoundException;
import org.acegisecurity.acls.Permission;
import org.acegisecurity.acls.UnloadedSidException;
import org.acegisecurity.acls.domain.AccessControlEntryImpl;
import org.acegisecurity.acls.domain.AclImpl;
import org.acegisecurity.acls.domain.BasePermission;
import org.acegisecurity.acls.objectidentity.ObjectIdentity;
import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
import org.acegisecurity.acls.sid.GrantedAuthoritySid;
import org.acegisecurity.acls.sid.PrincipalSid;
import org.acegisecurity.acls.sid.Sid;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.util.Assert;
/**
* Performs lookups in a manner that is compatible with ANSI SQL.
*
* <p>
* 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
* <code>LookupStrategy</code>. This class does not support subclassing, as
* it is likely to change in future releases and therefore subclassing is
* unsupported.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public final class BasicLookupStrategy implements LookupStrategy {
private int batchSize = 50;
private AclCache aclCache;
private JdbcTemplate jdbcTemplate;
private GrantedAuthority[] auths;
//~ Constructors ===========================================================
/**
* Constructor accepting mandatory arguments
*
* @param dataSource to access the database
* @param aclCache the cache where fully-loaded elements can be stored
* @param auths as per the format defined by {@link
* AclImpl#setAuthorities(GrantedAuthority[])} for instances
* created by this implementation
*/
public BasicLookupStrategy(DataSource dataSource, AclCache aclCache,
GrantedAuthority[] auths) {
Assert.notNull(dataSource, "DataSource required");
Assert.notNull(aclCache, "AclCache required");
Assert.notEmpty(auths, "GrantedAuthority[] with three elements required");
Assert.isTrue(auths.length == 3,
"GrantedAuthority[] with three elements required");
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.aclCache = aclCache;
this.auths = auths;
}
//~ Methods ================================================================
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
/**
* The main method.
*
* <p>
* WARNING: This implementation completely disregards the "sids" argument!
* Every item in the cache is expected to contain all SIDs.
*
* <p>
* 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 <code>ObjectIdentity</code>s directly from the
* database.
*
* <p>
* 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.
* </p>
*
* <p>
* This subclass is required to return fully valid <code>Acl</code>s,
* including properly-configured parent ACLs.
* </p>
*/
private Map lookupObjectIdentities(final ObjectIdentity[] objectIdentities) {
Assert.notEmpty(objectIdentities, "Must provide identities to lookup");
final Map acls = new HashMap(); // contains Acls with StubAclParents
// Make the "acls" map contain all requested objectIdentities
// (including markers to each parent in the hierarchy)
String sql = computeRepeatingSql("(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = ? and ACL_CLASS.CLASS = ?)", objectIdentities.length);
System.out.println("Executing lookupObjectIdentities; length: " + objectIdentities.length);
jdbcTemplate.query(sql,
new PreparedStatementSetter() {
public void setValues(PreparedStatement ps)
throws SQLException {
for (int i = 0; i < objectIdentities.length; i++) {
// Determine prepared statement values for this iteration
String javaType = objectIdentities[i].getJavaType().getName();
Assert.isInstanceOf(Long.class, objectIdentities[i].getIdentifier(),"This class requires ObjectIdentity.getIdentifier() to be a Long");
long id = ((Long) objectIdentities[i].getIdentifier()).longValue();
// Inject values
ps.setLong((2 * i) + 1, id);
ps.setString((2 * i) + 2, javaType);
}
}
}, new ProcessResultSet(acls));
// Finally, convert our "acls" containing StubAclParents into true Acls
Map resultMap = new HashMap();
Iterator iter = acls.values().iterator();
while (iter.hasNext()) {
Acl inputAcl = (Acl) iter.next();
Assert.isInstanceOf(AclImpl.class, inputAcl, "Map should have contained an AclImpl");
Assert.isInstanceOf(Long.class, ((AclImpl)inputAcl).getId(), "Acl.getId() must be Long");
Acl result = convert(acls, (Long)((AclImpl)inputAcl).getId());
resultMap.put(result.getObjectIdentity(), result);
}
return resultMap;
}
/**
* Locates the primary key IDs specified in "findNow", adding AclImpl
* instances with StubAclParents to the "acls" Map.
*
* @param acls the AclImpls (with StubAclParents)
* @param findNow Long-based primary keys to retrieve
*/
private void lookupPrimaryKeys(final Map acls, final Set findNow) {
Assert.notNull(acls, "ACLs are required");
Assert.notEmpty(findNow, "Items to find now required");
String sql = computeRepeatingSql("(ACL_OBJECT_IDENTITY.ID = ?)",findNow.size());
System.out.println("Executing lookupPrimaryKeys; length: " + findNow.size());
jdbcTemplate.query(sql,
new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
Iterator iter = findNow.iterator();
int i = 0;
while (iter.hasNext()) {
i++;
ps.setLong(i, ((Long)iter.next()).longValue());
}
}
}, new ProcessResultSet(acls));
}
/**
* Accepts the current <code>ResultSet</code> row, and converts it into
* an <code>AclImpl</code> that contains a <code>StubAclParent</code>
*
* @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 <code>Map</code> of <code>AclImpl</code> instances
* which contain <code>StubAclParent</code>s into proper, valid <code>AclImpl</code>s with
* correct ACL parents.
*
* @param inputMap the unconverted <code>AclImpl</code>s
* @param inputAcl the current <code>Acl</code> 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;
}
}
}

View File

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

View File

@ -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 <code>AclService</code>.
*
* <p>
* 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);
}
}

View File

@ -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
* <code>null</code> - implementations may elect not to provide SID
* optimisations)
*
* @return the <code>Map</code> pursuant to the interface contract for
* {@link
* org.acegisecurity.acls.AclService#readAclsById(ObjectIdentity[],
* Sid[])}
*/
public Map readAclsById(ObjectIdentity[] objects, Sid[] sids);
}

View File

@ -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.
*
* <P>
* 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 <code>ObjectIdentity</code>s equal if
* <code>identity1.equals(identity2)</code>, rather than reference-equality of
* <code>identity1==identity2</code>.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface ObjectIdentity 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);
/**
* Obtains the actual identifier. This identifier must not be reused to
* represent other domain objects with the same <code>javaType</code>.
*
* <p>
* 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.
* </p>
*
* @return the identifier (unique within this <code>javaType</code>
*/
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 <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,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}.
*
* <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>
*/
public class ObjectIdentityImpl implements ObjectIdentity {
//~ Instance fields ========================================================
private Class javaType;
private Serializable identifier;
//~ Constructors ===========================================================
public ObjectIdentityImpl(String javaType, Serializable identifier) {
Assert.hasText(javaType, "Java Type required");
Assert.notNull(identifier, "identifier required");
try {
this.javaType = Class.forName(javaType);
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
this.identifier = identifier;
}
public ObjectIdentityImpl(Class javaType, Serializable identifier) {
Assert.notNull(javaType, "Java Type required");
Assert.notNull(identifier, "identifier required");
this.javaType = javaType;
this.identifier = identifier;
}
/**
* 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 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.
*
* <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 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();
}
}

View File

@ -0,0 +1,5 @@
<html>
<body>
Enables retrieval of access control lists (ACLs) for domain object instances.
</body>
</html>

View File

@ -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 <code>GrantedAuthority</code> as a <code>Sid</code>.
*
* <p>
* This is a basic implementation that simply uses the
* <code>String</code>-based principal for <code>Sid</code> comparison. More
* complex principal objects may wish to provide an alternative
* <code>Sid</code> implementation that uses some other identifier.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class GrantedAuthoritySid implements Sid {
//~ Instance fields ========================================================
private String grantedAuthority;
//~ Constructors ===========================================================
public GrantedAuthoritySid(String grantedAuthority) {
Assert.hasText(grantedAuthority, "GrantedAuthority required");
this.grantedAuthority = grantedAuthority;
}
public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
Assert.notNull(grantedAuthority, "GrantedAuthority required");
Assert.notNull(grantedAuthority.getAuthority(),
"This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
this.grantedAuthority = grantedAuthority.getAuthority();
}
//~ Methods ================================================================
public boolean equals(Object object) {
if ((object == null) || !(object instanceof GrantedAuthoritySid)) {
return false;
}
// Delegate to getGrantedAuthority() to perform actual comparison (both should be identical)
return ((GrantedAuthoritySid) object).getGrantedAuthority()
.equals(this.getGrantedAuthority());
}
public String getGrantedAuthority() {
return grantedAuthority;
}
public String toString() {
return "GrantedAuthoritySid[" + this.grantedAuthority + "]";
}
}

View File

@ -0,0 +1,81 @@
/* 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.Authentication;
import org.acegisecurity.userdetails.UserDetails;
import org.springframework.util.Assert;
/**
* Represents an <code>Authentication.getPrincipal()</code> as a
* <code>Sid</code>.
*
* <p>
* This is a basic implementation that simply uses the
* <code>String</code>-based principal for <code>Sid</code> comparison. More
* complex principal objects may wish to provide an alternative
* <code>Sid</code> implementation that uses some other identifier.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class PrincipalSid implements Sid {
//~ Instance fields ========================================================
private String principal;
//~ Constructors ===========================================================
public PrincipalSid(String principal) {
Assert.hasText(principal, "Principal required");
this.principal = principal;
}
public PrincipalSid(Authentication authentication) {
Assert.notNull(authentication, "Authentication required");
Assert.notNull(authentication.getPrincipal(), "Principal required");
this.principal = authentication.getPrincipal().toString();
if (authentication.getPrincipal() instanceof UserDetails) {
this.principal = ((UserDetails) authentication.getPrincipal())
.getUsername();
}
}
//~ Methods ================================================================
public boolean equals(Object object) {
if ((object == null) || !(object instanceof PrincipalSid)) {
return false;
}
// Delegate to getPrincipal() to perform actual comparison (both should be identical)
return ((PrincipalSid) object).getPrincipal()
.equals(this.getPrincipal());
}
public String getPrincipal() {
return principal;
}
public String toString() {
return "PrincipalSid[" + this.principal + "]";
}
}

View File

@ -0,0 +1,54 @@
/* 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;
/**
* A security identity recognised by the ACL system.
*
* <p>
* This interface provides indirection between actual security objects (eg
* principals, roles, groups etc) and what is stored inside an
* <code>Acl</code>. This is because an <code>Acl</code> 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.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface Sid {
//~ 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,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());
}
}

View File

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

View File

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

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!--
- Application context containing business beans.
-
- Used by all artifacts.
-
- $Id$
-->
<beans>
<bean id="databaseSeeder" class="org.acegisecurity.acls.jdbc.DatabaseSeeder">
<constructor-arg ref="dataSource"/>
<constructor-arg value="classpath:org/acegisecurity/acls/jdbc/testData.sql"/>
</bean>
<bean id="aclCache" class="org.acegisecurity.acls.jdbc.EhCacheBasedAclCache">
<constructor-arg>
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
</property>
<property name="cacheName">
<value>aclCache</value>
</property>
</bean>
</constructor-arg>
</bean>
<bean id="lookupStrategy" class="org.acegisecurity.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="aclCache"/>
<constructor-arg>
<list>
<bean class="org.acegisecurity.GrantedAuthorityImpl">
<constructor-arg value="ROLE_ADMINISTRATOR"/>
</bean>
<bean class="org.acegisecurity.GrantedAuthorityImpl">
<constructor-arg value="ROLE_ADMINISTRATOR"/>
</bean>
<bean class="org.acegisecurity.GrantedAuthorityImpl">
<constructor-arg value="ROLE_ADMINISTRATOR"/>
</bean>
</list>
</constructor-arg>
</bean>
<bean id="aclService" class="org.acegisecurity.acls.jdbc.JdbcAclService">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="aclCache"/>
<constructor-arg ref="lookupStrategy"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="url">
<value>jdbc:hsqldb:mem:test</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value></value>
</property>
</bean>
</beans>

View File

@ -0,0 +1,23 @@
-- Not required. Just shows the sort of queries being sent to DB.
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 (
(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 1
and ACL_CLASS.CLASS = 'sample.contact.Contact')
or
(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 2
and ACL_CLASS.CLASS = 'sample.contact.Contact')
) order by ACL_ENTRY.ACL_OBJECT_IDENTITY asc, ACL_ENTRY.ACE_ORDER asc

View File

@ -0,0 +1,70 @@
CREATE TABLE ACL_SID(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
PRINCIPAL BOOLEAN NOT NULL,
SID VARCHAR_IGNORECASE(100) NOT NULL,
CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));
INSERT INTO ACL_SID VALUES (1, TRUE, 'MARISSA');
INSERT INTO ACL_SID VALUES (2, TRUE, 'DIANNE');
INSERT INTO ACL_SID VALUES (3, TRUE, 'SCOTT');
INSERT INTO ACL_SID VALUES (4, TRUE, 'PETER');
CREATE TABLE ACL_CLASS(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
CLASS VARCHAR_IGNORECASE(100) NOT NULL,
CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));
INSERT INTO ACL_CLASS VALUES (1, 'sample.contact.Contact');
CREATE TABLE ACL_OBJECT_IDENTITY(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
OBJECT_ID_CLASS BIGINT NOT NULL,
OBJECT_ID_IDENTITY BIGINT NOT NULL,
PARENT_OBJECT BIGINT,
OWNER_SID BIGINT,
ENTRIES_INHERITING BOOLEAN NOT NULL,
CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),
CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),
CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),
CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));
INSERT INTO ACL_OBJECT_IDENTITY VALUES (1, 1, 1, NULL, 1, TRUE);
INSERT INTO ACL_OBJECT_IDENTITY VALUES (2, 1, 2, 1, 2, TRUE);
INSERT INTO ACL_OBJECT_IDENTITY VALUES (3, 1, 3, 1, 1, FALSE);
INSERT INTO ACL_OBJECT_IDENTITY VALUES (4, 1, 4, 1, 2, TRUE);
INSERT INTO ACL_OBJECT_IDENTITY VALUES (5, 1, 5, 1, 2, FALSE);
INSERT INTO ACL_OBJECT_IDENTITY VALUES (6, 1, 6, NULL, 1, TRUE);
CREATE TABLE ACL_ENTRY(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
ACL_OBJECT_IDENTITY BIGINT NOT NULL,
ACE_ORDER INT NOT NULL,
SID BIGINT NOT NULL,
MASK INTEGER NOT NULL,
GRANTING BOOLEAN NOT NULL,
AUDIT_SUCCESS BOOLEAN NOT NULL,
AUDIT_FAILURE BOOLEAN NOT NULL,
CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),
CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),
CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));
INSERT INTO ACL_ENTRY VALUES (1, 1, 1, 2, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (2, 1, 2, 1, 2, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (3, 1, 3, 3, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (4, 2, 1, 3, 4, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (5, 2, 2, 4, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (6, 3, 1, 3, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (7, 3, 2, 4, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (8, 3, 3, 1, 8, FALSE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (9, 4, 1, 4, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (10, 5, 1, 2, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (11, 5, 2, 3, 8, FALSE, TRUE, TRUE);
INSERT INTO ACL_ENTRY VALUES (12, 5, 3, 1, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (13, 5, 4, 4, 8, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (14, 6, 1, 2, 1, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (15, 6, 2, 1, 2, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (16, 6, 3, 2, 4, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (17, 6, 4, 3, 2, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (18, 6, 5, 3, 1, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (19, 6, 6, 4, 4, TRUE, FALSE, FALSE);
INSERT INTO ACL_ENTRY VALUES (20, 6, 7, 4, 2, TRUE, FALSE, FALSE);