diff --git a/core/src/main/java/org/acegisecurity/taglibs/authz/AclTag.java b/core/src/main/java/org/acegisecurity/taglibs/authz/AclTag.java new file mode 100644 index 0000000000..bedb19cefe --- /dev/null +++ b/core/src/main/java/org/acegisecurity/taglibs/authz/AclTag.java @@ -0,0 +1,249 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.taglibs.authz; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.acl.AclEntry; +import net.sf.acegisecurity.acl.AclManager; +import net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry; +import net.sf.acegisecurity.context.ContextHolder; +import net.sf.acegisecurity.context.SecureContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationContext; + +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.util.ExpressionEvaluationUtils; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import javax.servlet.ServletContext; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.Tag; +import javax.servlet.jsp.tagext.TagSupport; + + +/** + * An implementation of {@link javax.servlet.jsp.tagext.Tag} that allows its + * body through if some authorizations are granted to the request's principal. + * + *

+ * Only works with permissions that are subclasses of {@link + * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry}. + *

+ * + *

+ * One or more comma separate integer permissions are specified via the + * hasPermission attribute. The tag will include its body if + * any of the integer permissions have been granted to the current + * Authentication (obtained from the ContextHolder). + *

+ * + *

+ * For this class to operate it must be able to access the application context + * via the WebApplicationContextUtils and locate an {@link + * AclManager}. Application contexts have no need to have more than one + * AclManager (as a provider-based implementation can be used so + * that it locates a provider that is authoritative for the given domain + * object instance), so the first AclManager located will be + * used. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class AclTag extends TagSupport { + //~ Static fields/initializers ============================================= + + protected static final Log logger = LogFactory.getLog(AclTag.class); + + //~ Instance fields ======================================================== + + private Object domainObject; + private String hasPermission = ""; + + //~ Methods ================================================================ + + public void setDomainObject(Object domainObject) { + this.domainObject = domainObject; + } + + public Object getDomainObject() { + return domainObject; + } + + public void setHasPermission(String hasPermission) { + this.hasPermission = hasPermission; + } + + public String getHasPermission() { + return hasPermission; + } + + public int doStartTag() throws JspException { + if ((null == hasPermission) || "".equals(hasPermission)) { + return Tag.SKIP_BODY; + } + + final String evaledPermissionsString = ExpressionEvaluationUtils + .evaluateString("hasPermission", hasPermission, pageContext); + + if ((null != evaledPermissionsString) + && !"".equals(evaledPermissionsString)) { + Integer[] requiredIntegers = null; + + try { + requiredIntegers = parseIntegersString(evaledPermissionsString); + } catch (NumberFormatException nfe) { + throw new JspException(nfe); + } + + if (requiredIntegers.length == 0) { + throw new JspException( + "A comma separate list of integers representing authorised permissions was NOT provided via the 'hasPermission' attribute"); + } + + Object resolvedDomainObject = null; + + if (domainObject instanceof String) { + resolvedDomainObject = ExpressionEvaluationUtils.evaluate("domainObject", + (String) domainObject, Object.class, pageContext); + } else { + resolvedDomainObject = domainObject; + } + + if (resolvedDomainObject == null) { + if (logger.isDebugEnabled()) { + logger.debug( + "domainObject resolved to null, so including tag body"); + } + + // Of course they have access to a null object! + return Tag.EVAL_BODY_INCLUDE; + } + + if ((ContextHolder.getContext() == null) + || !(ContextHolder.getContext() instanceof SecureContext) + || (((SecureContext) ContextHolder.getContext()) + .getAuthentication() == null)) { + if (logger.isDebugEnabled()) { + logger.debug( + "ContextHolder did not return a non-null Authentication object, so skipping tag body"); + } + + return Tag.SKIP_BODY; + } + + Authentication auth = ((SecureContext) ContextHolder.getContext()) + .getAuthentication(); + + ApplicationContext context = getContext(pageContext + .getServletContext()); + + if (context == null) { + throw new JspException( + "applicationContext unavailable from servlet context"); + } + + Map beans = context.getBeansOfType(AclManager.class, false, false); + + if (beans.size() == 0) { + throw new JspException( + "No AclManager would found the application context: " + + context.toString()); + } + + String beanName = (String) beans.keySet().iterator().next(); + AclManager aclManager = (AclManager) context.getBean(beanName); + + // Obtain aclEntrys applying to the current Authentication object + AclEntry[] acls = aclManager.getAcls(resolvedDomainObject, auth); + + if (logger.isDebugEnabled()) { + logger.debug("Authentication: '" + auth + "' has: " + + ((acls == null) ? 0 : acls.length) + + " AclEntrys for domain object: '" + resolvedDomainObject + + "' from AclManager: '" + aclManager.toString() + "'"); + } + + if ((acls == null) || (acls.length == 0)) { + return Tag.SKIP_BODY; + } + + for (int i = 0; i < acls.length; i++) { + // Locate processable AclEntrys + if (acls[i] instanceof AbstractBasicAclEntry) { + AbstractBasicAclEntry processableAcl = (AbstractBasicAclEntry) acls[i]; + + // See if principal has any of the required permissions + for (int y = 0; y < requiredIntegers.length; y++) { + if (processableAcl.isPermitted( + requiredIntegers[y].intValue())) { + if (logger.isDebugEnabled()) { + logger.debug( + "Including tag body as found permission: " + + requiredIntegers[y] + + " due to AclEntry: '" + processableAcl + + "'"); + } + + return Tag.EVAL_BODY_INCLUDE; + } + } + } + } + + if (logger.isDebugEnabled()) { + logger.debug("No permission, so skipping tag body"); + } + + return Tag.SKIP_BODY; + } else { + throw new JspException("Unsupported use of auth:acl tag"); + } + } + + /** + * Allows test cases to override where application context obtained from. + * + * @param servletContext as required by Spring's + * WebApplicationContextUtils + * + * @return the Spring application context + */ + protected ApplicationContext getContext(ServletContext servletContext) { + return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + } + + private Integer[] parseIntegersString(String integersString) + throws NumberFormatException { + final Set integers = new HashSet(); + final StringTokenizer tokenizer; + tokenizer = new StringTokenizer(integersString, ",", false); + + while (tokenizer.hasMoreTokens()) { + String integer = tokenizer.nextToken(); + integers.add(new Integer(integer)); + } + + return (Integer[]) integers.toArray(new Integer[] {}); + } +} diff --git a/core/src/main/java/org/acegisecurity/taglibs/authz/AuthenticationTag.java b/core/src/main/java/org/acegisecurity/taglibs/authz/AuthenticationTag.java new file mode 100644 index 0000000000..e361835ed2 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/taglibs/authz/AuthenticationTag.java @@ -0,0 +1,102 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.taglibs.authz; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.context.ContextHolder; +import net.sf.acegisecurity.context.SecureContext; + +import java.io.IOException; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.Tag; +import javax.servlet.jsp.tagext.TagSupport; + + +/** + * An {@link javax.servlet.jsp.tagext.Tag} implementation that allows + * convenient access to the current Authentication object. + * + *

+ * Whilst JSPs can access the ContextHolder directly, this tag + * avoids handling null and the incorrect type of + * Context in the ContextHolder. The tag also + * properly accommodates Authentication.getPrincipal(), which can + * either be a String or a UserDetails. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class AuthenticationTag extends TagSupport { + //~ Static fields/initializers ============================================= + + public static final String OPERATION_PRINCIPAL = "principal"; + + //~ Instance fields ======================================================== + + private String operation = ""; + + //~ Methods ================================================================ + + public void setOperation(String operation) { + this.operation = operation; + } + + public String getOperation() { + return operation; + } + + public int doStartTag() throws JspException { + if ((null == operation) || "".equals(operation)) { + return Tag.SKIP_BODY; + } + + if ((ContextHolder.getContext() == null) + || !(ContextHolder.getContext() instanceof SecureContext) + || (((SecureContext) ContextHolder.getContext()).getAuthentication() == null)) { + return Tag.SKIP_BODY; + } + + Authentication auth = ((SecureContext) ContextHolder.getContext()) + .getAuthentication(); + + if (OPERATION_PRINCIPAL.equalsIgnoreCase(operation)) { + if (auth.getPrincipal() == null) { + return Tag.SKIP_BODY; + } else if (auth.getPrincipal() instanceof UserDetails) { + writeMessage(((UserDetails) auth.getPrincipal()).getUsername()); + + return Tag.SKIP_BODY; + } else { + writeMessage(auth.getPrincipal().toString()); + + return Tag.SKIP_BODY; + } + } else { + throw new JspException("Unsupported use of auth:athentication tag"); + } + } + + protected void writeMessage(String msg) throws JspException { + try { + pageContext.getOut().write(String.valueOf(msg)); + } catch (IOException ioe) { + throw new JspException(ioe); + } + } +} diff --git a/core/src/main/resources/org/acegisecurity/taglibs/authz.tld b/core/src/main/resources/org/acegisecurity/taglibs/authz.tld index e26678ef2d..e46f254b16 100644 --- a/core/src/main/resources/org/acegisecurity/taglibs/authz.tld +++ b/core/src/main/resources/org/acegisecurity/taglibs/authz.tld @@ -50,4 +50,56 @@ + + + authentication + net.sf.acegisecurity.taglibs.authz.AuthenticationTag + + Allows access to the current Authentication object. + + + + operation + true + true + + Must be "principal", for a String representation of the + username. An attribute to aid in future extension of the tag. + + + + + + + acl + net.sf.acegisecurity.taglibs.authz.AclTag + + Allows inclusion of a tag body if the current Authentication + has one of the specified permissions to the presented + domain object instance. This tag uses the first AclManager + it locates via + WebApplicationContextUtils.getRequiredWebApplicationContext(HttpServletContext). + + + + hasPermission + true + true + + A comma separated list of integers, each representing a + required bit mask permission from a subclass of + net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry. + + + + domainObject + true + true + + The actual domain object instance for which permissions + are being evaluated. + + + +