]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / security / ConstraintSecurityHandler.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2014 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.server.handler.ContextHandler;
49 import org.eclipse.jetty.util.log.Log;
50 import org.eclipse.jetty.util.log.Logger;
51 import org.eclipse.jetty.util.security.Constraint;
52
53 /* ------------------------------------------------------------ */
54 /**
55  * ConstraintSecurityHandler
56  * 
57  * Handler to enforce SecurityConstraints. This implementation is servlet spec
58  * 3.1 compliant and pre-computes the constraint combinations for runtime
59  * efficiency.
60  *
61  */
62 public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
63 {
64     private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
65     
66     private static final String OMISSION_SUFFIX = ".omission";
67     private static final String ALL_METHODS = "*";
68     private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
69     private final Set<String> _roles = new CopyOnWriteArraySet<>();
70     private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
71     private boolean _denyUncoveredMethods = false;
72
73
74     /* ------------------------------------------------------------ */
75     public static Constraint createConstraint()
76     {
77         return new Constraint();
78     }
79     
80     /* ------------------------------------------------------------ */
81     /**
82      * @param constraint
83      */
84     public static Constraint createConstraint(Constraint constraint)
85     {
86         try
87         {
88             return (Constraint)constraint.clone();
89         }
90         catch (CloneNotSupportedException e)
91         {
92             throw new IllegalStateException (e);
93         }
94     }
95     
96     /* ------------------------------------------------------------ */
97     /**
98      * Create a security constraint
99      * 
100      * @param name
101      * @param authenticate
102      * @param roles
103      * @param dataConstraint
104      */
105     public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
106     {
107         Constraint constraint = createConstraint();
108         if (name != null)
109             constraint.setName(name);
110         constraint.setAuthenticate(authenticate);
111         constraint.setRoles(roles);
112         constraint.setDataConstraint(dataConstraint);
113         return constraint;
114     }
115     
116
117     /* ------------------------------------------------------------ */
118     /**
119      * @param name
120      * @param element
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      * @param name
131      * @param rolesAllowed
132      * @param permitOrDeny
133      * @param transport
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                 //Equivalent to <auth-constraint> with no roles
144                 constraint.setName(name+"-Deny");
145                 constraint.setAuthenticate(true);
146             }
147             else
148             {
149                 //Equivalent to no <auth-constraint>
150                 constraint.setName(name+"-Permit");
151                 constraint.setAuthenticate(false);
152             }
153         }
154         else
155         {
156             //Equivalent to <auth-constraint> with list of <security-role-name>s
157             constraint.setAuthenticate(true);
158             constraint.setRoles(rolesAllowed);
159             constraint.setName(name+"-RolesAllowed");           
160         } 
161
162         //Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
163         constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
164         return constraint; 
165     }
166     
167     
168
169     /* ------------------------------------------------------------ */
170     /**
171      * @param pathSpec
172      * @param constraintMappings
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     /** Take out of the constraint mappings those that match the 
193      * given path.
194      * 
195      * @param pathSpec
196      * @param constraintMappings a new list minus the matching constraints
197      */
198     public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
199     {
200         if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
201             return Collections.emptyList();
202         
203         List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
204         for (ConstraintMapping mapping:constraintMappings)
205         {
206             //Remove the matching mappings by only copying in non-matching mappings
207             if (!pathSpec.equals(mapping.getPathSpec()))
208             {
209                mappings.add(mapping);
210             }
211         }
212         return mappings;
213     }
214     
215     
216     
217     /* ------------------------------------------------------------ */
218     /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
219      * 
220      * @param name
221      * @param pathSpec
222      * @param securityElement
223      * @return
224      */
225     public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
226     {
227         List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
228
229         //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
230         Constraint httpConstraint = null;
231         ConstraintMapping httpConstraintMapping = null;
232         
233         if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
234             securityElement.getRolesAllowed().length != 0 ||
235             securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
236         {
237             httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
238
239             //Create a mapping for the pathSpec for the default case
240             httpConstraintMapping = new ConstraintMapping();
241             httpConstraintMapping.setPathSpec(pathSpec);
242             httpConstraintMapping.setConstraint(httpConstraint); 
243             mappings.add(httpConstraintMapping);
244         }
245         
246
247         //See Spec 13.4.1.2 p127
248         List<String> methodOmissions = new ArrayList<String>();
249         
250         //make constraint mappings for this url for each of the HttpMethodConstraintElements
251         Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
252         if (methodConstraintElements != null)
253         {
254             for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
255             {
256                 //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
257                 Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
258                 ConstraintMapping mapping = new ConstraintMapping();
259                 mapping.setConstraint(methodConstraint);
260                 mapping.setPathSpec(pathSpec);
261                 if (methodConstraintElement.getMethodName() != null)
262                 {
263                     mapping.setMethod(methodConstraintElement.getMethodName());
264                     //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
265                     methodOmissions.add(methodConstraintElement.getMethodName());
266                 }
267                 mappings.add(mapping);
268             }
269         }
270         //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
271         //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
272         if (methodOmissions.size() > 0  && httpConstraintMapping != null)
273             httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
274      
275         return mappings;
276     }
277     
278     
279
280
281     /* ------------------------------------------------------------ */
282     /**
283      * @return Returns the constraintMappings.
284      */
285     @Override
286     public List<ConstraintMapping> getConstraintMappings()
287     {
288         return _constraintMappings;
289     }
290
291     /* ------------------------------------------------------------ */
292     @Override
293     public Set<String> getRoles()
294     {
295         return _roles;
296     }
297
298     /* ------------------------------------------------------------ */
299     /**
300      * Process the constraints following the combining rules in Servlet 3.0 EA
301      * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
302      *
303      * @param constraintMappings
304      *            The constraintMappings to set, from which the set of known roles
305      *            is determined.
306      */
307     public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
308     {
309         setConstraintMappings(constraintMappings,null);
310     }
311
312     /**
313      * Process the constraints following the combining rules in Servlet 3.0 EA
314      * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
315      *
316      * @param constraintMappings
317      *            The constraintMappings to set as array, from which the set of known roles
318      *            is determined.  Needed to retain API compatibility for 7.x
319      */
320     public void setConstraintMappings( ConstraintMapping[] constraintMappings )
321     {
322         setConstraintMappings( Arrays.asList(constraintMappings), null);
323     }
324
325     /* ------------------------------------------------------------ */
326     /**
327      * Process the constraints following the combining rules in Servlet 3.0 EA
328      * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
329      *
330      * @param constraintMappings
331      *            The constraintMappings to set.
332      * @param roles The known roles (or null to determine them from the mappings)
333      */
334     @Override
335     public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
336     {
337         _constraintMappings.clear();
338         _constraintMappings.addAll(constraintMappings);
339
340         if (roles==null)
341         {
342             roles = new HashSet<>();
343             for (ConstraintMapping cm : constraintMappings)
344             {
345                 String[] cmr = cm.getConstraint().getRoles();
346                 if (cmr!=null)
347                 {
348                     for (String r : cmr)
349                         if (!ALL_METHODS.equals(r))
350                             roles.add(r);
351                 }
352             }
353         }
354         setRoles(roles);
355         
356         if (isStarted())
357         {
358             for (ConstraintMapping mapping : _constraintMappings)
359             {
360                 processConstraintMapping(mapping);
361             }
362         }
363     }
364
365     /* ------------------------------------------------------------ */
366     /**
367      * Set the known roles.
368      * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
369      * {@link #setConstraintMappings(List, Set)}.
370      * @param roles The known roles (or null to determine them from the mappings)
371      */
372     public void setRoles(Set<String> roles)
373     {
374         _roles.clear();
375         _roles.addAll(roles);
376     }
377
378
379
380     /* ------------------------------------------------------------ */
381     /**
382      * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
383      */
384     @Override
385     public void addConstraintMapping(ConstraintMapping mapping)
386     {
387         _constraintMappings.add(mapping);
388         if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
389         {
390             //allow for lazy role naming: if a role is named in a security constraint, try and
391             //add it to the list of declared roles (ie as if it was declared with a security-role
392             for (String role :  mapping.getConstraint().getRoles())
393             {
394                 if ("*".equals(role) || "**".equals(role))
395                     continue;
396                 addRole(role);
397             }
398         }
399
400         if (isStarted())
401         {
402             processConstraintMapping(mapping);
403         }
404     }
405
406     /* ------------------------------------------------------------ */
407     /**
408      * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
409      */
410     @Override
411     public void addRole(String role)
412     {
413         //add to list of declared roles
414         boolean modified = _roles.add(role);
415         if (isStarted() && modified)
416         {
417             // Add the new role to currently defined any role role infos
418             for (Map<String,RoleInfo> map : _constraintMap.values())
419             {
420                 for (RoleInfo info : map.values())
421                 {
422                     if (info.isAnyRole())
423                         info.addRole(role);
424                 }
425             }
426         }
427     }
428
429     /* ------------------------------------------------------------ */
430     /**
431      * @see org.eclipse.jetty.security.SecurityHandler#doStart()
432      */
433     @Override
434     protected void doStart() throws Exception
435     {
436         _constraintMap.clear();
437         if (_constraintMappings!=null)
438         {
439             for (ConstraintMapping mapping : _constraintMappings)
440             {
441                 processConstraintMapping(mapping);
442             }
443         }
444         
445         //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
446         checkPathsWithUncoveredHttpMethods();        
447        
448         super.doStart();
449     }
450
451     
452     /* ------------------------------------------------------------ */
453     @Override
454     protected void doStop() throws Exception
455     {
456         super.doStop();
457         _constraintMap.clear();
458     }
459     
460     
461     /* ------------------------------------------------------------ */
462     /**
463      * Create and combine the constraint with the existing processed
464      * constraints.
465      * 
466      * @param mapping
467      */
468     protected void processConstraintMapping(ConstraintMapping mapping)
469     {
470         Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
471         if (mappings == null)
472         {
473             mappings = new HashMap<String,RoleInfo>();
474             _constraintMap.put(mapping.getPathSpec(),mappings);
475         }
476         RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
477         if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
478             return;
479
480         if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
481         {
482             processConstraintMappingWithMethodOmissions(mapping, mappings);
483             return;
484         }
485
486         String httpMethod = mapping.getMethod();
487         if (httpMethod==null)
488             httpMethod=ALL_METHODS;
489         RoleInfo roleInfo = mappings.get(httpMethod);
490         if (roleInfo == null)
491         {
492             roleInfo = new RoleInfo();
493             mappings.put(httpMethod,roleInfo);
494             if (allMethodsRoleInfo != null)
495             {
496                 roleInfo.combine(allMethodsRoleInfo);
497             }
498         }
499         if (roleInfo.isForbidden())
500             return;
501
502         //add in info from the constraint
503         configureRoleInfo(roleInfo, mapping);
504         
505         if (roleInfo.isForbidden())
506         {
507             if (httpMethod.equals(ALL_METHODS))
508             {
509                 mappings.clear();
510                 mappings.put(ALL_METHODS,roleInfo);
511             }
512         }
513         else
514         {
515             //combine with any entry that covers all methods
516             if (httpMethod == null)
517             {
518                 for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
519                 {
520                     if (entry.getKey() != null)
521                     {
522                         RoleInfo specific = entry.getValue();
523                         specific.combine(roleInfo);
524                     }
525                 }
526             }
527         }
528     }
529
530     /* ------------------------------------------------------------ */
531     /** Constraints that name method omissions are dealt with differently.
532      * We create an entry in the mappings with key "&lt;method&gt;.omission". This entry
533      * is only ever combined with other omissions for the same method to produce a
534      * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
535      *  a given Request (in prepareConstraintInfo()), we consult 3 types of entries in 
536      * the mappings: an entry that names the method of the Request specifically, an
537      * entry that names constraints that apply to all methods, entries of the form
538      * &lt;method&gt;.omission, where the method of the Request is not named in the omission.
539      * @param mapping
540      * @param mappings
541      */
542     protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
543     {
544         String[] omissions = mapping.getMethodOmissions();
545         StringBuilder sb = new StringBuilder();
546         for (int i=0; i<omissions.length; i++)
547         {
548             if (i > 0)
549                 sb.append(".");
550             sb.append(omissions[i]);
551         }
552         sb.append(OMISSION_SUFFIX);
553         RoleInfo ri = new RoleInfo();
554         mappings.put(sb.toString(), ri);
555         configureRoleInfo(ri, mapping);
556     }
557
558     
559     /* ------------------------------------------------------------ */
560     /**
561      * Initialize or update the RoleInfo from the constraint
562      * @param ri
563      * @param mapping
564      */
565     protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
566     { 
567         Constraint constraint = mapping.getConstraint();
568         boolean forbidden = constraint.isForbidden();
569         ri.setForbidden(forbidden);
570         
571         //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
572         //which we need in order to do combining of omissions in prepareConstraintInfo
573         UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
574         ri.setUserDataConstraint(userDataConstraint);
575
576         //if forbidden, no point setting up roles
577         if (!ri.isForbidden())
578         {
579             //add in the roles
580             boolean checked = mapping.getConstraint().getAuthenticate();
581             ri.setChecked(checked);
582
583             if (ri.isChecked())
584             {
585                 if (mapping.getConstraint().isAnyRole())
586                 {
587                     // * means matches any defined role
588                     for (String role : _roles)
589                         ri.addRole(role);
590                     ri.setAnyRole(true);
591                 }
592                 else if (mapping.getConstraint().isAnyAuth())
593                 {
594                     //being authenticated is sufficient, not necessary to check roles
595                     ri.setAnyAuth(true);
596                 }
597                 else
598                 {   
599                     //user must be in one of the named roles
600                     String[] newRoles = mapping.getConstraint().getRoles();
601                      for (String role : newRoles)
602                      {
603                          //check role has been defined
604                          if (!_roles.contains(role))
605                              throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
606                         ri.addRole(role);
607                      }
608                  }
609              }
610          }
611      }
612
613    
614     /* ------------------------------------------------------------ */
615     /** 
616      * Find constraints that apply to the given path.
617      * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
618      * represents a merged set of user data constraints, roles etc -:
619      * <ol>
620      * <li>A mapping of an exact method name </li>
621      * <li>A mapping with key * that matches every method name</li>
622      * <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>
623      * </ol>
624      * 
625      * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
626      */
627     @Override
628     protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
629     {
630         Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
631
632         if (mappings != null)
633         {
634             String httpMethod = request.getMethod();
635             RoleInfo roleInfo = mappings.get(httpMethod);
636             if (roleInfo == null)
637             {
638                 //No specific http-method names matched
639                 List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
640
641                 //Get info for constraint that matches all methods if it exists
642                 RoleInfo all = mappings.get(ALL_METHODS);
643                 if (all != null)
644                     applicableConstraints.add(all);
645           
646                 
647                 //Get info for constraints that name method omissions where target method name is not omitted
648                 //(ie matches because target method is not omitted, hence considered covered by the constraint)
649                 for (Entry<String, RoleInfo> entry: mappings.entrySet())
650                 {
651                     if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
652                         applicableConstraints.add(entry.getValue());
653                 }
654                 
655                 if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
656                 {
657                     roleInfo = new RoleInfo();
658                     roleInfo.setForbidden(true);
659                 }
660                 else if (applicableConstraints.size() == 1)
661                     roleInfo = applicableConstraints.get(0);
662                 else
663                 {
664                     roleInfo = new RoleInfo();
665                     roleInfo.setUserDataConstraint(UserDataConstraint.None);
666                     
667                     for (RoleInfo r:applicableConstraints)
668                         roleInfo.combine(r);
669                 }
670
671             }
672            
673             return roleInfo;
674         }
675
676         return null;
677     }
678
679     @Override
680     protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
681     {
682         if (roleInfo == null)
683             return true;
684
685         if (roleInfo.isForbidden())
686             return false;
687
688         UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
689         if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
690             return true;
691
692         HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
693
694         if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
695         {
696             if (request.isSecure())
697                 return true;
698
699             if (httpConfig.getSecurePort() > 0)
700             {
701                 String scheme = httpConfig.getSecureScheme();
702                 int port = httpConfig.getSecurePort();
703                 String url = ("https".equalsIgnoreCase(scheme) && port==443)
704                     ? "https://"+request.getServerName()+request.getRequestURI()
705                     : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();                    
706                 if (request.getQueryString() != null)
707                     url += "?" + request.getQueryString();
708                 response.setContentLength(0);
709                 response.sendRedirect(url);
710             }
711             else
712                 response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
713
714             request.setHandled(true);
715             return false;
716         }
717         else
718         {
719             throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
720         }
721
722     }
723
724     @Override
725     protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
726     {
727         return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
728     }
729     
730     
731     /* ------------------------------------------------------------ */
732     /** 
733      * @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)
734      */
735     @Override
736     protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
737             throws IOException
738     {
739         if (constraintInfo == null)
740         {
741             return true;
742         }
743         RoleInfo roleInfo = (RoleInfo)constraintInfo;
744
745         if (!roleInfo.isChecked())
746         {
747             return true;
748         }
749
750         //handle ** role constraint
751         if (roleInfo.isAnyAuth() &&  request.getUserPrincipal() != null)
752         {
753             return true;
754         }
755         
756         //check if user is any of the allowed roles
757         boolean isUserInRole = false;
758         for (String role : roleInfo.getRoles())
759         {
760             if (userIdentity.isUserInRole(role, null))
761             {
762                 isUserInRole = true;
763                 break;
764             }
765         }
766         
767         //handle * role constraint
768         if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
769         {
770             return true;
771         }
772
773         //normal role check
774         if (isUserInRole)
775         {
776             return true;
777         }
778        
779         return false;
780     }
781
782     /* ------------------------------------------------------------ */
783     @Override
784     public void dump(Appendable out,String indent) throws IOException
785     {
786         // TODO these should all be beans
787         dumpBeans(out,indent,
788                 Collections.singleton(getLoginService()),
789                 Collections.singleton(getIdentityService()),
790                 Collections.singleton(getAuthenticator()),
791                 Collections.singleton(_roles),
792                 _constraintMap.entrySet());
793     }
794     
795     /* ------------------------------------------------------------ */
796     /** 
797      * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
798      */
799     @Override
800     public void setDenyUncoveredHttpMethods(boolean deny)
801     {
802         _denyUncoveredMethods = deny;
803     }
804     
805     /* ------------------------------------------------------------ */
806     @Override
807     public boolean isDenyUncoveredHttpMethods()
808     {
809         return _denyUncoveredMethods;
810     }
811     
812     
813     /* ------------------------------------------------------------ */
814     /**
815      * Servlet spec 3.1 pg. 147.
816      */
817     @Override
818     public boolean checkPathsWithUncoveredHttpMethods()
819     {
820         Set<String> paths = getPathsWithUncoveredHttpMethods();
821         if (paths != null && !paths.isEmpty())
822         {
823             for (String p:paths)
824                 LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
825             if (LOG.isDebugEnabled())
826                 LOG.debug(new Throwable());
827             return true;
828         }
829         return false; 
830     }
831     
832
833     /* ------------------------------------------------------------ */
834     /**
835      * Servlet spec 3.1 pg. 147.
836      * The container must check all the combined security constraint
837      * information and log any methods that are not protected and the
838      * urls at which they are not protected
839      * 
840      * @return list of paths for which there are uncovered methods
841      */
842     public Set<String> getPathsWithUncoveredHttpMethods ()
843     {
844         //if automatically denying uncovered methods, there are no uncovered methods
845         if (_denyUncoveredMethods)
846             return Collections.emptySet();
847         
848         Set<String> uncoveredPaths = new HashSet<String>();
849         
850         for (String path:_constraintMap.keySet())
851         {
852             Map<String, RoleInfo> methodMappings = _constraintMap.get(path);
853             //Each key is either:
854             // : an exact method name
855             // : * which means that the constraint applies to every method
856             // : a name of the form <method>.<method>.<method>.omission, which means it applies to every method EXCEPT those named
857             if (methodMappings.get(ALL_METHODS) != null)
858                 continue; //can't be any uncovered methods for this url path
859           
860             boolean hasOmissions = omissionsExist(path, methodMappings);
861             
862             for (String method:methodMappings.keySet())
863             {
864                 if (method.endsWith(OMISSION_SUFFIX))
865                 {
866                     Set<String> omittedMethods = getOmittedMethods(method);
867                     for (String m:omittedMethods)
868                     {
869                         if (!methodMappings.containsKey(m))
870                             uncoveredPaths.add(path);
871                     }
872                 }
873                 else
874                 {
875                     //an exact method name
876                     if (!hasOmissions)
877                         //a http-method does not have http-method-omission to cover the other method names
878                         uncoveredPaths.add(path);
879                 }
880                 
881             }
882         }
883         return uncoveredPaths;
884     }
885     
886     /* ------------------------------------------------------------ */
887     /**
888      * Check if any http method omissions exist in the list of method
889      * to auth info mappings.
890      * 
891      * @param path
892      * @param methodMappings
893      * @return
894      */
895     protected boolean omissionsExist (String path, Map<String, RoleInfo> methodMappings)
896     {
897         if (methodMappings == null)
898             return false;
899         boolean hasOmissions = false;
900         for (String m:methodMappings.keySet())
901         {
902             if (m.endsWith(OMISSION_SUFFIX))
903                 hasOmissions = true;
904         }
905         return hasOmissions;
906     }
907     
908     
909     /* ------------------------------------------------------------ */
910     /**
911      * Given a string of the form &lt;method&gt;.&lt;method&gt;.omission
912      * split out the individual method names.
913      * 
914      * @param omission
915      * @return
916      */
917     protected Set<String> getOmittedMethods (String omission)
918     {
919         if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
920             return Collections.emptySet();
921         
922         String[] strings = omission.split("\\.");
923         Set<String> methods = new HashSet<String>();
924         for (int i=0;i<strings.length-1;i++)
925             methods.add(strings[i]);
926         return methods;
927     }
928 }