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.server.handler.ContextHandler;
49 import org.eclipse.jetty.util.URIUtil;
50 import org.eclipse.jetty.util.log.Log;
51 import org.eclipse.jetty.util.log.Logger;
52 import org.eclipse.jetty.util.security.Constraint;
53
54
55
56
57
58
59
60
61
62
63 public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
64 {
65 private static final Logger LOG = Log.getLogger(SecurityHandler.class);
66
67 private static final String OMISSION_SUFFIX = ".omission";
68 private static final String ALL_METHODS = "*";
69 private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
70 private final Set<String> _roles = new CopyOnWriteArraySet<>();
71 private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
72 private boolean _denyUncoveredMethods = false;
73
74
75
76 public static Constraint createConstraint()
77 {
78 return new Constraint();
79 }
80
81
82
83
84
85 public static Constraint createConstraint(Constraint constraint)
86 {
87 try
88 {
89 return (Constraint)constraint.clone();
90 }
91 catch (CloneNotSupportedException e)
92 {
93 throw new IllegalStateException (e);
94 }
95 }
96
97
98
99
100
101
102
103
104
105
106 public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
107 {
108 Constraint constraint = createConstraint();
109 if (name != null)
110 constraint.setName(name);
111 constraint.setAuthenticate(authenticate);
112 constraint.setRoles(roles);
113 constraint.setDataConstraint(dataConstraint);
114 return constraint;
115 }
116
117
118
119
120
121
122
123 public static Constraint createConstraint (String name, HttpConstraintElement element)
124 {
125 return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());
126 }
127
128
129
130
131
132
133
134
135
136 public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
137 {
138 Constraint constraint = createConstraint();
139
140 if (rolesAllowed == null || rolesAllowed.length==0)
141 {
142 if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
143 {
144
145 constraint.setName(name+"-Deny");
146 constraint.setAuthenticate(true);
147 }
148 else
149 {
150
151 constraint.setName(name+"-Permit");
152 constraint.setAuthenticate(false);
153 }
154 }
155 else
156 {
157
158 constraint.setAuthenticate(true);
159 constraint.setRoles(rolesAllowed);
160 constraint.setName(name+"-RolesAllowed");
161 }
162
163
164 constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
165 return constraint;
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 public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
200 {
201 if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
202 return Collections.emptyList();
203
204 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
205 for (ConstraintMapping mapping:constraintMappings)
206 {
207
208 if (!pathSpec.equals(mapping.getPathSpec()))
209 {
210 mappings.add(mapping);
211 }
212 }
213 return mappings;
214 }
215
216
217
218
219
220
221
222
223
224
225
226 public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
227 {
228 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
229
230
231 Constraint httpConstraint = null;
232 ConstraintMapping httpConstraintMapping = null;
233
234 if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
235 securityElement.getRolesAllowed().length != 0 ||
236 securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
237 {
238 httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
239
240
241 httpConstraintMapping = new ConstraintMapping();
242 httpConstraintMapping.setPathSpec(pathSpec);
243 httpConstraintMapping.setConstraint(httpConstraint);
244 mappings.add(httpConstraintMapping);
245 }
246
247
248
249 List<String> methodOmissions = new ArrayList<String>();
250
251
252 Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
253 if (methodConstraintElements != null)
254 {
255 for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
256 {
257
258 Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
259 ConstraintMapping mapping = new ConstraintMapping();
260 mapping.setConstraint(methodConstraint);
261 mapping.setPathSpec(pathSpec);
262 if (methodConstraintElement.getMethodName() != null)
263 {
264 mapping.setMethod(methodConstraintElement.getMethodName());
265
266 methodOmissions.add(methodConstraintElement.getMethodName());
267 }
268 mappings.add(mapping);
269 }
270 }
271
272
273 if (methodOmissions.size() > 0 && httpConstraintMapping != null)
274 httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
275
276 return mappings;
277 }
278
279
280
281
282
283
284
285
286 @Override
287 public List<ConstraintMapping> getConstraintMappings()
288 {
289 return _constraintMappings;
290 }
291
292
293 @Override
294 public Set<String> getRoles()
295 {
296 return _roles;
297 }
298
299
300
301
302
303
304
305
306
307
308 public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
309 {
310 setConstraintMappings(constraintMappings,null);
311 }
312
313
314
315
316
317
318
319
320
321 public void setConstraintMappings( ConstraintMapping[] constraintMappings )
322 {
323 setConstraintMappings( Arrays.asList(constraintMappings), null);
324 }
325
326
327
328
329
330
331
332
333
334
335 @Override
336 public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
337 {
338 _constraintMappings.clear();
339 _constraintMappings.addAll(constraintMappings);
340
341 if (roles==null)
342 {
343 roles = new HashSet<>();
344 for (ConstraintMapping cm : constraintMappings)
345 {
346 String[] cmr = cm.getConstraint().getRoles();
347 if (cmr!=null)
348 {
349 for (String r : cmr)
350 if (!ALL_METHODS.equals(r))
351 roles.add(r);
352 }
353 }
354 }
355 setRoles(roles);
356
357 if (isStarted())
358 {
359 for (ConstraintMapping mapping : _constraintMappings)
360 {
361 processConstraintMapping(mapping);
362 }
363 }
364 }
365
366
367
368
369
370
371
372
373 public void setRoles(Set<String> roles)
374 {
375 _roles.clear();
376 _roles.addAll(roles);
377 }
378
379
380
381
382
383
384
385 @Override
386 public void addConstraintMapping(ConstraintMapping mapping)
387 {
388 _constraintMappings.add(mapping);
389 if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
390 {
391
392
393 for (String role : mapping.getConstraint().getRoles())
394 {
395 if ("*".equals(role) || "**".equals(role))
396 continue;
397 addRole(role);
398 }
399 }
400
401 if (isStarted())
402 {
403 processConstraintMapping(mapping);
404 }
405 }
406
407
408
409
410
411 @Override
412 public void addRole(String role)
413 {
414
415 boolean modified = _roles.add(role);
416 if (isStarted() && modified)
417 {
418
419 for (Map<String,RoleInfo> map : _constraintMap.values())
420 {
421 for (RoleInfo info : map.values())
422 {
423 if (info.isAnyRole())
424 info.addRole(role);
425 }
426 }
427 }
428 }
429
430
431
432
433
434 @Override
435 protected void doStart() throws Exception
436 {
437 _constraintMap.clear();
438 if (_constraintMappings!=null)
439 {
440 for (ConstraintMapping mapping : _constraintMappings)
441 {
442 processConstraintMapping(mapping);
443 }
444 }
445
446
447 checkPathsWithUncoveredHttpMethods();
448
449 super.doStart();
450 }
451
452
453
454 @Override
455 protected void doStop() throws Exception
456 {
457 super.doStop();
458 _constraintMap.clear();
459 }
460
461
462
463
464
465
466
467
468
469 protected void processConstraintMapping(ConstraintMapping mapping)
470 {
471 Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
472 if (mappings == null)
473 {
474 mappings = new HashMap<String,RoleInfo>();
475 _constraintMap.put(mapping.getPathSpec(),mappings);
476 }
477 RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
478 if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
479 return;
480
481 if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
482 {
483 processConstraintMappingWithMethodOmissions(mapping, mappings);
484 return;
485 }
486
487 String httpMethod = mapping.getMethod();
488 if (httpMethod==null)
489 httpMethod=ALL_METHODS;
490 RoleInfo roleInfo = mappings.get(httpMethod);
491 if (roleInfo == null)
492 {
493 roleInfo = new RoleInfo();
494 mappings.put(httpMethod,roleInfo);
495 if (allMethodsRoleInfo != null)
496 {
497 roleInfo.combine(allMethodsRoleInfo);
498 }
499 }
500 if (roleInfo.isForbidden())
501 return;
502
503
504 configureRoleInfo(roleInfo, mapping);
505
506 if (roleInfo.isForbidden())
507 {
508 if (httpMethod.equals(ALL_METHODS))
509 {
510 mappings.clear();
511 mappings.put(ALL_METHODS,roleInfo);
512 }
513 }
514 }
515
516
517
518
519
520
521
522
523
524
525
526
527
528 protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
529 {
530 String[] omissions = mapping.getMethodOmissions();
531 StringBuilder sb = new StringBuilder();
532 for (int i=0; i<omissions.length; i++)
533 {
534 if (i > 0)
535 sb.append(".");
536 sb.append(omissions[i]);
537 }
538 sb.append(OMISSION_SUFFIX);
539 RoleInfo ri = new RoleInfo();
540 mappings.put(sb.toString(), ri);
541 configureRoleInfo(ri, mapping);
542 }
543
544
545
546
547
548
549
550
551 protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
552 {
553 Constraint constraint = mapping.getConstraint();
554 boolean forbidden = constraint.isForbidden();
555 ri.setForbidden(forbidden);
556
557
558
559 UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
560 ri.setUserDataConstraint(userDataConstraint);
561
562
563 if (!ri.isForbidden())
564 {
565
566 boolean checked = mapping.getConstraint().getAuthenticate();
567 ri.setChecked(checked);
568
569 if (ri.isChecked())
570 {
571 if (mapping.getConstraint().isAnyRole())
572 {
573
574 for (String role : _roles)
575 ri.addRole(role);
576 ri.setAnyRole(true);
577 }
578 else if (mapping.getConstraint().isAnyAuth())
579 {
580
581 ri.setAnyAuth(true);
582 }
583 else
584 {
585
586 String[] newRoles = mapping.getConstraint().getRoles();
587 for (String role : newRoles)
588 {
589
590 if (!_roles.contains(role))
591 throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
592 ri.addRole(role);
593 }
594 }
595 }
596 }
597 }
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613 @Override
614 protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
615 {
616 Map<String, RoleInfo> mappings = _constraintMap.match(pathInContext);
617
618 if (mappings != null)
619 {
620 String httpMethod = request.getMethod();
621 RoleInfo roleInfo = mappings.get(httpMethod);
622 if (roleInfo == null)
623 {
624
625 List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
626
627
628 RoleInfo all = mappings.get(ALL_METHODS);
629 if (all != null)
630 applicableConstraints.add(all);
631
632
633
634
635 for (Entry<String, RoleInfo> entry: mappings.entrySet())
636 {
637 if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
638 applicableConstraints.add(entry.getValue());
639 }
640
641 if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
642 {
643 roleInfo = new RoleInfo();
644 roleInfo.setForbidden(true);
645 }
646 else if (applicableConstraints.size() == 1)
647 roleInfo = applicableConstraints.get(0);
648 else
649 {
650 roleInfo = new RoleInfo();
651 roleInfo.setUserDataConstraint(UserDataConstraint.None);
652
653 for (RoleInfo r:applicableConstraints)
654 roleInfo.combine(r);
655 }
656
657 }
658
659 return roleInfo;
660 }
661
662 return null;
663 }
664
665 @Override
666 protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
667 {
668 if (roleInfo == null)
669 return true;
670
671 if (roleInfo.isForbidden())
672 return false;
673
674 UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
675 if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
676 return true;
677
678 HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
679
680 if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
681 {
682 if (request.isSecure())
683 return true;
684
685 if (httpConfig.getSecurePort() > 0)
686 {
687 String scheme = httpConfig.getSecureScheme();
688 int port = httpConfig.getSecurePort();
689
690 String url = URIUtil.newURI(scheme, request.getServerName(), port,request.getRequestURI(),request.getQueryString());
691 response.setContentLength(0);
692 response.sendRedirect(url);
693 }
694 else
695 response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
696
697 request.setHandled(true);
698 return false;
699 }
700 else
701 {
702 throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
703 }
704
705 }
706
707 @Override
708 protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
709 {
710 return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
711 }
712
713
714
715
716
717
718 @Override
719 protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
720 throws IOException
721 {
722 if (constraintInfo == null)
723 {
724 return true;
725 }
726 RoleInfo roleInfo = (RoleInfo)constraintInfo;
727
728 if (!roleInfo.isChecked())
729 {
730 return true;
731 }
732
733
734 if (roleInfo.isAnyAuth() && request.getUserPrincipal() != null)
735 {
736 return true;
737 }
738
739
740 boolean isUserInRole = false;
741 for (String role : roleInfo.getRoles())
742 {
743 if (userIdentity.isUserInRole(role, null))
744 {
745 isUserInRole = true;
746 break;
747 }
748 }
749
750
751 if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
752 {
753 return true;
754 }
755
756
757 if (isUserInRole)
758 {
759 return true;
760 }
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
779
780
781
782 @Override
783 public void setDenyUncoveredHttpMethods(boolean deny)
784 {
785 _denyUncoveredMethods = deny;
786 }
787
788
789 @Override
790 public boolean isDenyUncoveredHttpMethods()
791 {
792 return _denyUncoveredMethods;
793 }
794
795
796
797
798
799
800 @Override
801 public boolean checkPathsWithUncoveredHttpMethods()
802 {
803 Set<String> paths = getPathsWithUncoveredHttpMethods();
804 if (paths != null && !paths.isEmpty())
805 {
806 for (String p:paths)
807 LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
808 if (LOG.isDebugEnabled())
809 LOG.debug(new Throwable());
810 return true;
811 }
812 return false;
813 }
814
815
816
817
818
819
820
821
822
823
824
825 public Set<String> getPathsWithUncoveredHttpMethods ()
826 {
827
828 if (_denyUncoveredMethods)
829 return Collections.emptySet();
830
831 Set<String> uncoveredPaths = new HashSet<String>();
832
833 for (String path:_constraintMap.keySet())
834 {
835 Map<String, RoleInfo> methodMappings = _constraintMap.get(path);
836
837
838
839
840 if (methodMappings.get(ALL_METHODS) != null)
841 continue;
842
843 boolean hasOmissions = omissionsExist(path, methodMappings);
844
845 for (String method:methodMappings.keySet())
846 {
847 if (method.endsWith(OMISSION_SUFFIX))
848 {
849 Set<String> omittedMethods = getOmittedMethods(method);
850 for (String m:omittedMethods)
851 {
852 if (!methodMappings.containsKey(m))
853 uncoveredPaths.add(path);
854 }
855 }
856 else
857 {
858
859 if (!hasOmissions)
860
861 uncoveredPaths.add(path);
862 }
863
864 }
865 }
866 return uncoveredPaths;
867 }
868
869
870
871
872
873
874
875
876
877
878 protected boolean omissionsExist (String path, Map<String, RoleInfo> methodMappings)
879 {
880 if (methodMappings == null)
881 return false;
882 boolean hasOmissions = false;
883 for (String m:methodMappings.keySet())
884 {
885 if (m.endsWith(OMISSION_SUFFIX))
886 hasOmissions = true;
887 }
888 return hasOmissions;
889 }
890
891
892
893
894
895
896
897
898
899
900 protected Set<String> getOmittedMethods (String omission)
901 {
902 if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
903 return Collections.emptySet();
904
905 String[] strings = omission.split("\\.");
906 Set<String> methods = new HashSet<String>();
907 for (int i=0;i<strings.length-1;i++)
908 methods.add(strings[i]);
909 return methods;
910 }
911 }