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