View Javadoc

1   /**
2    *
3    *  Licensed to the Apache Software Foundation (ASF) under one or more
4    *  contributor license agreements.  See the NOTICE file distributed with
5    *  this work for additional information regarding copyright ownership.
6    *  The ASF licenses this file to You under the Apache License, Version 2.0
7    *  (the "License"); you may not use this file except in compliance with
8    *  the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing, software
13   *  distributed under the License is distributed on an "AS IS" BASIS,
14   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *  See the License for the specific language governing permissions and
16   *  limitations under the License.
17   */
18  package org.apache.geronimo.jetty.interceptor;
19  
20  import java.io.IOException;
21  import java.security.AccessControlContext;
22  import java.security.AccessControlException;
23  import java.security.PermissionCollection;
24  import java.security.Principal;
25  
26  import javax.security.auth.Subject;
27  import javax.security.jacc.PolicyContext;
28  import javax.security.jacc.WebResourcePermission;
29  import javax.security.jacc.WebUserDataPermission;
30  
31  import org.apache.geronimo.common.DeploymentException;
32  import org.apache.geronimo.common.GeronimoSecurityException;
33  import org.apache.geronimo.jetty.JAASJettyPrincipal;
34  import org.apache.geronimo.jetty.JAASJettyRealm;
35  import org.apache.geronimo.jetty.JettyContainer;
36  import org.apache.geronimo.security.ContextManager;
37  import org.apache.geronimo.security.IdentificationPrincipal;
38  import org.apache.geronimo.security.SubjectId;
39  import org.apache.geronimo.security.deploy.DefaultPrincipal;
40  import org.apache.geronimo.security.util.ConfigurationUtil;
41  import org.mortbay.http.Authenticator;
42  import org.mortbay.http.HttpException;
43  import org.mortbay.http.HttpRequest;
44  import org.mortbay.http.HttpResponse;
45  import org.mortbay.http.SecurityConstraint;
46  import org.mortbay.jetty.servlet.FormAuthenticator;
47  import org.mortbay.jetty.servlet.ServletHttpRequest;
48  
49  
50  /**
51   * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
52   */
53  public class SecurityContextBeforeAfter implements BeforeAfter {
54  
55      private final BeforeAfter next;
56      private final int policyContextIDIndex;
57      private final int webAppContextIndex;
58      private final String policyContextID;
59      private final static ThreadLocal currentWebAppContext = new ThreadLocal();
60      private final JAASJettyPrincipal defaultPrincipal;
61  
62      private final String formLoginPath;
63  
64      private final PermissionCollection checked;
65      private final PermissionCollection excludedPermissions;
66      private final Authenticator authenticator;
67  
68      private final JAASJettyRealm realm;
69  
70      public SecurityContextBeforeAfter(BeforeAfter next,
71                                        int policyContextIDIndex,
72                                        int webAppContextIndex,
73                                        String policyContextID,
74                                        DefaultPrincipal defaultPrincipal,
75                                        Authenticator authenticator,
76                                        PermissionCollection checkedPermissions,
77                                        PermissionCollection excludedPermissions,
78                                        JAASJettyRealm realm,
79                                        ClassLoader classLoader)
80      {
81          assert realm != null;
82          assert authenticator != null;
83  
84          this.next = next;
85          this.policyContextIDIndex = policyContextIDIndex;
86          this.webAppContextIndex = webAppContextIndex;
87          this.policyContextID = policyContextID;
88  
89          this.defaultPrincipal = generateDefaultPrincipal(defaultPrincipal, classLoader);
90          this.checked = checkedPermissions;
91          this.excludedPermissions = excludedPermissions;
92  
93          if (authenticator instanceof FormAuthenticator) {
94              String formLoginPath = ((FormAuthenticator) authenticator).getLoginPage();
95              if (formLoginPath.indexOf('?') > 0) {
96                  formLoginPath = formLoginPath.substring(0, formLoginPath.indexOf('?'));
97              }
98              this.formLoginPath = formLoginPath;
99          } else {
100             formLoginPath = null;
101         }
102 
103         this.authenticator = authenticator;
104         /**
105          * Register our default principal with the ContextManager
106          */
107         Subject defaultSubject = this.defaultPrincipal.getSubject();
108         ContextManager.registerSubject(defaultSubject);
109         SubjectId id = ContextManager.getSubjectId(defaultSubject);
110         defaultSubject.getPrincipals().add(new IdentificationPrincipal(id));
111         this.realm = realm;
112     }
113 
114     public void stop(JettyContainer jettyContainer) {
115         Subject defaultSubject = this.defaultPrincipal.getSubject();
116         ContextManager.unregisterSubject(defaultSubject);
117         jettyContainer.removeRealm(realm.getSecurityRealmName());
118     }
119 
120     public void before(Object[] context, HttpRequest httpRequest, HttpResponse httpResponse) {
121         context[policyContextIDIndex] = PolicyContext.getContextID();
122         context[webAppContextIndex] = getCurrentSecurityInterceptor();
123 
124         PolicyContext.setContextID(policyContextID);
125         setCurrentSecurityInterceptor(this);
126 
127         if (httpRequest != null) {
128             ServletHttpRequest request = (ServletHttpRequest) httpRequest.getWrapper();
129             PolicyContext.setHandlerData(request);
130         }
131 
132         if (next != null) {
133             next.before(context, httpRequest, httpResponse);
134         }
135     }
136 
137     public void after(Object[] context, HttpRequest httpRequest, HttpResponse httpResponse) {
138         if (next != null) {
139             next.after(context, httpRequest, httpResponse);
140         }
141         setCurrentSecurityInterceptor((SecurityContextBeforeAfter) context[webAppContextIndex]);
142         PolicyContext.setContextID((String) context[policyContextIDIndex]);
143     }
144 
145     private static void setCurrentSecurityInterceptor(SecurityContextBeforeAfter context) {
146         SecurityManager sm = System.getSecurityManager();
147         if (sm != null) sm.checkPermission(ContextManager.SET_CONTEXT);
148 
149         currentWebAppContext.set(context);
150     }
151 
152     private static SecurityContextBeforeAfter getCurrentSecurityInterceptor() {
153         SecurityManager sm = System.getSecurityManager();
154         if (sm != null) sm.checkPermission(ContextManager.GET_CONTEXT);
155 
156         return (SecurityContextBeforeAfter) currentWebAppContext.get();
157     }
158 
159     //security check methods, delegated from WebAppContext
160 
161     /**
162      * Check the security constraints using JACC.
163      *
164      * @param pathInContext path in context
165      * @param request       HTTP request
166      * @param response      HTTP response
167      * @return true if the path in context passes the security check,
168      *         false if it fails or a redirection has occured during authentication.
169      */
170     public boolean checkSecurityConstraints(String pathInContext, HttpRequest request, HttpResponse response) throws HttpException, IOException {
171         if (formLoginPath != null) {
172             String pathToBeTested = (pathInContext.indexOf('?') > 0 ? pathInContext.substring(0, pathInContext.indexOf('?')) : pathInContext);
173 
174             if (pathToBeTested.equals(formLoginPath)) {
175                 return true;
176             }
177         }
178 
179         try {
180             ServletHttpRequest servletHttpRequest = (ServletHttpRequest) request.getWrapper();
181             WebUserDataPermission wudp = new WebUserDataPermission(servletHttpRequest);
182             WebResourcePermission webResourcePermission = new WebResourcePermission(servletHttpRequest);
183             Principal user = obtainUser(pathInContext, request, response, webResourcePermission, wudp);
184 
185             if (user == null) {
186                 return false;
187             }
188             if (user == SecurityConstraint.__NOBODY) {
189                 return true;
190             }
191 
192             AccessControlContext acc = ContextManager.getCurrentContext();
193 
194             /**
195              * JACC v1.0 secion 4.1.1
196              */
197 
198             acc.checkPermission(wudp);
199 
200             /**
201              * JACC v1.0 secion 4.1.2
202              */
203             acc.checkPermission(webResourcePermission);
204         } catch (HttpException he) {
205             response.sendError(he.getCode(), he.getReason());
206             return false;
207         } catch (AccessControlException ace) {
208             response.sendError(HttpResponse.__403_Forbidden);
209             return false;
210         }
211         return true;
212     }
213 
214     /**
215      * Obtain an authenticated user, if one is required.  Otherwise return the
216      * default principal.
217      * <p/>
218      * Also set the current caller for JACC security checks for the default
219      * principal.  This is automatically done by <code>JAASJettyRealm</code>.
220      *
221      * @param pathInContext path in context
222      * @param request       HTTP request
223      * @param response      HTTP response
224      * @return <code>null</code> if there is no authenticated user at the moment
225      *         and security checking should not proceed and servlet handling should also
226      *         not proceed, e.g. redirect. <code>SecurityConstraint.__NOBODY</code> if
227      *         security checking should not proceed and servlet handling should proceed,
228      *         e.g. login page.
229      */
230     private Principal obtainUser(String pathInContext, HttpRequest request, HttpResponse response, WebResourcePermission resourcePermission, WebUserDataPermission dataPermission) throws IOException {
231         boolean unauthenticated = !(checked.implies(resourcePermission) || checked.implies(dataPermission));
232         boolean forbidden = excludedPermissions.implies(resourcePermission) || excludedPermissions.implies(dataPermission);
233 
234         if (!unauthenticated && !forbidden) {
235             return authenticator.authenticate(realm, pathInContext, request, response);
236         } else
237         if (authenticator instanceof FormAuthenticator && pathInContext.endsWith(FormAuthenticator.__J_SECURITY_CHECK))
238         {
239             /**
240              * This could be a post request to __J_SECURITY_CHECK.
241              */
242             return authenticator.authenticate(realm, pathInContext, request, response);
243         }
244 
245         //attempt to access an unprotected resource that is not the j_security_check.
246         //if we are logged in, return the logged in principal.
247         if (request != null) {
248             //null response appears to prevent redirect to login page
249             Principal user = authenticator.authenticate(realm, pathInContext, request, null);
250             if (user != null) {
251                 return user;
252             }
253         }
254 
255         /**
256          * No authentication is required.  Return the defaultPrincipal.
257          */
258         //TODO use run-as as nextCaller if present
259         ContextManager.setCallers(defaultPrincipal.getSubject(), defaultPrincipal.getSubject());
260         //??????? next line does nothing!
261 //        ContextManager.setNextCaller(defaultPrincipal.getSubject());
262         return defaultPrincipal;
263     }
264 
265 
266     /**
267      * Generate the default principal from the security config.
268      *
269      * @param defaultPrincipal The Geronimo security configuration.
270      * @param classLoader
271      * @return the default principal
272      */
273     protected JAASJettyPrincipal generateDefaultPrincipal(DefaultPrincipal defaultPrincipal, ClassLoader classLoader) throws GeronimoSecurityException {
274 
275         if (defaultPrincipal == null) {
276             throw new GeronimoSecurityException("Unable to generate default principal");
277         }
278 
279         try {
280             JAASJettyPrincipal result = new JAASJettyPrincipal("default");
281             Subject defaultSubject = ConfigurationUtil.generateDefaultSubject(defaultPrincipal, classLoader);
282 
283             result.setSubject(defaultSubject);
284 
285             return result;
286         } catch (DeploymentException de) {
287             throw new GeronimoSecurityException("Unable to generate default principal", de);
288         }
289     }
290 
291 }