1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.web.bind;
17
18 import org.apache.commons.lang.ObjectUtils;
19 import org.kuali.rice.core.api.CoreApiServiceLocator;
20 import org.kuali.rice.core.api.encryption.EncryptionService;
21 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
22 import org.kuali.rice.krad.uif.lifecycle.ViewPostMetadata;
23 import org.kuali.rice.krad.uif.util.CloneUtils;
24 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
25 import org.kuali.rice.krad.uif.view.ViewModel;
26 import org.springframework.beans.BeanWrapperImpl;
27 import org.springframework.beans.BeansException;
28 import org.springframework.beans.InvalidPropertyException;
29 import org.springframework.beans.NotReadablePropertyException;
30 import org.springframework.beans.NullValueInNestedPathException;
31 import org.springframework.beans.PropertyAccessorUtils;
32 import org.springframework.beans.PropertyValue;
33 import org.springframework.util.StringUtils;
34 import org.springframework.web.bind.annotation.RequestMethod;
35 import org.springframework.web.context.request.RequestContextHolder;
36 import org.springframework.web.context.request.ServletRequestAttributes;
37
38 import javax.servlet.http.HttpServletRequest;
39 import java.beans.PropertyDescriptor;
40 import java.beans.PropertyEditor;
41 import java.security.GeneralSecurityException;
42 import java.util.ArrayList;
43 import java.util.HashSet;
44 import java.util.List;
45 import java.util.Set;
46
47
48
49
50
51
52
53
54
55
56 public class UifViewBeanWrapper extends BeanWrapperImpl {
57 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(UifViewBeanWrapper.class);
58
59
60
61 private Set<String> processedProperties;
62
63 private final UifBeanPropertyBindingResult bindingResult;
64
65 public UifViewBeanWrapper(ViewModel model, UifBeanPropertyBindingResult bindingResult) {
66 super(model);
67
68 this.bindingResult = bindingResult;
69 this.processedProperties = new HashSet<String>();
70 }
71
72
73
74
75
76
77
78
79 protected void registerEditorFromView(String propertyName) {
80 if (LOG.isDebugEnabled()) {
81 LOG.debug("Attempting to find property editor for property '" + propertyName + "'");
82 }
83
84
85 if (processedProperties.contains(propertyName)) {
86 return;
87 }
88
89 ViewPostMetadata viewPostMetadata = ((ViewModel) getWrappedInstance()).getViewPostMetadata();
90 if (viewPostMetadata == null) {
91 return;
92 }
93
94 PropertyEditor propertyEditor = null;
95 boolean requiresEncryption = false;
96
97 if ((viewPostMetadata.getFieldPropertyEditors() != null) && viewPostMetadata.getFieldPropertyEditors()
98 .containsKey(propertyName)) {
99 propertyEditor = viewPostMetadata.getFieldPropertyEditors().get(propertyName);
100 } else if ((viewPostMetadata.getSecureFieldPropertyEditors() != null) && viewPostMetadata
101 .getSecureFieldPropertyEditors().containsKey(propertyName)) {
102 propertyEditor = viewPostMetadata.getSecureFieldPropertyEditors().get(propertyName);
103 requiresEncryption = true;
104 }
105
106 if (propertyEditor != null) {
107 if (LOG.isDebugEnabled()) {
108 LOG.debug("Registering custom editor for property path '"
109 + propertyName
110 + "' and property editor class '"
111 + propertyEditor.getClass().getName()
112 + "'");
113 }
114
115 if (requiresEncryption) {
116 if (LOG.isDebugEnabled()) {
117 LOG.debug("Enabling encryption for custom editor '" + propertyName +
118 "' and property editor class '" + propertyEditor.getClass().getName() + "'");
119 }
120 this.registerCustomEditor(null, propertyName, new UifEncryptionPropertyEditorWrapper(propertyEditor));
121 } else {
122 this.registerCustomEditor(null, propertyName, propertyEditor);
123 }
124 } else if (requiresEncryption) {
125 if (LOG.isDebugEnabled()) {
126 LOG.debug("No custom formatter for property path '"
127 + propertyName
128 + "' but property does require encryption");
129 }
130
131 this.registerCustomEditor(null, propertyName, new UifEncryptionPropertyEditorWrapper(
132 findEditorForPropertyName(propertyName)));
133 }
134
135 processedProperties.add(propertyName);
136 }
137
138
139
140
141
142
143
144
145 protected PropertyEditor findEditorForPropertyName(String propertyName) {
146 Class<?> clazz = getPropertyType(propertyName);
147 if (LOG.isDebugEnabled()) {
148 LOG.debug("Attempting retrieval of property editor using class '"
149 + clazz
150 + "' and property path '"
151 + propertyName
152 + "'");
153 }
154
155 PropertyEditor editor = findCustomEditor(clazz, propertyName);
156 if (editor == null) {
157 if (LOG.isDebugEnabled()) {
158 LOG.debug("No custom property editor found using class '"
159 + clazz
160 + "' and property path '"
161 + propertyName
162 + "'. Attempting to find default property editor class.");
163 }
164 editor = getDefaultEditor(clazz);
165 }
166
167 return editor;
168 }
169
170
171
172
173 @Override
174 public Class<?> getPropertyType(String propertyName) throws BeansException {
175 try {
176 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
177 if (pd != null) {
178 return pd.getPropertyType();
179 }
180
181
182 Object value = super.getPropertyValue(propertyName);
183 if (value != null) {
184 return value.getClass();
185 }
186
187
188
189 Class<?> editorType = guessPropertyTypeFromEditors(propertyName);
190 if (editorType != null) {
191 return editorType;
192 }
193 } catch (InvalidPropertyException ex) {
194
195 }
196
197 return null;
198 }
199
200
201
202
203
204
205 @Override
206 protected BeanWrapperImpl getBeanWrapperForPropertyPath(String propertyPath) {
207 BeanWrapperImpl beanWrapper = super.getBeanWrapperForPropertyPath(propertyPath);
208
209 PropertyTokenHolder tokens = getPropertyNameTokens(propertyPath);
210 String canonicalName = tokens.canonicalName;
211
212 int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(canonicalName);
213 if (pos != -1) {
214 canonicalName = canonicalName.substring(0, pos);
215 }
216
217 copyCustomEditorsTo(beanWrapper, canonicalName);
218
219 return beanWrapper;
220 }
221
222
223
224
225
226
227 @Override
228 public Object getPropertyValue(String propertyName) throws BeansException {
229 registerEditorFromView(propertyName);
230
231 Object value = null;
232 try {
233 value = super.getPropertyValue(propertyName);
234 } catch (NullValueInNestedPathException e) {
235
236 }
237
238 return value;
239 }
240
241
242
243
244
245
246
247
248
249
250
251 @Override
252 public void setPropertyValue(PropertyValue pv) throws BeansException {
253 boolean isPropertyAccessible = checkPropertyBindingAccess(pv.getName());
254 if (!isPropertyAccessible) {
255 return;
256 }
257
258 Object value = processValueBeforeSet(pv.getName(), pv.getValue());
259
260 pv = new PropertyValue(pv, value);
261
262
263 boolean originalValueSaved = true;
264 Object originalValue = null;
265 if (bindingResult.isChangeTracking()) {
266 try {
267 originalValue = getPropertyValue(pv.getName());
268 } catch (Exception e) {
269
270
271 originalValueSaved = false;
272 }
273 }
274
275
276 super.setPropertyValue(pv);
277
278
279 if (bindingResult.isChangeTracking() && originalValueSaved) {
280 try {
281 Object newValue = getPropertyValue(pv.getName());
282 if (ObjectUtils.notEqual(originalValue, newValue)) {
283
284 bindingResult.addModifiedPath(pv.getName());
285 }
286 } catch (Exception e) {
287
288 }
289 }
290 }
291
292
293
294
295
296
297
298
299
300
301
302 @Override
303 public void setPropertyValue(String propertyName, Object value) throws BeansException {
304 boolean isPropertyAccessible = checkPropertyBindingAccess(propertyName);
305 if (!isPropertyAccessible) {
306 return;
307 }
308
309 value = processValueBeforeSet(propertyName, value);
310
311
312 boolean originalValueSaved = true;
313 Object originalValue = null;
314 try {
315 originalValue = getPropertyValue(propertyName);
316 } catch (Exception e) {
317
318
319 originalValueSaved = false;
320 }
321
322
323 super.setPropertyValue(propertyName, value);
324
325
326 if (originalValueSaved) {
327 try {
328 Object newValue = getPropertyValue(propertyName);
329 if (ObjectUtils.notEqual(originalValue, newValue)) {
330
331 bindingResult.addModifiedPath(propertyName);
332 }
333 } catch (Exception e) {
334
335 }
336 }
337 }
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353 protected boolean checkPropertyBindingAccess(String propertyName) {
354 boolean isAccessible = false;
355
356
357 Boolean bindingAnnotationAccess = checkBindingAnnotationsInPath(propertyName);
358 if (bindingAnnotationAccess != null) {
359 isAccessible = bindingAnnotationAccess.booleanValue();
360 } else {
361
362 ViewPostMetadata viewPostMetadata = ((ViewModel) getWrappedInstance()).getViewPostMetadata();
363 if ((viewPostMetadata != null) && (viewPostMetadata.getAccessibleBindingPaths() != null)) {
364 isAccessible = viewPostMetadata.getAccessibleBindingPaths().contains(propertyName);
365 }
366 }
367
368 if (!isAccessible) {
369 LOG.debug("Request parameter sent for inaccessible binding path: " + propertyName);
370
371 bindingResult.recordSuppressedField(propertyName);
372 }
373
374 return isAccessible;
375 }
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390 protected Boolean checkBindingAnnotationsInPath(String propertyPath) {
391 HttpServletRequest request =
392 ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
393
394
395 String[] propertyPaths = ObjectPropertyUtils.splitPropertyPath(propertyPath);
396 for (int i = (propertyPaths.length - 1); i >= 0; i--) {
397 Class<?> parentPropertyClass = getWrappedClass();
398
399
400 if (i != 0) {
401 String path = org.apache.commons.lang.StringUtils.join(propertyPaths, ".", 0, i);
402
403 parentPropertyClass = ObjectPropertyUtils.getPropertyType(getWrappedInstance(), path);
404 }
405
406
407 String nestedPath = propertyPaths[i];
408 if (org.apache.commons.lang.StringUtils.endsWith(nestedPath, "]")) {
409 nestedPath = org.apache.commons.lang.StringUtils.substringBefore(nestedPath, "[");
410 }
411
412 RequestProtected protectedAnnotation = (RequestProtected) CloneUtils.getFieldAnnotation(parentPropertyClass,
413 nestedPath, RequestProtected.class);
414 if ((protectedAnnotation != null) && annotationMatchesRequestMethod(protectedAnnotation.method(),
415 request.getMethod())) {
416 return Boolean.FALSE;
417 }
418
419 RequestAccessible accessibleAnnotation = (RequestAccessible) CloneUtils.getFieldAnnotation(
420 parentPropertyClass, nestedPath, RequestAccessible.class);
421 if ((accessibleAnnotation != null) && annotationMatchesRequestMethod(accessibleAnnotation.method(),
422 request.getMethod())) {
423 return Boolean.TRUE;
424 }
425 }
426
427 return null;
428 }
429
430
431
432
433
434
435
436
437
438 protected boolean annotationMatchesRequestMethod(RequestMethod[] annotationMethods, String requestMethod) {
439
440 if ((annotationMethods == null) || (annotationMethods.length == 0)) {
441 return true;
442 }
443
444 for (RequestMethod annotationMethod : annotationMethods) {
445 if (org.apache.commons.lang.StringUtils.equals(annotationMethod.name(), requestMethod)) {
446 return true;
447 }
448 }
449
450 return false;
451 }
452
453
454
455
456
457
458
459
460
461 protected Object processValueBeforeSet(String propertyName, Object value) {
462 registerEditorFromView(propertyName);
463
464 Object processedValue = value;
465
466
467
468 if (value instanceof String) {
469 String propertyValue = (String) value;
470
471 if (StringUtils.isEmpty(propertyValue)) {
472 processedValue = null;
473 } else {
474 processedValue = decryptValueIfNecessary(propertyName, propertyValue);
475 }
476 }
477
478 return processedValue;
479 }
480
481
482
483
484
485
486
487
488 protected String decryptValueIfNecessary(String propertyName, String propertyValue) {
489 String decryptedPropertyValue = propertyValue;
490
491 if (propertyValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
492 propertyValue = org.apache.commons.lang.StringUtils.removeEnd(propertyValue,
493 EncryptionService.ENCRYPTION_POST_PREFIX);
494 }
495
496 if (isSecure(getWrappedClass(), propertyName)) {
497 try {
498 if (CoreApiServiceLocator.getEncryptionService().isEnabled()) {
499 decryptedPropertyValue = CoreApiServiceLocator.getEncryptionService().decrypt(propertyValue);
500 }
501 } catch (GeneralSecurityException e) {
502 throw new RuntimeException(e);
503 }
504 }
505
506 return decryptedPropertyValue;
507 }
508
509
510
511
512
513
514
515
516 protected boolean isSecure(Class<?> wrappedClass, String propertyPath) {
517 if (KRADServiceLocatorWeb.getDataObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(
518 wrappedClass, propertyPath)) {
519 return true;
520 }
521
522 BeanWrapperImpl beanWrapper;
523 try {
524 beanWrapper = getBeanWrapperForPropertyPath(propertyPath);
525 } catch (NotReadablePropertyException nrpe) {
526 LOG.debug("Bean wrapper was not found for "
527 + propertyPath
528 + ", but since it cannot be accessed it will not be set as secure.", nrpe);
529 return false;
530 }
531
532 if (org.apache.commons.lang.StringUtils.isNotBlank(beanWrapper.getNestedPath())) {
533 PropertyTokenHolder tokens = getPropertyNameTokens(propertyPath);
534 String nestedPropertyPath = org.apache.commons.lang.StringUtils.removeStart(tokens.canonicalName,
535 beanWrapper.getNestedPath());
536
537 return isSecure(beanWrapper.getWrappedClass(), nestedPropertyPath);
538 }
539
540 return false;
541 }
542
543
544
545
546
547
548
549 private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
550 PropertyTokenHolder tokens = new PropertyTokenHolder();
551 String actualName = null;
552 List<String> keys = new ArrayList<String>(2);
553 int searchIndex = 0;
554 while (searchIndex != -1) {
555 int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
556 searchIndex = -1;
557 if (keyStart != -1) {
558 int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
559 if (keyEnd != -1) {
560 if (actualName == null) {
561 actualName = propertyName.substring(0, keyStart);
562 }
563 String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
564 if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) {
565 key = key.substring(1, key.length() - 1);
566 }
567 keys.add(key);
568 searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
569 }
570 }
571 }
572 tokens.actualName = (actualName != null ? actualName : propertyName);
573 tokens.canonicalName = tokens.actualName;
574 if (!keys.isEmpty()) {
575 tokens.canonicalName += PROPERTY_KEY_PREFIX +
576 StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
577 PROPERTY_KEY_SUFFIX;
578 tokens.keys = StringUtils.toStringArray(keys);
579 }
580 return tokens;
581 }
582
583 private static class PropertyTokenHolder {
584
585 public String canonicalName;
586
587 public String actualName;
588
589 public String[] keys;
590 }
591 }