001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015: package org.acegisecurity.acls.domain;
016:
017: import org.acegisecurity.acls.AccessControlEntry;
018: import org.acegisecurity.acls.Acl;
019: import org.acegisecurity.acls.AuditableAcl;
020: import org.acegisecurity.acls.MutableAcl;
021: import org.acegisecurity.acls.NotFoundException;
022: import org.acegisecurity.acls.OwnershipAcl;
023: import org.acegisecurity.acls.Permission;
024: import org.acegisecurity.acls.UnloadedSidException;
025: import org.acegisecurity.acls.objectidentity.ObjectIdentity;
026: import org.acegisecurity.acls.sid.Sid;
027:
028: import org.springframework.util.Assert;
029:
030: import java.io.Serializable;
031:
032: import java.util.Iterator;
033: import java.util.List;
034: import java.util.Vector;
035:
036: /**
037: * Base implementation of <code>Acl</code>.
038: *
039: * @author Ben Alex
040: * @version $Id
041: */
042: public class AclImpl implements Acl, MutableAcl, AuditableAcl,
043: OwnershipAcl {
044: //~ Instance fields ================================================================================================
045:
046: private Acl parentAcl;
047: private AclAuthorizationStrategy aclAuthorizationStrategy;
048: private AuditLogger auditLogger;
049: private List aces = new Vector();
050: private ObjectIdentity objectIdentity;
051: private Serializable id;
052: private Sid owner; // OwnershipAcl
053: private Sid[] loadedSids = null; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
054: private boolean entriesInheriting = true;
055:
056: //~ Constructors ===================================================================================================
057:
058: /**
059: * Minimal constructor, which should be used {@link
060: * org.acegisecurity.acls.MutableAclService#createAcl(ObjectIdentity)}.
061: *
062: * @param objectIdentity the object identity this ACL relates to (required)
063: * @param id the primary key assigned to this ACL (required)
064: * @param aclAuthorizationStrategy authorization strategy (required)
065: * @param auditLogger audit logger (required)
066: */
067: public AclImpl(ObjectIdentity objectIdentity, Serializable id,
068: AclAuthorizationStrategy aclAuthorizationStrategy,
069: AuditLogger auditLogger) {
070: Assert.notNull(objectIdentity, "Object Identity required");
071: Assert.notNull(id, "Id required");
072: Assert.notNull(aclAuthorizationStrategy,
073: "AclAuthorizationStrategy required");
074: Assert.notNull(auditLogger, "AuditLogger required");
075: this .objectIdentity = objectIdentity;
076: this .id = id;
077: this .aclAuthorizationStrategy = aclAuthorizationStrategy;
078: this .auditLogger = auditLogger;
079: }
080:
081: /**
082: * Full constructor, which should be used by persistence tools that do not
083: * provide field-level access features.
084: *
085: * @param objectIdentity the object identity this ACL relates to (required)
086: * @param id the primary key assigned to this ACL (required)
087: * @param aclAuthorizationStrategy authorization strategy (required)
088: * @param auditLogger audit logger (required)
089: * @param parentAcl the parent (may be <code>null</code>)
090: * @param loadedSids the loaded SIDs if only a subset were loaded (may be
091: * <code>null</code>)
092: * @param entriesInheriting if ACEs from the parent should inherit into
093: * this ACL
094: * @param owner the owner (required)
095: */
096: public AclImpl(ObjectIdentity objectIdentity, Serializable id,
097: AclAuthorizationStrategy aclAuthorizationStrategy,
098: AuditLogger auditLogger, Acl parentAcl, Sid[] loadedSids,
099: boolean entriesInheriting, Sid owner) {
100: Assert.notNull(objectIdentity, "Object Identity required");
101: Assert.notNull(id, "Id required");
102: Assert.notNull(aclAuthorizationStrategy,
103: "AclAuthorizationStrategy required");
104: Assert.notNull(owner, "Owner required");
105: Assert.notNull(auditLogger, "AuditLogger required");
106: this .objectIdentity = objectIdentity;
107: this .id = id;
108: this .aclAuthorizationStrategy = aclAuthorizationStrategy;
109: this .auditLogger = auditLogger;
110: this .parentAcl = parentAcl; // may be null
111: this .loadedSids = loadedSids; // may be null
112: this .entriesInheriting = entriesInheriting;
113: this .owner = owner;
114: }
115:
116: /**
117: * Private no-argument constructor for use by reflection-based persistence
118: * tools along with field-level access.
119: */
120: private AclImpl() {
121: }
122:
123: //~ Methods ========================================================================================================
124:
125: public void deleteAce(Serializable aceId) throws NotFoundException {
126: aclAuthorizationStrategy.securityCheck(this ,
127: AclAuthorizationStrategy.CHANGE_GENERAL);
128:
129: synchronized (aces) {
130: int offset = findAceOffset(aceId);
131:
132: if (offset == -1) {
133: throw new NotFoundException(
134: "Requested ACE ID not found");
135: }
136:
137: this .aces.remove(offset);
138: }
139: }
140:
141: private int findAceOffset(Serializable aceId) {
142: Assert.notNull(aceId, "ACE ID is required");
143:
144: synchronized (aces) {
145: for (int i = 0; i < aces.size(); i++) {
146: AccessControlEntry ace = (AccessControlEntry) aces
147: .get(i);
148:
149: if (ace.getId().equals(aceId)) {
150: return i;
151: }
152: }
153: }
154:
155: return -1;
156: }
157:
158: public AccessControlEntry[] getEntries() {
159: // Can safely return AccessControlEntry directly, as they're immutable outside the ACL package
160: return (AccessControlEntry[]) aces
161: .toArray(new AccessControlEntry[] {});
162: }
163:
164: public Serializable getId() {
165: return this .id;
166: }
167:
168: public ObjectIdentity getObjectIdentity() {
169: return objectIdentity;
170: }
171:
172: public Sid getOwner() {
173: return this .owner;
174: }
175:
176: public Acl getParentAcl() {
177: return parentAcl;
178: }
179:
180: public void insertAce(Serializable afterAceId,
181: Permission permission, Sid sid, boolean granting)
182: throws NotFoundException {
183: aclAuthorizationStrategy.securityCheck(this ,
184: AclAuthorizationStrategy.CHANGE_GENERAL);
185: Assert.notNull(permission, "Permission required");
186: Assert.notNull(sid, "Sid required");
187:
188: AccessControlEntryImpl ace = new AccessControlEntryImpl(null,
189: this , sid, permission, granting, false, false);
190:
191: synchronized (aces) {
192: if (afterAceId != null) {
193: int offset = findAceOffset(afterAceId);
194:
195: if (offset == -1) {
196: throw new NotFoundException(
197: "Requested ACE ID not found");
198: }
199:
200: this .aces.add(offset + 1, ace);
201: } else {
202: this .aces.add(ace);
203: }
204: }
205: }
206:
207: public boolean isEntriesInheriting() {
208: return entriesInheriting;
209: }
210:
211: /**
212: * Determines authorization. The order of the <code>permission</code> and <code>sid</code> arguments is
213: * <em>extremely important</em>! The method will iterate through each of the <code>permission</code>s in the order
214: * specified. For each iteration, all of the <code>sid</code>s will be considered, again in the order they are
215: * presented. A search will then be performed for the first {@link AccessControlEntry} object that directly
216: * matches that <code>permission:sid</code> combination. When the <em>first full match</em> is found (ie an ACE
217: * that has the SID currently being searched for and the exact permission bit mask being search for), the grant or
218: * deny flag for that ACE will prevail. If the ACE specifies to grant access, the method will return
219: * <code>true</code>. If the ACE specifies to deny access, the loop will stop and the next <code>permission</code>
220: * iteration will be performed. If each permission indicates to deny access, the first deny ACE found will be
221: * considered the reason for the failure (as it was the first match found, and is therefore the one most logically
222: * requiring changes - although not always). If absolutely no matching ACE was found at all for any permission,
223: * the parent ACL will be tried (provided that there is a parent and {@link #isEntriesInheriting()} is
224: * <code>true</code>. The parent ACL will also scan its parent and so on. If ultimately no matching ACE is found,
225: * a <code>NotFoundException</code> will be thrown and the caller will need to decide how to handle the permission
226: * check. Similarly, if any of the SID arguments presented to the method were not loaded by the ACL,
227: * <code>UnloadedSidException</code> will be thrown.
228: *
229: * @param permission the exact permissions to scan for (order is important)
230: * @param sids the exact SIDs to scan for (order is important)
231: * @param administrativeMode if <code>true</code> denotes the query is for administrative purposes and no auditing
232: * will be undertaken
233: *
234: * @return <code>true</code> if one of the permissions has been granted, <code>false</code> if one of the
235: * permissions has been specifically revoked
236: *
237: * @throws NotFoundException if an exact ACE for one of the permission bit masks and SID combination could not be
238: * found
239: * @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the ACL was only loaded for a
240: * subset of SIDs
241: */
242: public boolean isGranted(Permission[] permission, Sid[] sids,
243: boolean administrativeMode) throws NotFoundException,
244: UnloadedSidException {
245: Assert.notEmpty(permission, "Permissions required");
246: Assert.notEmpty(sids, "SIDs required");
247:
248: if (!this .isSidLoaded(sids)) {
249: throw new UnloadedSidException(
250: "ACL was not loaded for one or more SID");
251: }
252:
253: AccessControlEntry firstRejection = null;
254:
255: for (int i = 0; i < permission.length; i++) {
256: for (int x = 0; x < sids.length; x++) {
257: // Attempt to find exact match for this permission mask and SID
258: Iterator acesIterator = aces.iterator();
259: boolean scanNextSid = true;
260:
261: while (acesIterator.hasNext()) {
262: AccessControlEntry ace = (AccessControlEntry) acesIterator
263: .next();
264:
265: if ((ace.getPermission().getMask() == permission[i]
266: .getMask())
267: && ace.getSid().equals(sids[x])) {
268: // Found a matching ACE, so its authorization decision will prevail
269: if (ace.isGranting()) {
270: // Success
271: if (!administrativeMode) {
272: auditLogger.logIfNeeded(true, ace);
273: }
274:
275: return true;
276: } else {
277: // Failure for this permission, so stop search
278: // We will see if they have a different permission
279: // (this permission is 100% rejected for this SID)
280: if (firstRejection == null) {
281: // Store first rejection for auditing reasons
282: firstRejection = ace;
283: }
284:
285: scanNextSid = false; // helps break the loop
286:
287: break; // exit "aceIterator" while loop
288: }
289: }
290: }
291:
292: if (!scanNextSid) {
293: break; // exit SID for loop (now try next permission)
294: }
295: }
296: }
297:
298: if (firstRejection != null) {
299: // We found an ACE to reject the request at this point, as no
300: // other ACEs were found that granted a different permission
301: if (!administrativeMode) {
302: auditLogger.logIfNeeded(false, firstRejection);
303: }
304:
305: return false;
306: }
307:
308: // No matches have been found so far
309: if (isEntriesInheriting() && (parentAcl != null)) {
310: // We have a parent, so let them try to find a matching ACE
311: return parentAcl.isGranted(permission, sids, false);
312: } else {
313: // We either have no parent, or we're the uppermost parent
314: throw new NotFoundException(
315: "Unable to locate a matching ACE for passed permissions and SIDs");
316: }
317: }
318:
319: public boolean isSidLoaded(Sid[] sids) {
320: // If loadedSides is null, this indicates all SIDs were loaded
321: // Also return true if the caller didn't specify a SID to find
322: if ((this .loadedSids == null) || (sids == null)
323: || (sids.length == 0)) {
324: return true;
325: }
326:
327: // This ACL applies to a SID subset only. Iterate to check it applies.
328: for (int i = 0; i < sids.length; i++) {
329: boolean found = false;
330:
331: for (int y = 0; y < this .loadedSids.length; y++) {
332: if (sids[i].equals(this .loadedSids[y])) {
333: // this SID is OK
334: found = true;
335:
336: break; // out of loadedSids for loop
337: }
338: }
339:
340: if (!found) {
341: return false;
342: }
343: }
344:
345: return true;
346: }
347:
348: public void setEntriesInheriting(boolean entriesInheriting) {
349: aclAuthorizationStrategy.securityCheck(this ,
350: AclAuthorizationStrategy.CHANGE_GENERAL);
351: this .entriesInheriting = entriesInheriting;
352: }
353:
354: public void setOwner(Sid newOwner) {
355: aclAuthorizationStrategy.securityCheck(this ,
356: AclAuthorizationStrategy.CHANGE_OWNERSHIP);
357: Assert.notNull(newOwner, "Owner required");
358: this .owner = newOwner;
359: }
360:
361: public void setParent(Acl newParent) {
362: aclAuthorizationStrategy.securityCheck(this ,
363: AclAuthorizationStrategy.CHANGE_GENERAL);
364: Assert.notNull(newParent, "New Parent required");
365: Assert.isTrue(!newParent.equals(this ),
366: "Cannot be the parent of yourself");
367: this .parentAcl = newParent;
368: }
369:
370: public String toString() {
371: StringBuffer sb = new StringBuffer();
372: sb.append("AclImpl[");
373: sb.append("id: ").append(this .id).append("; ");
374: sb.append("objectIdentity: ").append(this .objectIdentity)
375: .append("; ");
376: sb.append("owner: ").append(this .owner).append("; ");
377:
378: Iterator iterator = this .aces.iterator();
379: int count = 0;
380:
381: while (iterator.hasNext()) {
382: count++;
383:
384: if (count == 1) {
385: sb.append("\r\n");
386: }
387:
388: sb.append(iterator.next().toString()).append("\r\n");
389: }
390:
391: if (count == 0) {
392: sb.append("no ACEs; ");
393: }
394:
395: sb.append("inheriting: ").append(this .entriesInheriting)
396: .append("; ");
397: sb.append("parent: ").append(
398: (this .parentAcl == null) ? "Null" : this .parentAcl
399: .getObjectIdentity().toString());
400: sb.append("]");
401:
402: return sb.toString();
403: }
404:
405: public void updateAce(Serializable aceId, Permission permission)
406: throws NotFoundException {
407: aclAuthorizationStrategy.securityCheck(this ,
408: AclAuthorizationStrategy.CHANGE_GENERAL);
409:
410: synchronized (aces) {
411: int offset = findAceOffset(aceId);
412:
413: if (offset == 1) {
414: throw new NotFoundException(
415: "Requested ACE ID not found");
416: }
417:
418: AccessControlEntryImpl ace = (AccessControlEntryImpl) aces
419: .get(offset);
420: ace.setPermission(permission);
421: }
422: }
423:
424: public void updateAuditing(Serializable aceId,
425: boolean auditSuccess, boolean auditFailure) {
426: aclAuthorizationStrategy.securityCheck(this ,
427: AclAuthorizationStrategy.CHANGE_AUDITING);
428:
429: synchronized (aces) {
430: int offset = findAceOffset(aceId);
431:
432: if (offset == 1) {
433: throw new NotFoundException(
434: "Requested ACE ID not found");
435: }
436:
437: AccessControlEntryImpl ace = (AccessControlEntryImpl) aces
438: .get(offset);
439: ace.setAuditSuccess(auditSuccess);
440: ace.setAuditFailure(auditFailure);
441: }
442: }
443: }
|