EnvironmentPrefixHelper.java
/*
* Copyright 2015-2019 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.cloud.config.server.encryption;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.util.StringUtils;
/**
* Shared helper class for encryption and decryption concerns where the plain text and
* cipher texts can optionally contain prefixes of <code>{name:value}</code> pairs.
* Special treatment is given to the "name" and "profiles" keys, which can be provided by
* the caller, explicitly instead of by the input text strings. This is to support
* independent decryptions using different cryptographic keys for different applications
* and profiles, if needed (this class does not have any crypto features, but it can be
* used by components that do).
*
* @author Dave Syer
*
*/
class EnvironmentPrefixHelper {
/**
* key for the "profiles" prefix pair (usually a Spring profile or comma separated
* value).
*/
private static final String PROFILES = "profiles";
/**
* key for the "name" (Environment name or application name).
*/
private static final String NAME = "name";
/**
* If plain text actually starts with text in the form <code>{name:value}</code>
* prefix it with this to signal the start of the plain text.
*/
private static final String ESCAPE = "{plain}";
/**
* Extract keys for looking up a {@link TextEncryptor} from the input text in the form
* of a prefix of zero or many <code>{name:value}</code> pairs. The name and profiles
* properties are always added to the keys (replacing any provided in the inputs).
* @param name application name
* @param profiles list of profiles
* @param text text to cipher
* @return encryptor keys
*/
public Map<String, String> getEncryptorKeys(String name, String profiles, String text) {
Map<String, String> keys = new LinkedHashMap<String, String>();
text = removeEnvironmentPrefix(text);
keys.put(NAME, name);
keys.put(PROFILES, profiles);
if (text.contains(ESCAPE)) {
text = text.substring(0, text.indexOf(ESCAPE));
}
String[] tokens = StringUtils.split(text, "}");
while (tokens != null) {
String token = tokens[0].trim();
if (token.startsWith("{")) {
String key = "";
String value = "";
if (token.contains(":") && !token.endsWith(":")) {
key = token.substring(1, token.indexOf(":"));
value = token.substring(token.indexOf(":") + 1);
}
else {
key = token.substring(1);
}
keys.put(key, value);
}
text = tokens[1];
tokens = StringUtils.split(text, "}");
}
return keys;
}
/**
* Add a prefix to the input text (usually a cipher) consisting of the
* <code>{name:value}</code> pairs. The "name" and "profiles" keys are special in that
* they are stripped since that information is always available when deriving the keys
* in {@link #getEncryptorKeys(String, String, String)}.
* @param keys name, value pairs
* @param input input to append
* @return prefixed input
*/
public String addPrefix(Map<String, String> keys, String input) {
keys.remove(NAME);
keys.remove(PROFILES);
StringBuilder builder = new StringBuilder();
for (String key : keys.keySet()) {
builder.append("{").append(key).append(":").append(keys.get(key)).append("}");
}
builder.append(input);
return builder.toString();
}
public String stripPrefix(String value) {
if (!value.contains("}")) {
return value;
}
if (value.contains(ESCAPE)) {
return value.substring(value.indexOf(ESCAPE) + ESCAPE.length());
}
return value.replaceFirst("^(\\{.*?:.*?\\})+", "");
}
private String removeEnvironmentPrefix(String input) {
return input.replaceFirst("\\{name:.*\\}", "").replaceFirst("\\{profiles:.*\\}", "");
}
}