BaseJmxAuthorizer.java
package com.ziesemer.utils.jmx.server.authorization;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.management.MBeanServer;
import javax.management.remote.JMXPrincipal;
import javax.management.remote.MBeanServerForwarder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>By default, only allows the execution of the method names defined in
* {@link #READ_ONLY_MBEAN_SERVER_METHOD_NAMES}, and
* {@link MBeanServer#invoke(javax.management.ObjectName, String, Object[], String[])}
* using {@link ReadOnlyInvokeHandler}.</p>
* <p>Most common customizations will involve setting new handlers on a per-method basis by
* editing the {@link Map} returned by {@link #getMethodHandlers()}.
* If a method is not defined in the map, {@link #getDefaultHandler()} is used instead,
* and may be set using {@link #setDefaultHandler(IJmxInvocationAuthorizer)}.</p>
* <p>Based on <a href="http://blogs.sun.com/lmalventosa/entry/jmx_authentication_authorization"
* >http://blogs.sun.com/lmalventosa/entry/jmx_authentication_authorization</a>.</p>
* @author Mark A. Ziesemer
* <a href="http://www.ziesemer.com."><www.ziesemer.com></a>
*/
public class BaseJmxAuthorizer implements InvocationHandler{
protected final Logger logger = LoggerFactory.getLogger(BaseJmxAuthorizer.class);
protected static final Set<String> READ_ONLY_MBEAN_SERVER_METHOD_NAMES
= Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"getAttribute", "getAttributes",
"getDefaultDomain", "getDomains", "getMBeanCount", "getMBeanInfo",
"hashCode", "isInstanceOf", "isRegistered", "queryNames")));
protected MBeanServer mBeanServer;
protected final Map<String, IJmxInvocationAuthorizer> methodHandlers
= new HashMap<String, IJmxInvocationAuthorizer>(32);
/**
* Must never be null.
*/
private IJmxInvocationAuthorizer defaultHandler = new DenyHandler("default");
public MBeanServerForwarder asMBeanServerForwarder(){
final Object proxy = Proxy.newProxyInstance(
MBeanServerForwarder.class.getClassLoader(),
new Class[]{MBeanServerForwarder.class},
this);
return MBeanServerForwarder.class.cast(proxy);
}
public BaseJmxAuthorizer(){
for(final String readOnlyMethodName : READ_ONLY_MBEAN_SERVER_METHOD_NAMES){
methodHandlers.put(readOnlyMethodName, AllowHandler.INSTANCE);
}
methodHandlers.put("getMBeanServer", new GetMBeanServerHandler());
methodHandlers.put("setMBeanServer", new SetMBeanServerHandler());
final DenyHandler mBeanModificationHandler = new DenyHandler("MBean modification");
methodHandlers.put("createMBean", mBeanModificationHandler);
methodHandlers.put("unregisterMBean", mBeanModificationHandler);
methodHandlers.put("invoke", new ReadOnlyInvokeHandler());
final ServerSelfAllowHandler serverSelfAllowHandler = new ServerSelfAllowHandler();
methodHandlers.put("getClassLoader", serverSelfAllowHandler);
methodHandlers.put("getClassLoaderFor", serverSelfAllowHandler);
methodHandlers.put("getClassLoaderRepository", serverSelfAllowHandler);
methodHandlers.put("addNotificationListener", serverSelfAllowHandler);
methodHandlers.put("removeNotificationListener", serverSelfAllowHandler);
}
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable{
final JmxAuthorizeContext ac = new JmxAuthorizeContext(mBeanServer, proxy, method, args);
return invoke(ac);
}
/**
* <p>This method is designed to be overridden by child classes.
* A common practice may be to call <code>super</code>, surrounded with
* calls that make use of the {@link JmxAuthorizeContext}.</p>
*/
protected Object invoke(final JmxAuthorizeContext ac) throws Throwable{
try{
IJmxInvocationAuthorizer ia = methodHandlers.get(ac.method.getName());
if(ia == null){
ia = defaultHandler;
}
return ia.invoke(ac);
}catch(final SecurityException se){
final JMXPrincipal p = ac.getPrincipal();
final Object[] args = ac.args;
logger.warn("{}: method: {}, identity: {}, args: {}",
se.getMessage(), ac.method.getName(),
p != null ? p.getName() : null,
args != null ? Arrays.asList(args) : null);
throw se;
}
}
protected Object logAndExecute(final JmxAuthorizeContext ac) throws Throwable{
logExecution(ac);
return ac.invoke();
}
protected void logExecution(final JmxAuthorizeContext ac){
final JMXPrincipal p = ac.getPrincipal();
final Object[] args = ac.args;
logger.debug("Executing: {}, identity: {}, args: {}",
ac.method.getName(),
p != null ? p.getName() : null,
args != null ? Arrays.asList(args) : null);
}
public MBeanServer getMBeanServer(){
return mBeanServer;
}
public Map<String, IJmxInvocationAuthorizer> getMethodHandlers(){
return methodHandlers;
}
public IJmxInvocationAuthorizer getDefaultHandler(){
return defaultHandler;
}
public void setDefaultHandler(final IJmxInvocationAuthorizer defaultHandler){
if(defaultHandler == null){
throw new IllegalArgumentException("defaultHandler must not be null.");
}
this.defaultHandler = defaultHandler;
}
protected static class GetMBeanServerHandler implements IJmxInvocationAuthorizer{
public Object invoke(final JmxAuthorizeContext ac) throws Throwable{
return ac.mBeanServer;
}
}
protected class SetMBeanServerHandler implements IJmxInvocationAuthorizer{
public Object invoke(final JmxAuthorizeContext ac) throws Throwable{
final Object[] args = ac.args;
if(args == null || args.length < 1 || args[0] == null){
throw new IllegalArgumentException("No MBeanServer specified.");
}
if(mBeanServer != null){
throw new IllegalStateException("MBeanServer already initialized.");
}
mBeanServer = (MBeanServer)args[0];
return null;
}
}
protected static class ServerSelfAllowHandler implements IJmxInvocationAuthorizer{
public Object invoke(final JmxAuthorizeContext ac) throws Throwable{
if(ac.getSubject() == null){
return ac.invoke();
}
throw new SecurityException("Access denied (must be server)");
}
}
protected static class ImpactAndSig{
public int impact;
public String[] signature;
}
}