1 /**
2 *
3 * Copyright 2003-2004 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.geronimo.tomcat.realm;
18
19 import org.apache.catalina.Context;
20 import org.apache.catalina.LifecycleException;
21 import org.apache.catalina.Wrapper;
22 import org.apache.catalina.connector.Request;
23 import org.apache.catalina.connector.Response;
24 import org.apache.catalina.deploy.LoginConfig;
25 import org.apache.catalina.deploy.SecurityConstraint;
26 import org.apache.catalina.realm.JAASRealm;
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.geronimo.security.ContextManager;
30 import org.apache.geronimo.security.jacc.PolicyContextHandlerContainerSubject;
31 import org.apache.geronimo.security.realm.providers.CertificateChainCallbackHandler;
32 import org.apache.geronimo.security.realm.providers.PasswordCallbackHandler;
33 import org.apache.geronimo.tomcat.JAASTomcatPrincipal;
34
35 import javax.security.auth.Subject;
36 import javax.security.auth.callback.CallbackHandler;
37 import javax.security.auth.login.AccountExpiredException;
38 import javax.security.auth.login.CredentialExpiredException;
39 import javax.security.auth.login.FailedLoginException;
40 import javax.security.auth.login.LoginContext;
41 import javax.security.auth.login.LoginException;
42 import javax.security.jacc.PolicyContext;
43 import javax.security.jacc.PolicyContextException;
44 import javax.security.jacc.WebResourcePermission;
45 import javax.security.jacc.WebRoleRefPermission;
46 import javax.security.jacc.WebUserDataPermission;
47 import javax.servlet.ServletRequest;
48
49 import java.io.IOException;
50 import java.security.AccessControlContext;
51 import java.security.AccessControlException;
52 import java.security.Principal;
53 import java.security.cert.X509Certificate;
54
55
56 public class TomcatGeronimoRealm extends JAASRealm {
57
58 private static final Log log = LogFactory.getLog(TomcatGeronimoRealm.class);
59
60 private static ThreadLocal currentRequestWrapperName = new ThreadLocal();
61
62 /**
63 * Descriptive information about this <code>Realm</code> implementation.
64 */
65 protected static final String info = "org.apache.geronimo.tomcat.TomcatGeronimoRealm/1.0";
66
67 /**
68 * Descriptive information about this <code>Realm</code> implementation.
69 */
70 protected static final String name = "TomcatGeronimoRealm";
71
72 public TomcatGeronimoRealm() {
73
74 }
75
76 public static String setRequestWrapperName(String requestWrapperName) {
77 String old = (String) currentRequestWrapperName.get();
78 currentRequestWrapperName.set(requestWrapperName);
79 return old;
80 }
81
82 /**
83 * Enforce any user data constraint required by the security constraint
84 * guarding this request URI. Return <code>true</code> if this constraint
85 * was not violated and processing should continue, or <code>false</code>
86 * if we have created a response already.
87 *
88 * @param request Request we are processing
89 * @param response Response we are creating
90 * @param constraints Security constraint being checked
91 * @throws IOException if an input/output error occurs
92 */
93 public boolean hasUserDataPermission(Request request,
94 Response response,
95 SecurityConstraint[] constraints)
96 throws IOException {
97
98
99 Subject subject = null;
100 try {
101
102
103
104
105 subject = (Subject) PolicyContext.getContext(PolicyContextHandlerContainerSubject.HANDLER_KEY);
106
107 } catch (PolicyContextException e) {
108 log.error(e);
109 }
110
111
112 if (subject == null)
113 return super.hasUserDataPermission(request, response, constraints);
114
115 ContextManager.setCallers(subject, subject);
116
117 try {
118
119 AccessControlContext acc = ContextManager.getCurrentContext();
120
121 /**
122 * JACC v1.0 secion 4.1.1
123 */
124 WebUserDataPermission wudp = new WebUserDataPermission(request);
125 acc.checkPermission(wudp);
126
127 } catch (AccessControlException ace) {
128 response.sendError(Response.SC_FORBIDDEN);
129 return false;
130 }
131
132 return true;
133 }
134
135 /**
136 * Perform access control based on the specified authorization constraint.
137 * Return <code>true</code> if this constraint is satisfied and processing
138 * should continue, or <code>false</code> otherwise.
139 *
140 * @param request Request we are processing
141 * @param response Response we are creating
142 * @param constraints Security constraints we are enforcing
143 * @param context The Context to which client of this class is attached.
144 * @throws java.io.IOException if an input/output error occurs
145 */
146 public boolean hasResourcePermission(Request request,
147 Response response,
148 SecurityConstraint[] constraints,
149 Context context)
150 throws IOException {
151
152
153
154 LoginConfig config = context.getLoginConfig();
155 if ((config != null) &&
156 (org.apache.catalina.realm.Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
157 String requestURI = request.getDecodedRequestURI();
158 String loginPage = context.getPath() + config.getLoginPage();
159 if (loginPage.equals(requestURI)) {
160 if (log.isDebugEnabled())
161 log.debug(" Allow access to login page " + loginPage);
162 return (true);
163 }
164 String errorPage = context.getPath() + config.getErrorPage();
165 if (errorPage.equals(requestURI)) {
166 if (log.isDebugEnabled())
167 log.debug(" Allow access to error page " + errorPage);
168 return (true);
169 }
170 if (requestURI.endsWith(org.apache.catalina.realm.Constants.FORM_ACTION)) {
171 if (log.isDebugEnabled())
172 log.debug(" Allow access to username/password submission");
173 return (true);
174 }
175 }
176
177
178 currentRequestWrapperName.set(request.getWrapper().getName());
179
180
181 Principal principal = request.getUserPrincipal();
182
183
184 if (principal == null) {
185 return request.isSecure();
186
187 } else {
188 Subject currentCaller = ((JAASTomcatPrincipal) principal).getSubject();
189 ContextManager.setCallers(currentCaller, currentCaller);
190 }
191
192 try {
193
194 AccessControlContext acc = ContextManager.getCurrentContext();
195
196
197 /**
198 * JACC v1.0 section 4.1.2
199 */
200 acc.checkPermission(new WebResourcePermission(request));
201
202 } catch (AccessControlException ace) {
203 response.sendError(Response.SC_FORBIDDEN);
204 return false;
205 }
206
207 return true;
208
209 }
210
211 /**
212 * Return <code>true</code> if the specified Principal has the specified
213 * security role, within the context of this Realm; otherwise return
214 * <code>false</code>.
215 *
216 * @param principal Principal for whom the role is to be checked
217 * @param role Security role to be checked
218 */
219 public boolean hasRole(Principal principal, String role) {
220
221 if ((principal == null) || (role == null) || !(principal instanceof JAASTomcatPrincipal)) {
222 return false;
223 }
224
225 String name = (String)currentRequestWrapperName.get();
226
227 /**
228 * JACC v1.0 secion B.19
229 */
230 if (name == null || name.equals("jsp")) {
231 name = "";
232 }
233
234
235 Subject currentCaller = ((JAASTomcatPrincipal) principal).getSubject();
236 ContextManager.setCallers(currentCaller, currentCaller);
237
238 AccessControlContext acc = ContextManager.getCurrentContext();
239
240 try {
241 /**
242 * JACC v1.0 section 4.1.3
243 */
244 acc.checkPermission(new WebRoleRefPermission(name, role));
245 } catch (AccessControlException e) {
246 return false;
247 }
248
249 return true;
250 }
251
252 /**
253 * Return the <code>Principal</code> associated with the specified
254 * username and credentials, if there is one; otherwise return
255 * <code>null</code>.
256 * <p/>
257 * If there are any errors with the JDBC connection, executing the query or
258 * anything we return null (don't authenticate). This event is also logged,
259 * and the connection will be closed so that a subsequent request will
260 * automatically re-open it.
261 *
262 * @param username Username of the <code>Principal</code> to look up
263 * @param credentials Password or other credentials to use in authenticating this
264 * username
265 */
266 public Principal authenticate(String username, String credentials) {
267
268 char[] cred = credentials == null? null: credentials.toCharArray();
269 CallbackHandler callbackHandler = new PasswordCallbackHandler(username, cred);
270 return authenticate(callbackHandler, username);
271 }
272
273 public Principal authenticate(X509Certificate[] certs) {
274 if (certs == null || certs.length == 0) {
275 return null;
276 }
277 CallbackHandler callbackHandler = new CertificateChainCallbackHandler(certs);
278 String principalName = certs[0].getSubjectX500Principal().getName();
279 return authenticate(callbackHandler, principalName);
280 }
281
282 public Principal authenticate(CallbackHandler callbackHandler, String principalName) {
283
284
285 try {
286
287 if ( (principalName!=null) && (!principalName.equals("")) ) {
288 LoginContext loginContext = null;
289 if (appName == null)
290 appName = "Tomcat";
291
292 if (log.isDebugEnabled())
293 log.debug(sm.getString("jaasRealm.beginLogin", principalName, appName));
294
295
296 ClassLoader ocl = null;
297
298 if (isUseContextClassLoader()) {
299 ocl = Thread.currentThread().getContextClassLoader();
300 Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
301 }
302
303 try {
304 loginContext = new LoginContext(appName, callbackHandler);
305 } catch (Throwable e) {
306 log.error(sm.getString("jaasRealm.unexpectedError"), e);
307 return (null);
308 } finally {
309 if (isUseContextClassLoader()) {
310 Thread.currentThread().setContextClassLoader(ocl);
311 }
312 }
313
314 if (log.isDebugEnabled())
315 log.debug("Login context created " + principalName);
316
317
318 Subject subject;
319 try {
320 loginContext.login();
321 Subject tempSubject = loginContext.getSubject();
322 if (tempSubject == null) {
323 if (log.isDebugEnabled())
324 log.debug(sm.getString("jaasRealm.failedLogin", principalName));
325 return (null);
326 }
327
328 subject = ContextManager.getServerSideSubject(tempSubject);
329 if (subject == null) {
330 if (log.isDebugEnabled())
331 log.debug(sm.getString("jaasRealm.failedLogin", principalName));
332 return (null);
333 }
334
335 ContextManager.setCallers(subject, subject);
336
337 } catch (AccountExpiredException e) {
338 if (log.isDebugEnabled())
339 log.debug(sm.getString("jaasRealm.accountExpired", principalName));
340 return (null);
341 } catch (CredentialExpiredException e) {
342 if (log.isDebugEnabled())
343 log.debug(sm.getString("jaasRealm.credentialExpired", principalName));
344 return (null);
345 } catch (FailedLoginException e) {
346 if (log.isDebugEnabled())
347 log.debug(sm.getString("jaasRealm.failedLogin", principalName));
348 return (null);
349 } catch (LoginException e) {
350 log.warn(sm.getString("jaasRealm.loginException", principalName), e);
351 return (null);
352 } catch (Throwable e) {
353 log.error(sm.getString("jaasRealm.unexpectedError"), e);
354 return (null);
355 }
356
357 if (log.isDebugEnabled())
358 log.debug(sm.getString("jaasRealm.loginContextCreated", principalName));
359
360
361
362
363
364
365
366
367
368
369
370 JAASTomcatPrincipal jaasPrincipal = new JAASTomcatPrincipal(principalName);
371 jaasPrincipal.setSubject(subject);
372
373 return (jaasPrincipal);
374 }
375 else {
376 if (log.isDebugEnabled())
377 log.debug("Login Failed - null userID");
378 return null;
379 }
380
381 } catch (Throwable t) {
382 log.error("error ", t);
383 return null;
384 }
385 }
386 /**
387 * Prepare for active use of the public methods of this <code>Component</code>.
388 *
389 * @throws org.apache.catalina.LifecycleException
390 * if this component detects a fatal error
391 * that prevents it from being started
392 */
393 public void start() throws LifecycleException {
394
395
396 super.start();
397
398 }
399
400 /**
401 * Gracefully shut down active use of the public methods of this <code>Component</code>.
402 *
403 * @throws LifecycleException if this component detects a fatal error
404 * that needs to be reported
405 */
406 public void stop() throws LifecycleException {
407
408
409 super.stop();
410
411 }
412 }