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