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.vote;
016:
017: import java.lang.reflect.InvocationTargetException;
018: import java.lang.reflect.Method;
019: import java.util.Iterator;
020:
021: import org.acegisecurity.Authentication;
022: import org.acegisecurity.AuthorizationServiceException;
023: import org.acegisecurity.ConfigAttribute;
024: import org.acegisecurity.ConfigAttributeDefinition;
025: import org.acegisecurity.acls.Acl;
026: import org.acegisecurity.acls.AclService;
027: import org.acegisecurity.acls.NotFoundException;
028: import org.acegisecurity.acls.Permission;
029: import org.acegisecurity.acls.objectidentity.ObjectIdentity;
030: import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategy;
031: import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategyImpl;
032: import org.acegisecurity.acls.sid.Sid;
033: import org.acegisecurity.acls.sid.SidRetrievalStrategy;
034: import org.acegisecurity.acls.sid.SidRetrievalStrategyImpl;
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037: import org.springframework.util.Assert;
038:
039: /**
040: * <p>Given a domain object instance passed as a method argument, ensures the principal has appropriate permission
041: * as indicated by the {@link AclService}.</p>
042: * <p>The <code>AclService</code> is used to retrieve the access control list (ACL) permissions associated with a
043: * domain object instance for the current <code>Authentication</code> object.</p>
044: * <p>The voter will vote if any {@link ConfigAttribute#getAttribute()} matches the {@link
045: * #processConfigAttribute}. The provider will then locate the first method argument of type {@link
046: * #processDomainObjectClass}. Assuming that method argument is non-null, the provider will then lookup the ACLs from
047: * the <code>AclManager</code> and ensure the principal is {@link Acl#isGranted(org.acegisecurity.acls.Permission[],
048: * org.acegisecurity.acls.sid.Sid[], boolean)} when presenting the {@link #requirePermission} array to that method.</p>
049: * <p>If the method argument is <code>null</code>, the voter will abstain from voting. If the method argument
050: * could not be found, an {@link org.acegisecurity.AuthorizationServiceException} will be thrown.</p>
051: * <p>In practical terms users will typically setup a number of <code>AclEntryVoter</code>s. Each will have a
052: * different {@link #processDomainObjectClass}, {@link #processConfigAttribute} and {@link #requirePermission}
053: * combination. For example, a small application might employ the following instances of <code>AclEntryVoter</code>:
054: * <ul>
055: * <li>Process domain object class <code>BankAccount</code>, configuration attribute
056: * <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission <code>BasePermission.READ</code></li>
057: * <li>Process domain object class <code>BankAccount</code>, configuration attribute
058: * <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list <code>BasePermission.WRITE</code> and
059: * <code>BasePermission.CREATE</code> (allowing the principal to have <b>either</b> of these two permissions</li>
060: * <li>Process domain object class <code>Customer</code>, configuration attribute
061: * <code>VOTE_ACL_CUSTOMER_READ</code>, require permission <code>BasePermission.READ</code></li>
062: * <li>Process domain object class <code>Customer</code>, configuration attribute
063: * <code>VOTE_ACL_CUSTOMER_WRITE</code>, require permission list <code>BasePermission.WRITE</code> and
064: * <code>BasePermission.CREATE</code></li>
065: * </ul>
066: * Alternatively, you could have used a common superclass or interface for the {@link #processDomainObjectClass}
067: * if both <code>BankAccount</code> and <code>Customer</code> had common parents.</p>
068: * <p>If the principal does not have sufficient permissions, the voter will vote to deny access.</p>
069: * <p>All comparisons and prefixes are case sensitive.</p>
070: *
071: * @author Ben Alex
072: * @version $Id: AclEntryVoter.java 1784 2007-02-24 21:00:24Z luke_t $
073: */
074: public class AclEntryVoter extends AbstractAclVoter {
075: //~ Static fields/initializers =====================================================================================
076:
077: private static final Log logger = LogFactory
078: .getLog(AclEntryVoter.class);
079:
080: //~ Instance fields ================================================================================================
081:
082: private AclService aclService;
083: private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
084: private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
085: private String internalMethod;
086: private String processConfigAttribute;
087: private Permission[] requirePermission;
088:
089: //~ Constructors ===================================================================================================
090:
091: public AclEntryVoter(AclService aclService,
092: String processConfigAttribute,
093: Permission[] requirePermission) {
094: Assert.notNull(processConfigAttribute,
095: "A processConfigAttribute is mandatory");
096: Assert.notNull(aclService, "An AclService is mandatory");
097:
098: if ((requirePermission == null)
099: || (requirePermission.length == 0)) {
100: throw new IllegalArgumentException(
101: "One or more requirePermission entries is mandatory");
102: }
103:
104: this .aclService = aclService;
105: this .processConfigAttribute = processConfigAttribute;
106: this .requirePermission = requirePermission;
107: }
108:
109: //~ Methods ========================================================================================================
110:
111: /**
112: * Optionally specifies a method of the domain object that will be used to obtain a contained domain
113: * object. That contained domain object will be used for the ACL evaluation. This is useful if a domain object
114: * contains a parent that an ACL evaluation should be targeted for, instead of the child domain object (which
115: * perhaps is being created and as such does not yet have any ACL permissions)
116: *
117: * @return <code>null</code> to use the domain object, or the name of a method (that requires no arguments) that
118: * should be invoked to obtain an <code>Object</code> which will be the domain object used for ACL
119: * evaluation
120: */
121: public String getInternalMethod() {
122: return internalMethod;
123: }
124:
125: public String getProcessConfigAttribute() {
126: return processConfigAttribute;
127: }
128:
129: public void setInternalMethod(String internalMethod) {
130: this .internalMethod = internalMethod;
131: }
132:
133: public void setObjectIdentityRetrievalStrategy(
134: ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
135: Assert.notNull(objectIdentityRetrievalStrategy,
136: "ObjectIdentityRetrievalStrategy required");
137: this .objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
138: }
139:
140: public void setSidRetrievalStrategy(
141: SidRetrievalStrategy sidRetrievalStrategy) {
142: Assert.notNull(sidRetrievalStrategy,
143: "SidRetrievalStrategy required");
144: this .sidRetrievalStrategy = sidRetrievalStrategy;
145: }
146:
147: public boolean supports(ConfigAttribute attribute) {
148: if ((attribute.getAttribute() != null)
149: && attribute.getAttribute().equals(
150: getProcessConfigAttribute())) {
151: return true;
152: } else {
153: return false;
154: }
155: }
156:
157: public int vote(Authentication authentication, Object object,
158: ConfigAttributeDefinition config) {
159: Iterator iter = config.getConfigAttributes();
160:
161: while (iter.hasNext()) {
162: ConfigAttribute attr = (ConfigAttribute) iter.next();
163:
164: if (this .supports(attr)) {
165: // Need to make an access decision on this invocation
166: // Attempt to locate the domain object instance to process
167: Object domainObject = getDomainObjectInstance(object);
168:
169: // Evaluate if we are required to use an inner domain object
170: if (domainObject != null && internalMethod != null
171: && (!"".equals(internalMethod))) {
172: try {
173: Class clazz = domainObject.getClass();
174: Method method = clazz.getMethod(internalMethod,
175: new Class[] {});
176: domainObject = method.invoke(domainObject,
177: new Object[] {});
178: } catch (NoSuchMethodException nsme) {
179: throw new AuthorizationServiceException(
180: "Object of class '"
181: + domainObject.getClass()
182: + "' does not provide the requested internalMethod: "
183: + internalMethod);
184: } catch (IllegalAccessException iae) {
185: if (logger.isDebugEnabled()) {
186: logger.debug("IllegalAccessException", iae);
187:
188: if (iae.getCause() != null) {
189: logger.debug("Cause: "
190: + iae.getCause().getMessage(),
191: iae.getCause());
192: }
193: }
194:
195: throw new AuthorizationServiceException(
196: "Problem invoking internalMethod: "
197: + internalMethod
198: + " for object: "
199: + domainObject);
200: } catch (InvocationTargetException ite) {
201: if (logger.isDebugEnabled()) {
202: logger.debug("InvocationTargetException",
203: ite);
204:
205: if (ite.getCause() != null) {
206: logger.debug("Cause: "
207: + ite.getCause().getMessage(),
208: ite.getCause());
209: }
210: }
211:
212: throw new AuthorizationServiceException(
213: "Problem invoking internalMethod: "
214: + internalMethod
215: + " for object: "
216: + domainObject);
217: }
218: }
219:
220: // If domain object is null, vote to abstain
221: if (domainObject == null) {
222: if (logger.isDebugEnabled()) {
223: logger
224: .debug("Voting to abstain - domainObject is null");
225: }
226:
227: return AccessDecisionVoter.ACCESS_ABSTAIN;
228: }
229:
230: // Obtain the OID applicable to the domain object
231: ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy
232: .getObjectIdentity(domainObject);
233:
234: // Obtain the SIDs applicable to the principal
235: Sid[] sids = sidRetrievalStrategy
236: .getSids(authentication);
237:
238: Acl acl;
239:
240: try {
241: // Lookup only ACLs for SIDs we're interested in
242: acl = aclService.readAclById(objectIdentity, sids);
243: } catch (NotFoundException nfe) {
244: if (logger.isDebugEnabled()) {
245: logger
246: .debug("Voting to deny access - no ACLs apply for this principal");
247: }
248:
249: return AccessDecisionVoter.ACCESS_DENIED;
250: }
251:
252: try {
253: if (acl.isGranted(requirePermission, sids, false)) {
254: if (logger.isDebugEnabled()) {
255: logger.debug("Voting to grant access");
256: }
257:
258: return AccessDecisionVoter.ACCESS_GRANTED;
259: } else {
260: if (logger.isDebugEnabled()) {
261: logger
262: .debug("Voting to deny access - ACLs returned, but insufficient permissions for this principal");
263: }
264:
265: return AccessDecisionVoter.ACCESS_DENIED;
266: }
267: } catch (NotFoundException nfe) {
268: if (logger.isDebugEnabled()) {
269: logger
270: .debug("Voting to deny access - no ACLs apply for this principal");
271: }
272:
273: return AccessDecisionVoter.ACCESS_DENIED;
274: }
275: }
276: }
277:
278: // No configuration attribute matched, so abstain
279: return AccessDecisionVoter.ACCESS_ABSTAIN;
280: }
281: }
|