001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.management;
018    
019    import javax.management.Descriptor;
020    import javax.management.MBeanException;
021    import javax.management.MBeanOperationInfo;
022    import javax.management.ReflectionException;
023    import javax.management.RuntimeOperationsException;
024    import javax.management.modelmbean.ModelMBeanInfo;
025    import javax.management.modelmbean.RequiredModelMBean;
026    
027    import org.apache.camel.util.ObjectHelper;
028    import org.apache.camel.util.URISupport;
029    import org.slf4j.Logger;
030    import org.slf4j.LoggerFactory;
031    
032    /**
033     * A {@link RequiredModelMBean} which allows us to intercept invoking operations on the MBean.
034     * <p/>
035     * For example if mask has been enabled on JMX, then we use this implementation
036     * to hide sensitive information from the returned JMX attributes / operations.
037     */
038    public class MaskRequiredModelMBean extends RequiredModelMBean {
039    
040        private static final Logger LOG = LoggerFactory.getLogger(MaskRequiredModelMBean.class);
041        private boolean mask;
042    
043        public MaskRequiredModelMBean() throws MBeanException, RuntimeOperationsException {
044            // must have default no-arg constructor
045        }
046    
047        public MaskRequiredModelMBean(ModelMBeanInfo mbi, boolean mask) throws MBeanException, RuntimeOperationsException {
048            super(mbi);
049            this.mask = mask;
050        }
051    
052        public boolean isMask() {
053            return mask;
054        }
055    
056        @Override
057        public Object invoke(String opName, Object[] opArgs, String[] sig) throws MBeanException, ReflectionException {
058            Object answer = super.invoke(opName, opArgs, sig);
059            // mask the answer if enabled and it was a String type (we cannot mask other types)
060            if (mask && answer instanceof String && ObjectHelper.isNotEmpty(answer) && isMaskOperation(opName)) {
061                answer = mask(opName, (String) answer);
062            }
063            return answer;
064        }
065    
066        protected boolean isMaskOperation(String opName) {
067            for (MBeanOperationInfo info : getMBeanInfo().getOperations()) {
068                if (info.getName().equals(opName)) {
069                    Descriptor desc = info.getDescriptor();
070                    if (desc != null) {
071                        Object val = desc.getFieldValue("mask");
072                        return val != null && "true".equals(val);
073                    }
074                }
075            }
076            return false;
077        }
078    
079        /**
080         * Masks the returned value from invoking the operation
081         *
082         * @param opName  the operation name invoked
083         * @param value   the current value
084         * @return the masked value
085         */
086        protected String mask(String opName, String value) {
087            // use sanitize uri which will mask sensitive information
088            String answer = URISupport.sanitizeUri(value);
089            if (LOG.isTraceEnabled()) {
090                LOG.trace("Masking JMX operation: {}.{} value: {} -> {}",
091                        new Object[]{getMBeanInfo().getClassName(), opName, value, answer});
092            }
093            return answer;
094        }
095    }