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 javax.servlet.HttpConstraintElement;
35 import javax.servlet.HttpMethodConstraintElement;
36 import javax.servlet.ServletSecurityElement;
37 import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
38 import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
39
40 import org.eclipse.jetty.http.PathMap;
41 import org.eclipse.jetty.server.AbstractHttpConnection;
42 import org.eclipse.jetty.server.Connector;
43 import org.eclipse.jetty.server.Request;
44 import org.eclipse.jetty.server.Response;
45 import org.eclipse.jetty.server.UserIdentity;
46 import org.eclipse.jetty.util.StringMap;
47 import org.eclipse.jetty.util.TypeUtil;
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
61 private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<ConstraintMapping>();
62 private final Set<String> _roles = new CopyOnWriteArraySet<String>();
63 private final PathMap _constraintMap = new PathMap();
64 private boolean _strict = true;
65
66
67
68
69
70
71 public static Constraint createConstraint()
72 {
73 return new Constraint();
74 }
75
76
77
78
79
80
81 public static Constraint createConstraint(Constraint constraint)
82 {
83 try
84 {
85 return (Constraint)constraint.clone();
86 }
87 catch (CloneNotSupportedException e)
88 {
89 throw new IllegalStateException (e);
90 }
91 }
92
93
94
95
96
97
98
99
100
101
102
103 public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
104 {
105 Constraint constraint = createConstraint();
106 if (name != null)
107 constraint.setName(name);
108 constraint.setAuthenticate(authenticate);
109 constraint.setRoles(roles);
110 constraint.setDataConstraint(dataConstraint);
111 return constraint;
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 public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
136 {
137 Constraint constraint = createConstraint();
138
139 if (rolesAllowed == null || rolesAllowed.length==0)
140 {
141 if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
142 {
143
144 constraint.setName(name+"-Deny");
145 constraint.setAuthenticate(true);
146 }
147 else
148 {
149
150 constraint.setName(name+"-Permit");
151 constraint.setAuthenticate(false);
152 }
153 }
154 else
155 {
156
157 constraint.setAuthenticate(true);
158 constraint.setRoles(rolesAllowed);
159 constraint.setName(name+"-RolesAllowed");
160 }
161
162
163 constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
164 return constraint;
165 }
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
200 public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
201 {
202 if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
203 return Collections.emptyList();
204
205 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
206 for (ConstraintMapping mapping:constraintMappings)
207 {
208
209 if (!pathSpec.equals(mapping.getPathSpec()))
210 {
211 mappings.add(mapping);
212 }
213 }
214 return mappings;
215 }
216
217
218
219
220
221
222
223
224
225
226
227 public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
228 {
229 List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
230
231
232 Constraint constraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
233
234
235 ConstraintMapping defaultMapping = new ConstraintMapping();
236 defaultMapping.setPathSpec(pathSpec);
237 defaultMapping.setConstraint(constraint);
238 mappings.add(defaultMapping);
239
240
241
242 List<String> methodOmissions = new ArrayList<String>();
243
244
245 Collection<HttpMethodConstraintElement> methodConstraints = securityElement.getHttpMethodConstraints();
246 if (methodConstraints != null)
247 {
248 for (HttpMethodConstraintElement methodConstraint:methodConstraints)
249 {
250
251 Constraint mconstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraint);
252 ConstraintMapping mapping = new ConstraintMapping();
253 mapping.setConstraint(mconstraint);
254 mapping.setPathSpec(pathSpec);
255 if (methodConstraint.getMethodName() != null)
256 {
257 mapping.setMethod(methodConstraint.getMethodName());
258
259 methodOmissions.add(methodConstraint.getMethodName());
260 }
261 mappings.add(mapping);
262 }
263 }
264
265 if (methodOmissions.size() > 0)
266 defaultMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
267
268 return mappings;
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 public List<ConstraintMapping> getConstraintMappings()
307 {
308 return _constraintMappings;
309 }
310
311
312 public Set<String> getRoles()
313 {
314 return _roles;
315 }
316
317
318
319
320
321
322
323
324
325
326 public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
327 {
328 setConstraintMappings(constraintMappings,null);
329 }
330
331
332
333
334
335
336
337
338
339 public void setConstraintMappings( ConstraintMapping[] constraintMappings )
340 {
341 setConstraintMappings( Arrays.asList(constraintMappings), null);
342 }
343
344
345
346
347
348
349
350
351
352
353 public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
354 {
355 _constraintMappings.clear();
356 _constraintMappings.addAll(constraintMappings);
357
358 if (roles==null)
359 {
360 roles = new HashSet<String>();
361 for (ConstraintMapping cm : constraintMappings)
362 {
363 String[] cmr = cm.getConstraint().getRoles();
364 if (cmr!=null)
365 {
366 for (String r : cmr)
367 if (!"*".equals(r))
368 roles.add(r);
369 }
370 }
371 }
372 setRoles(roles);
373
374 if (isStarted())
375 {
376 for (ConstraintMapping mapping : _constraintMappings)
377 {
378 processConstraintMapping(mapping);
379 }
380 }
381 }
382
383
384
385
386
387
388
389
390
391 public void setRoles(Set<String> roles)
392 {
393 _roles.clear();
394 _roles.addAll(roles);
395 }
396
397
398
399
400
401
402
403 public void addConstraintMapping(ConstraintMapping mapping)
404 {
405 _constraintMappings.add(mapping);
406 if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
407 for (String role : mapping.getConstraint().getRoles())
408 addRole(role);
409
410 if (isStarted())
411 {
412 processConstraintMapping(mapping);
413 }
414 }
415
416
417
418
419
420 public void addRole(String role)
421 {
422 boolean modified = _roles.add(role);
423 if (isStarted() && modified && _strict)
424 {
425
426 for (Map<String,RoleInfo> map : (Collection<Map<String,RoleInfo>>)_constraintMap.values())
427 {
428 for (RoleInfo info : map.values())
429 {
430 if (info.isAnyRole())
431 info.addRole(role);
432 }
433 }
434 }
435 }
436
437
438
439
440
441 @Override
442 protected void doStart() throws Exception
443 {
444 _constraintMap.clear();
445 if (_constraintMappings!=null)
446 {
447 for (ConstraintMapping mapping : _constraintMappings)
448 {
449 processConstraintMapping(mapping);
450 }
451 }
452 super.doStart();
453 }
454
455
456
457 @Override
458 protected void doStop() throws Exception
459 {
460 _constraintMap.clear();
461 _constraintMappings.clear();
462 _roles.clear();
463 super.doStop();
464 }
465
466
467
468
469
470
471
472
473
474 protected void processConstraintMapping(ConstraintMapping mapping)
475 {
476 Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.get(mapping.getPathSpec());
477 if (mappings == null)
478 {
479 mappings = new StringMap();
480 _constraintMap.put(mapping.getPathSpec(),mappings);
481 }
482 RoleInfo allMethodsRoleInfo = mappings.get(null);
483 if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
484 return;
485
486 if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
487 {
488
489 processConstraintMappingWithMethodOmissions(mapping, mappings);
490 return;
491 }
492
493 String httpMethod = mapping.getMethod();
494 RoleInfo roleInfo = mappings.get(httpMethod);
495 if (roleInfo == null)
496 {
497 roleInfo = new RoleInfo();
498 mappings.put(httpMethod,roleInfo);
499 if (allMethodsRoleInfo != null)
500 {
501 roleInfo.combine(allMethodsRoleInfo);
502 }
503 }
504 if (roleInfo.isForbidden())
505 return;
506
507
508 configureRoleInfo(roleInfo, mapping);
509
510 if (roleInfo.isForbidden())
511 {
512 if (httpMethod == null)
513 {
514 mappings.clear();
515 mappings.put(null,roleInfo);
516 }
517 }
518 else
519 {
520
521 if (httpMethod == null)
522 {
523 for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
524 {
525 if (entry.getKey() != null)
526 {
527 RoleInfo specific = entry.getValue();
528 specific.combine(roleInfo);
529 }
530 }
531 }
532 }
533 }
534
535
536
537
538
539
540
541
542
543
544
545
546
547 protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
548 {
549 String[] omissions = mapping.getMethodOmissions();
550
551 for (String omission:omissions)
552 {
553
554 RoleInfo ri = mappings.get(omission+OMISSION_SUFFIX);
555 if (ri == null)
556 {
557
558 ri = new RoleInfo();
559 mappings.put(omission+OMISSION_SUFFIX, ri);
560 }
561
562
563 configureRoleInfo(ri, mapping);
564 }
565 }
566
567
568
569
570
571
572
573
574 protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
575 {
576 Constraint constraint = mapping.getConstraint();
577 boolean forbidden = constraint.isForbidden();
578 ri.setForbidden(forbidden);
579
580
581
582 UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
583 ri.setUserDataConstraint(userDataConstraint);
584
585
586
587 if (!ri.isForbidden())
588 {
589
590 boolean checked = mapping.getConstraint().getAuthenticate();
591 ri.setChecked(checked);
592 if (ri.isChecked())
593 {
594 if (mapping.getConstraint().isAnyRole())
595 {
596 if (_strict)
597 {
598
599 for (String role : _roles)
600 ri.addRole(role);
601 }
602 else
603
604 ri.setAnyRole(true);
605 }
606 else
607 {
608 String[] newRoles = mapping.getConstraint().getRoles();
609 for (String role : newRoles)
610 {
611 if (_strict &&!_roles.contains(role))
612 throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
613 ri.addRole(role);
614 }
615 }
616 }
617 }
618 }
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634 protected Object prepareConstraintInfo(String pathInContext, Request request)
635 {
636 Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
637
638 if (mappings != null)
639 {
640 String httpMethod = request.getMethod();
641 RoleInfo roleInfo = mappings.get(httpMethod);
642 if (roleInfo == null)
643 {
644
645 List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
646
647
648 RoleInfo all = mappings.get(null);
649 if (all != null)
650 applicableConstraints.add(all);
651
652
653
654
655 for (Entry<String, RoleInfo> entry: mappings.entrySet())
656 {
657 if (entry.getKey() != null && entry.getKey().contains(OMISSION_SUFFIX) && !(httpMethod+OMISSION_SUFFIX).equals(entry.getKey()))
658 applicableConstraints.add(entry.getValue());
659 }
660
661 if (applicableConstraints.size() == 1)
662 roleInfo = applicableConstraints.get(0);
663 else
664 {
665 roleInfo = new RoleInfo();
666 roleInfo.setUserDataConstraint(UserDataConstraint.None);
667
668 for (RoleInfo r:applicableConstraints)
669 roleInfo.combine(r);
670 }
671
672 }
673 return roleInfo;
674 }
675 return null;
676 }
677
678
679
680
681
682
683 protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException
684 {
685 if (constraintInfo == null)
686 return true;
687
688 RoleInfo roleInfo = (RoleInfo)constraintInfo;
689 if (roleInfo.isForbidden())
690 return false;
691
692
693 UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
694 if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
695 {
696 return true;
697 }
698 AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
699 Connector connector = connection.getConnector();
700
701 if (dataConstraint == UserDataConstraint.Integral)
702 {
703 if (connector.isIntegral(request))
704 return true;
705 if (connector.getIntegralPort() > 0)
706 {
707 String url = connector.getIntegralScheme() + "://" + request.getServerName() + ":" + connector.getIntegralPort() + request.getRequestURI();
708 if (request.getQueryString() != null)
709 url += "?" + request.getQueryString();
710 response.setContentLength(0);
711 response.sendRedirect(url);
712 }
713 else
714 response.sendError(Response.SC_FORBIDDEN,"!Integral");
715
716 request.setHandled(true);
717 return false;
718 }
719 else if (dataConstraint == UserDataConstraint.Confidential)
720 {
721 if (connector.isConfidential(request))
722 return true;
723
724 if (connector.getConfidentialPort() > 0)
725 {
726 String url = connector.getConfidentialScheme() + "://" + request.getServerName() + ":" + connector.getConfidentialPort()
727 + request.getRequestURI();
728 if (request.getQueryString() != null)
729 url += "?" + request.getQueryString();
730
731 response.setContentLength(0);
732 response.sendRedirect(url);
733 }
734 else
735 response.sendError(Response.SC_FORBIDDEN,"!Confidential");
736
737 request.setHandled(true);
738 return false;
739 }
740 else
741 {
742 throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
743 }
744
745 }
746
747
748
749
750
751 protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
752 {
753 if (constraintInfo == null)
754 {
755 return false;
756 }
757 return ((RoleInfo)constraintInfo).isChecked();
758 }
759
760
761
762
763
764
765 @Override
766 protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
767 throws IOException
768 {
769 if (constraintInfo == null)
770 {
771 return true;
772 }
773 RoleInfo roleInfo = (RoleInfo)constraintInfo;
774
775 if (!roleInfo.isChecked())
776 {
777 return true;
778 }
779
780 if (roleInfo.isAnyRole() && request.getAuthType()!=null)
781 return true;
782
783 for (String role : roleInfo.getRoles())
784 {
785 if (userIdentity.isUserInRole(role, null))
786 return true;
787 }
788 return false;
789 }
790
791
792 @Override
793 public void dump(Appendable out,String indent) throws IOException
794 {
795 dumpThis(out);
796 dump(out,indent,
797 Collections.singleton(getLoginService()),
798 Collections.singleton(getIdentityService()),
799 Collections.singleton(getAuthenticator()),
800 Collections.singleton(_roles),
801 _constraintMap.entrySet(),
802 getBeans(),
803 TypeUtil.asList(getHandlers()));
804 }
805
806 }