View Javadoc

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      private List aces = new Vector();
50      private ObjectIdentity objectIdentity;
51      private Serializable id;
52      private Sid owner; // OwnershipAcl
53      private Sid[] loadedSids = null; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
54      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          AuditLogger auditLogger) {
69          Assert.notNull(objectIdentity, "Object Identity required");
70          Assert.notNull(id, "Id required");
71          Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
72          Assert.notNull(auditLogger, "AuditLogger required");
73          this.objectIdentity = objectIdentity;
74          this.id = id;
75          this.aclAuthorizationStrategy = aclAuthorizationStrategy;
76          this.auditLogger = auditLogger;
77      }
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          AuditLogger auditLogger, Acl parentAcl, Sid[] loadedSids, boolean entriesInheriting, Sid owner) {
96          Assert.notNull(objectIdentity, "Object Identity required");
97          Assert.notNull(id, "Id required");
98          Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
99          Assert.notNull(owner, "Owner required");
100         Assert.notNull(auditLogger, "AuditLogger required");
101         this.objectIdentity = objectIdentity;
102         this.id = id;
103         this.aclAuthorizationStrategy = aclAuthorizationStrategy;
104         this.auditLogger = auditLogger;
105         this.parentAcl = parentAcl; // may be null
106         this.loadedSids = loadedSids; // may be null
107         this.entriesInheriting = entriesInheriting;
108         this.owner = owner;
109     }
110 
111 /**
112      * Private no-argument constructor for use by reflection-based persistence
113      * tools along with field-level access.
114      */
115     private AclImpl() {}
116 
117     //~ Methods ========================================================================================================
118 
119     public void deleteAce(Serializable aceId) throws NotFoundException {
120         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
121 
122         synchronized (aces) {
123             int offset = findAceOffset(aceId);
124 
125             if (offset == -1) {
126                 throw new NotFoundException("Requested ACE ID not found");
127             }
128 
129             this.aces.remove(offset);
130         }
131     }
132 
133     private int findAceOffset(Serializable aceId) {
134         Assert.notNull(aceId, "ACE ID is required");
135 
136         synchronized (aces) {
137             for (int i = 0; i < aces.size(); i++) {
138                 AccessControlEntry ace = (AccessControlEntry) aces.get(i);
139 
140                 if (ace.getId().equals(aceId)) {
141                     return i;
142                 }
143             }
144         }
145 
146         return -1;
147     }
148 
149     public AccessControlEntry[] getEntries() {
150         // Can safely return AccessControlEntry directly, as they're immutable outside the ACL package
151         return (AccessControlEntry[]) aces.toArray(new AccessControlEntry[] {});
152     }
153 
154     public Serializable getId() {
155         return this.id;
156     }
157 
158     public ObjectIdentity getObjectIdentity() {
159         return objectIdentity;
160     }
161 
162     public Sid getOwner() {
163         return this.owner;
164     }
165 
166     public Acl getParentAcl() {
167         return parentAcl;
168     }
169 
170     public void insertAce(Serializable afterAceId, Permission permission, Sid sid, boolean granting)
171         throws NotFoundException {
172         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
173         Assert.notNull(permission, "Permission required");
174         Assert.notNull(sid, "Sid required");
175 
176         AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this, sid, permission, granting, false, false);
177 
178         synchronized (aces) {
179             if (afterAceId != null) {
180                 int offset = findAceOffset(afterAceId);
181 
182                 if (offset == -1) {
183                     throw new NotFoundException("Requested ACE ID not found");
184                 }
185 
186                 this.aces.add(offset + 1, ace);
187             } else {
188                 this.aces.add(ace);
189             }
190         }
191     }
192 
193     public boolean isEntriesInheriting() {
194         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         Assert.notEmpty(permission, "Permissions required");
231         Assert.notEmpty(sids, "SIDs required");
232 
233         if (!this.isSidLoaded(sids)) {
234             throw new UnloadedSidException("ACL was not loaded for one or more SID");
235         }
236 
237         AccessControlEntry firstRejection = null;
238 
239         for (int i = 0; i < permission.length; i++) {
240             for (int x = 0; x < sids.length; x++) {
241                 // Attempt to find exact match for this permission mask and SID
242                 Iterator acesIterator = aces.iterator();
243                 boolean scanNextSid = true;
244 
245                 while (acesIterator.hasNext()) {
246                     AccessControlEntry ace = (AccessControlEntry) acesIterator.next();
247 
248                     if ((ace.getPermission().getMask() == permission[i].getMask()) && ace.getSid().equals(sids[x])) {
249                         // Found a matching ACE, so its authorization decision will prevail
250                         if (ace.isGranting()) {
251                             // Success
252                             if (!administrativeMode) {
253                                 auditLogger.logIfNeeded(true, ace);
254                             }
255 
256                             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                             if (firstRejection == null) {
262                                 // Store first rejection for auditing reasons
263                                 firstRejection = ace;
264                             }
265 
266                             scanNextSid = false; // helps break the loop
267 
268                             break; // exit "aceIterator" while loop
269                         }
270                     }
271                 }
272 
273                 if (!scanNextSid) {
274                     break; // exit SID for loop (now try next permission)
275                 }
276             }
277         }
278 
279         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             if (!administrativeMode) {
283                 auditLogger.logIfNeeded(false, firstRejection);
284             }
285 
286             return false;
287         }
288 
289         // No matches have been found so far
290         if (isEntriesInheriting() && (parentAcl != null)) {
291             // We have a parent, so let them try to find a matching ACE
292             return parentAcl.isGranted(permission, sids, false);
293         } else {
294             // We either have no parent, or we're the uppermost parent
295             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         if ((this.loadedSids == null) || (sids == null) || (sids.length == 0)) {
303             return true;
304         }
305 
306         // This ACL applies to a SID subset only. Iterate to check it applies.
307         for (int i = 0; i < sids.length; i++) {
308             boolean found = false;
309 
310             for (int y = 0; y < this.loadedSids.length; y++) {
311                 if (sids[i].equals(this.loadedSids[y])) {
312                     // this SID is OK
313                     found = true;
314 
315                     break; // out of loadedSids for loop
316                 }
317             }
318 
319             if (!found) {
320                 return false;
321             }
322         }
323 
324         return true;
325     }
326 
327     public void setEntriesInheriting(boolean entriesInheriting) {
328         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
329         this.entriesInheriting = entriesInheriting;
330     }
331 
332     public void setOwner(Sid newOwner) {
333         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
334         Assert.notNull(newOwner, "Owner required");
335         this.owner = newOwner;
336     }
337 
338     public void setParent(Acl newParent) {
339         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
340         Assert.notNull(newParent, "New Parent required");
341         Assert.isTrue(!newParent.equals(this), "Cannot be the parent of yourself");
342         this.parentAcl = newParent;
343     }
344 
345     public String toString() {
346         StringBuffer sb = new StringBuffer();
347         sb.append("AclImpl[");
348         sb.append("id: ").append(this.id).append("; ");
349         sb.append("objectIdentity: ").append(this.objectIdentity).append("; ");
350         sb.append("owner: ").append(this.owner).append("; ");
351 
352         Iterator iterator = this.aces.iterator();
353         int count = 0;
354 
355         while (iterator.hasNext()) {
356             count++;
357 
358             if (count == 1) {
359                 sb.append("\r\n");
360             }
361 
362             sb.append(iterator.next().toString()).append("\r\n");
363         }
364 
365         if (count == 0) {
366             sb.append("no ACEs; ");
367         }
368 
369         sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
370         sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString());
371         sb.append("]");
372 
373         return sb.toString();
374     }
375 
376     public void updateAce(Serializable aceId, Permission permission)
377         throws NotFoundException {
378         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
379 
380         synchronized (aces) {
381             int offset = findAceOffset(aceId);
382 
383             if (offset == 1) {
384                 throw new NotFoundException("Requested ACE ID not found");
385             }
386 
387             AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
388             ace.setPermission(permission);
389         }
390     }
391 
392     public void updateAuditing(Serializable aceId, boolean auditSuccess, boolean auditFailure) {
393         aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_AUDITING);
394 
395         synchronized (aces) {
396             int offset = findAceOffset(aceId);
397 
398             if (offset == 1) {
399                 throw new NotFoundException("Requested ACE ID not found");
400             }
401 
402             AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
403             ace.setAuditSuccess(auditSuccess);
404             ace.setAuditFailure(auditFailure);
405         }
406     }
407 }