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  
23  import org.apache.commons.lang3.StringUtils;
24  import org.jasypt.util.text.TextEncryptor;
25  import org.kuali.common.util.EncUtils;
26  import org.kuali.common.util.EncryptionMode;
27  import org.kuali.common.util.EncryptionStrength;
28  import org.kuali.common.util.LoggerUtils;
29  import org.kuali.common.util.PropertyUtils;
30  import org.kuali.common.util.Str;
31  import org.kuali.common.util.obscure.DefaultObscurer;
32  import org.kuali.common.util.obscure.Obscurer;
33  import org.kuali.common.util.property.processor.AddPrefixProcessor;
34  import org.kuali.common.util.property.processor.EndsWithDecryptProcessor;
35  import org.kuali.common.util.property.processor.EndsWithEncryptProcessor;
36  import org.kuali.common.util.property.processor.GlobalOverrideProcessor;
37  import org.kuali.common.util.property.processor.OverrideProcessor;
38  import org.kuali.common.util.property.processor.PropertyProcessor;
39  import org.kuali.common.util.property.processor.ReformatKeysAsEnvVarsProcessor;
40  import org.kuali.common.util.property.processor.ResolvePlaceholdersProcessor;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  import org.springframework.util.Assert;
44  import org.springframework.util.PropertyPlaceholderHelper;
45  
46  public class DefaultPropertyContext implements PropertyContext {
47  
48  	private static final Logger logger = LoggerFactory.getLogger(DefaultPropertyContext.class);
49  	PropertyPlaceholderHelper helper = Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER;
50  	String globalPropertiesMode = Constants.DEFAULT_GLOBAL_PROPERTIES_MODE.name();
51  	String resolvePlaceholders = Boolean.toString(Constants.DEFAULT_RESOLVE_PLACEHOLDERS);
52  	Obscurer obscurer = new DefaultObscurer();
53  	String style = PropertyStyle.NORMAL.name();
54  	String encryptionMode = EncryptionMode.NONE.name();
55  	String encryptionStrength = EncryptionStrength.BASIC.name();
56  	String encryptionPassword;
57  	String prefix;
58  	List<PropertyProcessor> processors;
59  	Properties properties;
60  	Properties buildProperties;
61  	List<String> buildPropertyIncludes;
62  	List<String> buildPropertyExcludes;
63  
64  	protected List<PropertyProcessor> getDefaultProcessors() {
65  		List<PropertyProcessor> processors = new ArrayList<PropertyProcessor>();
66  
67  		// If this context is being loaded as part of a build process, build properties win over properties from .properties files
68  		if (buildProperties != null) {
69  			OverrideProcessor overrideProcessor = new OverrideProcessor(Constants.DEFAULT_PROPERTY_OVERWRITE_MODE, buildProperties);
70  			if (buildPropertyIncludes != null) {
71  				overrideProcessor.setIncludes(buildPropertyIncludes);
72  			}
73  			if (buildPropertyExcludes != null) {
74  				overrideProcessor.setExcludes(buildPropertyExcludes);
75  			}
76  			processors.add(overrideProcessor);
77  		}
78  
79  		// Decrypt/encrypt as appropriate
80  		if (encryptionMode != null) {
81  			EncryptionMode mode = EncryptionMode.valueOf(encryptionMode);
82  			EncryptionStrength strength = EncryptionStrength.valueOf(encryptionStrength);
83  			processors.add(getEncProcessor(mode, strength, encryptionPassword));
84  		}
85  
86  		/**
87  		 * Remove the local reference to the encryption password now that the TextEncryptor has been created.<br>
88  		 * Note that the encryption password is VERY likely to be hanging around in memory even after being set to null locally.<br>
89  		 * Setting it to null here just makes it slightly tougher for someone to obtain the password.<br>
90  		 * Having a reference to this bean no longer does them any good, they'll have to search around in memory to find it.<br>
91  		 */
92  		this.encryptionPassword = null;
93  
94  		GlobalPropertiesMode gpm = GlobalPropertiesMode.valueOf(globalPropertiesMode);
95  
96  		// By default, system/environment properties override loaded properties
97  		processors.add(new GlobalOverrideProcessor(gpm));
98  
99  		// By default, all placeholders in the properties are resolved
100 		if (Boolean.parseBoolean(resolvePlaceholders)) {
101 			processors.add(new ResolvePlaceholdersProcessor(helper, gpm));
102 		}
103 
104 		// Add a prefix to the property keys if appropriate
105 		if (!StringUtils.isBlank(prefix)) {
106 			processors.add(new AddPrefixProcessor(prefix));
107 		}
108 
109 		// Reformat the keys in environment variable format if appropriate
110 		if (style != null) {
111 			processors.add(getStyleProcessor(style));
112 		}
113 
114 		// Return the list of processors
115 		return processors;
116 	}
117 
118 	protected PropertyProcessor getStyleProcessor(String style) {
119 		switch (PropertyStyle.valueOf(style)) {
120 		case NORMAL:
121 			return Constants.NO_OP_PROCESSOR;
122 		case ENVIRONMENT_VARIABLE:
123 			return new ReformatKeysAsEnvVarsProcessor();
124 		default:
125 			throw new IllegalArgumentException("Property style " + style + " is unknown");
126 		}
127 	}
128 
129 	protected PropertyProcessor getEncProcessor(EncryptionMode mode, EncryptionStrength strength, String password) {
130 		switch (mode) {
131 		case NONE:
132 			return Constants.NO_OP_PROCESSOR;
133 		case ENCRYPT:
134 			TextEncryptor encryptor = EncUtils.getTextEncryptor(strength, password);
135 			return new EndsWithEncryptProcessor(encryptor);
136 		case DECRYPT:
137 			TextEncryptor decryptor = EncUtils.getTextEncryptor(strength, password);
138 			return new EndsWithDecryptProcessor(decryptor);
139 		default:
140 			throw new IllegalArgumentException("Encryption mode '" + mode + "' is unknown");
141 		}
142 	}
143 
144 	protected void log() {
145 		if (!StringUtils.equals(EncryptionMode.NONE.name(), encryptionMode)) {
146 			logger.info("Encryption mode - " + StringUtils.trimToEmpty(encryptionMode));
147 			logger.info("Encryption strength - " + StringUtils.trimToEmpty(encryptionStrength));
148 			String displayPassword = LoggerUtils.getNullAsNone(encryptionPassword);
149 			if (encryptionPassword != null) {
150 				displayPassword = obscurer.obscure(encryptionPassword);
151 			}
152 			logger.info("Encryption password - " + displayPassword);
153 		}
154 		if (!StringUtils.equals(PropertyStyle.NORMAL.name(), style)) {
155 			logger.info("Property style - " + StringUtils.trimToEmpty(style));
156 		}
157 		if (!StringUtils.isEmpty(prefix)) {
158 			logger.info("Property prefix - " + StringUtils.trimToEmpty(prefix));
159 		}
160 		if (!StringUtils.equals(Boolean.toString(Constants.DEFAULT_RESOLVE_PLACEHOLDERS), resolvePlaceholders)) {
161 			logger.info("Resolve placeholders - " + resolvePlaceholders);
162 		}
163 	}
164 
165 	@Override
166 	public void initialize(Properties properties) {
167 		GlobalPropertiesMode gpm = GlobalPropertiesMode.valueOf(globalPropertiesMode);
168 		Properties global = PropertyUtils.getProperties(properties, gpm);
169 		this.encryptionMode = resolve(encryptionMode, global);
170 		this.encryptionPassword = resolveAndRemove(encryptionPassword, global, properties);
171 		this.encryptionStrength = resolve(encryptionStrength, global);
172 		this.style = resolve(style, global);
173 		this.prefix = resolve(prefix, global);
174 		this.resolvePlaceholders = resolve(resolvePlaceholders, global);
175 		log();
176 		validate();
177 		addProcessors();
178 		logger.info("Proceeding with " + processors.size() + " processors.");
179 	}
180 
181 	protected void addProcessors() {
182 		List<PropertyProcessor> defaultProcessors = getDefaultProcessors();
183 		if (processors == null) {
184 			processors = defaultProcessors;
185 		} else {
186 			processors.addAll(0, defaultProcessors);
187 		}
188 	}
189 
190 	protected void validate() {
191 		EncryptionMode.valueOf(encryptionMode);
192 		EncryptionStrength.valueOf(encryptionStrength);
193 		PropertyStyle.valueOf(style);
194 		Boolean.parseBoolean(resolvePlaceholders);
195 	}
196 
197 	protected String getPlaceholderKey(String string) {
198 		String prefix = Constants.DEFAULT_PLACEHOLDER_PREFIX;
199 		String suffix = Constants.DEFAULT_PLACEHOLDER_SUFFIX;
200 		String separator = Constants.DEFAULT_VALUE_SEPARATOR;
201 		String key = StringUtils.substringBetween(string, prefix, separator);
202 		if (key == null) {
203 			return StringUtils.substringBetween(string, prefix, suffix);
204 		} else {
205 			return key;
206 		}
207 	}
208 
209 	protected void remove(String string, String resolvedString, Properties properties) {
210 		boolean placeholder = PropertyUtils.isSingleUnresolvedPlaceholder(string);
211 		boolean resolved = !StringUtils.equals(string, resolvedString);
212 		boolean irrelevant = Str.contains(Arrays.asList(Constants.NONE, Constants.NULL), resolvedString, false);
213 		boolean remove = placeholder && resolved && !irrelevant;
214 		if (remove) {
215 			String key = getPlaceholderKey(string);
216 			Assert.notNull(key, "key is null");
217 			if (properties.getProperty(key) != null) {
218 				logger.info("Removing property '" + key + "'");
219 				properties.remove(key);
220 			}
221 		}
222 	}
223 
224 	protected String resolveAndRemove(String string, Properties global, Properties properties) {
225 		String resolvedString = resolve(string, global);
226 		remove(string, resolvedString, properties);
227 		return resolvedString;
228 	}
229 
230 	protected String resolve(String string, Properties properties) {
231 		if (string == null) {
232 			return null;
233 		} else {
234 			String resolvedValue = helper.replacePlaceholders(string, properties);
235 			if (!StringUtils.equals(string, resolvedValue)) {
236 				logger.debug("Resolved {} -> {}", string, resolvedValue);
237 			}
238 			return resolvedValue;
239 		}
240 	}
241 
242 	public String getPrefix() {
243 		return prefix;
244 	}
245 
246 	public void setPrefix(String prefix) {
247 		this.prefix = prefix;
248 	}
249 
250 	public String getStyle() {
251 		return style;
252 	}
253 
254 	public void setStyle(String style) {
255 		this.style = style;
256 	}
257 
258 	public PropertyPlaceholderHelper getHelper() {
259 		return helper;
260 	}
261 
262 	public void setHelper(PropertyPlaceholderHelper helper) {
263 		this.helper = helper;
264 	}
265 
266 	public String getEncryptionMode() {
267 		return encryptionMode;
268 	}
269 
270 	public void setEncryptionMode(String encryptionMode) {
271 		this.encryptionMode = encryptionMode;
272 	}
273 
274 	public String getEncryptionStrength() {
275 		return encryptionStrength;
276 	}
277 
278 	public void setEncryptionStrength(String encryptionStrength) {
279 		this.encryptionStrength = encryptionStrength;
280 	}
281 
282 	public String getEncryptionPassword() {
283 		return encryptionPassword;
284 	}
285 
286 	public void setEncryptionPassword(String encryptionPassword) {
287 		this.encryptionPassword = encryptionPassword;
288 	}
289 
290 	@Override
291 	public List<PropertyProcessor> getProcessors() {
292 		return processors;
293 	}
294 
295 	public void setProcessors(List<PropertyProcessor> processors) {
296 		this.processors = processors;
297 	}
298 
299 	public Properties getProperties() {
300 		return properties;
301 	}
302 
303 	public void setProperties(Properties properties) {
304 		this.properties = properties;
305 	}
306 
307 	public String getGlobalPropertiesMode() {
308 		return globalPropertiesMode;
309 	}
310 
311 	public void setGlobalPropertiesMode(String globalPropertiesMode) {
312 		this.globalPropertiesMode = globalPropertiesMode;
313 	}
314 
315 	public String getResolvePlaceholders() {
316 		return resolvePlaceholders;
317 	}
318 
319 	public void setResolvePlaceholders(String resolvePlaceholders) {
320 		this.resolvePlaceholders = resolvePlaceholders;
321 	}
322 
323 	public Obscurer getObscurer() {
324 		return obscurer;
325 	}
326 
327 	public void setObscurer(Obscurer obscurer) {
328 		this.obscurer = obscurer;
329 	}
330 
331 	public Properties getBuildProperties() {
332 		return buildProperties;
333 	}
334 
335 	public void setBuildProperties(Properties buildProperties) {
336 		this.buildProperties = buildProperties;
337 	}
338 
339 	public List<String> getBuildPropertyIncludes() {
340 		return buildPropertyIncludes;
341 	}
342 
343 	public void setBuildPropertyIncludes(List<String> buildPropertyIncludes) {
344 		this.buildPropertyIncludes = buildPropertyIncludes;
345 	}
346 
347 	public List<String> getBuildPropertyExcludes() {
348 		return buildPropertyExcludes;
349 	}
350 
351 	public void setBuildPropertyExcludes(List<String> buildPropertyExcludes) {
352 		this.buildPropertyExcludes = buildPropertyExcludes;
353 	}
354 }