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