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