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.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Set;
31 import java.util.concurrent.CopyOnWriteArrayList;
32 import java.util.concurrent.CopyOnWriteArraySet;
33
34 import org.eclipse.jetty.http.HttpSchemes;
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.PathMap;
42 import org.eclipse.jetty.server.AbstractHttpConnection;
43 import org.eclipse.jetty.server.Connector;
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.util.StringMap;
48 import org.eclipse.jetty.util.TypeUtil;
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
62 private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<ConstraintMapping>();
63 private final Set<String> _roles = new CopyOnWriteArraySet<String>();
64 private final PathMap _constraintMap = new PathMap();
65 private boolean _strict = true;
66
67
68
69
70
71
72 public static Constraint createConstraint()
73 {
74 return new Constraint();
75 }
76
77
78
79
80
81
82 public static Constraint createConstraint(Constraint constraint)
83 {
84 try
85 {
86 return (Constraint)constraint.clone();
87 }
88 catch (CloneNotSupportedException e)
89 {
90 throw new IllegalStateException (e);
91 }
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
122 public static Constraint createConstraint (String name, HttpConstraintElement element)
123 {
124 return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());
125 }
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
176 public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
177 {
178 if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
179 return Collections.emptyList();
180
181 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
182 for (ConstraintMapping mapping:constraintMappings)
183 {
184 if (pathSpec.equals(mapping.getPathSpec()))
185 {
186 mappings.add(mapping);
187 }
188 }
189 return mappings;
190 }
191
192
193
194
195
196
197
198
199
200
201 public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
202 {
203 if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
204 return Collections.emptyList();
205
206 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
207 for (ConstraintMapping mapping:constraintMappings)
208 {
209
210 if (!pathSpec.equals(mapping.getPathSpec()))
211 {
212 mappings.add(mapping);
213 }
214 }
215 return mappings;
216 }
217
218
219
220
221
222
223
224
225
226
227
228 public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
229 {
230 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
231
232
233 Constraint constraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
234
235
236 ConstraintMapping defaultMapping = new ConstraintMapping();
237 defaultMapping.setPathSpec(pathSpec);
238 defaultMapping.setConstraint(constraint);
239 mappings.add(defaultMapping);
240
241
242
243 List<String> methodOmissions = new ArrayList<String>();
244
245
246 Collection<HttpMethodConstraintElement> methodConstraints = securityElement.getHttpMethodConstraints();
247 if (methodConstraints != null)
248 {
249 for (HttpMethodConstraintElement methodConstraint:methodConstraints)
250 {
251
252 Constraint mconstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraint);
253 ConstraintMapping mapping = new ConstraintMapping();
254 mapping.setConstraint(mconstraint);
255 mapping.setPathSpec(pathSpec);
256 if (methodConstraint.getMethodName() != null)
257 {
258 mapping.setMethod(methodConstraint.getMethodName());
259
260 methodOmissions.add(methodConstraint.getMethodName());
261 }
262 mappings.add(mapping);
263 }
264 }
265
266 if (methodOmissions.size() > 0)
267 defaultMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
268
269 return mappings;
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 public List<ConstraintMapping> getConstraintMappings()
308 {
309 return _constraintMappings;
310 }
311
312
313 public Set<String> getRoles()
314 {
315 return _roles;
316 }
317
318
319
320
321
322
323
324
325
326
327 public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
328 {
329 setConstraintMappings(constraintMappings,null);
330 }
331
332
333
334
335
336
337
338
339
340 public void setConstraintMappings( ConstraintMapping[] constraintMappings )
341 {
342 setConstraintMappings( Arrays.asList(constraintMappings), null);
343 }
344
345
346
347
348
349
350
351
352
353
354 public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
355 {
356 _constraintMappings.clear();
357 _constraintMappings.addAll(constraintMappings);
358
359 if (roles==null)
360 {
361 roles = new HashSet<String>();
362 for (ConstraintMapping cm : constraintMappings)
363 {
364 String[] cmr = cm.getConstraint().getRoles();
365 if (cmr!=null)
366 {
367 for (String r : cmr)
368 if (!"*".equals(r))
369 roles.add(r);
370 }
371 }
372 }
373 setRoles(roles);
374
375 if (isStarted())
376 {
377 for (ConstraintMapping mapping : _constraintMappings)
378 {
379 processConstraintMapping(mapping);
380 }
381 }
382 }
383
384
385
386
387
388
389
390
391
392 public void setRoles(Set<String> roles)
393 {
394 _roles.clear();
395 _roles.addAll(roles);
396 }
397
398
399
400
401
402
403
404 public void addConstraintMapping(ConstraintMapping mapping)
405 {
406 _constraintMappings.add(mapping);
407 if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
408 for (String role : mapping.getConstraint().getRoles())
409 addRole(role);
410
411 if (isStarted())
412 {
413 processConstraintMapping(mapping);
414 }
415 }
416
417
418
419
420
421 public void addRole(String role)
422 {
423 boolean modified = _roles.add(role);
424 if (isStarted() && modified && _strict)
425 {
426
427 for (Map<String,RoleInfo> map : (Collection<Map<String,RoleInfo>>)_constraintMap.values())
428 {
429 for (RoleInfo info : map.values())
430 {
431 if (info.isAnyRole())
432 info.addRole(role);
433 }
434 }
435 }
436 }
437
438
439
440
441
442 @Override
443 protected void doStart() throws Exception
444 {
445 _constraintMap.clear();
446 if (_constraintMappings!=null)
447 {
448 for (ConstraintMapping mapping : _constraintMappings)
449 {
450 processConstraintMapping(mapping);
451 }
452 }
453 super.doStart();
454 }
455
456
457
458 @Override
459 protected void doStop() throws Exception
460 {
461 _constraintMap.clear();
462 _constraintMappings.clear();
463 _roles.clear();
464 super.doStop();
465 }
466
467
468
469
470
471
472
473
474
475 protected void processConstraintMapping(ConstraintMapping mapping)
476 {
477 Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.get(mapping.getPathSpec());
478 if (mappings == null)
479 {
480 mappings = new StringMap();
481 _constraintMap.put(mapping.getPathSpec(),mappings);
482 }
483 RoleInfo allMethodsRoleInfo = mappings.get(null);
484 if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
485 return;
486
487 if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
488 {
489
490 processConstraintMappingWithMethodOmissions(mapping, mappings);
491 return;
492 }
493
494 String httpMethod = mapping.getMethod();
495 RoleInfo roleInfo = mappings.get(httpMethod);
496 if (roleInfo == null)
497 {
498 roleInfo = new RoleInfo();
499 mappings.put(httpMethod,roleInfo);
500 if (allMethodsRoleInfo != null)
501 {
502 roleInfo.combine(allMethodsRoleInfo);
503 }
504 }
505 if (roleInfo.isForbidden())
506 return;
507
508
509 configureRoleInfo(roleInfo, mapping);
510
511 if (roleInfo.isForbidden())
512 {
513 if (httpMethod == null)
514 {
515 mappings.clear();
516 mappings.put(null,roleInfo);
517 }
518 }
519 else
520 {
521
522 if (httpMethod == null)
523 {
524 for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
525 {
526 if (entry.getKey() != null)
527 {
528 RoleInfo specific = entry.getValue();
529 specific.combine(roleInfo);
530 }
531 }
532 }
533 }
534 }
535
536
537
538
539
540
541
542
543
544
545
546
547
548 protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
549 {
550 String[] omissions = mapping.getMethodOmissions();
551
552 for (String omission:omissions)
553 {
554
555 RoleInfo ri = mappings.get(omission+OMISSION_SUFFIX);
556 if (ri == null)
557 {
558
559 ri = new RoleInfo();
560 mappings.put(omission+OMISSION_SUFFIX, ri);
561 }
562
563
564 configureRoleInfo(ri, mapping);
565 }
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 protected Object prepareConstraintInfo(String pathInContext, Request request)
636 {
637 Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
638
639 if (mappings != null)
640 {
641 String httpMethod = request.getMethod();
642 RoleInfo roleInfo = mappings.get(httpMethod);
643 if (roleInfo == null)
644 {
645
646 List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
647
648
649 RoleInfo all = mappings.get(null);
650 if (all != null)
651 applicableConstraints.add(all);
652
653
654
655
656 for (Entry<String, RoleInfo> entry: mappings.entrySet())
657 {
658 if (entry.getKey() != null && entry.getKey().contains(OMISSION_SUFFIX) && !(httpMethod+OMISSION_SUFFIX).equals(entry.getKey()))
659 applicableConstraints.add(entry.getValue());
660 }
661
662 if (applicableConstraints.size() == 1)
663 roleInfo = applicableConstraints.get(0);
664 else
665 {
666 roleInfo = new RoleInfo();
667 roleInfo.setUserDataConstraint(UserDataConstraint.None);
668
669 for (RoleInfo r:applicableConstraints)
670 roleInfo.combine(r);
671 }
672
673 }
674 return roleInfo;
675 }
676 return null;
677 }
678
679
680
681
682
683
684 protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException
685 {
686 if (constraintInfo == null)
687 return true;
688
689 RoleInfo roleInfo = (RoleInfo)constraintInfo;
690 if (roleInfo.isForbidden())
691 return false;
692
693
694 UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
695 if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
696 {
697 return true;
698 }
699 AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
700 Connector connector = connection.getConnector();
701
702 if (dataConstraint == UserDataConstraint.Integral)
703 {
704 if (connector.isIntegral(request))
705 return true;
706 if (connector.getIntegralPort() > 0)
707 {
708 String scheme=connector.getIntegralScheme();
709 int port=connector.getIntegralPort();
710 String url = (HttpSchemes.HTTPS.equalsIgnoreCase(scheme) && port==443)
711 ? "https://"+request.getServerName()+request.getRequestURI()
712 : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();
713 if (request.getQueryString() != null)
714 url += "?" + request.getQueryString();
715 response.setContentLength(0);
716 response.sendRedirect(url);
717 }
718 else
719 response.sendError(Response.SC_FORBIDDEN,"!Integral");
720
721 request.setHandled(true);
722 return false;
723 }
724 else if (dataConstraint == UserDataConstraint.Confidential)
725 {
726 if (connector.isConfidential(request))
727 return true;
728
729 if (connector.getConfidentialPort() > 0)
730 {
731 String scheme=connector.getConfidentialScheme();
732 int port=connector.getConfidentialPort();
733 String url = (HttpSchemes.HTTPS.equalsIgnoreCase(scheme) && port==443)
734 ? "https://"+request.getServerName()+request.getRequestURI()
735 : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();
736 if (request.getQueryString() != null)
737 url += "?" + request.getQueryString();
738 response.setContentLength(0);
739 response.sendRedirect(url);
740 }
741 else
742 response.sendError(Response.SC_FORBIDDEN,"!Confidential");
743
744 request.setHandled(true);
745 return false;
746 }
747 else
748 {
749 throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
750 }
751
752 }
753
754
755
756
757
758 protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
759 {
760 if (constraintInfo == null)
761 {
762 return false;
763 }
764 return ((RoleInfo)constraintInfo).isChecked();
765 }
766
767
768
769
770
771
772 @Override
773 protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
774 throws IOException
775 {
776 if (constraintInfo == null)
777 {
778 return true;
779 }
780 RoleInfo roleInfo = (RoleInfo)constraintInfo;
781
782 if (!roleInfo.isChecked())
783 {
784 return true;
785 }
786
787 if (roleInfo.isAnyRole() && request.getAuthType()!=null)
788 return true;
789
790 for (String role : roleInfo.getRoles())
791 {
792 if (userIdentity.isUserInRole(role, null))
793 return true;
794 }
795 return false;
796 }
797
798
799 @Override
800 public void dump(Appendable out,String indent) throws IOException
801 {
802 dumpThis(out);
803 dump(out,indent,
804 Collections.singleton(getLoginService()),
805 Collections.singleton(getIdentityService()),
806 Collections.singleton(getAuthenticator()),
807 Collections.singleton(_roles),
808 _constraintMap.entrySet(),
809 getBeans(),
810 TypeUtil.asList(getHandlers()));
811 }
812
813 }