/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.ejb3.security;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.HashSet;
import java.util.Set;
import javax.security.jacc.PolicyContext;
import org.jboss.as.core.security.ServerSecurityManager;
import org.jboss.as.ee.component.Component;
import org.jboss.as.ee.component.ComponentView;
import org.jboss.as.ejb3.logging.EjbLogger;
import org.jboss.as.ejb3.component.EJBComponent;
import org.jboss.as.ejb3.component.MethodIntf;
import org.jboss.invocation.Interceptor;
import org.jboss.invocation.InterceptorContext;
import org.jboss.metadata.ejb.spec.MethodInterfaceType;
import org.jboss.security.AnybodyPrincipal;
import org.jboss.security.NobodyPrincipal;
import org.jboss.security.SimplePrincipal;
import org.wildfly.security.manager.WildFlySecurityManager;
/**
* EJB authorization interceptor responsible for handling invocation on EJB methods and doing the necessary authorization
* checks on the invoked method.
* <p/>
* User: Jaikiran Pai
*/
public class AuthorizationInterceptor implements Interceptor {
/**
* EJB method security metadata
*/
private final EJBMethodSecurityAttribute ejbMethodSecurityMetaData;
/**
* The view class name to which this interceptor is applicable
*/
private final String viewClassName;
/**
* The view method to which this interceptor is applicable
*/
private final Method viewMethod;
/*
* The JACC contextID to be used by this interceptor.
*/
private final String contextID;
public AuthorizationInterceptor(final EJBMethodSecurityAttribute ejbMethodSecurityMetaData, final String viewClassName, final Method viewMethod, final String contextID) {
if (ejbMethodSecurityMetaData == null) {
throw EjbLogger.ROOT_LOGGER.ejbMethodSecurityMetaDataIsNull();
}
if (viewClassName == null || viewClassName.trim().isEmpty()) {
throw EjbLogger.ROOT_LOGGER.viewClassNameIsNull();
}
if (viewMethod == null) {
throw EjbLogger.ROOT_LOGGER.viewMethodIsNull();
}
this.ejbMethodSecurityMetaData = ejbMethodSecurityMetaData;
this.viewClassName = viewClassName;
this.viewMethod = viewMethod;
this.contextID = contextID;
}
@Override
public Object processInvocation(InterceptorContext context) throws Exception {
final Component component = context.getPrivateData(Component.class);
if (component instanceof EJBComponent == false) {
throw EjbLogger.ROOT_LOGGER.unexpectedComponent(component, EJBComponent.class);
}
final Method invokedMethod = context.getMethod();
final ComponentView componentView = context.getPrivateData(ComponentView.class);
final String viewClassOfInvokedMethod = componentView.getViewClass().getName();
// shouldn't really happen if the interceptor was setup correctly. But let's be safe and do a check
if (!this.viewClassName.equals(viewClassOfInvokedMethod) || !this.viewMethod.equals(invokedMethod)) {
throw EjbLogger.ROOT_LOGGER.failProcessInvocation(this.getClass().getName(), invokedMethod, viewClassOfInvokedMethod, viewMethod, viewClassName);
}
final EJBComponent ejbComponent = (EJBComponent) component;
final ServerSecurityManager securityManager = ejbComponent.getSecurityManager();
final MethodInterfaceType methodIntfType = this.getMethodInterfaceType(componentView.getPrivateData(MethodIntf.class));
// set the JACC contextID before calling the security manager.
final String previousContextID = setContextID(this.contextID);
try {
if(WildFlySecurityManager.isChecking()) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@Override
public ProtectionDomain run() {
if (!securityManager.authorize(ejbComponent.getComponentName(), componentView.getProxyClass().getProtectionDomain().getCodeSource(),
methodIntfType.name(), AuthorizationInterceptor.this.viewMethod, AuthorizationInterceptor.this.getMethodRolesAsPrincipals(), AuthorizationInterceptor.this.contextID)) {
throw EjbLogger.ROOT_LOGGER.invocationOfMethodNotAllowed(invokedMethod,ejbComponent.getComponentName());
}
return null;
}
});
} catch (PrivilegedActionException e) {
throw e.getException();
}
} else {
if (!securityManager.authorize(ejbComponent.getComponentName(), componentView.getProxyClass().getProtectionDomain().getCodeSource(),
methodIntfType.name(), this.viewMethod, this.getMethodRolesAsPrincipals(), this.contextID)) {
throw EjbLogger.ROOT_LOGGER.invocationOfMethodNotAllowed(invokedMethod,ejbComponent.getComponentName());
}
}
// successful authorization, let the invocation proceed
return context.proceed();
}
finally {
// reset the previous JACC contextID.
setContextID(previousContextID);
}
}
/**
* <p>
* Returns the method roles as a set of {@code Principal} instances. All roles specified in the method-permissions or
* via {@code RolesAllowed} for this method are wrapped by a {@code SimplePrincipal}. If the method has been added to
* the exclude-list or annotated with {@code DenyAll}, a NOBODY_PRINCIPAL is returned. If the method has been added
* to the unchecked list or annotated with {@code PermitAll}, an ANYBODY_PRINCIPAL is returned.
* </p>
*
* @return the constructed set of role principals.
*/
protected Set<Principal> getMethodRolesAsPrincipals() {
Set<Principal> methodRoles = new HashSet<Principal>();
if (this.ejbMethodSecurityMetaData.isDenyAll())
methodRoles.add(NobodyPrincipal.NOBODY_PRINCIPAL);
else if (this.ejbMethodSecurityMetaData.isPermitAll())
methodRoles.add(AnybodyPrincipal.ANYBODY_PRINCIPAL);
else {
for (String role : this.ejbMethodSecurityMetaData.getRolesAllowed())
methodRoles.add(new SimplePrincipal(role));
}
return methodRoles;
}
/**
* <p>
* Gets the {@code MethodInterfaceType} that corresponds to the specified {@code MethodIntf}.
* </p>
*
* @param viewType the {@code MethodIntf} type to be converted.
* @return the converted type or {@code null} if the type cannot be converted.
*/
protected MethodInterfaceType getMethodInterfaceType(MethodIntf viewType) {
switch (viewType) {
case HOME:
return MethodInterfaceType.Home;
case LOCAL_HOME:
return MethodInterfaceType.LocalHome;
case SERVICE_ENDPOINT:
return MethodInterfaceType.ServiceEndpoint;
case LOCAL:
return MethodInterfaceType.Local;
case REMOTE:
return MethodInterfaceType.Remote;
case TIMER:
return MethodInterfaceType.Timer;
case MESSAGE_ENDPOINT:
return MethodInterfaceType.MessageEndpoint;
default:
return null;
}
}
/**
* <p>
* Sets the JACC contextID using a privileged action and returns the previousID from the {@code PolicyContext}.
* </p>
*
* @param contextID the JACC contextID to be set.
* @return the previous contextID as retrieved from the {@code PolicyContext}.
*/
protected String setContextID(final String contextID) {
if (! WildFlySecurityManager.isChecking()) {
final String previousID = PolicyContext.getContextID();
PolicyContext.setContextID(contextID);
return previousID;
} else {
final PrivilegedAction<String> action = new SetContextIDAction(contextID);
return AccessController.doPrivileged(action);
}
}
/**
* PrivilegedAction that sets the {@code PolicyContext} id.
*/
private static class SetContextIDAction implements PrivilegedAction<String> {
private String contextID;
SetContextIDAction(final String contextID) {
this.contextID = contextID;
}
@Override
public String run() {
final String previousID = PolicyContext.getContextID();
PolicyContext.setContextID(this.contextID);
return previousID;
}
}
}