package org.jolokia.restrictor.policy;
/*
* Copyright 2009-2011 Roland Huss
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.HashSet;
import java.util.Set;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.jolokia.util.RequestType;
import org.w3c.dom.*;
/**
* Checker, which checks for specific MBean attributes or operations which can be either
* defined in an <code><allow></code> or <code><deny></code> seciton.
* <p/>
* MBean names can be specified either a full names or as patterns in which case the rule
* applies to all MBeans matching this pattern. For attribute and operations names, the wildcard
* <code>*</code> is allowed, too.
* <p/>
* Example:
* <pre>
* <allow>
* <mbean>
* <name>java.lang:type=Memory</name>
* <operation>gc</operation>
* </mbean>
* </allow>
*
* <deny>
* <mbean>
* <name>com.mchange.v2.c3p0:type=PooledDataSource,*</name>
* <attribute>properties</attribute>
* </mbean>
* </deny>
* </pre>
*
* @author roland
* @since 03.09.11
*/
public class MBeanAccessChecker extends AbstractChecker<MBeanAccessChecker.Arg> {
// Configuration for allowed and denied MBean attributes and operations.
private MBeanPolicyConfig allow;
private MBeanPolicyConfig deny;
/**
* Constructor which extracts the information relevant for this checker from the given document.
*
* @param pDoc document to examine
* @throws MalformedObjectNameException if the configuration contains malformed object names.
*/
public MBeanAccessChecker(Document pDoc) throws MalformedObjectNameException {
for (String tag : new String[] { "allow", "mbeans" }) {
NodeList nodes = pDoc.getElementsByTagName(tag);
if (nodes.getLength() > 0) {
// "allow" and "mbeans" are synonyms
if (allow == null) {
allow = new MBeanPolicyConfig();
}
extractMbeanConfiguration(nodes, allow);
}
}
NodeList nodes = pDoc.getElementsByTagName("deny");
if (nodes.getLength() > 0) {
deny = new MBeanPolicyConfig();
extractMbeanConfiguration(nodes,deny);
}
}
/** {@inheritDoc} */
@Override
public boolean check(Arg pArg) {
if (pArg.isTypeAllowed()) {
// Its allowed in general, so we only need to check
// the denied section, whether its forbidded
return deny == null || !matches(deny, pArg);
} else {
// Its forbidden by default, so we need to check the
// allowed section
return allow != null && matches(allow, pArg);
}
}
// =======================================================================================
// Extract configuration and put it into a given MBeanPolicyConfig
private void extractMbeanConfiguration(NodeList pNodes,MBeanPolicyConfig pConfig) throws MalformedObjectNameException {
for (int i = 0;i< pNodes.getLength();i++) {
Node node = pNodes.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
extractPolicyConfig(pConfig, node.getChildNodes());
}
}
private void extractPolicyConfig(MBeanPolicyConfig pConfig, NodeList pChilds) throws MalformedObjectNameException {
for (int j = 0;j< pChilds.getLength();j++) {
Node mBeanNode = pChilds.item(j);
if (mBeanNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
assertNodeName(mBeanNode,"mbean");
extractMBeanPolicy(pConfig, mBeanNode);
}
}
private void extractMBeanPolicy(MBeanPolicyConfig pConfig, Node pMBeanNode) throws MalformedObjectNameException {
NodeList params = pMBeanNode.getChildNodes();
String name = null;
Set<String> readAttributes = new HashSet<String>();
Set<String> writeAttributes = new HashSet<String>();
Set<String> operations = new HashSet<String>();
for (int k = 0; k < params.getLength(); k++) {
Node param = params.item(k);
if (param.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
assertNodeName(param,"name","attribute","operation");
String tag = param.getNodeName();
if (tag.equals("name")) {
if (name != null) {
throw new SecurityException("<name> given twice as MBean name");
} else {
name = param.getTextContent().trim();
}
} else if (tag.equals("attribute")) {
extractAttribute(readAttributes, writeAttributes, param);
} else if (tag.equals("operation")) {
operations.add(param.getTextContent().trim());
} else {
throw new SecurityException("Tag <" + tag + "> invalid");
}
}
if (name == null) {
throw new SecurityException("No <name> given for <mbean>");
}
pConfig.addValues(new ObjectName(name),readAttributes,writeAttributes,operations);
}
private void extractAttribute(Set<String> pReadAttributes, Set<String> pWriteAttributes, Node pParam) {
Node mode = pParam.getAttributes().getNamedItem("mode");
pReadAttributes.add(pParam.getTextContent().trim());
if (mode == null || !mode.getNodeValue().equalsIgnoreCase("read")) {
pWriteAttributes.add(pParam.getTextContent().trim());
}
}
// Lookup methods
private boolean matches(MBeanPolicyConfig pConfig, Arg pArg) {
Set<String> values = pConfig.getValues(pArg.getType(),pArg.getName());
if (values == null) {
ObjectName pattern = pConfig.findMatchingMBeanPattern(pArg.getName());
if (pattern != null) {
values = pConfig.getValues(pArg.getType(),pattern);
}
}
return values != null && (values.contains(pArg.getValue()) || wildcardMatch(values,pArg.getValue()));
}
// Check whether a value matches patterns in pValues
private boolean wildcardMatch(Set<String> pValues, String pValue) {
for (String pattern : pValues) {
if (pattern.contains("*") && pValue.matches(pattern.replaceAll("\\*",".*"))) {
return true;
}
}
return false;
}
// ===========================================================================================
/**
* Class encapsulation the arguments for the check command
*/
public static class Arg {
private boolean isTypeAllowed;
private RequestType type;
private ObjectName name;
private String value;
/**
* Constructor for this immutable object
*
* @param pIsTypeAllowed whether the type is allowed in principal (i.e. whether it is mentioned in
* s <code><commands></code> section)
* @param pType the type to check
* @param pName the MBean name to check
* @param pValue attribute or operation to check
*/
public Arg(boolean pIsTypeAllowed,RequestType pType, ObjectName pName, String pValue) {
isTypeAllowed = pIsTypeAllowed;
type = pType;
name = pName;
value = pValue;
}
/**
* Whethe the command type is allowed generally.
* @return
*/
public boolean isTypeAllowed() {
return isTypeAllowed;
}
/**
* Get request type
*
* @return type
*/
public RequestType getType() {
return type;
}
/**
* MBean name
* @return name of MBean
*/
public ObjectName getName() {
return name;
}
/**
* Value which is interpreted as operation or attribute name,
* dependening on the type
*
* @return attribute/operation name
*/
public String getValue() {
return value;
}
}
}