View Javadoc

1   // ========================================================================
2   // Copyright (c) 1999-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.security;
15  
16  import java.io.IOException;
17  import java.util.Arrays;
18  import java.util.Collection;
19  import java.util.Collections;
20  import java.util.HashSet;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  import java.util.concurrent.CopyOnWriteArrayList;
25  import java.util.concurrent.CopyOnWriteArraySet;
26  
27  import org.eclipse.jetty.http.PathMap;
28  import org.eclipse.jetty.http.security.Constraint;
29  import org.eclipse.jetty.server.Connector;
30  import org.eclipse.jetty.server.HttpConnection;
31  import org.eclipse.jetty.server.Request;
32  import org.eclipse.jetty.server.Response;
33  import org.eclipse.jetty.server.UserIdentity;
34  import org.eclipse.jetty.util.StringMap;
35  import org.eclipse.jetty.util.TypeUtil;
36  
37  /* ------------------------------------------------------------ */
38  /**
39   * Handler to enforce SecurityConstraints. This implementation is servlet spec
40   * 2.4 compliant and precomputes the constraint combinations for runtime
41   * efficiency.
42   *
43   */
44  public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
45  {
46      private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<ConstraintMapping>();
47      private final Set<String> _roles = new CopyOnWriteArraySet<String>();
48      private final PathMap _constraintMap = new PathMap();
49      private boolean _strict = true;
50  
51      /* ------------------------------------------------------------ */
52      /** Get the strict mode.
53       * @return true if the security handler is running in strict mode.
54       */
55      public boolean isStrict()
56      {
57          return _strict;
58      }
59  
60      /* ------------------------------------------------------------ */
61      /** Set the strict mode of the security handler.
62       * <p>
63       * When in strict mode (the default), the full servlet specification
64       * will be implemented.
65       * If not in strict mode, some additional flexibility in configuration
66       * is allowed:<ul>
67       * <li>All users do not need to have a role defined in the deployment descriptor
68       * <li>The * role in a constraint applies to ANY role rather than all roles defined in
69       * the deployment descriptor.
70       * </ul>
71       *
72       * @param strict the strict to set
73       * @see #setRoles(Set)
74       * @see #setConstraintMappings(List, Set)
75       */
76      public void setStrict(boolean strict)
77      {
78          _strict = strict;
79      }
80  
81      /* ------------------------------------------------------------ */
82      /**
83       * @return Returns the constraintMappings.
84       */
85      public List<ConstraintMapping> getConstraintMappings()
86      {
87          return _constraintMappings;
88      }
89  
90      /* ------------------------------------------------------------ */
91      public Set<String> getRoles()
92      {
93          return _roles;
94      }
95  
96      /* ------------------------------------------------------------ */
97      /**
98       * Process the constraints following the combining rules in Servlet 3.0 EA
99       * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
100      *
101      * @param constraintMappings
102      *            The constraintMappings to set, from which the set of known roles
103      *            is determined.
104      */
105     public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
106     {
107         setConstraintMappings(constraintMappings,null);
108     }
109 
110     /**
111      * Process the constraints following the combining rules in Servlet 3.0 EA
112      * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
113      *
114      * @param constraintMappings
115      *            The constraintMappings to set as array, from which the set of known roles
116      *            is determined.  Needed to retain API compatibility for 7.x
117      */
118     public void setConstraintMappings( ConstraintMapping[] constraintMappings )
119     {
120         setConstraintMappings( Arrays.asList(constraintMappings), null);
121     }
122 
123     /* ------------------------------------------------------------ */
124     /**
125      * Process the constraints following the combining rules in Servlet 3.0 EA
126      * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
127      *
128      * @param constraintMappings
129      *            The constraintMappings to set.
130      * @param roles The known roles (or null to determine them from the mappings)
131      */
132     public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
133     {
134         if (isStarted())
135             throw new IllegalStateException("Started");
136         _constraintMappings.clear();
137         _constraintMappings.addAll(constraintMappings);
138 
139         if (roles==null)
140         {
141             roles = new HashSet<String>();
142             for (ConstraintMapping cm : constraintMappings)
143             {
144                 String[] cmr = cm.getConstraint().getRoles();
145                 if (cmr!=null)
146                 {
147                     for (String r : cmr)
148                         if (!"*".equals(r))
149                             roles.add(r);
150                 }
151             }
152         }
153         setRoles(roles);
154     }
155 
156     /* ------------------------------------------------------------ */
157     /**
158      * Set the known roles.
159      * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
160      * {@link #setConstraintMappings(List, Set)}.
161      * @see #setStrict(boolean)
162      * @param roles The known roles (or null to determine them from the mappings)
163      */
164     public void setRoles(Set<String> roles)
165     {
166         if (isStarted())
167             throw new IllegalStateException("Started");
168 
169         _roles.clear();
170         _roles.addAll(roles);
171     }
172 
173 
174 
175     /* ------------------------------------------------------------ */
176     /**
177      * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
178      */
179     public void addConstraintMapping(ConstraintMapping mapping)
180     {
181         _constraintMappings.add(mapping);
182         if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
183             for (String role :  mapping.getConstraint().getRoles())
184                 addRole(role);
185 
186         if (isStarted())
187         {
188             processConstraintMapping(mapping);
189         }
190     }
191 
192     /* ------------------------------------------------------------ */
193     /**
194      * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
195      */
196     public void addRole(String role)
197     {
198         boolean modified = _roles.add(role);
199         if (isStarted() && modified && _strict)
200         {
201             // Add the new role to currently defined any role role infos
202             for (Map<String,RoleInfo> map : (Collection<Map<String,RoleInfo>>)_constraintMap.values())
203             {
204                 for (RoleInfo info : map.values())
205                 {
206                     if (info.isAnyRole())
207                         info.addRole(role);
208                 }
209             }
210         }
211     }
212 
213     /* ------------------------------------------------------------ */
214     /**
215      * @see org.eclipse.jetty.security.SecurityHandler#doStart()
216      */
217     @Override
218     protected void doStart() throws Exception
219     {
220         _constraintMap.clear();
221         if (_constraintMappings!=null)
222         {
223             for (ConstraintMapping mapping : _constraintMappings)
224             {
225                 processConstraintMapping(mapping);
226             }
227         }
228         super.doStart();
229     }
230 
231     @Override
232     protected void doStop() throws Exception
233     {
234         _constraintMap.clear();
235         _constraintMappings.clear();
236         _roles.clear();
237         super.doStop();
238     }
239 
240     protected void processConstraintMapping(ConstraintMapping mapping)
241     {
242         Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.get(mapping.getPathSpec());
243         if (mappings == null)
244         {
245             mappings = new StringMap();
246             _constraintMap.put(mapping.getPathSpec(),mappings);
247         }
248         RoleInfo allMethodsRoleInfo = mappings.get(null);
249         if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
250             return;
251 
252         String httpMethod = mapping.getMethod();
253         RoleInfo roleInfo = mappings.get(httpMethod);
254         if (roleInfo == null)
255         {
256             roleInfo = new RoleInfo();
257             mappings.put(httpMethod,roleInfo);
258             if (allMethodsRoleInfo != null)
259             {
260                 roleInfo.combine(allMethodsRoleInfo);
261             }
262         }
263         if (roleInfo.isForbidden())
264             return;
265 
266         Constraint constraint = mapping.getConstraint();
267         boolean forbidden = constraint.isForbidden();
268         roleInfo.setForbidden(forbidden);
269         if (forbidden)
270         {
271             if (httpMethod == null)
272             {
273                 mappings.clear();
274                 mappings.put(null,roleInfo);
275             }
276         }
277         else
278         {
279             UserDataConstraint userDataConstraint = UserDataConstraint.get(constraint.getDataConstraint());
280             roleInfo.setUserDataConstraint(userDataConstraint);
281 
282             boolean checked = constraint.getAuthenticate();
283             roleInfo.setChecked(checked);
284             if (roleInfo.isChecked())
285             {
286                 if (constraint.isAnyRole())
287                 {
288                     if (_strict)
289                     {
290                         // * means "all defined roles"
291                         for (String role : _roles)
292                             roleInfo.addRole(role);
293                     }
294                     else
295                         // * means any role
296                         roleInfo.setAnyRole(true);
297                 }
298                 else
299                 {
300                     String[] newRoles = constraint.getRoles();
301                     for (String role : newRoles)
302                     {
303                         if (_strict &&!_roles.contains(role))
304                             throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
305                         roleInfo.addRole(role);
306                     }
307                 }
308             }
309             if (httpMethod == null)
310             {
311                 for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
312                 {
313                     if (entry.getKey() != null)
314                     {
315                         RoleInfo specific = entry.getValue();
316                         specific.combine(roleInfo);
317                     }
318                 }
319             }
320         }
321     }
322 
323     protected Object prepareConstraintInfo(String pathInContext, Request request)
324     {
325         Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
326 
327         if (mappings != null)
328         {
329             String httpMethod = request.getMethod();
330             RoleInfo roleInfo = mappings.get(httpMethod);
331             if (roleInfo == null)
332                 roleInfo = mappings.get(null);
333             return roleInfo;
334         }
335 
336         return null;
337     }
338 
339     protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException
340     {
341         if (constraintInfo == null)
342             return true;
343 
344         RoleInfo roleInfo = (RoleInfo)constraintInfo;
345         if (roleInfo.isForbidden())
346             return false;
347 
348 
349         UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
350         if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
351         {
352             return true;
353         }
354         HttpConnection connection = HttpConnection.getCurrentConnection();
355         Connector connector = connection.getConnector();
356 
357         if (dataConstraint == UserDataConstraint.Integral)
358         {
359             if (connector.isIntegral(request))
360                 return true;
361             if (connector.getConfidentialPort() > 0)
362             {
363                 String url = connector.getIntegralScheme() + "://" + request.getServerName() + ":" + connector.getIntegralPort() + request.getRequestURI();
364                 if (request.getQueryString() != null)
365                     url += "?" + request.getQueryString();
366                 response.setContentLength(0);
367                 response.sendRedirect(url);
368             }
369             else
370                 response.sendError(Response.SC_FORBIDDEN,"!Integral");
371 
372             request.setHandled(true);
373             return false;
374         }
375         else if (dataConstraint == UserDataConstraint.Confidential)
376         {
377             if (connector.isConfidential(request))
378                 return true;
379 
380             if (connector.getConfidentialPort() > 0)
381             {
382                 String url = connector.getConfidentialScheme() + "://" + request.getServerName() + ":" + connector.getConfidentialPort()
383                         + request.getRequestURI();
384                 if (request.getQueryString() != null)
385                     url += "?" + request.getQueryString();
386 
387                 response.setContentLength(0);
388                 response.sendRedirect(url);
389             }
390             else
391                 response.sendError(Response.SC_FORBIDDEN,"!Confidential");
392 
393             request.setHandled(true);
394             return false;
395         }
396         else
397         {
398             throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
399         }
400 
401     }
402 
403     protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
404     {
405         if (constraintInfo == null)
406         {
407             return false;
408         }
409         return ((RoleInfo)constraintInfo).isChecked();
410     }
411 
412     @Override
413     protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
414             throws IOException
415     {
416         if (constraintInfo == null)
417         {
418             return true;
419         }
420         RoleInfo roleInfo = (RoleInfo)constraintInfo;
421 
422         if (!roleInfo.isChecked())
423         {
424             return true;
425         }
426 
427         if (roleInfo.isAnyRole() && request.getAuthType()!=null)
428             return true;
429 
430         for (String role : roleInfo.getRoles())
431         {
432             if (userIdentity.isUserInRole(role, null))
433                 return true;
434         }
435         return false;
436     }
437 
438     /* ------------------------------------------------------------ */
439     @Override
440     public void dump(Appendable out,String indent) throws IOException
441     {
442         dumpThis(out);
443         dump(out,indent,TypeUtil.asList(getHandlers()),getBeans(),Collections.singleton(_roles),_constraintMap.entrySet());
444     }
445 }