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 */
017package org.apache.camel.processor;
018
019import java.io.PrintWriter;
020import java.io.StringWriter;
021import java.util.Arrays;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Set;
025import java.util.TreeMap;
026import java.util.concurrent.Future;
027import java.util.regex.Pattern;
028
029import org.apache.camel.Exchange;
030import org.apache.camel.Message;
031import org.apache.camel.spi.ExchangeFormatter;
032import org.apache.camel.spi.MaskingFormatter;
033import org.apache.camel.spi.UriParam;
034import org.apache.camel.spi.UriParams;
035import org.apache.camel.util.MessageHelper;
036import org.apache.camel.util.ObjectHelper;
037import org.apache.camel.util.StringHelper;
038
039/**
040 * The {@link MaskingFormatter} that searches the specified keywards in the source
041 * and replace its value with mask string. By default passphrase, password and secretKey
042 * are used as keywards to replace its value.
043 */
044public class DefaultMaskingFormatter implements MaskingFormatter {
045
046    private static final Set<String> DEFAULT_KEYWORDS = new HashSet<String>(Arrays.asList("passphrase", "password", "secretKey"));
047    private Set<String> keywords;
048    private boolean maskKeyValue;
049    private boolean maskXmlElement;
050    private boolean maskJson;
051    private String maskString = "xxxxx";
052    private Pattern keyValueMaskPattern;
053    private Pattern xmlElementMaskPattern;
054    private Pattern jsonMaskPattern;
055
056    public DefaultMaskingFormatter() {
057        this(DEFAULT_KEYWORDS, true, true, true);
058    }
059
060    public DefaultMaskingFormatter(boolean maskKeyValue, boolean maskXml, boolean maskJson) {
061        this(DEFAULT_KEYWORDS, maskKeyValue, maskXml, maskJson);
062    }
063
064    public DefaultMaskingFormatter(Set<String> keywords, boolean maskKeyValue, boolean maskXmlElement, boolean maskJson) {
065        this.keywords = keywords;
066        setMaskKeyValue(maskKeyValue);
067        setMaskXmlElement(maskXmlElement);
068        setMaskJson(maskJson);
069    }
070
071    public String format(String source) {
072        if (keywords == null || keywords.isEmpty()) {
073            return source;
074        }
075
076        String answer = source;
077        if (maskKeyValue) {
078            answer = keyValueMaskPattern.matcher(answer).replaceAll("$1\"" + maskString + "\"");
079        }
080        if (maskXmlElement) {
081            answer = xmlElementMaskPattern.matcher(answer).replaceAll("$1" + maskString + "$3");
082        }
083        if (maskJson) {
084            answer = jsonMaskPattern.matcher(answer).replaceAll("$1\"" + maskString + "\"");
085        }
086        return answer;
087    }
088
089    public boolean isMaskKeyValue() {
090        return maskKeyValue;
091    }
092
093    public void setMaskKeyValue(boolean maskKeyValue) {
094        this.maskKeyValue = maskKeyValue;
095        if (maskKeyValue) {
096            keyValueMaskPattern = createKeyValueMaskPattern(keywords);
097        } else {
098            keyValueMaskPattern = null;
099        }
100    }
101
102    public boolean isMaskXmlElement() {
103        return maskXmlElement;
104    }
105
106    public void setMaskXmlElement(boolean maskXml) {
107        this.maskXmlElement = maskXml;
108        if (maskXml) {
109            xmlElementMaskPattern = createXmlElementMaskPattern(keywords);
110        } else {
111            xmlElementMaskPattern = null;
112        }
113    }
114
115    public boolean isMaskJson() {
116        return maskJson;
117    }
118
119    public void setMaskJson(boolean maskJson) {
120        this.maskJson = maskJson;
121        if (maskJson) {
122            jsonMaskPattern = createJsonMaskPattern(keywords);
123        } else {
124            jsonMaskPattern = null;
125        }
126    }
127
128    public String getMaskString() {
129        return maskString;
130    }
131
132    public void setMaskString(String maskString) {
133        this.maskString = maskString;
134    }
135
136    protected Pattern createKeyValueMaskPattern(Set<String> keywords) {
137        StringBuilder regex = createOneOfThemRegex(keywords);
138        if (regex == null) {
139            return null;
140        }
141        regex.insert(0, "([\\w]*(?:");
142        regex.append(")[\\w]*[\\s]*?=[\\s]*?)([\\S&&[^'\",\\}\\]\\)]]+[\\S&&[^,\\}\\]\\)>]]*?|\"[^\"]*?\"|'[^']*?')");
143        return Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE);
144    }
145
146    protected Pattern createXmlElementMaskPattern(Set<String> keywords) {
147        StringBuilder regex = createOneOfThemRegex(keywords);
148        if (regex == null) {
149            return null;
150        }
151        regex.insert(0, "(<([\\w]*(?:");
152        regex.append(")[\\w]*)(?:[\\s]+.+)*?>[\\s]*?)(?:[\\S&&[^<]]+(?:\\s+[\\S&&[^<]]+)*?)([\\s]*?</\\2>)");
153        return Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE);
154    }
155
156    protected Pattern createJsonMaskPattern(Set<String> keywords) {
157        StringBuilder regex = createOneOfThemRegex(keywords);
158        if (regex == null) {
159            return null;
160        }
161        regex.insert(0, "(\"(?:[^\"]|(?:\\\"))*?(?:");
162        regex.append(")(?:[^\"]|(?:\\\"))*?\"\\s*?\\:\\s*?)(?:\"(?:[^\"]|(?:\\\"))*?\")");
163        return Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE);
164    }
165
166    protected StringBuilder createOneOfThemRegex(Set<String> keywords) {
167        StringBuilder regex = new StringBuilder();
168        if (keywords == null || keywords.isEmpty()) {
169            return null;
170        }
171        String[] strKeywords = keywords.toArray(new String[0]);
172        regex.append(Pattern.quote(strKeywords[0]));
173        if (strKeywords.length > 1) {
174            for (int i = 1; i < strKeywords.length; i++) {
175                regex.append('|');
176                regex.append(Pattern.quote(strKeywords[i]));
177            }
178        }
179        return regex;
180    }
181}