KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > ejb > plugins > SecurityInterceptor


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 org.jboss.ejb.Container;
25 import org.jboss.invocation.Invocation;
26 import org.jboss.metadata.ApplicationMetaData;
27 import org.jboss.metadata.AssemblyDescriptorMetaData;
28 import org.jboss.metadata.BeanMetaData;
29 import org.jboss.metadata.SecurityIdentityMetaData;
30 import org.jboss.security.AuthenticationManager;
31 import org.jboss.security.AuthorizationManager;
32 import org.jboss.security.RealmMapping;
33 import org.jboss.security.RunAsIdentity;
34 import org.jboss.security.SecurityConstants;
35 import org.jboss.security.SecurityContext;
36 import org.jboss.security.SecurityRolesAssociation;
37 import org.jboss.security.SecurityContext.SubjectInfo;
38 import org.jboss.security.audit.AuditContext;
39 import org.jboss.security.audit.AuditEvent;
40 import org.jboss.security.audit.AuditLevel;
41 import org.jboss.security.audit.AuditManager;
42 import org.jboss.security.authorization.AuthorizationContext;
43 import org.jboss.security.authorization.EJBResource;
44 import org.jboss.security.authorization.ResourceKeys;
45 import org.jboss.security.plugins.JBossSecurityContext;
46 import org.jboss.system.Registry;
47
48 import java.security.CodeSource JavaDoc;
49 import java.security.Principal JavaDoc;
50 import java.util.HashMap JavaDoc;
51 import java.util.Map JavaDoc;
52 import java.util.Set JavaDoc;
53 import java.lang.reflect.Method JavaDoc;
54 import javax.security.auth.Subject JavaDoc;
55 import javax.security.jacc.PolicyContextException JavaDoc;
56 import javax.ejb.TimedObject JavaDoc;
57 import javax.ejb.Timer JavaDoc;
58
59 /**
60  * The SecurityInterceptor is where the EJB 2.0 declarative security model
61  * is enforced. This is where the caller identity propagation is controlled as well.
62  *
63  * @author <a HREF="on@ibis.odessa.ua">Oleg Nitz</a>
64  * @author <a HREF="mailto:Scott.Stark@jboss.org">Scott Stark</a>.
65  * @author <a HREF="mailto:Thomas.Diesler@jboss.org">Thomas Diesler</a>.
66  * @author <a HREF="mailto:Anil.Saldhana@jboss.org">Anil Saldhana</a>
67  * @version $Revision: 58441 $
68  */

69 public class SecurityInterceptor extends AbstractInterceptor
70 {
71    /** The interface of an observer that should be notified when principal
72     authentication fails.
73     */

74    public interface AuthenticationObserver
75    {
76       final String JavaDoc KEY = "SecurityInterceptor.AuthenticationObserver";
77       void authenticationFailed();
78    }
79
80    /** The authentication manager plugin
81     */

82    protected AuthenticationManager securityManager;
83    
84    protected AuthorizationManager authorizationManager;
85
86    /** The authorization manager plugin
87     */

88    protected RealmMapping realmMapping;
89
90    // The bean uses this run-as identity to call out
91
protected RunAsIdentity runAsIdentity;
92
93    // A map of SecurityRolesMetaData from jboss.xml
94
protected Map JavaDoc securityRoles;
95    
96    //A map of principal versus roles from jboss-app.xml/jboss.xml
97
protected Map JavaDoc deploymentRoles;
98
99    // The observer to be notified when principal authentication fails.
100
// This is a hook for the CSIv2 code. The authenticationObserver may
101
// send out a ContextError message, as required by the CSIv2 protocol.
102
protected AuthenticationObserver authenticationObserver;
103    /** The TimedObject.ejbTimeout callback */
104    protected Method JavaDoc ejbTimeout;
105    //Authorization Framework changes
106
protected String JavaDoc ejbName = null;
107    protected CodeSource JavaDoc ejbCS = null;
108    /**
109     * Security Domain configured as part of the application
110     */

111    protected String JavaDoc appSecurityDomain = null;
112    //Fallback Security Domain
113
protected String JavaDoc defaultAuthorizationSecurityDomain = SecurityConstants.DEFAULT_EJB_APPLICATION_POLICY;
114     
115    /** Called by the super class to set the container to which this interceptor
116     belongs. We obtain the security manager and runAs identity to use here.
117     */

118    public void setContainer(Container container)
119    {
120       super.setContainer(container);
121       if (container != null)
122       {
123          BeanMetaData beanMetaData = container.getBeanMetaData();
124          ApplicationMetaData applicationMetaData = beanMetaData.getApplicationMetaData();
125          AssemblyDescriptorMetaData assemblyDescriptor = applicationMetaData.getAssemblyDescriptor();
126          securityRoles = assemblyDescriptor.getSecurityRoles();
127          deploymentRoles = assemblyDescriptor.getPrincipalVersusRolesMap();
128          
129          SecurityIdentityMetaData secMetaData = beanMetaData.getSecurityIdentityMetaData();
130          if (secMetaData != null && secMetaData.getUseCallerIdentity() == false)
131          {
132             String JavaDoc roleName = secMetaData.getRunAsRoleName();
133             String JavaDoc principalName = secMetaData.getRunAsPrincipalName();
134
135             // the run-as principal might have extra roles mapped in the assembly-descriptor
136
Set JavaDoc extraRoleNames = assemblyDescriptor.getSecurityRoleNamesByPrincipal(principalName);
137             runAsIdentity = new RunAsIdentity(roleName, principalName, extraRoleNames);
138          }
139
140          securityManager = container.getSecurityManager();
141          realmMapping = container.getRealmMapping();
142          authorizationManager = container.getAuthorizationManager();
143
144          try
145          {
146             // Get the timeout method
147
ejbTimeout = TimedObject JavaDoc.class.getMethod("ejbTimeout", new Class JavaDoc[]{Timer JavaDoc.class});
148          }
149          catch (NoSuchMethodException JavaDoc ignore)
150          {
151          }
152          //Authorization Framework changes
153
appSecurityDomain = applicationMetaData.getSecurityDomain();
154          ejbName = beanMetaData.getEjbName();
155          ejbCS = container.getBeanClass().getProtectionDomain().getCodeSource();
156       }
157    }
158
159    // Container implementation --------------------------------------
160
public void start() throws Exception JavaDoc
161    {
162       super.start();
163       authenticationObserver =
164          (AuthenticationObserver) Registry.lookup(AuthenticationObserver.KEY);
165    }
166
167    public Object JavaDoc invokeHome(Invocation mi) throws Exception JavaDoc
168    {
169       // Authenticate the subject and apply any declarative security checks
170
checkSecurityAssociation(mi);
171
172       /* If a run-as role was specified, push it so that any calls made
173        by this bean will have the runAsRole available for declarative
174        security checks.
175       */

176       SecurityActions.pushRunAsIdentity(runAsIdentity);
177
178       try
179       {
180          Object JavaDoc returnValue = getNext().invokeHome(mi);
181          return returnValue;
182       }
183       finally
184       {
185          SecurityActions.popRunAsIdentity();
186          SecurityActions.popSubjectContext();
187          //Clear the SecurityContext
188
SecurityActions.clearSecurityContext(appSecurityDomain);
189       }
190    }
191
192
193    public Object JavaDoc invoke(Invocation mi) throws Exception JavaDoc
194    {
195       // Authenticate the subject and apply any declarative security checks
196
checkSecurityAssociation(mi);
197
198       /* If a run-as role was specified, push it so that any calls made
199        by this bean will have the runAsRole available for declarative
200        security checks.
201       */

202       SecurityActions.pushRunAsIdentity(runAsIdentity);
203
204       try
205       {
206          Object JavaDoc returnValue = getNext().invoke(mi);
207          return returnValue;
208       }
209       finally
210       {
211          SecurityActions.popRunAsIdentity();
212          SecurityActions.popSubjectContext();
213          //Clear the SecurityContext
214
SecurityActions.clearSecurityContext(appSecurityDomain);
215       }
216    }
217    
218    /** The EJB 2.0 declarative security algorithm:
219    1. Authenticate the caller using the principal and credentials in the MethodInfocation
220    2. Validate access to the method by checking the principal's roles against
221    those required to access the method.
222    */

223   private void checkSecurityAssociation(Invocation mi)
224      throws Exception JavaDoc
225   {
226      Principal JavaDoc principal = mi.getPrincipal();
227      Object JavaDoc credential = mi.getCredential();
228      boolean trace = log.isTraceEnabled();
229
230      // If there is not a security manager then there is no authentication required
231
Method JavaDoc m = mi.getMethod();
232      boolean containerMethod = m == null || m.equals(ejbTimeout);
233      if ( containerMethod == true || securityManager == null || container == null )
234      {
235         // Allow for the progatation of caller info to other beans
236
SecurityActions.pushSubjectContext(principal, credential, null);
237         return;
238      }
239      
240      if (realmMapping == null)
241      {
242         throw new SecurityException JavaDoc("Role mapping manager has not been set");
243      }
244
245      // authenticate the current principal
246
RunAsIdentity callerRunAsIdentity = SecurityActions.peekRunAsIdentity();
247      if (callerRunAsIdentity == null)
248      {
249         // Check the security info from the method invocation
250
Subject JavaDoc subject = new Subject JavaDoc();
251         if (securityManager.isValid(principal, credential, subject) == false)
252         {
253            // Notify authentication observer
254
if (authenticationObserver != null)
255               authenticationObserver.authenticationFailed();
256            // Check for the security association exception
257
Exception JavaDoc ex = SecurityActions.getContextException();
258            errorAudit(principal,m.getName(),ex);
259            if( ex != null )
260               throw ex;
261            // Else throw a generic SecurityException
262
String JavaDoc msg = "Authentication exception, principal=" + principal;
263            SecurityException JavaDoc e = new SecurityException JavaDoc(msg);
264            failureAudit(principal,m.getName());
265            throw e;
266         }
267         else
268         {
269            SecurityActions.pushSubjectContext(principal, credential, subject);
270            establishSecurityContext(securityManager.getSecurityDomain(),principal, credential, subject);
271            successAudit(principal,m.getName());
272            if (trace)
273            {
274               log.trace("Authenticated principal=" + principal);
275            }
276         }
277      }
278      else
279      {
280         // Duplicate the current subject context on the stack since
281
SecurityActions.dupSubjectContext();
282      }
283      
284      Method JavaDoc ejbMethod = mi.getMethod();
285      // Ignore internal container calls
286
if( ejbMethod== null )
287         return;
288      // Get the caller
289
Subject JavaDoc caller = getContextCallerSubject();
290      
291      //Establish the deployment rolename-principalset custom mapping(if available)
292
SecurityRolesAssociation.setSecurityRoles(this.deploymentRoles);
293       
294      final HashMap JavaDoc map = new HashMap JavaDoc();
295      map.put(ResourceKeys.EJB_NAME ,this.ejbName);
296      map.put(ResourceKeys.EJB_METHOD,ejbMethod);
297      map.put(ResourceKeys.EJB_PRINCIPAL, mi.getPrincipal());
298      map.put(ResourceKeys.EJB_METHODINTERFACE, mi.getType().toInterfaceString());
299      map.put(ResourceKeys.EJB_CODESOURCE, ejbCS);
300      map.put(ResourceKeys.CALLER_SUBJECT, caller);
301      map.put(ResourceKeys.AUTHORIZATION_MANAGER,authorizationManager);
302      map.put(ResourceKeys.RUNASIDENTITY, callerRunAsIdentity);
303      map.put(ResourceKeys.EJB_METHODROLES, container.getMethodPermissions(ejbMethod, mi.getType()));
304      
305      EJBResource ejbResource = new EJBResource(map);
306      boolean isAuthorized = false;
307      try
308      {
309         int check = authorizationManager.authorize(ejbResource);
310         isAuthorized = (check == AuthorizationContext.PERMIT);
311         authorizationAudit((isAuthorized ? AuditLevel.SUCCESS : AuditLevel.FAILURE)
312                             ,ejbResource, null);
313      }
314      catch (Exception JavaDoc e)
315      {
316         isAuthorized = false;
317         if(trace)
318            log.trace("Error in authorization:",e);
319         authorizationAudit(AuditLevel.ERROR,ejbResource,e);
320      }
321      String JavaDoc msg = "Denied: caller=" + caller;
322      if(!isAuthorized)
323         throw new SecurityException JavaDoc(msg);
324   }
325   
326   /**
327    * Context caller subject is used by the Jacc layer. Since the
328    * PolicyContext.getContext(Subject_Key) checks the RunAs threadlocal
329    * stack in the Security Association at a depth of 1, there is a need to
330    * push/pop the current runAsIdentity surrounding the getContextSubject
331    * call on SecurityActions
332    *
333    * @return
334    * @throws PolicyContextException
335    */

336   private Subject JavaDoc getContextCallerSubject() throws PolicyContextException JavaDoc
337   {
338      /**
339       * There is a need for current RunAsIdentity on the stack due to
340       * PolicyContext.getContext(Subject_Key) check at a depth of 1
341       * (Get the same behavior as split JaasAuthenticationInterceptor
342       * and JaccAuthorizationInterceptor)
343       */

344      SecurityActions.pushRunAsIdentity(runAsIdentity);
345      Subject JavaDoc caller = SecurityActions.getContextSubject();
346      SecurityActions.popRunAsIdentity();
347      return caller;
348   }
349   
350   //******************************************************
351
// Audit Methods
352
//******************************************************
353
private void audit(String JavaDoc level,
354         Map JavaDoc contextMap, Exception JavaDoc e)
355   {
356      contextMap.put("Source", getClass().getName());
357      String JavaDoc secDomain = securityManager.getSecurityDomain();
358      SecurityContext sc = SecurityActions.getSecurityContext(secDomain);
359      AuditContext ac = sc != null ? sc.getAuditContext() :
360                          AuditManager.getAuditContext(secDomain);
361      AuditEvent ae = new AuditEvent(level);
362      ae.setContextMap(contextMap);
363      ae.setUnderlyingException(e);
364      ac.audit(ae);
365   }
366   
367   private void successAudit(Principal JavaDoc principal, String JavaDoc methodName)
368   {
369      audit(AuditLevel.SUCCESS,getContextMap(principal, methodName),null);
370   }
371   
372   private void failureAudit(Principal JavaDoc principal, String JavaDoc methodName)
373   {
374      audit(AuditLevel.FAILURE,getContextMap(principal, methodName),null);
375   }
376   
377   private void errorAudit(Principal JavaDoc principal,
378         String JavaDoc methodName, Exception JavaDoc e)
379   {
380      audit(AuditLevel.ERROR,getContextMap(principal, methodName),e);
381   }
382   
383   private void authorizationAudit(String JavaDoc level, EJBResource resource, Exception JavaDoc e)
384   {
385      //Authorization Exception stacktrace is huge. Scale it down
386
//as the original stack trace can be seen in server.log (if needed)
387
String JavaDoc exceptionMessage = e != null ? e.getLocalizedMessage() : "";
388      Map JavaDoc cmap = new HashMap JavaDoc();
389      cmap.putAll(resource.getMap());
390      cmap.put("Exception:", exceptionMessage);
391      audit(level,cmap,null);
392   }
393   
394   private Map JavaDoc getContextMap(Principal JavaDoc principal, String JavaDoc methodName)
395   {
396      Map JavaDoc cmap = new HashMap JavaDoc();
397      cmap.put("principal", principal);
398      cmap.put("method", methodName);
399      return cmap;
400   }
401   
402   //Security Context
403
private void establishSecurityContext(String JavaDoc domain, Principal JavaDoc p, Object JavaDoc cred,
404         Subject JavaDoc subject)
405   {
406      JBossSecurityContext jsc = new JBossSecurityContext(domain);
407      SubjectInfo si = jsc.new SubjectInfo();
408      si.setAuthenticatedSubject(subject);
409      si.setAuthenticationCredential(cred);
410      si.setAuthenticationPrincipal(p);
411      jsc.setSubjectInfo(si);
412      SecurityActions.setSecurityContext(jsc, domain);
413   }
414 }
415
Popular Tags