View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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   * ConstraintSecurityHandler
55   * 
56   * Handler to enforce SecurityConstraints. This implementation is servlet spec
57   * 3.1 compliant and pre-computes the constraint combinations for runtime
58   * efficiency.
59   *
60   */
61  public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
62  {
63      private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
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       * @param constraint
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       * Create a security constraint
98       * 
99       * @param name
100      * @param authenticate
101      * @param roles
102      * @param dataConstraint
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      * @param name
119      * @param element
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      * @param name
130      * @param rolesAllowed
131      * @param permitOrDeny
132      * @param transport
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                 //Equivalent to <auth-constraint> with no roles
143                 constraint.setName(name+"-Deny");
144                 constraint.setAuthenticate(true);
145             }
146             else
147             {
148                 //Equivalent to no <auth-constraint>
149                 constraint.setName(name+"-Permit");
150                 constraint.setAuthenticate(false);
151             }
152         }
153         else
154         {
155             //Equivalent to <auth-constraint> with list of <security-role-name>s
156             constraint.setAuthenticate(true);
157             constraint.setRoles(rolesAllowed);
158             constraint.setName(name+"-RolesAllowed");           
159         } 
160 
161         //Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
162         constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
163         return constraint; 
164     }
165     
166     
167 
168     /* ------------------------------------------------------------ */
169     /**
170      * @param pathSpec
171      * @param constraintMappings
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     /** Take out of the constraint mappings those that match the 
192      * given path.
193      * 
194      * @param pathSpec
195      * @param constraintMappings a new list minus the matching constraints
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             //Remove the matching mappings by only copying in non-matching mappings
206             if (!pathSpec.equals(mapping.getPathSpec()))
207             {
208                mappings.add(mapping);
209             }
210         }
211         return mappings;
212     }
213     
214     
215     
216     /* ------------------------------------------------------------ */
217     /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
218      * 
219      * @param name
220      * @param pathSpec
221      * @param securityElement
222      * @return
223      */
224     public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
225     {
226         List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
227 
228         //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
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             //Create a mapping for the pathSpec for the default case
239             httpConstraintMapping = new ConstraintMapping();
240             httpConstraintMapping.setPathSpec(pathSpec);
241             httpConstraintMapping.setConstraint(httpConstraint); 
242             mappings.add(httpConstraintMapping);
243         }
244         
245 
246         //See Spec 13.4.1.2 p127
247         List<String> methodOmissions = new ArrayList<String>();
248         
249         //make constraint mappings for this url for each of the HttpMethodConstraintElements
250         Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
251         if (methodConstraintElements != null)
252         {
253             for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
254             {
255                 //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
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                     //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
264                     methodOmissions.add(methodConstraintElement.getMethodName());
265                 }
266                 mappings.add(mapping);
267             }
268         }
269         //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
270         //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
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      * @return Returns the constraintMappings.
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      * Process the constraints following the combining rules in Servlet 3.0 EA
300      * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
301      *
302      * @param constraintMappings
303      *            The constraintMappings to set, from which the set of known roles
304      *            is determined.
305      */
306     public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
307     {
308         setConstraintMappings(constraintMappings,null);
309     }
310 
311     /**
312      * Process the constraints following the combining rules in Servlet 3.0 EA
313      * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
314      *
315      * @param constraintMappings
316      *            The constraintMappings to set as array, from which the set of known roles
317      *            is determined.  Needed to retain API compatibility for 7.x
318      */
319     public void setConstraintMappings( ConstraintMapping[] constraintMappings )
320     {
321         setConstraintMappings( Arrays.asList(constraintMappings), null);
322     }
323 
324     /* ------------------------------------------------------------ */
325     /**
326      * Process the constraints following the combining rules in Servlet 3.0 EA
327      * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
328      *
329      * @param constraintMappings
330      *            The constraintMappings to set.
331      * @param roles The known roles (or null to determine them from the mappings)
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      * Set the known roles.
367      * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
368      * {@link #setConstraintMappings(List, Set)}.
369      * @param roles The known roles (or null to determine them from the mappings)
370      */
371     public void setRoles(Set<String> roles)
372     {
373         _roles.clear();
374         _roles.addAll(roles);
375     }
376 
377 
378 
379     /* ------------------------------------------------------------ */
380     /**
381      * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
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             //allow for lazy role naming: if a role is named in a security constraint, try and
390             //add it to the list of declared roles (ie as if it was declared with a security-role
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      * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
408      */
409     @Override
410     public void addRole(String role)
411     {
412         //add to list of declared roles
413         boolean modified = _roles.add(role);
414         if (isStarted() && modified)
415         {
416             // Add the new role to currently defined any role role infos
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      * @see org.eclipse.jetty.security.SecurityHandler#doStart()
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         //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
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      * Create and combine the constraint with the existing processed
463      * constraints.
464      * 
465      * @param mapping
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         //add in info from the constraint
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             //combine with any entry that covers all methods
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     /** Constraints that name method omissions are dealt with differently.
531      * We create an entry in the mappings with key "&lt;method&gt;.omission". This entry
532      * is only ever combined with other omissions for the same method to produce a
533      * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
534      *  a given Request (in prepareConstraintInfo()), we consult 3 types of entries in 
535      * the mappings: an entry that names the method of the Request specifically, an
536      * entry that names constraints that apply to all methods, entries of the form
537      * &lt;method&gt;.omission, where the method of the Request is not named in the omission.
538      * @param mapping
539      * @param mappings
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      * Initialize or update the RoleInfo from the constraint
561      * @param ri
562      * @param mapping
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         //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
571         //which we need in order to do combining of omissions in prepareConstraintInfo
572         UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
573         ri.setUserDataConstraint(userDataConstraint);
574 
575         //if forbidden, no point setting up roles
576         if (!ri.isForbidden())
577         {
578             //add in the roles
579             boolean checked = mapping.getConstraint().getAuthenticate();
580             ri.setChecked(checked);
581 
582             if (ri.isChecked())
583             {
584                 if (mapping.getConstraint().isAnyRole())
585                 {
586                     // * means matches any defined role
587                     for (String role : _roles)
588                         ri.addRole(role);
589                     ri.setAnyRole(true);
590                 }
591                 else if (mapping.getConstraint().isAnyAuth())
592                 {
593                     //being authenticated is sufficient, not necessary to check roles
594                     ri.setAnyAuth(true);
595                 }
596                 else
597                 {   
598                     //user must be in one of the named roles
599                     String[] newRoles = mapping.getConstraint().getRoles();
600                      for (String role : newRoles)
601                      {
602                          //check role has been defined
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      * Find constraints that apply to the given path.
616      * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
617      * represents a merged set of user data constraints, roles etc -:
618      * <ol>
619      * <li>A mapping of an exact method name </li>
620      * <li>A mapping with key * that matches every method name</li>
621      * <li>Mappings with keys of the form "&lt;method&gt;.&lt;method&gt;.&lt;method&gt;.omission" that indicates it will match every method name EXCEPT those given</li>
622      * </ol>
623      * 
624      * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
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                 //No specific http-method names matched
638                 List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
639 
640                 //Get info for constraint that matches all methods if it exists
641                 RoleInfo all = mappings.get(ALL_METHODS);
642                 if (all != null)
643                     applicableConstraints.add(all);
644           
645                 
646                 //Get info for constraints that name method omissions where target method name is not omitted
647                 //(ie matches because target method is not omitted, hence considered covered by the constraint)
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      * @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
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         //handle ** role constraint
750         if (roleInfo.isAnyAuth() &&  request.getUserPrincipal() != null)
751         {
752             return true;
753         }
754         
755         //check if user is any of the allowed roles
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         //handle * role constraint
767         if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
768         {
769             return true;
770         }
771 
772         //normal role check
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         // TODO these should all be beans
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      * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
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      * Servlet spec 3.1 pg. 147.
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      * Servlet spec 3.1 pg. 147.
833      * The container must check all the combined security constraint
834      * information and log any methods that are not protected and the
835      * urls at which they are not protected
836      * 
837      * @return list of paths for which there are uncovered methods
838      */
839     public Set<String> getPathsWithUncoveredHttpMethods ()
840     {
841         //if automatically denying uncovered methods, there are no uncovered methods
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             //Each key is either:
851             // : an exact method name
852             // : * which means that the constraint applies to every method
853             // : a name of the form <method>.<method>.<method>.omission, which means it applies to every method EXCEPT those named
854             if (methodMappings.get(ALL_METHODS) != null)
855                 continue; //can't be any uncovered methods for this url path
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                     //an exact method name
873                     if (!hasOmissions)
874                         //a http-method does not have http-method-omission to cover the other method names
875                         uncoveredPaths.add(path);
876                 }
877                 
878             }
879         }
880         return uncoveredPaths;
881     }
882     
883     /* ------------------------------------------------------------ */
884     /**
885      * Check if any http method omissions exist in the list of method
886      * to auth info mappings.
887      * 
888      * @param path
889      * @param methodMappings
890      * @return
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      * Given a string of the form &lt;method&gt;.&lt;method&gt;.omission
909      * split out the individual method names.
910      * 
911      * @param omission
912      * @return
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 }