View Javadoc

1   // ========================================================================
2   // Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.annotations;
15  
16  import java.util.ArrayList;
17  import java.util.List;
18  
19  import javax.servlet.annotation.HttpConstraint;
20  import javax.servlet.annotation.HttpMethodConstraint;
21  import javax.servlet.annotation.ServletSecurity;
22  import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
23  import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
24  
25  import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
26  import org.eclipse.jetty.http.security.Constraint;
27  import org.eclipse.jetty.security.ConstraintAware;
28  import org.eclipse.jetty.security.ConstraintMapping;
29  import org.eclipse.jetty.servlet.ServletHolder;
30  import org.eclipse.jetty.servlet.ServletMapping;
31  import org.eclipse.jetty.util.LazyList;
32  import org.eclipse.jetty.util.log.Log;
33  import org.eclipse.jetty.webapp.WebAppContext;
34  
35  /**
36   * ServletSecurityAnnotationHandler
37   *
38   * Inspect a class to see if it has an @ServletSecurity annotation on it,
39   * setting up the <security-constraint>s.
40   * 
41   * A servlet can be defined in:
42   * <ul>
43   * <li>web.xml
44   * <li>web-fragment.xml
45   * <li>@WebServlet annotation discovered
46   * <li>ServletContext.createServlet
47   * </ul>
48   * 
49   * The ServletSecurity annotation for a servlet should only be processed
50   * iff metadata-complete == false.
51   */
52  public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnnotationHandler
53  {
54  
55      private WebAppContext _context;
56      
57      public ServletSecurityAnnotationHandler(WebAppContext wac)
58      {
59          super(false);
60          _context = wac;
61      }
62      
63      /** 
64       * @see org.eclipse.jetty.annotations.AnnotationIntrospector.IntrospectableAnnotationHandler#handle(java.lang.Class)
65       */
66      public void doHandle(Class clazz)
67      {
68          if (!(_context.getSecurityHandler() instanceof ConstraintAware))
69          {
70              Log.warn("SecurityHandler not ConstraintAware, skipping security annotation processing");
71              return;
72          }
73          
74         ServletSecurity servletSecurity = (ServletSecurity)clazz.getAnnotation(ServletSecurity.class);
75         if (servletSecurity == null)
76             return;
77         
78         //If there are already constraints defined (ie from web.xml or programmatically(?)) that match any 
79         //of the url patterns defined for this servlet, then skip the security annotation.
80        
81         List<ServletMapping> servletMappings = getServletMappings(clazz.getCanonicalName());
82         List<ConstraintMapping> constraintMappings =  ((ConstraintAware)_context.getSecurityHandler()).getConstraintMappings();
83       
84         if (constraintsExist(servletMappings, constraintMappings))
85         {
86             Log.warn("Constraints already defined for "+clazz.getName()+", skipping ServletSecurity annotation");
87             return;
88         }
89  
90         //Make a fresh list
91         constraintMappings = new ArrayList<ConstraintMapping>();
92         
93         //Get the values that form the constraints that will apply unless there are HttpMethodConstraints to augment them
94         HttpConstraint defaults = servletSecurity.value();
95  
96         //Make a Constraint for the <auth-constraint> and <user-data-constraint> specified by the HttpConstraint
97         Constraint defaultConstraint = makeConstraint (clazz, 
98                                                        defaults.rolesAllowed(), 
99                                                        defaults.value(), 
100                                                       defaults.transportGuarantee());
101 
102        constraintMappings.addAll(makeMethodMappings(clazz, 
103                                                     defaultConstraint, 
104                                                     servletMappings, 
105                                                     servletSecurity.httpMethodConstraints()));
106 
107        //set up the security constraints produced by the annotation
108        ConstraintAware securityHandler = (ConstraintAware)_context.getSecurityHandler();
109 
110        for (ConstraintMapping m:constraintMappings)
111            securityHandler.addConstraintMapping(m); 
112     }
113     
114  
115     
116     /**
117      * Make a jetty Constraint object, which represents the <auth-constraint> and
118      * <user-data-constraint> elements, based on the security annotation.
119      * @param servlet
120      * @param rolesAllowed
121      * @param permitOrDeny
122      * @param transport
123      * @return
124      */
125     protected Constraint makeConstraint (Class servlet, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
126     {  
127         Constraint constraint = new Constraint();
128         if (rolesAllowed == null || rolesAllowed.length==0)
129         {           
130            if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
131            {
132                //Equivalent to <auth-constraint> with no roles
133                constraint.setName(servlet.getName()+"-Deny");
134                constraint.setAuthenticate(true);
135            }
136            else
137            {
138                //Equivalent to no <auth-constraint>
139                constraint.setAuthenticate(false);
140                constraint.setName(servlet.getName()+"-Permit");
141            }
142         }
143         else
144         {
145             //Equivalent to <auth-constraint> with list of <security-role-name>s
146             constraint.setAuthenticate(true);
147             constraint.setRoles(rolesAllowed);
148             constraint.setName(servlet.getName()+"-RolesAllowed");           
149         } 
150         
151       //Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
152       constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
153       return constraint;
154     }
155     
156     
157     /**
158      * Make a ConstraintMapping which captures the <http-method> or <http-method-omission> elements for a particular url pattern,
159      * and relates it to a Constraint object (<auth-constraint> and <user-data-constraint>).
160      * @param constraint
161      * @param url
162      * @param method
163      * @param omissions
164      * @return
165      */
166     protected ConstraintMapping makeConstraintMapping (Constraint constraint, String url, String method, String[] omissions)
167     {
168         ConstraintMapping mapping = new ConstraintMapping();
169         mapping.setConstraint(constraint);
170         mapping.setPathSpec(url);
171         if (method != null)
172             mapping.setMethod(method);
173         if (omissions != null)
174             mapping.setMethodOmissions(omissions);
175         return mapping;
176     }
177   
178     /**
179      * Make the Jetty Constraints and ConstraintMapping objects that correspond to the HttpMethodConstraint
180      * annotations for each url pattern for the servlet.
181      * @param servlet
182      * @param defaultConstraint
183      * @param servletMappings
184      * @param annotations
185      * @return
186      */
187     protected List<ConstraintMapping> makeMethodMappings (Class servlet, Constraint defaultConstraint, List<ServletMapping> servletMappings, HttpMethodConstraint[] annotations)
188     {
189         List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
190         
191         //for each url-pattern existing for the servlet make a ConstraintMapping for the HttpConstraint, and ConstraintMappings for
192         //each HttpMethodConstraint
193         for (ServletMapping sm : servletMappings)
194         {
195             for (String url : sm.getPathSpecs())
196             {
197                 //Make a ConstraintMapping that matches the defaultConstraint
198                 ConstraintMapping defaultMapping = makeConstraintMapping(defaultConstraint, url, null, null);
199                 
200                 //If there are HttpMethodConstraint annotations, make a Constraint and a ConstraintMapping for it
201                 if (annotations != null && annotations.length>0)
202                 {    
203                     List<String> omissions = new ArrayList<String>();
204                     
205                     //for each HttpMethodConstraint annotation, make a new Constraint and ConstraintMappings for this url
206                     for (int i=0;  i < annotations.length;i++)
207                     {
208                         //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements
209                         Constraint methodConstraint = makeConstraint(servlet, 
210                                                                      annotations[i].rolesAllowed(), 
211                                                                      annotations[i].emptyRoleSemantic(),
212                                                                      annotations[i].transportGuarantee());
213 
214                         //Make ConstraintMapping that captures the <http-method> elements                        
215                         ConstraintMapping methodConstraintMapping = makeConstraintMapping (methodConstraint,
216                                                                                            url,annotations[i].value(), 
217                                                                                            null);
218                         mappings.add(methodConstraintMapping);
219                         omissions.add(annotations[i].value()); 
220                     }   
221                     defaultMapping.setMethodOmissions(omissions.toArray(new String[0]));
222                 }
223 
224                 //add the constraint mapping containing the http-method-omissions, if there are any
225                 mappings.add(defaultMapping);
226             }
227         }             
228         return mappings;
229     }
230     
231    
232     
233     /**
234      * Get the ServletMappings for the servlet's class.
235      * @param className
236      * @return
237      */
238     protected List<ServletMapping> getServletMappings(String className)
239     {
240         List<ServletMapping> results = new ArrayList<ServletMapping>();
241         ServletMapping[] mappings = _context.getServletHandler().getServletMappings();
242         for (ServletMapping mapping : mappings)
243         {
244             //Check the name of the servlet that this mapping applies to, and then find the ServletHolder for it to find it's class
245             ServletHolder holder = _context.getServletHandler().getServlet(mapping.getServletName());
246             if (holder.getClassName().equals(className))
247               results.add(mapping);
248         }
249         return results;
250     }
251     
252     
253     
254     /**
255      * Check if there are already <security-constraint> elements defined that match the url-patterns for
256      * the servlet.
257      * @param servletMappings
258      * @return
259      */
260     protected boolean constraintsExist (List<ServletMapping> servletMappings, List<ConstraintMapping> constraintMappings)
261     {
262         boolean exists = false;
263 
264         //Check to see if the path spec on each constraint mapping matches a pathSpec in the servlet mappings.
265         //If it does, then we should ignore the security annotations.
266         for (ServletMapping mapping : servletMappings)
267         {  
268             //Get its url mappings
269             String[] pathSpecs = mapping.getPathSpecs();
270             if (pathSpecs == null)
271                 continue;
272 
273             //Check through the constraints to see if there are any whose pathSpecs (url mappings)
274             //match the servlet. If so, then we already have constraints defined for this servlet,
275             //and we will not be processing the annotation (ie web.xml or programmatic override).
276            for (int i=0; constraintMappings != null && i < constraintMappings.size() && !exists; i++)
277            {
278                for (int j=0; j < pathSpecs.length; j++)
279                {
280                    if (pathSpecs[j].equals(constraintMappings.get(i).getPathSpec()))
281                    {
282                        exists = true;
283                        break;
284                    }
285                }
286            }
287         }      
288         return exists;
289     }
290 
291 }