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
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
246
247 if (request != null) {
248
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
259 ContextManager.setCallers(defaultPrincipal.getSubject(), defaultPrincipal.getSubject());
260
261
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 }