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