ReadOnlyInvokeHandler.java

package com.ziesemer.utils.jmx.server.authorization;

import java.lang.management.ManagementFactory;
import java.util.logging.LogManager;

import javax.management.MBeanOperationInfo;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

/**
 * <p>Checks for attempts to call &quot;read-only&quot; methods.</p>
 * <p>Default implementation allows all calls to
 * 		{@link javax.management.MBeanServer#invoke(ObjectName, String, Object[], String[])}
 * 		where ({@link JmxAuthorizeContext#getImpact()} == {@link MBeanOperationInfo#INFO},
 * 		or {@link MBeanOperationInfo#UNKNOWN} with an <code>operationName</code>
 * 		checked against {@link #isReadOnlyName(String)}).
 * 	Additionally allows &quot;<code>dumpAllThreads</code>&quot; on {@link java.lang.management.ThreadMXBean}
 * 		only (introduced in Java 1.6 / 6).</p>
 * <p>Unfortunately, under Java 1.5 / 5.0, this currently denies access to
 * 		<code>get</code>* methods on {@link java.lang.management.ThreadMXBean}
 * 		and {@link java.util.logging.LoggingMXBean}, as these operations are marked as
 * 		{@link MBeanOperationInfo#ACTION_INFO} rather than {@link MBeanOperationInfo#UNKNOWN}.
 * 	This appears to have been somewhat fixed in Java 1.6 / 6 (by using
 * 		{@link MBeanOperationInfo#UNKNOWN}, even though {@link MBeanOperationInfo#INFO}
 * 		would be more appropriate.
 * 	(<a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6320104"
 * 		>http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6320104</a>? /
 * 		<a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6933325"
 * 		>http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6933325</a>.)
 * 	Use {@link ReadOnlyJavaFixInvokeHandler} as a work-around for Java 1.5 / 5.0.</p>
 * @author Mark A. Ziesemer
 * 	<a href="http://www.ziesemer.com.">&lt;www.ziesemer.com&gt;</a>
 */
public class ReadOnlyInvokeHandler implements IJmxInvocationAuthorizer{
	
	/**
	 * {@link ObjectName} for {@link ManagementFactory#THREAD_MXBEAN_NAME}.
	 */
	protected static final ObjectName THREAD_MX_ON;
	/**
	 * {@link ObjectName} for {@link LogManager#LOGGING_MXBEAN_NAME}.
	 */
	protected static final ObjectName LOGGING_MX_ON;
	
	static{
		try{
			THREAD_MX_ON = ObjectName.getInstance(ManagementFactory.THREAD_MXBEAN_NAME);
			LOGGING_MX_ON = ObjectName.getInstance(LogManager.LOGGING_MXBEAN_NAME);
		}catch(final MalformedObjectNameException mone){
			// Should never happen.
			throw new RuntimeException(mone.getMessage(), mone);
		}
	}
	
	public Object invoke(final JmxAuthorizeContext ac) throws Throwable{
		if(isAllow(ac)){
			return ac.invoke();
		}
		throw new SecurityException("Access denied (non-read-only invoke)");
	}
	
	protected boolean isAllow(final JmxAuthorizeContext ac) throws Exception{
		if(isDefaultReadOnlyOperation(ac)){
			return true;
		}
		final ObjectName on = (ObjectName)ac.args[0];
		final String methodName = (String)ac.args[1];
		if(THREAD_MX_ON.equals(on) && isThreadReadOnlyName(methodName)){
			return true;
		}
		return false;
	}
	
	protected boolean isReadOnlyName(final String name){
		return name.startsWith("get")
			|| name.startsWith("is")
			|| name.startsWith("list")
			|| name.startsWith("query")
			|| "hashCode".equals(name);
	}
	
	protected boolean isThreadReadOnlyName(final String name){
		return "dumpAllThreads".equals(name);
	}
	
	protected boolean isDefaultReadOnlyOperation(final JmxAuthorizeContext ac) throws Exception{
		final int impact = ac.getImpact();
		return (impact == MBeanOperationInfo.INFO)
			|| (impact == MBeanOperationInfo.UNKNOWN && isReadOnlyName((String)ac.args[1]));
	}
	
}