From 9769394c928dc67f066ec77a58fe947c51b3453f Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 30 Aug 2006 22:15:29 +0000 Subject: [PATCH] Fixes to new ACL implementation. Thanks to Nathan Sarr. --- .../acegisecurity/acls/domain/AclImpl.java | 1000 ++++++++--------- .../acls/domain/BasePermission.java | 208 ++-- .../acls/domain/PermissionTests.java | 140 +-- 3 files changed, 676 insertions(+), 672 deletions(-) diff --git a/sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java b/sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java index b99fb33b69..43432aee74 100644 --- a/sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java +++ b/sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java @@ -1,500 +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 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; - -import java.io.Serializable; - -import java.util.Iterator; -import java.util.List; -import java.util.Vector; - - -/** - * Base implementation of Acl. - * - * @author Ben Alex - * @version $Id - */ -public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { - //~ Static fields/initializers ===================================================================================== - - private static final int CHANGE_OWNERSHIP = 0; - private static final int CHANGE_AUDITING = 1; - private static final int CHANGE_GENERAL = 2; - - //~ Instance fields ================================================================================================ - - private Acl parentAcl; - private AuditLogger auditLogger = new ConsoleAuditLogger(); // AuditableAcl - private GrantedAuthority gaGeneralChanges; - private GrantedAuthority gaModifyAuditing; - private GrantedAuthority gaTakeOwnership; - private List aces = new Vector(); - private List deletedAces = new Vector(); - private Long id; - private ObjectIdentity objectIdentity; - private Sid owner; // OwnershipAcl - 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 entriesInheriting = false; - private boolean updatedAces = false; // for snapshot detection - - //~ Constructors =================================================================================================== - -/** - * Minimal constructor, which should be used {@link - * org.acegisecurity.acls.MutableAclService#createAcl(ObjectIdentity)}. - * - * @param objectIdentity the object identity this ACL relates to (required) - * @param id the primary key assigned to this ACL (required) - * @param auths an array of GrantedAuthoritys that have - * special permissions (index 0 is the authority needed to change - * ownership, index 1 is the authority needed to modify auditing details, - * index 2 is the authority needed to change other ACL and ACE details) (required) - */ - public AclImpl(ObjectIdentity objectIdentity, Long id, GrantedAuthority[] auths) { - Assert.notNull(objectIdentity, "Object Identity required"); - Assert.notNull(id, "Id required"); - this.objectIdentity = objectIdentity; - this.id = id; - this.setAuthorities(auths); - } - -/** - * Full constructor, which should be used by persistence tools that do not - * provide field-level access features. - * - * @param objectIdentity the object identity this ACL relates to (required) - * @param id the primary key assigned to this ACL (required) - * @param auths an array of GrantedAuthoritys that have - * special permissions (index 0 is the authority needed to change - * ownership, index 1 is the authority needed to modify auditing details, - * index 2 is the authority needed to change other ACL and ACE details) (required) - * @param parentAcl the parent (may be null) - * @param loadedSids the loaded SIDs if only a subset were loaded (may be - * null) - * @param entriesInheriting if ACEs from the parent should inherit into - * this ACL - * @param owner the owner (required) - */ - public AclImpl(ObjectIdentity objectIdentity, Long id, Acl parentAcl, GrantedAuthority[] auths, Sid[] loadedSids, - boolean entriesInheriting, Sid owner) { - Assert.notNull(objectIdentity, "Object Identity required"); - Assert.notNull(id, "Id required"); - Assert.notNull(owner, "Owner required"); - this.objectIdentity = objectIdentity; - this.id = id; - setAuthorities(auths); - this.parentAcl = parentAcl; // may be null - this.loadedSids = loadedSids; // may be null - this.entriesInheriting = entriesInheriting; - this.owner = owner; - } - -/** - * Private no-argument constructor for use by reflection-based persistence - * tools along with field-level access. - */ - private AclImpl() {} - - //~ Methods ======================================================================================================== - - /** - * Clears the dirty flags on the Acl, but not any associated ACEs. - */ - public void clearDirtyFlags() { - this.aclDirty = false; - this.addedAces = false; - this.updatedAces = false; - } - - public 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 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 isAclDirty() { - return aclDirty; - } - - public boolean isEntriesInheriting() { - return entriesInheriting; - } - - /** - * Determines authorization. The order of the permission and sid arguments is - * extremely important! The method will iterate through each of the permissions in the order - * specified. For each iteration, all of the sids will be considered, again in the order they are - * presented. The iteration of each permission:sid combination will then inspect the ACEs in the - * order they appear in the ACL. When the first full match is found (ie an ACE that has the SID currently - * being searched for and the exact permission bit mask being search for), the grant or deny flag for that ACE - * will prevail. If the ACE specifies to grant access, the method will return true. If the ACE - * specifies to deny access, the loop will stop and the next permission iteration will be performed. - * If each permission indicates to deny access, the first deny ACE found will be considered the reason for the - * failure (as it was the first match found, and is therefore the one most logically requiring changes - although - * not always). If absolutely no matching ACE was found at all for any permission, the parent ACL will be tried - * (provided that there is a parent and {@link #isEntriesInheriting()} is true. The parent ACL will - * also scan its parent and so on. If ultimately no matching ACE is found, a NotFoundException will - * be thrown and the caller will need to decide how to handle the permission check. Similarly, if any of the - * passed SIDs were not loaded by the ACL, the UnloadedSidException will be thrown. - * - * @param permission the exact permissions to scan for (order is important) - * @param sids the exact SIDs to scan for (order is important) - * @param administrativeMode if true denotes the query is for administrative purposes and no auditing - * will be undertaken - * - * @return true if one of the permissions has been granted, false if one of the - * permissions has been specifically revoked - * - * @throws NotFoundException if an exact ACE for one of the permission bit masks and SID combination could not be - * found - * @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the ACL was only loaded for a - * subset of SIDs - */ - public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode) - throws NotFoundException, UnloadedSidException { - Assert.notEmpty(permission, "Permissions required"); - Assert.notEmpty(sids, "SIDs required"); - - if (!this.isSidLoaded(sids)) { - throw new UnloadedSidException("ACL was not loaded for one or more SID"); - } - - AccessControlEntry firstRejection = null; - - for (int i = 0; i < permission.length; i++) { - for (int x = 0; x < sids.length; x++) { - // Attempt to find exact match for this permission mask and SID - Iterator acesIterator = aces.iterator(); - boolean scanNextSid = true; - - while (acesIterator.hasNext()) { - AccessControlEntry ace = (AccessControlEntry) acesIterator.next(); - - if ((ace.getPermission().getMask() == permission[i].getMask()) && ace.getSid().equals(sids[x])) { - // Found a matching ACE, so its authorization decision will prevail - if (ace.isGranting()) { - // Success - if (!administrativeMode) { - auditLogger.logIfNeeded(true, ace); - } - - return true; - } else { - // Failure for this permission, so stop search - // We will see if they have a different permission - // (this permission is 100% rejected for this SID) - if (firstRejection == null) { - // Store first rejection for auditing reasons - firstRejection = ace; - } - - scanNextSid = false; // helps break the loop - - break; // exit "aceIterator" while loop - } - } - } - - if (!scanNextSid) { - break; // exit SID for loop (now try next permission) - } - } - } - - if (firstRejection != null) { - // We found an ACE to reject the request at this point, as no - // other ACEs were found that granted a different permission - if (!administrativeMode) { - auditLogger.logIfNeeded(false, firstRejection); - } - - return false; - } - - // No matches have been found so far - if (isEntriesInheriting() && (parentAcl != null)) { - // We have a parent, so let them try to find a matching ACE - return parentAcl.isGranted(permission, sids, false); - } else { - // We either have no parent, or we're the uppermost parent - throw new NotFoundException("Unable to locate a matching ACE for passed permissions and SIDs"); - } - } - - public 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; - } - - 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"); - } - - /** - * Change the special adminstrative permissions honoured by this object.

Normally a principal must be the - * owner of the ACL in order to make most changes. The authorities passed to this method provide a way for - * non-owners to modify the ACL (and indeed modify audit parameters, which are not available to ACL owners).

- * - * @param auths an array of GrantedAuthoritys that have administrative permissions (index 0 is the - * authority needed to change ownership, index 1 is the authority needed to modify auditing details, index - * 2 is the authority needed to change other ACL and ACE details) - */ - private void setAuthorities(GrantedAuthority[] auths) { - Assert.notEmpty(auths, "GrantedAuthority[] with three elements required"); - Assert.isTrue(auths.length == 3, "GrantedAuthority[] with three elements required"); - this.gaTakeOwnership = auths[0]; - this.gaModifyAuditing = auths[1]; - this.gaGeneralChanges = auths[2]; - } - - public void setEntriesInheriting(boolean entriesInheriting) { - securityCheck(CHANGE_GENERAL); - this.entriesInheriting = entriesInheriting; - this.aclDirty = true; - } - - 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 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(); - } - - 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; - } -} +/* 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.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; + +import java.io.Serializable; + +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + + +/** + * Base implementation of Acl. + * + * @author Ben Alex + * @version $Id + */ +public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { + //~ Static fields/initializers ===================================================================================== + + private static final int CHANGE_OWNERSHIP = 0; + private static final int CHANGE_AUDITING = 1; + private static final int CHANGE_GENERAL = 2; + + //~ Instance fields ================================================================================================ + + private Acl parentAcl; + private AuditLogger auditLogger = new ConsoleAuditLogger(); // AuditableAcl + private GrantedAuthority gaGeneralChanges; + private GrantedAuthority gaModifyAuditing; + private GrantedAuthority gaTakeOwnership; + private List aces = new Vector(); + private List deletedAces = new Vector(); + private Long id; + private ObjectIdentity objectIdentity; + private Sid owner; // OwnershipAcl + 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 entriesInheriting = false; + private boolean updatedAces = false; // for snapshot detection + + //~ Constructors =================================================================================================== + +/** + * Minimal constructor, which should be used {@link + * org.acegisecurity.acls.MutableAclService#createAcl(ObjectIdentity)}. + * + * @param objectIdentity the object identity this ACL relates to (required) + * @param id the primary key assigned to this ACL (required) + * @param auths an array of GrantedAuthoritys that have + * special permissions (index 0 is the authority needed to change + * ownership, index 1 is the authority needed to modify auditing details, + * index 2 is the authority needed to change other ACL and ACE details) (required) + */ + public AclImpl(ObjectIdentity objectIdentity, Long id, GrantedAuthority[] auths) { + Assert.notNull(objectIdentity, "Object Identity required"); + Assert.notNull(id, "Id required"); + this.objectIdentity = objectIdentity; + this.id = id; + this.setAuthorities(auths); + } + +/** + * Full constructor, which should be used by persistence tools that do not + * provide field-level access features. + * + * @param objectIdentity the object identity this ACL relates to (required) + * @param id the primary key assigned to this ACL (required) + * @param auths an array of GrantedAuthoritys that have + * special permissions (index 0 is the authority needed to change + * ownership, index 1 is the authority needed to modify auditing details, + * index 2 is the authority needed to change other ACL and ACE details) (required) + * @param parentAcl the parent (may be null) + * @param loadedSids the loaded SIDs if only a subset were loaded (may be + * null) + * @param entriesInheriting if ACEs from the parent should inherit into + * this ACL + * @param owner the owner (required) + */ + public AclImpl(ObjectIdentity objectIdentity, Long id, Acl parentAcl, GrantedAuthority[] auths, Sid[] loadedSids, + boolean entriesInheriting, Sid owner) { + Assert.notNull(objectIdentity, "Object Identity required"); + Assert.notNull(id, "Id required"); + Assert.notNull(owner, "Owner required"); + this.objectIdentity = objectIdentity; + this.id = id; + setAuthorities(auths); + this.parentAcl = parentAcl; // may be null + this.loadedSids = loadedSids; // may be null + this.entriesInheriting = entriesInheriting; + this.owner = owner; + } + +/** + * Private no-argument constructor for use by reflection-based persistence + * tools along with field-level access. + */ + private AclImpl() {} + + //~ Methods ======================================================================================================== + + /** + * Clears the dirty flags on the Acl, but not any associated ACEs. + */ + public void clearDirtyFlags() { + this.aclDirty = false; + this.addedAces = false; + this.updatedAces = false; + } + + public 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 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 isAclDirty() { + return aclDirty; + } + + public boolean isEntriesInheriting() { + return entriesInheriting; + } + + /** + * Determines authorization. The order of the permission and sid arguments is + * extremely important! The method will iterate through each of the permissions in the order + * specified. For each iteration, all of the sids will be considered, again in the order they are + * presented. The iteration of each permission:sid combination will then inspect the ACEs in the + * order they appear in the ACL. When the first full match is found (ie an ACE that has the SID currently + * being searched for and the exact permission bit mask being search for), the grant or deny flag for that ACE + * will prevail. If the ACE specifies to grant access, the method will return true. If the ACE + * specifies to deny access, the loop will stop and the next permission iteration will be performed. + * If each permission indicates to deny access, the first deny ACE found will be considered the reason for the + * failure (as it was the first match found, and is therefore the one most logically requiring changes - although + * not always). If absolutely no matching ACE was found at all for any permission, the parent ACL will be tried + * (provided that there is a parent and {@link #isEntriesInheriting()} is true. The parent ACL will + * also scan its parent and so on. If ultimately no matching ACE is found, a NotFoundException will + * be thrown and the caller will need to decide how to handle the permission check. Similarly, if any of the + * passed SIDs were not loaded by the ACL, the UnloadedSidException will be thrown. + * + * @param permission the exact permissions to scan for (order is important) + * @param sids the exact SIDs to scan for (order is important) + * @param administrativeMode if true denotes the query is for administrative purposes and no auditing + * will be undertaken + * + * @return true if one of the permissions has been granted, false if one of the + * permissions has been specifically revoked + * + * @throws NotFoundException if an exact ACE for one of the permission bit masks and SID combination could not be + * found + * @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the ACL was only loaded for a + * subset of SIDs + */ + public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode) + throws NotFoundException, UnloadedSidException { + Assert.notEmpty(permission, "Permissions required"); + Assert.notEmpty(sids, "SIDs required"); + + if (!this.isSidLoaded(sids)) { + throw new UnloadedSidException("ACL was not loaded for one or more SID"); + } + + AccessControlEntry firstRejection = null; + + for (int i = 0; i < permission.length; i++) { + for (int x = 0; x < sids.length; x++) { + // Attempt to find exact match for this permission mask and SID + Iterator acesIterator = aces.iterator(); + boolean scanNextSid = true; + + while (acesIterator.hasNext()) { + AccessControlEntry ace = (AccessControlEntry) acesIterator.next(); + + if ((ace.getPermission().getMask() == permission[i].getMask()) && ace.getSid().equals(sids[x])) { + // Found a matching ACE, so its authorization decision will prevail + if (ace.isGranting()) { + // Success + if (!administrativeMode) { + auditLogger.logIfNeeded(true, ace); + } + + return true; + } else { + // Failure for this permission, so stop search + // We will see if they have a different permission + // (this permission is 100% rejected for this SID) + if (firstRejection == null) { + // Store first rejection for auditing reasons + firstRejection = ace; + } + + scanNextSid = false; // helps break the loop + + break; // exit "aceIterator" while loop + } + } + } + + if (!scanNextSid) { + break; // exit SID for loop (now try next permission) + } + } + } + + if (firstRejection != null) { + // We found an ACE to reject the request at this point, as no + // other ACEs were found that granted a different permission + if (!administrativeMode) { + auditLogger.logIfNeeded(false, firstRejection); + } + + return false; + } + + // No matches have been found so far + if (isEntriesInheriting() && (parentAcl != null)) { + // We have a parent, so let them try to find a matching ACE + return parentAcl.isGranted(permission, sids, false); + } else { + // We either have no parent, or we're the uppermost parent + throw new NotFoundException("Unable to locate a matching ACE for passed permissions and SIDs"); + } + } + + public 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; + } + + 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"); + } + + /** + * Change the special adminstrative permissions honoured by this object.

Normally a principal must be the + * owner of the ACL in order to make most changes. The authorities passed to this method provide a way for + * non-owners to modify the ACL (and indeed modify audit parameters, which are not available to ACL owners).

+ * + * @param auths an array of GrantedAuthoritys that have administrative permissions (index 0 is the + * authority needed to change ownership, index 1 is the authority needed to modify auditing details, index + * 2 is the authority needed to change other ACL and ACE details) + */ + private void setAuthorities(GrantedAuthority[] auths) { + Assert.notEmpty(auths, "GrantedAuthority[] with three elements required"); + Assert.isTrue(auths.length == 3, "GrantedAuthority[] with three elements required"); + this.gaTakeOwnership = auths[0]; + this.gaModifyAuditing = auths[1]; + this.gaGeneralChanges = auths[2]; + } + + public void setEntriesInheriting(boolean entriesInheriting) { + securityCheck(CHANGE_GENERAL); + this.entriesInheriting = entriesInheriting; + this.aclDirty = true; + } + + 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 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(); + } + + 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; + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/acls/domain/BasePermission.java b/sandbox/src/main/java/org/acegisecurity/acls/domain/BasePermission.java index e840c4a3bf..b8398aec23 100644 --- a/sandbox/src/main/java/org/acegisecurity/acls/domain/BasePermission.java +++ b/sandbox/src/main/java/org/acegisecurity/acls/domain/BasePermission.java @@ -1,102 +1,106 @@ -/* 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; - - -/** - * DOCUMENT ME! - * - * @author $author$ - * @version $Revision$ - */ -public class BasePermission implements Permission { - //~ Static fields/initializers ===================================================================================== - - 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 - - //~ Instance fields ================================================================================================ - - private char code; - private int mask; - - //~ Constructors =================================================================================================== - - private BasePermission(int mask, char code) { - this.mask = mask; - this.code = code; - } - - //~ Methods ======================================================================================================== - - /** - * Dynamically creates a CumulativePermission representing the active bits in the passed mask. - * NB: Only uses BasePermission! - * - * @param mask to review - * - * @return DOCUMENT ME! - */ - 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 boolean equals(Object arg0) { - if (!(arg0 instanceof BasePermission)) { - return false; - } - - BasePermission rhs = (BasePermission) arg0; - - return (this.mask == rhs.getMask()); - } - - public int getMask() { - return mask; - } - - public String getPattern() { - return AclFormattingUtils.printBinary(mask, code); - } - - public String toString() { - return "BasePermission[" + getPattern() + "=" + mask + "]"; - } -} +/* 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; + +/** + * DOCUMENT ME! + * + * @author $author$ + * @version $Revision$ + */ +public class BasePermission implements Permission { + //~ Static fields/initializers ===================================================================================== + + 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 DELETE = new BasePermission(1 << 3, 'D'); // 8 + public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16 + + //~ Instance fields ================================================================================================ + + private char code; + private int mask; + + //~ Constructors =================================================================================================== + + private BasePermission(int mask, char code) { + this.mask = mask; + this.code = code; + } + + //~ Methods ======================================================================================================== + + /** + * Dynamically creates a CumulativePermission representing the active bits in the passed mask. + * NB: Only uses BasePermission! + * + * @param mask to review + * + * @return DOCUMENT ME! + */ + 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(DELETE); + } + + if (mask == 16) { + permission.set(ADMINISTRATION); + } + + return permission; + } + + public boolean equals(Object arg0) { + if (!(arg0 instanceof BasePermission)) { + return false; + } + + BasePermission rhs = (BasePermission) arg0; + + return (this.mask == rhs.getMask()); + } + + public int getMask() { + return mask; + } + + public String getPattern() { + return AclFormattingUtils.printBinary(mask, code); + } + + public String toString() { + return "BasePermission[" + getPattern() + "=" + mask + "]"; + } +} diff --git a/sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java b/sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java index e5e19db92f..4b63c6818f 100644 --- a/sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java +++ b/sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java @@ -1,70 +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.domain; - -import junit.framework.TestCase; - - -/** - * Tests BasePermission and CumulativePermission. - * - * @author Ben Alex - * @version $Id${date} - */ -public class PermissionTests extends TestCase { - //~ Methods ======================================================================================================== - - public void testExpectedIntegerValues() { - assertEquals(1, BasePermission.READ.getMask()); - assertEquals(8, BasePermission.ADMINISTRATION.getMask()); - assertEquals(9, new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask()); - } - - 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()); - } -} +/* 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 junit.framework.TestCase; + + +/** + * Tests BasePermission and CumulativePermission. + * + * @author Ben Alex + * @version $Id${date} + */ +public class PermissionTests extends TestCase { + //~ Methods ======================================================================================================== + + public void testExpectedIntegerValues() { + assertEquals(1, BasePermission.READ.getMask()); + assertEquals(16, BasePermission.ADMINISTRATION.getMask()); + assertEquals(17, new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask()); + } + + 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....=16]", 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....=16]", + new CumulativePermission().set(BasePermission.ADMINISTRATION).toString()); + + System.out.println("RA = " + + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString()); + assertEquals("CumulativePermission[...........................A...R=17]", + 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()); + } +}