/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.authz; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.enterprise.server.auth.SubjectManagerLocal; import org.rhq.enterprise.server.util.LookupUtil; /** * An EJB3 interceptor that checks to ensure a given {@link Subject} has all of the global permissions that are * specified via the {@link RequiredPermissions} annotation on the method to be invoked. If the method being invoked is * not annotated with {@link RequiredPermissions} or it has an empty list of permissions, this interceptor passes the * security check immediately. Otherwise, the method must have a {@link Subject} as its first parameter - that * {@link Subject} will be checked to see if it has all the permissions required. If it does not, or if there is no * {@link Subject} as the method's first parameter, this interceptor throws an exception and does not allow the method * to be invoked. * * @author John Mazzitelli */ public class RequiredPermissionsInterceptor { private static Log LOG = LogFactory.getLog(RequiredPermissionsInterceptor.class); /** * Checks to ensure the method can be invoked. * * @param invocation_context the invocation context * * @return the results of the invocation * * @throws Exception if an error occurred further down the interceptor stack * @throws PermissionException if the security check fails */ @AroundInvoke public Object checkRequiredPermissions(InvocationContext invocation_context) throws Exception { try { Map<Permission, String> perms_errors_list = new HashMap<Permission, String>(); Method method = invocation_context.getMethod(); RequiredPermissions perms_anno = method.getAnnotation(RequiredPermissions.class); RequiredPermission perm_anno = method.getAnnotation(RequiredPermission.class); // process the list of permissions, if specified if (((perms_anno != null) && (perms_anno.value().length > 0))) { for (RequiredPermission rq : perms_anno.value()) { perms_errors_list.put(rq.value(), rq.error()); } } // process the individual permission, if specified if ((perm_anno != null) && (perm_anno.value() != null)) { perms_errors_list.put(perm_anno.value(), perm_anno.error()); } // get the subject, if there is one as the first parameter to the method invocation Subject subject = null; Object[] params = invocation_context.getParameters(); if ((params != null) && (params.length > 0) && (params[0] instanceof Subject)) { subject = (Subject) params[0]; } // Make sure someone is not spoofing another user - ensure the associated session ID is valid. // This means that anytime we pass Subject as the first parameter, we are assuming it needs // its session validated. If there is ever a case where we pass Subject as the first parameter // to an EJB and we do NOT want to validate its session, you need to annotate that EJB // method with @ExcludeDefaultInterceptors so we don't call this interceptor. if (subject != null) { if (subject.getSessionId() != null) { SubjectManagerLocal subject_manager = LookupUtil.getSubjectManager(); // isValidSessionId will also update the session's last-access-time if (!subject_manager.isValidSessionId(subject.getSessionId(), subject.getName(), subject.getId())) { // if this happens, it is possible someone is trying to spoof an authenticated user! throw buildPermissionException("The session ID for user [" + subject.getName() + "] is invalid!", invocation_context); } } else { throw buildPermissionException("The subject [" + subject.getName() + "] did not have a session", invocation_context); } } // if the method is not annotated or it has no permissions that are required for it to be invoked, // don't do anything; otherwise, we need to check the permissions if (perms_errors_list.size() > 0) { // the method to be invoked has one or more required permissions; // therefore, the method must have a Subject as its first argument value if (subject == null) { throw buildPermissionException("Method requires permissions but does not have a subject parameter", invocation_context); } // look these up now - we don't use @EJB because I don't want the container wasting time // injecting EJBs if I don't need them for those methods not annotated with @RequiredPermissions AuthorizationManagerLocal authorization_manager = LookupUtil.getAuthorizationManager(); Set<Permission> required_permissions = perms_errors_list.keySet(); Set<Permission> subject_permissions = authorization_manager.getExplicitGlobalPermissions(subject); for (Permission required_permission : required_permissions) { if (!Permission.Target.GLOBAL.equals(required_permission.getTarget())) { throw buildPermissionException("@RequiredPermissions must be Permission.Target.GLOBAL: [" + required_permission + "]", invocation_context); } if (!subject_permissions.contains(required_permission)) { String perm_error = perms_errors_list.get(required_permission); String full_error = "Subject [" + subject.getName() + "] is not authorized for [" + required_permission + "]"; if ((perm_error != null) && (perm_error.length() > 0)) { full_error = perm_error + ": " + full_error; } throw buildPermissionException(full_error, invocation_context); } } } } catch (PermissionException pe) { LOG.debug("Interceptor detected a permission exception", pe); throw pe; } catch (Exception e) { Exception ex = buildPermissionException("Failed to check required permissions to invoke: ", invocation_context, e); LOG.debug("Permission Exception", ex); throw ex; } // we are authorized for all the required permissions - let the invocation continue return invocation_context.proceed(); } private PermissionException buildPermissionException(String message, InvocationContext context) { return buildPermissionException(message, context, null); } private PermissionException buildPermissionException(String message, InvocationContext context, Exception e) { return new PermissionException(message + ": " + getInvocationString(context), e); } /** * Returns a string representation of the given invocation context so it can be displayed in messages. * * @param invocation * * @return string containing information about the invocation */ private String getInvocationString(InvocationContext invocation) { StringBuffer buf = new StringBuffer("invocation: "); buf.append("method=" + invocation.getMethod().toGenericString()); buf.append(",context-data=" + invocation.getContextData()); return buf.toString(); } }