1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.security;
20
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.Set;
32 import java.util.concurrent.CopyOnWriteArrayList;
33 import java.util.concurrent.CopyOnWriteArraySet;
34
35 import javax.servlet.HttpConstraintElement;
36 import javax.servlet.HttpMethodConstraintElement;
37 import javax.servlet.ServletSecurityElement;
38 import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
39 import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
40
41 import org.eclipse.jetty.http.HttpStatus;
42 import org.eclipse.jetty.http.PathMap;
43 import org.eclipse.jetty.server.HttpChannel;
44 import org.eclipse.jetty.server.HttpConfiguration;
45 import org.eclipse.jetty.server.Request;
46 import org.eclipse.jetty.server.Response;
47 import org.eclipse.jetty.server.UserIdentity;
48 import org.eclipse.jetty.util.StringMap;
49 import org.eclipse.jetty.util.security.Constraint;
50
51
52
53
54
55
56
57
58 public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
59 {
60 private static final String OMISSION_SUFFIX = ".omission";
61 private static final String ALL_METHODS = "*";
62 private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
63 private final Set<String> _roles = new CopyOnWriteArraySet<>();
64 private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
65 private boolean _strict = true;
66
67
68
69
70
71 public static Constraint createConstraint()
72 {
73 return new Constraint();
74 }
75
76
77
78
79
80
81 public static Constraint createConstraint(Constraint constraint)
82 {
83 try
84 {
85 return (Constraint)constraint.clone();
86 }
87 catch (CloneNotSupportedException e)
88 {
89 throw new IllegalStateException (e);
90 }
91 }
92
93
94
95
96
97
98
99
100
101
102
103 public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
104 {
105 Constraint constraint = createConstraint();
106 if (name != null)
107 constraint.setName(name);
108 constraint.setAuthenticate(authenticate);
109 constraint.setRoles(roles);
110 constraint.setDataConstraint(dataConstraint);
111 return constraint;
112 }
113
114
115
116
117
118
119
120
121 public static Constraint createConstraint (String name, HttpConstraintElement element)
122 {
123 return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());
124 }
125
126
127
128
129
130
131
132
133
134
135 public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
136 {
137 Constraint constraint = createConstraint();
138
139 if (rolesAllowed == null || rolesAllowed.length==0)
140 {
141 if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
142 {
143
144 constraint.setName(name+"-Deny");
145 constraint.setAuthenticate(true);
146 }
147 else
148 {
149
150 constraint.setName(name+"-Permit");
151 constraint.setAuthenticate(false);
152 }
153 }
154 else
155 {
156
157 constraint.setAuthenticate(true);
158 constraint.setRoles(rolesAllowed);
159 constraint.setName(name+"-RolesAllowed");
160 }
161
162
163 constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
164 return constraint;
165 }
166
167
168
169
170
171
172
173
174
175 public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
176 {
177 if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
178 return Collections.emptyList();
179
180 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
181 for (ConstraintMapping mapping:constraintMappings)
182 {
183 if (pathSpec.equals(mapping.getPathSpec()))
184 {
185 mappings.add(mapping);
186 }
187 }
188 return mappings;
189 }
190
191
192
193
194
195
196
197
198
199
200 public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
201 {
202 if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
203 return Collections.emptyList();
204
205 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
206 for (ConstraintMapping mapping:constraintMappings)
207 {
208
209 if (!pathSpec.equals(mapping.getPathSpec()))
210 {
211 mappings.add(mapping);
212 }
213 }
214 return mappings;
215 }
216
217
218
219
220
221
222
223
224
225
226
227 public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
228 {
229 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
230
231
232 Constraint constraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
233
234
235 ConstraintMapping defaultMapping = new ConstraintMapping();
236 defaultMapping.setPathSpec(pathSpec);
237 defaultMapping.setConstraint(constraint);
238 mappings.add(defaultMapping);
239
240
241
242 List<String> methodOmissions = new ArrayList<String>();
243
244
245 Collection<HttpMethodConstraintElement> methodConstraints = securityElement.getHttpMethodConstraints();
246 if (methodConstraints != null)
247 {
248 for (HttpMethodConstraintElement methodConstraint:methodConstraints)
249 {
250
251 Constraint mconstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraint);
252 ConstraintMapping mapping = new ConstraintMapping();
253 mapping.setConstraint(mconstraint);
254 mapping.setPathSpec(pathSpec);
255 if (methodConstraint.getMethodName() != null)
256 {
257 mapping.setMethod(methodConstraint.getMethodName());
258
259 methodOmissions.add(methodConstraint.getMethodName());
260 }
261 mappings.add(mapping);
262 }
263 }
264
265 if (methodOmissions.size() > 0)
266 defaultMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
267
268 return mappings;
269 }
270
271
272
273
274
275
276
277 public boolean isStrict()
278 {
279 return _strict;
280 }
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298 public void setStrict(boolean strict)
299 {
300 _strict = strict;
301 }
302
303
304
305
306
307 @Override
308 public List<ConstraintMapping> getConstraintMappings()
309 {
310 return _constraintMappings;
311 }
312
313
314 @Override
315 public Set<String> getRoles()
316 {
317 return _roles;
318 }
319
320
321
322
323
324
325
326
327
328
329 public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
330 {
331 setConstraintMappings(constraintMappings,null);
332 }
333
334
335
336
337
338
339
340
341
342 public void setConstraintMappings( ConstraintMapping[] constraintMappings )
343 {
344 setConstraintMappings( Arrays.asList(constraintMappings), null);
345 }
346
347
348
349
350
351
352
353
354
355
356 @Override
357 public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
358 {
359 _constraintMappings.clear();
360 _constraintMappings.addAll(constraintMappings);
361
362 if (roles==null)
363 {
364 roles = new HashSet<>();
365 for (ConstraintMapping cm : constraintMappings)
366 {
367 String[] cmr = cm.getConstraint().getRoles();
368 if (cmr!=null)
369 {
370 for (String r : cmr)
371 if (!ALL_METHODS.equals(r))
372 roles.add(r);
373 }
374 }
375 }
376 setRoles(roles);
377
378 if (isStarted())
379 {
380 for (ConstraintMapping mapping : _constraintMappings)
381 {
382 processConstraintMapping(mapping);
383 }
384 }
385 }
386
387
388
389
390
391
392
393
394
395 public void setRoles(Set<String> roles)
396 {
397 _roles.clear();
398 _roles.addAll(roles);
399 }
400
401
402
403
404
405
406
407 @Override
408 public void addConstraintMapping(ConstraintMapping mapping)
409 {
410 _constraintMappings.add(mapping);
411 if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
412 for (String role : mapping.getConstraint().getRoles())
413 addRole(role);
414
415 if (isStarted())
416 {
417 processConstraintMapping(mapping);
418 }
419 }
420
421
422
423
424
425 @Override
426 public void addRole(String role)
427 {
428 boolean modified = _roles.add(role);
429 if (isStarted() && modified && isStrict())
430 {
431
432 for (Map<String,RoleInfo> map : _constraintMap.values())
433 {
434 for (RoleInfo info : map.values())
435 {
436 if (info.isAnyRole())
437 info.addRole(role);
438 }
439 }
440 }
441 }
442
443
444
445
446
447 @Override
448 protected void doStart() throws Exception
449 {
450 _constraintMap.clear();
451 if (_constraintMappings!=null)
452 {
453 for (ConstraintMapping mapping : _constraintMappings)
454 {
455 processConstraintMapping(mapping);
456 }
457 }
458 super.doStart();
459 }
460
461
462
463 @Override
464 protected void doStop() throws Exception
465 {
466 super.doStop();
467 _constraintMap.clear();
468 }
469
470
471
472
473
474
475
476
477
478 protected void processConstraintMapping(ConstraintMapping mapping)
479 {
480 Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
481 if (mappings == null)
482 {
483 mappings = new HashMap<String,RoleInfo>();
484 _constraintMap.put(mapping.getPathSpec(),mappings);
485 }
486 RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
487 if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
488 return;
489
490 if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
491 {
492 processConstraintMappingWithMethodOmissions(mapping, mappings);
493 return;
494 }
495
496 String httpMethod = mapping.getMethod();
497 if (httpMethod==null)
498 httpMethod=ALL_METHODS;
499 RoleInfo roleInfo = mappings.get(httpMethod);
500 if (roleInfo == null)
501 {
502 roleInfo = new RoleInfo();
503 mappings.put(httpMethod,roleInfo);
504 if (allMethodsRoleInfo != null)
505 {
506 roleInfo.combine(allMethodsRoleInfo);
507 }
508 }
509 if (roleInfo.isForbidden())
510 return;
511
512
513 configureRoleInfo(roleInfo, mapping);
514
515 if (roleInfo.isForbidden())
516 {
517 if (httpMethod.equals(ALL_METHODS))
518 {
519 mappings.clear();
520 mappings.put(ALL_METHODS,roleInfo);
521 }
522 }
523 else
524 {
525
526 if (httpMethod == null)
527 {
528 for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
529 {
530 if (entry.getKey() != null)
531 {
532 RoleInfo specific = entry.getValue();
533 specific.combine(roleInfo);
534 }
535 }
536 }
537 }
538 }
539
540
541
542
543
544
545
546
547
548
549
550
551
552 protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
553 {
554 String[] omissions = mapping.getMethodOmissions();
555
556 for (String omission:omissions)
557 {
558
559 RoleInfo ri = mappings.get(omission+OMISSION_SUFFIX);
560 if (ri == null)
561 {
562
563 ri = new RoleInfo();
564 mappings.put(omission+OMISSION_SUFFIX, ri);
565 }
566
567
568 configureRoleInfo(ri, mapping);
569 }
570 }
571
572
573
574
575
576
577
578
579 protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
580 {
581 Constraint constraint = mapping.getConstraint();
582 boolean forbidden = constraint.isForbidden();
583 ri.setForbidden(forbidden);
584
585
586
587 UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
588 ri.setUserDataConstraint(userDataConstraint);
589
590
591
592 if (!ri.isForbidden())
593 {
594
595 boolean checked = mapping.getConstraint().getAuthenticate();
596 ri.setChecked(checked);
597 if (ri.isChecked())
598 {
599 if (mapping.getConstraint().isAnyRole())
600 {
601 if (_strict)
602 {
603
604 for (String role : _roles)
605 ri.addRole(role);
606 }
607 else
608
609 ri.setAnyRole(true);
610 }
611 else
612 {
613 String[] newRoles = mapping.getConstraint().getRoles();
614 for (String role : newRoles)
615 {
616 if (_strict &&!_roles.contains(role))
617 throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
618 ri.addRole(role);
619 }
620 }
621 }
622 }
623 }
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639 @Override
640 protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
641 {
642 Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
643
644 if (mappings != null)
645 {
646 String httpMethod = request.getMethod();
647 RoleInfo roleInfo = mappings.get(httpMethod);
648 if (roleInfo == null)
649 {
650
651 List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
652
653
654 RoleInfo all = mappings.get(ALL_METHODS);
655 if (all != null)
656 applicableConstraints.add(all);
657
658
659
660
661 for (Entry<String, RoleInfo> entry: mappings.entrySet())
662 {
663 if (entry.getKey() != null && entry.getKey().contains(OMISSION_SUFFIX) && !(httpMethod+OMISSION_SUFFIX).equals(entry.getKey()))
664 applicableConstraints.add(entry.getValue());
665 }
666
667 if (applicableConstraints.size() == 1)
668 roleInfo = applicableConstraints.get(0);
669 else
670 {
671 roleInfo = new RoleInfo();
672 roleInfo.setUserDataConstraint(UserDataConstraint.None);
673
674 for (RoleInfo r:applicableConstraints)
675 roleInfo.combine(r);
676 }
677
678 }
679 return roleInfo;
680 }
681
682 return null;
683 }
684
685 @Override
686 protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
687 {
688 if (roleInfo == null)
689 return true;
690
691 if (roleInfo.isForbidden())
692 return false;
693
694 UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
695 if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
696 return true;
697
698 HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
699
700 if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
701 {
702 if (request.isSecure())
703 return true;
704
705 if (httpConfig.getSecurePort() > 0)
706 {
707 String url = httpConfig.getSecureScheme() + "://" + request.getServerName() + ":" + httpConfig.getSecurePort()
708 + request.getRequestURI();
709 if (request.getQueryString() != null)
710 url += "?" + request.getQueryString();
711
712 response.setContentLength(0);
713 response.sendRedirect(url);
714 }
715 else
716 response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
717
718 request.setHandled(true);
719 return false;
720 }
721 else
722 {
723 throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
724 }
725
726 }
727
728 @Override
729 protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
730 {
731 return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
732 }
733
734
735
736
737
738
739 @Override
740 protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
741 throws IOException
742 {
743 if (constraintInfo == null)
744 {
745 return true;
746 }
747 RoleInfo roleInfo = (RoleInfo)constraintInfo;
748
749 if (!roleInfo.isChecked())
750 {
751 return true;
752 }
753
754 if (roleInfo.isAnyRole() && request.getAuthType()!=null)
755 return true;
756
757 for (String role : roleInfo.getRoles())
758 {
759 if (userIdentity.isUserInRole(role, null))
760 return true;
761 }
762 return false;
763 }
764
765
766 @Override
767 public void dump(Appendable out,String indent) throws IOException
768 {
769
770 dumpBeans(out,indent,
771 Collections.singleton(getLoginService()),
772 Collections.singleton(getIdentityService()),
773 Collections.singleton(getAuthenticator()),
774 Collections.singleton(_roles),
775 _constraintMap.entrySet());
776 }
777
778 }