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