View Javadoc

1   /**
2    * Copyright 2010-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.util.property;
17  
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.List;
21  import java.util.Properties;
22  import java.util.Random;
23  
24  import org.apache.commons.lang3.StringUtils;
25  import org.jasypt.util.text.TextEncryptor;
26  import org.kuali.common.util.EncUtils;
27  import org.kuali.common.util.EncryptionMode;
28  import org.kuali.common.util.EncryptionStrength;
29  import org.kuali.common.util.PropertyUtils;
30  import org.kuali.common.util.Str;
31  import org.kuali.common.util.property.processor.AddPrefixProcessor;
32  import org.kuali.common.util.property.processor.EndsWithDecryptProcessor;
33  import org.kuali.common.util.property.processor.EndsWithEncryptProcessor;
34  import org.kuali.common.util.property.processor.GlobalOverrideProcessor;
35  import org.kuali.common.util.property.processor.PropertyProcessor;
36  import org.kuali.common.util.property.processor.ReformatKeysAsEnvVarsProcessor;
37  import org.kuali.common.util.property.processor.ResolvePlaceholdersProcessor;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  import org.springframework.util.Assert;
41  import org.springframework.util.PropertyPlaceholderHelper;
42  
43  public class DefaultPropertyContext implements PropertyContext {
44  
45  	private static final Logger logger = LoggerFactory.getLogger(DefaultPropertyContext.class);
46  	private static final Random RANDOM = new Random(System.currentTimeMillis());
47  
48  	PropertyPlaceholderHelper helper = Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER;
49  	String globalPropertiesMode = Constants.DEFAULT_GLOBAL_PROPERTIES_MODE.name();
50  	String resolvePlaceholders = Boolean.toString(Constants.DEFAULT_RESOLVE_PLACEHOLDERS);
51  	String style = PropertyStyle.NORMAL.name();
52  	String encryptionMode = EncryptionMode.NONE.name();
53  	String encryptionStrength = EncryptionStrength.BASIC.name();
54  	String encryptionPassword;
55  	String prefix;
56  	List<PropertyProcessor> processors;
57  	Properties properties;
58  
59  	protected List<PropertyProcessor> getDefaultProcessors() {
60  		List<PropertyProcessor> processors = new ArrayList<PropertyProcessor>();
61  
62  		// Decrypt/encrypt as appropriate
63  		if (encryptionMode != null) {
64  			EncryptionMode mode = EncryptionMode.valueOf(encryptionMode);
65  			EncryptionStrength strength = EncryptionStrength.valueOf(encryptionStrength);
66  			processors.add(getEncProcessor(mode, strength, encryptionPassword));
67  		}
68  
69  		/**
70  		 * Remove the local reference to the encryption password now that the TextEncryptor has been created.<br>
71  		 * Note that the encryption password is VERY likely to be hanging around in memory even after being set to null locally.<br>
72  		 * Setting it to null here just makes it slightly tougher for someone to obtain the password.<br>
73  		 * Having a reference to this bean no longer does them any good, they'll have to search around in memory to find it.<br>
74  		 */
75  		this.encryptionPassword = null;
76  
77  		GlobalPropertiesMode gpm = GlobalPropertiesMode.valueOf(globalPropertiesMode);
78  
79  		// By default, system/environment properties override loaded properties
80  		processors.add(new GlobalOverrideProcessor(gpm));
81  
82  		// By default, all placeholders in the properties are resolved
83  		if (Boolean.parseBoolean(resolvePlaceholders)) {
84  			processors.add(new ResolvePlaceholdersProcessor(helper, gpm));
85  		}
86  
87  		// Add a prefix to the property keys if appropriate
88  		if (!StringUtils.isBlank(prefix)) {
89  			processors.add(new AddPrefixProcessor(prefix));
90  		}
91  
92  		// Reformat the keys in environment variable format if appropriate
93  		if (style != null) {
94  			processors.add(getStyleProcessor(style));
95  		}
96  
97  		// Return the list of processors
98  		return processors;
99  	}
100 
101 	protected PropertyProcessor getStyleProcessor(String style) {
102 		switch (PropertyStyle.valueOf(style)) {
103 		case NORMAL:
104 			return Constants.NO_OP_PROCESSOR;
105 		case ENVIRONMENT_VARIABLE:
106 			return new ReformatKeysAsEnvVarsProcessor();
107 		default:
108 			throw new IllegalArgumentException("Property style " + style + " is unknown");
109 		}
110 	}
111 
112 	protected PropertyProcessor getEncProcessor(EncryptionMode mode, EncryptionStrength strength, String password) {
113 		switch (mode) {
114 		case NONE:
115 			return Constants.NO_OP_PROCESSOR;
116 		case ENCRYPT:
117 			TextEncryptor encryptor = EncUtils.getTextEncryptor(strength, password);
118 			return new EndsWithEncryptProcessor(encryptor);
119 		case DECRYPT:
120 			TextEncryptor decryptor = EncUtils.getTextEncryptor(strength, password);
121 			return new EndsWithDecryptProcessor(decryptor);
122 		default:
123 			throw new IllegalArgumentException("Encryption mode '" + mode + "' is unknown");
124 		}
125 	}
126 
127 	protected void log() {
128 		if (!StringUtils.equals(EncryptionMode.NONE.name(), encryptionMode)) {
129 			logger.info("Encryption mode - " + StringUtils.trimToEmpty(encryptionMode));
130 			logger.info("Encryption strength - " + StringUtils.trimToEmpty(encryptionStrength));
131 			String displayPassword = null;
132 			if (!StringUtils.isBlank(encryptionPassword)) {
133 				int len = encryptionPassword.length();
134 				displayPassword = StringUtils.repeat("*", Math.max(RANDOM.nextInt(len * 2), len / 2));
135 			}
136 			logger.info("Encryption password - " + StringUtils.trimToEmpty(displayPassword));
137 		}
138 		if (!StringUtils.equals(PropertyStyle.NORMAL.name(), style)) {
139 			logger.info("Property style - " + StringUtils.trimToEmpty(style));
140 		}
141 		if (!StringUtils.isEmpty(prefix)) {
142 			logger.info("Property prefix - " + StringUtils.trimToEmpty(prefix));
143 		}
144 		if (!StringUtils.equals(Boolean.toString(Constants.DEFAULT_RESOLVE_PLACEHOLDERS), resolvePlaceholders)) {
145 			logger.info("Resolve placeholders - " + resolvePlaceholders);
146 		}
147 	}
148 
149 	@Override
150 	public void initialize(Properties properties) {
151 		GlobalPropertiesMode gpm = GlobalPropertiesMode.valueOf(globalPropertiesMode);
152 		Properties global = PropertyUtils.getProperties(properties, gpm);
153 		this.encryptionMode = resolve(encryptionMode, global);
154 		this.encryptionPassword = resolveAndRemove(encryptionPassword, global, properties);
155 		this.encryptionStrength = resolve(encryptionStrength, global);
156 		this.style = resolve(style, global);
157 		this.prefix = resolve(prefix, global);
158 		this.resolvePlaceholders = resolve(resolvePlaceholders, global);
159 		log();
160 		validate();
161 		addProcessors();
162 		logger.info("Proceeding with " + processors.size() + " processors.");
163 	}
164 
165 	protected void addProcessors() {
166 		List<PropertyProcessor> defaultProcessors = getDefaultProcessors();
167 		if (processors == null) {
168 			processors = defaultProcessors;
169 		} else {
170 			processors.addAll(0, defaultProcessors);
171 		}
172 	}
173 
174 	protected void validate() {
175 		EncryptionMode.valueOf(encryptionMode);
176 		EncryptionStrength.valueOf(encryptionStrength);
177 		PropertyStyle.valueOf(style);
178 		Boolean.parseBoolean(resolvePlaceholders);
179 	}
180 
181 	protected String getPlaceholderKey(String string) {
182 		String prefix = Constants.DEFAULT_PLACEHOLDER_PREFIX;
183 		String suffix = Constants.DEFAULT_PLACEHOLDER_SUFFIX;
184 		String separator = Constants.DEFAULT_VALUE_SEPARATOR;
185 		String key = StringUtils.substringBetween(string, prefix, separator);
186 		if (key == null) {
187 			return StringUtils.substringBetween(string, prefix, suffix);
188 		} else {
189 			return key;
190 		}
191 	}
192 
193 	protected void remove(String string, String resolvedString, Properties properties) {
194 		boolean placeholder = PropertyUtils.isSingleUnresolvedPlaceholder(string);
195 		boolean resolved = !StringUtils.equals(string, resolvedString);
196 		boolean irrelevant = Str.contains(Arrays.asList(Constants.NONE, Constants.NULL), resolvedString, false);
197 		boolean remove = placeholder && resolved && !irrelevant;
198 		if (remove) {
199 			String key = getPlaceholderKey(string);
200 			Assert.notNull(key, "key is null");
201 			if (properties.getProperty(key) != null) {
202 				logger.info("Removing property '" + key + "'");
203 				properties.remove(key);
204 			}
205 		}
206 	}
207 
208 	protected String resolveAndRemove(String string, Properties global, Properties properties) {
209 		String resolvedString = resolve(string, global);
210 		remove(string, resolvedString, properties);
211 		return resolvedString;
212 	}
213 
214 	protected String resolve(String string, Properties properties) {
215 		if (string == null) {
216 			return null;
217 		} else {
218 			String resolvedValue = helper.replacePlaceholders(string, properties);
219 			if (!StringUtils.equals(string, resolvedValue)) {
220 				logger.debug("Resolved {} -> {}", string, resolvedValue);
221 			}
222 			return resolvedValue;
223 		}
224 	}
225 
226 	public String getPrefix() {
227 		return prefix;
228 	}
229 
230 	public void setPrefix(String prefix) {
231 		this.prefix = prefix;
232 	}
233 
234 	public String getStyle() {
235 		return style;
236 	}
237 
238 	public void setStyle(String style) {
239 		this.style = style;
240 	}
241 
242 	public PropertyPlaceholderHelper getHelper() {
243 		return helper;
244 	}
245 
246 	public void setHelper(PropertyPlaceholderHelper helper) {
247 		this.helper = helper;
248 	}
249 
250 	public String getEncryptionMode() {
251 		return encryptionMode;
252 	}
253 
254 	public void setEncryptionMode(String encryptionMode) {
255 		this.encryptionMode = encryptionMode;
256 	}
257 
258 	public String getEncryptionStrength() {
259 		return encryptionStrength;
260 	}
261 
262 	public void setEncryptionStrength(String encryptionStrength) {
263 		this.encryptionStrength = encryptionStrength;
264 	}
265 
266 	public String getEncryptionPassword() {
267 		return encryptionPassword;
268 	}
269 
270 	public void setEncryptionPassword(String encryptionPassword) {
271 		this.encryptionPassword = encryptionPassword;
272 	}
273 
274 	@Override
275 	public List<PropertyProcessor> getProcessors() {
276 		return processors;
277 	}
278 
279 	public void setProcessors(List<PropertyProcessor> processors) {
280 		this.processors = processors;
281 	}
282 
283 	public Properties getProperties() {
284 		return properties;
285 	}
286 
287 	public void setProperties(Properties properties) {
288 		this.properties = properties;
289 	}
290 
291 	public String getGlobalPropertiesMode() {
292 		return globalPropertiesMode;
293 	}
294 
295 	public void setGlobalPropertiesMode(String globalPropertiesMode) {
296 		this.globalPropertiesMode = globalPropertiesMode;
297 	}
298 
299 	public String getResolvePlaceholders() {
300 		return resolvePlaceholders;
301 	}
302 
303 	public void setResolvePlaceholders(String resolvePlaceholders) {
304 		this.resolvePlaceholders = resolvePlaceholders;
305 	}
306 }