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 }