View Javadoc

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