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:
016: package org.acegisecurity.afterinvocation;
017:
018: import org.acegisecurity.AccessDeniedException;
019: import org.acegisecurity.AcegiMessageSource;
020: import org.acegisecurity.Authentication;
021: import org.acegisecurity.ConfigAttribute;
022: import org.acegisecurity.ConfigAttributeDefinition;
023:
024: import org.acegisecurity.acl.AclEntry;
025: import org.acegisecurity.acl.AclManager;
026: import org.acegisecurity.acl.basic.BasicAclEntry;
027: import org.acegisecurity.acl.basic.SimpleAclEntry;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import org.springframework.beans.factory.InitializingBean;
033:
034: import org.springframework.context.MessageSource;
035: import org.springframework.context.MessageSourceAware;
036: import org.springframework.context.i18n.LocaleContextHolder;
037: import org.springframework.context.support.MessageSourceAccessor;
038:
039: import org.springframework.util.Assert;
040:
041: import java.util.Iterator;
042:
043: /**
044: * <p>Given a domain object instance returned from a secure object invocation, ensures the principal has
045: * appropriate permission as defined by the {@link AclManager}.</p>
046: * <p>The <code>AclManager</code> is used to retrieve the access control list (ACL) permissions associated with a
047: * domain object instance for the current <code>Authentication</code> object. This class is designed to process {@link
048: * AclEntry}s that are subclasses of {@link org.acegisecurity.acl.basic.BasicAclEntry} only. Generally these are
049: * obtained by using the {@link org.acegisecurity.acl.basic.BasicAclProvider}.</p>
050: * <p>This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} matches the {@link
051: * #processConfigAttribute}. The provider will then lookup the ACLs from the <code>AclManager</code> and ensure the
052: * principal is {@link org.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for at least one of the {@link
053: * #requirePermission}s.</p>
054: * <p>Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code> with a {@link
055: * #processConfigAttribute} of <code>AFTER_ACL_READ</code> and a {@link #requirePermission} of
056: * <code>SimpleAclEntry.READ</code>. These are also the defaults.</p>
057: * <p>If the principal does not have sufficient permissions, an <code>AccessDeniedException</code> will be thrown.</p>
058: * <p>The <code>AclManager</code> is allowed to return any implementations of <code>AclEntry</code> it wishes.
059: * However, this provider will only be able to validate against <code>BasicAclEntry</code>s, and thus access will be
060: * denied if no <code>AclEntry</code> is of type <code>BasicAclEntry</code>.</p>
061: * <p>If the provided <code>returnObject</code> is <code>null</code>, permission will always be granted and
062: * <code>null</code> will be returned.</p>
063: * <p>All comparisons and prefixes are case sensitive.</p>
064: */
065: public class BasicAclEntryAfterInvocationProvider implements
066: AfterInvocationProvider, InitializingBean, MessageSourceAware {
067: //~ Static fields/initializers =====================================================================================
068:
069: protected static final Log logger = LogFactory
070: .getLog(BasicAclEntryAfterInvocationProvider.class);
071:
072: //~ Instance fields ================================================================================================
073:
074: private AclManager aclManager;
075: private Class processDomainObjectClass = Object.class;
076: protected MessageSourceAccessor messages = AcegiMessageSource
077: .getAccessor();
078: private String processConfigAttribute = "AFTER_ACL_READ";
079: private int[] requirePermission = { SimpleAclEntry.READ };
080:
081: //~ Methods ========================================================================================================
082:
083: public void afterPropertiesSet() throws Exception {
084: Assert.notNull(processConfigAttribute,
085: "A processConfigAttribute is mandatory");
086: Assert.notNull(aclManager, "An aclManager is mandatory");
087: Assert.notNull(messages, "A message source must be set");
088:
089: if ((requirePermission == null)
090: || (requirePermission.length == 0)) {
091: throw new IllegalArgumentException(
092: "One or more requirePermission entries is mandatory");
093: }
094: }
095:
096: public Object decide(Authentication authentication, Object object,
097: ConfigAttributeDefinition config, Object returnedObject)
098: throws AccessDeniedException {
099: Iterator iter = config.getConfigAttributes();
100:
101: while (iter.hasNext()) {
102: ConfigAttribute attr = (ConfigAttribute) iter.next();
103:
104: if (this .supports(attr)) {
105: // Need to make an access decision on this invocation
106: if (returnedObject == null) {
107: // AclManager interface contract prohibits nulls
108: // As they have permission to null/nothing, grant access
109: if (logger.isDebugEnabled()) {
110: logger.debug("Return object is null, skipping");
111: }
112:
113: return null;
114: }
115:
116: if (!processDomainObjectClass
117: .isAssignableFrom(returnedObject.getClass())) {
118: if (logger.isDebugEnabled()) {
119: logger
120: .debug("Return object is not applicable for this provider, skipping");
121: }
122:
123: return null;
124: }
125:
126: AclEntry[] acls = aclManager.getAcls(returnedObject,
127: authentication);
128:
129: if ((acls == null) || (acls.length == 0)) {
130: throw new AccessDeniedException(
131: messages
132: .getMessage(
133: "BasicAclEntryAfterInvocationProvider.noPermission",
134: new Object[] {
135: authentication
136: .getName(),
137: returnedObject },
138: "Authentication {0} has NO permissions at all to the domain object {1}",
139: LocaleContextHolder
140: .getLocale()));
141: }
142:
143: for (int i = 0; i < acls.length; i++) {
144: // Locate processable AclEntrys
145: if (acls[i] instanceof BasicAclEntry) {
146: BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
147:
148: // See if principal has any of the required permissions
149: for (int y = 0; y < requirePermission.length; y++) {
150: if (processableAcl
151: .isPermitted(requirePermission[y])) {
152: if (logger.isDebugEnabled()) {
153: logger
154: .debug("Principal DOES have permission to return object: "
155: + returnedObject
156: + " due to ACL: "
157: + processableAcl
158: .toString());
159: }
160:
161: return returnedObject;
162: }
163: }
164: }
165: }
166:
167: // No permissions match
168: throw new AccessDeniedException(
169: messages
170: .getMessage(
171: "BasicAclEntryAfterInvocationProvider.insufficientPermission",
172: new Object[] {
173: authentication
174: .getName(),
175: returnedObject },
176: "Authentication {0} has ACL permissions to the domain object, "
177: + "but not the required ACL permission to the domain object {1}",
178: LocaleContextHolder.getLocale()));
179: }
180: }
181:
182: return returnedObject;
183: }
184:
185: public AclManager getAclManager() {
186: return aclManager;
187: }
188:
189: public String getProcessConfigAttribute() {
190: return processConfigAttribute;
191: }
192:
193: public int[] getRequirePermission() {
194: return requirePermission;
195: }
196:
197: public void setAclManager(AclManager aclManager) {
198: this .aclManager = aclManager;
199: }
200:
201: public void setMessageSource(MessageSource messageSource) {
202: this .messages = new MessageSourceAccessor(messageSource);
203: }
204:
205: public void setProcessConfigAttribute(String processConfigAttribute) {
206: this .processConfigAttribute = processConfigAttribute;
207: }
208:
209: public void setProcessDomainObjectClass(
210: Class processDomainObjectClass) {
211: Assert.notNull(processDomainObjectClass,
212: "processDomainObjectClass cannot be set to null");
213: this .processDomainObjectClass = processDomainObjectClass;
214: }
215:
216: public void setRequirePermission(int[] requirePermission) {
217: this .requirePermission = requirePermission;
218: }
219:
220: /**
221: * Allow setting permissions with String literals instead of integers as {@link #setRequirePermission(int[])}
222: *
223: * @param requiredPermissions Permission literals
224: * @see SimpleAclEntry#parsePermissions(String[]) for valid values
225: */
226: public void setRequirePermissionFromString(
227: String[] requiredPermissions) {
228: setRequirePermission(SimpleAclEntry
229: .parsePermissions(requiredPermissions));
230: }
231:
232: public boolean supports(ConfigAttribute attribute) {
233: if ((attribute.getAttribute() != null)
234: && attribute.getAttribute().equals(
235: getProcessConfigAttribute())) {
236: return true;
237: } else {
238: return false;
239: }
240: }
241:
242: /**
243: * This implementation supports any type of class, because it does not query the presented secure object.
244: *
245: * @param clazz the secure object
246: *
247: * @return always <code>true</code>
248: */
249: public boolean supports(Class clazz) {
250: return true;
251: }
252: }
|