Coverage Report - org.acegisecurity.acls.domain.AclImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
AclImpl
55% 
62% 
2.95
 
 1  
 /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 2  
  *
 3  
  * Licensed under the Apache License, Version 2.0 (the "License");
 4  
  * you may not use this file except in compliance with the License.
 5  
  * You may obtain a copy of the License at
 6  
  *
 7  
  *     http://www.apache.org/licenses/LICENSE-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing, software
 10  
  * distributed under the License is distributed on an "AS IS" BASIS,
 11  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
  * See the License for the specific language governing permissions and
 13  
  * limitations under the License.
 14  
  */
 15  
 package org.acegisecurity.acls.domain;
 16  
 
 17  
 import org.acegisecurity.acls.AccessControlEntry;
 18  
 import org.acegisecurity.acls.Acl;
 19  
 import org.acegisecurity.acls.AuditableAcl;
 20  
 import org.acegisecurity.acls.MutableAcl;
 21  
 import org.acegisecurity.acls.NotFoundException;
 22  
 import org.acegisecurity.acls.OwnershipAcl;
 23  
 import org.acegisecurity.acls.Permission;
 24  
 import org.acegisecurity.acls.UnloadedSidException;
 25  
 import org.acegisecurity.acls.objectidentity.ObjectIdentity;
 26  
 import org.acegisecurity.acls.sid.Sid;
 27  
 
 28  
 import org.springframework.util.Assert;
 29  
 
 30  
 import java.io.Serializable;
 31  
 
 32  
 import java.util.Iterator;
 33  
 import java.util.List;
 34  
 import java.util.Vector;
 35  
 
 36  
 
 37  
 /**
 38  
  * Base implementation of <code>Acl</code>.
 39  
  *
 40  
  * @author Ben Alex
 41  
  * @version $Id
 42  
  */
 43  
 public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
 44  
     //~ Instance fields ================================================================================================
 45  
 
 46  
     private Acl parentAcl;
 47  
     private AclAuthorizationStrategy aclAuthorizationStrategy;
 48  
     private AuditLogger auditLogger;
 49  28
     private List aces = new Vector();
 50  
     private ObjectIdentity objectIdentity;
 51  
     private Serializable id;
 52  
     private Sid owner; // OwnershipAcl
 53  28
     private Sid[] loadedSids = null; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
 54  28
     private boolean entriesInheriting = true;
 55  
 
 56  
     //~ Constructors ===================================================================================================
 57  
 
 58  
 /**
 59  
      * Minimal constructor, which should be used {@link
 60  
      * org.acegisecurity.acls.MutableAclService#createAcl(ObjectIdentity)}.
 61  
      *
 62  
      * @param objectIdentity the object identity this ACL relates to (required)
 63  
      * @param id the primary key assigned to this ACL (required)
 64  
      * @param aclAuthorizationStrategy authorization strategy (required)
 65  
      * @param auditLogger audit logger (required)
 66  
      */
 67  
     public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
 68  0
         AuditLogger auditLogger) {
 69  0
         Assert.notNull(objectIdentity, "Object Identity required");
 70  0
         Assert.notNull(id, "Id required");
 71  0
         Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
 72  0
         Assert.notNull(auditLogger, "AuditLogger required");
 73  0
         this.objectIdentity = objectIdentity;
 74  0
         this.id = id;
 75  0
         this.aclAuthorizationStrategy = aclAuthorizationStrategy;
 76  0
         this.auditLogger = auditLogger;
 77  0
     }
 78  
 
 79  
 /**
 80  
      * Full constructor, which should be used by persistence tools that do not
 81  
      * provide field-level access features.
 82  
      *
 83  
      * @param objectIdentity the object identity this ACL relates to (required)
 84  
      * @param id the primary key assigned to this ACL (required)
 85  
      * @param aclAuthorizationStrategy authorization strategy (required)
 86  
      * @param auditLogger audit logger (required)
 87  
      * @param parentAcl the parent (may be <code>null</code>)
 88  
      * @param loadedSids the loaded SIDs if only a subset were loaded (may be
 89  
      *        <code>null</code>)
 90  
      * @param entriesInheriting if ACEs from the parent should inherit into
 91  
      *        this ACL
 92  
      * @param owner the owner (required)
 93  
      */
 94  
     public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
 95  28
         AuditLogger auditLogger, Acl parentAcl, Sid[] loadedSids, boolean entriesInheriting, Sid owner) {
 96  28
         Assert.notNull(objectIdentity, "Object Identity required");
 97  28
         Assert.notNull(id, "Id required");
 98  28
         Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
 99  28
         Assert.notNull(owner, "Owner required");
 100  28
         Assert.notNull(auditLogger, "AuditLogger required");
 101  28
         this.objectIdentity = objectIdentity;
 102  28
         this.id = id;
 103  28
         this.aclAuthorizationStrategy = aclAuthorizationStrategy;
 104  28
         this.auditLogger = auditLogger;
 105  28
         this.parentAcl = parentAcl; // may be null
 106  28
         this.loadedSids = loadedSids; // may be null
 107  28
         this.entriesInheriting = entriesInheriting;
 108  28
         this.owner = owner;
 109  28
     }
 110  
 
 111  
 /**
 112  
      * Private no-argument constructor for use by reflection-based persistence
 113  
      * tools along with field-level access.
 114  
      */
 115  0
     private AclImpl() {}
 116  
 
 117  
     //~ Methods ========================================================================================================
 118  
 
 119  
     public void deleteAce(Serializable aceId) throws NotFoundException {
 120  1
         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
 121  
 
 122  1
         synchronized (aces) {
 123  1
             int offset = findAceOffset(aceId);
 124  
 
 125  1
             if (offset == -1) {
 126  0
                 throw new NotFoundException("Requested ACE ID not found");
 127  
             }
 128  
 
 129  1
             this.aces.remove(offset);
 130  1
         }
 131  1
     }
 132  
 
 133  
     private int findAceOffset(Serializable aceId) {
 134  1
         Assert.notNull(aceId, "ACE ID is required");
 135  
 
 136  1
         synchronized (aces) {
 137  1
             for (int i = 0; i < aces.size(); i++) {
 138  1
                 AccessControlEntry ace = (AccessControlEntry) aces.get(i);
 139  
 
 140  1
                 if (ace.getId().equals(aceId)) {
 141  1
                     return i;
 142  
                 }
 143  
             }
 144  0
         }
 145  
 
 146  0
         return -1;
 147  
     }
 148  
 
 149  
     public AccessControlEntry[] getEntries() {
 150  
         // Can safely return AccessControlEntry directly, as they're immutable outside the ACL package
 151  29
         return (AccessControlEntry[]) aces.toArray(new AccessControlEntry[] {});
 152  
     }
 153  
 
 154  
     public Serializable getId() {
 155  137
         return this.id;
 156  
     }
 157  
 
 158  
     public ObjectIdentity getObjectIdentity() {
 159  128
         return objectIdentity;
 160  
     }
 161  
 
 162  
     public Sid getOwner() {
 163  41
         return this.owner;
 164  
     }
 165  
 
 166  
     public Acl getParentAcl() {
 167  91
         return parentAcl;
 168  
     }
 169  
 
 170  
     public void insertAce(Serializable afterAceId, Permission permission, Sid sid, boolean granting)
 171  
         throws NotFoundException {
 172  6
         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
 173  6
         Assert.notNull(permission, "Permission required");
 174  6
         Assert.notNull(sid, "Sid required");
 175  
 
 176  6
         AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this, sid, permission, granting, false, false);
 177  
 
 178  6
         synchronized (aces) {
 179  6
             if (afterAceId != null) {
 180  0
                 int offset = findAceOffset(afterAceId);
 181  
 
 182  0
                 if (offset == -1) {
 183  0
                     throw new NotFoundException("Requested ACE ID not found");
 184  
                 }
 185  
 
 186  0
                 this.aces.add(offset + 1, ace);
 187  0
             } else {
 188  6
                 this.aces.add(ace);
 189  
             }
 190  6
         }
 191  6
     }
 192  
 
 193  
     public boolean isEntriesInheriting() {
 194  35
         return entriesInheriting;
 195  
     }
 196  
 
 197  
     /**
 198  
      * Determines authorization.  The order of the <code>permission</code> and <code>sid</code> arguments is
 199  
      * <em>extremely important</em>! The method will iterate through each of the <code>permission</code>s in the order
 200  
      * specified. For each iteration, all of the <code>sid</code>s will be considered, again in the order they are
 201  
      * presented. A search will then be performed for the first {@link AccessControlEntry} object that directly
 202  
      * matches that <code>permission:sid</code> combination. When the <em>first full match</em> is found (ie an ACE
 203  
      * that has the SID currently being searched for and the exact permission bit mask being search for), the grant or
 204  
      * deny flag for that ACE will prevail. If the ACE specifies to grant access, the method will return
 205  
      * <code>true</code>. If the ACE specifies to deny access, the loop will stop and the next <code>permission</code>
 206  
      * iteration will be performed. If each permission indicates to deny access, the first deny ACE found will be
 207  
      * considered the reason for the failure (as it was the first match found, and is therefore the one most logically
 208  
      * requiring changes - although not always). If absolutely no matching ACE was found at all for any permission,
 209  
      * the parent ACL will be tried (provided that there is a parent and {@link #isEntriesInheriting()} is
 210  
      * <code>true</code>. The parent ACL will also scan its parent and so on. If ultimately no matching ACE is found,
 211  
      * a <code>NotFoundException</code> will be thrown and the caller will need to decide how to handle the permission
 212  
      * check. Similarly, if any of the SID arguments presented to the method were not loaded by the ACL,
 213  
      * <code>UnloadedSidException</code> will be thrown.
 214  
      *
 215  
      * @param permission the exact permissions to scan for (order is important)
 216  
      * @param sids the exact SIDs to scan for (order is important)
 217  
      * @param administrativeMode if <code>true</code> denotes the query is for administrative purposes and no auditing
 218  
      *        will be undertaken
 219  
      *
 220  
      * @return <code>true</code> if one of the permissions has been granted, <code>false</code> if one of the
 221  
      *         permissions has been specifically revoked
 222  
      *
 223  
      * @throws NotFoundException if an exact ACE for one of the permission bit masks and SID combination could not be
 224  
      *         found
 225  
      * @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the ACL was only loaded for a
 226  
      *         subset of SIDs
 227  
      */
 228  
     public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
 229  
         throws NotFoundException, UnloadedSidException {
 230  20
         Assert.notEmpty(permission, "Permissions required");
 231  20
         Assert.notEmpty(sids, "SIDs required");
 232  
 
 233  20
         if (!this.isSidLoaded(sids)) {
 234  0
             throw new UnloadedSidException("ACL was not loaded for one or more SID");
 235  
         }
 236  
 
 237  20
         AccessControlEntry firstRejection = null;
 238  
 
 239  35
         for (int i = 0; i < permission.length; i++) {
 240  29
             for (int x = 0; x < sids.length; x++) {
 241  
                 // Attempt to find exact match for this permission mask and SID
 242  20
                 Iterator acesIterator = aces.iterator();
 243  20
                 boolean scanNextSid = true;
 244  
 
 245  34
                 while (acesIterator.hasNext()) {
 246  25
                     AccessControlEntry ace = (AccessControlEntry) acesIterator.next();
 247  
 
 248  25
                     if ((ace.getPermission().getMask() == permission[i].getMask()) && ace.getSid().equals(sids[x])) {
 249  
                         // Found a matching ACE, so its authorization decision will prevail
 250  11
                         if (ace.isGranting()) {
 251  
                             // Success
 252  5
                             if (!administrativeMode) {
 253  4
                                 auditLogger.logIfNeeded(true, ace);
 254  
                             }
 255  
 
 256  5
                             return true;
 257  
                         } else {
 258  
                             // Failure for this permission, so stop search
 259  
                             // We will see if they have a different permission
 260  
                             // (this permission is 100% rejected for this SID)
 261  6
                             if (firstRejection == null) {
 262  
                                 // Store first rejection for auditing reasons
 263  6
                                 firstRejection = ace;
 264  
                             }
 265  
 
 266  6
                             scanNextSid = false; // helps break the loop
 267  
 
 268  6
                             break; // exit "aceIterator" while loop
 269  
                         }
 270  
                     }
 271  14
                 }
 272  
 
 273  15
                 if (!scanNextSid) {
 274  6
                     break; // exit SID for loop (now try next permission)
 275  
                 }
 276  
             }
 277  
         }
 278  
 
 279  15
         if (firstRejection != null) {
 280  
             // We found an ACE to reject the request at this point, as no
 281  
             // other ACEs were found that granted a different permission
 282  6
             if (!administrativeMode) {
 283  4
                 auditLogger.logIfNeeded(false, firstRejection);
 284  
             }
 285  
 
 286  6
             return false;
 287  
         }
 288  
 
 289  
         // No matches have been found so far
 290  9
         if (isEntriesInheriting() && (parentAcl != null)) {
 291  
             // We have a parent, so let them try to find a matching ACE
 292  6
             return parentAcl.isGranted(permission, sids, false);
 293  
         } else {
 294  
             // We either have no parent, or we're the uppermost parent
 295  3
             throw new NotFoundException("Unable to locate a matching ACE for passed permissions and SIDs");
 296  
         }
 297  
     }
 298  
 
 299  
     public boolean isSidLoaded(Sid[] sids) {
 300  
         // If loadedSides is null, this indicates all SIDs were loaded
 301  
         // Also return true if the caller didn't specify a SID to find
 302  30
         if ((this.loadedSids == null) || (sids == null) || (sids.length == 0)) {
 303  30
             return true;
 304  
         }
 305  
 
 306  
         // This ACL applies to a SID subset only. Iterate to check it applies.
 307  0
         for (int i = 0; i < sids.length; i++) {
 308  0
             boolean found = false;
 309  
 
 310  0
             for (int y = 0; y < this.loadedSids.length; y++) {
 311  0
                 if (sids[i].equals(this.loadedSids[y])) {
 312  
                     // this SID is OK
 313  0
                     found = true;
 314  
 
 315  0
                     break; // out of loadedSids for loop
 316  
                 }
 317  
             }
 318  
 
 319  0
             if (!found) {
 320  0
                 return false;
 321  
             }
 322  
         }
 323  
 
 324  0
         return true;
 325  
     }
 326  
 
 327  
     public void setEntriesInheriting(boolean entriesInheriting) {
 328  1
         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
 329  1
         this.entriesInheriting = entriesInheriting;
 330  1
     }
 331  
 
 332  
     public void setOwner(Sid newOwner) {
 333  0
         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
 334  0
         Assert.notNull(newOwner, "Owner required");
 335  0
         this.owner = newOwner;
 336  0
     }
 337  
 
 338  
     public void setParent(Acl newParent) {
 339  2
         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
 340  2
         Assert.notNull(newParent, "New Parent required");
 341  2
         Assert.isTrue(!newParent.equals(this), "Cannot be the parent of yourself");
 342  2
         this.parentAcl = newParent;
 343  2
     }
 344  
 
 345  
     public String toString() {
 346  0
         StringBuffer sb = new StringBuffer();
 347  0
         sb.append("AclImpl[");
 348  0
         sb.append("id: ").append(this.id).append("; ");
 349  0
         sb.append("objectIdentity: ").append(this.objectIdentity).append("; ");
 350  0
         sb.append("owner: ").append(this.owner).append("; ");
 351  
 
 352  0
         Iterator iterator = this.aces.iterator();
 353  0
         int count = 0;
 354  
 
 355  0
         while (iterator.hasNext()) {
 356  0
             count++;
 357  
 
 358  0
             if (count == 1) {
 359  0
                 sb.append("\r\n");
 360  
             }
 361  
 
 362  0
             sb.append(iterator.next().toString()).append("\r\n");
 363  
         }
 364  
 
 365  0
         if (count == 0) {
 366  0
             sb.append("no ACEs; ");
 367  
         }
 368  
 
 369  0
         sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
 370  0
         sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString());
 371  0
         sb.append("]");
 372  
 
 373  0
         return sb.toString();
 374  
     }
 375  
 
 376  
     public void updateAce(Serializable aceId, Permission permission)
 377  
         throws NotFoundException {
 378  0
         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
 379  
 
 380  0
         synchronized (aces) {
 381  0
             int offset = findAceOffset(aceId);
 382  
 
 383  0
             if (offset == 1) {
 384  0
                 throw new NotFoundException("Requested ACE ID not found");
 385  
             }
 386  
 
 387  0
             AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
 388  0
             ace.setPermission(permission);
 389  0
         }
 390  0
     }
 391  
 
 392  
     public void updateAuditing(Serializable aceId, boolean auditSuccess, boolean auditFailure) {
 393  0
         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_AUDITING);
 394  
 
 395  0
         synchronized (aces) {
 396  0
             int offset = findAceOffset(aceId);
 397  
 
 398  0
             if (offset == 1) {
 399  0
                 throw new NotFoundException("Requested ACE ID not found");
 400  
             }
 401  
 
 402  0
             AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
 403  0
             ace.setAuditSuccess(auditSuccess);
 404  0
             ace.setAuditFailure(auditFailure);
 405  0
         }
 406  0
     }
 407  
 }