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