Save This Page
Home » jboss-5.0.0.CR1-src » org » jboss » ejb » plugins » [javadoc | source]
    1   /*
    2   * JBoss, Home of Professional Open Source
    3   * Copyright 2005, JBoss Inc., and individual contributors as indicated
    4   * by the @authors tag. See the copyright.txt in the distribution for a
    5   * full listing of individual contributors.
    6   *
    7   * This is free software; you can redistribute it and/or modify it
    8   * under the terms of the GNU Lesser General Public License as
    9   * published by the Free Software Foundation; either version 2.1 of
   10   * the License, or (at your option) any later version.
   11   *
   12   * This software is distributed in the hope that it will be useful,
   13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   15   * Lesser General Public License for more details.
   16   *
   17   * You should have received a copy of the GNU Lesser General Public
   18   * License along with this software; if not, write to the Free
   19   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
   20   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
   21   */
   22   package org.jboss.ejb.plugins;
   23   
   24   import static org.jboss.security.SecurityConstants.DEFAULT_EJB_APPLICATION_POLICY;
   25   
   26   import java.lang.reflect.Method;
   27   import java.security.CodeSource;
   28   import java.security.Principal;
   29   import java.util.Map;
   30   import java.util.Set;
   31   
   32   import javax.ejb.TimedObject;
   33   import javax.ejb.Timer;
   34   import javax.security.auth.Subject;
   35   
   36   import org.jboss.ejb.Container;
   37   import org.jboss.invocation.Invocation;
   38   import org.jboss.metadata.ApplicationMetaData;
   39   import org.jboss.metadata.AssemblyDescriptorMetaData;
   40   import org.jboss.metadata.BeanMetaData;
   41   import org.jboss.metadata.SecurityIdentityMetaData;
   42   import org.jboss.security.AuthenticationManager;
   43   import org.jboss.security.ISecurityManagement;
   44   import org.jboss.security.RealmMapping;
   45   import org.jboss.security.RunAs;
   46   import org.jboss.security.RunAsIdentity;
   47   import org.jboss.security.SecurityContext;
   48   import org.jboss.security.SecurityRolesAssociation;
   49   import org.jboss.security.SecurityUtil; 
   50   import org.jboss.security.identity.plugins.SimpleRoleGroup;
   51   import org.jboss.security.javaee.AbstractEJBAuthorizationHelper;
   52   import org.jboss.security.javaee.EJBAuthenticationHelper;
   53   import org.jboss.security.javaee.SecurityHelperFactory;
   54   import org.jboss.system.Registry;
   55   
   56   /**
   57    * The SecurityInterceptor is where the EJB 2.0 declarative security model
   58    * is enforced. This is where the caller identity propagation is controlled as well.
   59    *
   60    * @author <a href="on@ibis.odessa.ua">Oleg Nitz</a>
   61    * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>.
   62    * @author <a href="mailto:Thomas.Diesler@jboss.org">Thomas Diesler</a>.
   63    * @author <a href="mailto:Anil.Saldhana@jboss.org">Anil Saldhana</a>
   64    * @version $Revision: 74858 $
   65    */
   66   public class SecurityInterceptor extends AbstractInterceptor
   67   {
   68      /** The interface of an observer that should be notified when principal 
   69       authentication fails.
   70       */
   71      public interface AuthenticationObserver
   72      {
   73         final String KEY = "SecurityInterceptor.AuthenticationObserver";
   74         void authenticationFailed();
   75      }
   76   
   77      /** The authentication manager plugin
   78       */
   79      protected AuthenticationManager securityManager; 
   80   
   81      /** The authorization manager plugin
   82       */
   83      protected RealmMapping realmMapping;
   84   
   85      // The bean uses this run-as identity to call out
   86      protected RunAs runAsIdentity;
   87   
   88      // A map of SecurityRolesMetaData from jboss.xml
   89      protected Map securityRoles;
   90      
   91      //A map of principal versus roles from jboss-app.xml/jboss.xml
   92      protected Map<String,Set<String>> deploymentRoles;
   93   
   94      // The observer to be notified when principal authentication fails.
   95      // This is a hook for the CSIv2 code. The authenticationObserver may
   96      // send out a ContextError message, as required by the CSIv2 protocol.
   97      protected AuthenticationObserver authenticationObserver;
   98      /** The TimedObject.ejbTimeout callback */
   99      protected Method ejbTimeout;
  100      //Authorization Framework changes
  101      protected String ejbName = null; 
  102      protected CodeSource ejbCS = null; 
  103      /**
  104       * Security Domain configured as part of the application
  105       */
  106      protected String appSecurityDomain = null; 
  107      //Fallback Security Domain
  108      protected String defaultAuthorizationSecurityDomain = DEFAULT_EJB_APPLICATION_POLICY;  
  109      
  110      /**
  111       * Specify whether <use-caller-identity> is configured, mainly
  112       * for the use case of caller identity coming with run-as
  113       */
  114      protected boolean isUseCallerIdentity = false;
  115      
  116      /**
  117       * Represents the holder of the various security managers
  118       * configured at the container level
  119       */
  120      protected ISecurityManagement securityManagement = null;
  121       
  122      /** Called by the super class to set the container to which this interceptor
  123       belongs. We obtain the security manager and runAs identity to use here.
  124       */
  125      public void setContainer(Container container)
  126      {
  127         super.setContainer(container);
  128         if (container != null)
  129         {
  130            BeanMetaData beanMetaData = container.getBeanMetaData();
  131            ApplicationMetaData applicationMetaData = beanMetaData.getApplicationMetaData();
  132            AssemblyDescriptorMetaData assemblyDescriptor = applicationMetaData.getAssemblyDescriptor();
  133            securityRoles = assemblyDescriptor.getSecurityRoles();
  134            deploymentRoles = assemblyDescriptor.getPrincipalVersusRolesMap();
  135            
  136            SecurityIdentityMetaData secMetaData = beanMetaData.getSecurityIdentityMetaData();
  137            if (secMetaData != null && secMetaData.getUseCallerIdentity() == false)
  138            {
  139               String roleName = secMetaData.getRunAsRoleName();
  140               String principalName = secMetaData.getRunAsPrincipalName();
  141               
  142               //Special Case: if RunAsPrincipal is not configured, then we use unauthenticatedIdentity
  143               if(principalName == null)
  144                  principalName = applicationMetaData.getUnauthenticatedPrincipal();
  145   
  146               // the run-as principal might have extra roles mapped in the assembly-descriptor
  147               Set extraRoleNames = assemblyDescriptor.getSecurityRoleNamesByPrincipal(principalName);
  148               runAsIdentity = new RunAsIdentity(roleName, principalName, extraRoleNames);
  149            }
  150            
  151            if (secMetaData != null && secMetaData.getUseCallerIdentity())
  152               this.isUseCallerIdentity = true;
  153   
  154            securityManager = container.getSecurityManager();
  155            realmMapping = container.getRealmMapping();
  156            //authorizationManager = container.getAuthorizationManager();
  157   
  158            try
  159            {
  160               // Get the timeout method
  161               ejbTimeout = TimedObject.class.getMethod("ejbTimeout", new Class[]{Timer.class});
  162            }
  163            catch (NoSuchMethodException ignore)
  164            {
  165            }
  166            if(securityManager != null)
  167            {
  168               appSecurityDomain =  securityManager.getSecurityDomain();
  169               appSecurityDomain = SecurityUtil.unprefixSecurityDomain(appSecurityDomain); 
  170            } 
  171            ejbName = beanMetaData.getEjbName();  
  172            ejbCS = container.getBeanClass().getProtectionDomain().getCodeSource();
  173            securityManagement = (ISecurityManagement) container.getSecurityManagement();
  174         }
  175      }
  176   
  177      // Container implementation --------------------------------------
  178      public void start() throws Exception
  179      {
  180         super.start();
  181         authenticationObserver = 
  182            (AuthenticationObserver) Registry.lookup(AuthenticationObserver.KEY);
  183         
  184         //Take care of hot deployed security domains
  185         if(container != null)
  186         {
  187            securityManager = container.getSecurityManager();
  188            if(securityManager != null)
  189            {
  190               appSecurityDomain =  securityManager.getSecurityDomain();
  191               appSecurityDomain = SecurityUtil.unprefixSecurityDomain(appSecurityDomain); 
  192            }  
  193         }
  194      }
  195   
  196      public Object invokeHome(Invocation mi) throws Exception
  197      {  
  198         if(this.shouldBypassSecurity(mi))
  199            return getNext().invokeHome(mi);
  200     
  201         SecurityContext sc = SecurityActions.getSecurityContext(); 
  202         if( sc == null)
  203            throw new IllegalStateException("Security Context is null"); 
  204     
  205         RunAs callerRunAsIdentity = sc.getIncomingRunAs(); 
  206         
  207         // Authenticate the subject and apply any declarative security checks
  208         try
  209         {
  210            checkSecurityContext(mi, callerRunAsIdentity); 
  211         }  
  212         catch(Exception e)
  213         {
  214            log.error("Error in Security Interceptor",e);
  215            throw e;
  216         }
  217         
  218         /**
  219          * Special case: if <use-caller-identity> configured and
  220          * the caller is arriving with a run-as, we need to push that run-as
  221          */
  222         if(callerRunAsIdentity != null && this.isUseCallerIdentity)
  223            this.runAsIdentity = callerRunAsIdentity;
  224         
  225   
  226         /* If a run-as role was specified, push it so that any calls made
  227          by this bean will have the runAsRole available for declarative
  228          security checks.
  229         */
  230         SecurityActions.pushRunAsIdentity(runAsIdentity); 
  231   
  232         try
  233         { 
  234            Object returnValue = getNext().invokeHome(mi);
  235            return returnValue;
  236         }
  237         finally
  238         {  
  239            SecurityActions.popRunAsIdentity(); 
  240            SecurityActions.popSubjectContext();   
  241         }
  242      }
  243   
  244   
  245      public Object invoke(Invocation mi) throws Exception
  246      {  
  247         if(this.shouldBypassSecurity(mi))
  248            return getNext().invoke(mi);
  249         
  250         SecurityContext sc = SecurityActions.getSecurityContext(); 
  251         if( sc == null)
  252            throw new IllegalStateException("Security Context is null"); 
  253     
  254         RunAs callerRunAsIdentity = sc.getIncomingRunAs(); 
  255         
  256         // Authenticate the subject and apply any declarative security checks
  257         try
  258         {
  259            checkSecurityContext(mi, callerRunAsIdentity); 
  260         }  
  261         catch(Exception e)
  262         {
  263            log.error("Error in Security Interceptor",e);
  264            throw e;
  265         }
  266         
  267         /**
  268          * Special case: if <use-caller-identity> configured and
  269          * the caller is arriving with a run-as, we need to push that run-as
  270          */
  271         if(callerRunAsIdentity != null && this.isUseCallerIdentity)
  272            this.runAsIdentity = callerRunAsIdentity;
  273         
  274         /* If a run-as role was specified, push it so that any calls made
  275          by this bean will have the runAsRole available for declarative
  276          security checks.
  277         */
  278         SecurityActions.pushRunAsIdentity(runAsIdentity); 
  279   
  280         try
  281         {
  282            Object returnValue = getNext().invoke(mi);
  283            return returnValue;
  284         }
  285         finally
  286         {  
  287            SecurityActions.popRunAsIdentity(); 
  288            SecurityActions.popSubjectContext();  
  289         }
  290      }  
  291      
  292      /** The EJB 2.0 declarative security algorithm:
  293      1. Authenticate the caller using the principal and credentials in the MethodInfocation
  294      2. Validate access to the method by checking the principal's roles against
  295      those required to access the method.
  296      */
  297     private void checkSecurityContext(Invocation mi, RunAs callerRunAsIdentity)
  298        throws Exception
  299     {
  300        Principal principal = mi.getPrincipal();
  301        Object credential = mi.getCredential();
  302        
  303        boolean trace = log.isTraceEnabled();
  304        
  305        // If there is not a security manager then there is no authentication required
  306        Method m = mi.getMethod();
  307        boolean containerMethod = m == null || m.equals(ejbTimeout);
  308        if ( containerMethod == true || securityManager == null || container == null )
  309        {
  310           // Allow for the progatation of caller info to other beans
  311           SecurityActions.pushSubjectContext(principal, credential, null); 
  312           return;
  313        } 
  314        
  315        if (realmMapping == null)
  316        {
  317           throw new SecurityException("Role mapping manager has not been set");
  318        }
  319        
  320        SecurityContext sc = SecurityActions.getSecurityContext(); 
  321        
  322        EJBAuthenticationHelper helper = SecurityHelperFactory.getEJBAuthenticationHelper(sc); 
  323        boolean isTrusted = helper.isTrusted();
  324        
  325        if (!isTrusted)
  326        {
  327           // Check the security info from the method invocation
  328           Subject subject = new Subject();
  329           if(helper.isValid(subject, m.getName()) == false) 
  330           {
  331              // Notify authentication observer
  332              if (authenticationObserver != null)
  333                 authenticationObserver.authenticationFailed(); 
  334              // Else throw a generic SecurityException
  335              String msg = "Authentication exception, principal=" + principal;
  336              throw new SecurityException(msg); 
  337           }
  338           else
  339           {
  340              SecurityActions.pushSubjectContext(principal, credential, subject);   
  341              if (trace)
  342              {
  343                 log.trace("Authenticated  principal=" + principal);
  344              }
  345           }
  346        }
  347        else
  348        {
  349           // Duplicate the current subject context on the stack since
  350           //SecurityActions.dupSubjectContext();  
  351           SecurityActions.pushRunAsIdentity(callerRunAsIdentity);
  352        } 
  353        
  354        Method ejbMethod = mi.getMethod();
  355        // Ignore internal container calls
  356        if( ejbMethod== null  )
  357           return; 
  358        // Get the caller
  359        Subject caller = SecurityActions.getContextSubject(); 
  360        if(caller == null)
  361           throw new IllegalStateException("Authenticated User. But caller subject is null");
  362        
  363        //Establish the deployment rolename-principalset custom mapping(if available)
  364        SecurityRolesAssociation.setSecurityRoles(this.deploymentRoles);
  365        
  366        boolean isAuthorized = false;  
  367        Set<Principal> methodRoles = container.getMethodPermissions(ejbMethod, mi.getType());
  368           
  369        SecurityContext currentSC = SecurityActions.getSecurityContext();
  370        if(currentSC.getSecurityManagement() == null)
  371           currentSC.setSecurityManagement(securityManagement); 
  372        
  373        AbstractEJBAuthorizationHelper authorizationHelper = SecurityHelperFactory.getEJBAuthorizationHelper(sc);
  374        authorizationHelper.setPolicyRegistration(container.getPolicyRegistration());
  375        
  376        isAuthorized = authorizationHelper.authorize(ejbName, 
  377              ejbMethod, 
  378              mi.getPrincipal(), 
  379              mi.getType().toInterfaceString(), 
  380              ejbCS, 
  381              caller, 
  382              callerRunAsIdentity, 
  383              container.getJaccContextID(),
  384              new SimpleRoleGroup(methodRoles)) ; 
  385         
  386        String msg = "Denied: caller with subject=" + caller 
  387                           + " and security context post-mapping roles=" + 
  388                           currentSC.getUtil().getRoles() +
  389                           ": ejbMethod="+ejbMethod;
  390        if(!isAuthorized)
  391           throw new SecurityException(msg); 
  392     } 
  393     
  394     private boolean shouldBypassSecurity(Invocation mi) throws Exception
  395     {
  396        // If there is not a security manager then there is no authentication required
  397        Method m = mi.getMethod();
  398        boolean containerMethod = m == null || m.equals(ejbTimeout);
  399        if ( containerMethod == true || securityManager == null || container == null )
  400        {
  401           // Allow for the propagation of caller info to other beans
  402           SecurityActions.createAndSetSecurityContext(mi.getPrincipal(),
  403                 mi.getCredential(), "BYPASSED-SECURITY"); 
  404           if(this.runAsIdentity != null)
  405              SecurityActions.pushRunAsIdentity(runAsIdentity);
  406           return true;
  407        } 
  408        return false; 
  409     } 
  410   }

Save This Page
Home » jboss-5.0.0.CR1-src » org » jboss » ejb » plugins » [javadoc | source]