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