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