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