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