001: /*
002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/realm/JAASRealm.java,v 1.4 2002/06/18 09:14:49 remm Exp $
003: * $Revision: 1.4 $
004: * $Date: 2002/06/18 09:14:49 $
005: *
006: * ====================================================================
007: * The Apache Software License, Version 1.1
008: *
009: * Copyright (c) 2001-2002 The Apache Software Foundation. All rights
010: * reserved.
011: *
012: * Redistribution and use in source and binary forms, with or without
013: * modification, are permitted provided that the following conditions
014: * are met:
015: *
016: * 1. Redistributions of source code must retain the above copyright
017: * notice, this list of conditions and the following disclaimer.
018: *
019: * 2. Redistributions in binary form must reproduce the above copyright
020: * notice, this list of conditions and the following disclaimer in
021: * the documentation and/or other materials provided with the
022: * distribution.
023: *
024: * 3. The end-user documentation included with the redistribution, if
025: * any, must include the following acknowlegement:
026: * "This product includes software developed by the
027: * Apache Software Foundation (http://www.apache.org/)."
028: * Alternately, this acknowlegement may appear in the software itself,
029: * if and wherever such third-party acknowlegements normally appear.
030: *
031: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
032: * Foundation" must not be used to endorse or promote products derived
033: * from this software without prior written permission. For written
034: * permission, please contact apache@apache.org.
035: *
036: * 5. Products derived from this software may not be called "Apache"
037: * nor may "Apache" appear in their names without prior written
038: * permission of the Apache Group.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
044: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
045: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
046: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Apache Software Foundation. For more
056: * information on the Apache Software Foundation, please see
057: * <http://www.apache.org/>.
058: *
059: * [Additional notices, if required by prior licensing conditions]
060: *
061: */
062:
063: package org.apache.catalina.realm;
064:
065: import java.security.Principal;
066: import java.util.ArrayList;
067: import java.util.Iterator;
068: import java.util.Set;
069: import javax.security.auth.Subject;
070: import javax.security.auth.login.AccountExpiredException;
071: import javax.security.auth.login.CredentialExpiredException;
072: import javax.security.auth.login.FailedLoginException;
073: import javax.security.auth.login.LoginContext;
074: import javax.security.auth.login.LoginException;
075: import org.apache.catalina.Lifecycle;
076: import org.apache.catalina.LifecycleException;
077: import org.apache.catalina.util.StringManager;
078:
079: /**
080: * <p>Implmentation of <b>Realm</b> that authenticates users via the <em>Java
081: * Authentication and Authorization Service</em> (JAAS). JAAS support requires
082: * either JDK 1.4 (which includes it as part of the standard platform) or
083: * JDK 1.3 (with the plug-in <code>jaas.jar</code> file).</p>
084: *
085: * <p>The value configured for the <code>appName</code> property is passed to
086: * the <code>javax.security.auth.login.LoginContext</code> constructor, to
087: * specify the <em>application name</em> used to select the set of relevant
088: * <code>LoginModules</code> required.</p>
089: *
090: * <p>The JAAS Specification describes the result of a successful login as a
091: * <code>javax.security.auth.Subject</code> instance, which can contain zero
092: * or more <code>java.security.Principal</code> objects in the return value
093: * of the <code>Subject.getPrincipals()</code> method. However, it provides
094: * no guidance on how to distinguish Principals that describe the individual
095: * user (and are thus appropriate to return as the value of
096: * request.getUserPrincipal() in a web application) from the Principal(s)
097: * that describe the authorized roles for this user. To maintain as much
098: * independence as possible from the underlying <code>LoginMethod</code>
099: * implementation executed by JAAS, the following policy is implemented by
100: * this Realm:</p>
101: * <ul>
102: * <li>The JAAS <code>LoginModule</code> is assumed to return a
103: * <code>Subject with at least one <code>Principal</code> instance
104: * representing the user himself or herself, and zero or more separate
105: * <code>Principals</code> representing the security roles authorized
106: * for this user.</li>
107: * <li>On the <code>Principal</code> representing the user, the Principal
108: * name is an appropriate value to return via the Servlet API method
109: * <code>HttpServletRequest.getRemoteUser()</code>.</li>
110: * <li>On the <code>Principals</code> representing the security roles, the
111: * name is the name of the authorized security role.</li>
112: * <li>This Realm will be configured with two lists of fully qualified Java
113: * class names of classes that implement
114: * <code>java.security.Principal</code> - one that identifies class(es)
115: * representing a user, and one that identifies class(es) representing
116: * a security role.</li>
117: * <li>As this Realm iterates over the <code>Principals</code> returned by
118: * <code>Subject.getPrincipals()</code>, it will identify the first
119: * <code>Principal</code> that matches the "user classes" list as the
120: * <code>Principal</code> for this user.</li>
121: * <li>As this Realm iterates over the <code>Princpals</code> returned by
122: * <code>Subject.getPrincipals()</code>, it will accumulate the set of
123: * all <code>Principals</code> matching the "role classes" list as
124: * identifying the security roles for this user.</li>
125: * <li>It is a configuration error for the JAAS login method to return a
126: * validated <code>Subject</code> without a <code>Principal</code> that
127: * matches the "user classes" list.</li>
128: * </ul>
129: *
130: * @author Craig R. McClanahan
131: * @version $Revision: 1.4 $ $Date: 2002/06/18 09:14:49 $
132: */
133:
134: public class JAASRealm extends RealmBase {
135:
136: // ----------------------------------------------------- Instance Variables
137:
138: /**
139: * The application name passed to the JAAS <code>LoginContext</code>,
140: * which uses it to select the set of relevant <code>LoginModules</code>.
141: */
142: protected String appName = "Tomcat";
143:
144: /**
145: * Descriptive information about this Realm implementation.
146: */
147: protected static final String info = "org.apache.catalina.realm.JAASRealm/1.0";
148:
149: /**
150: * Descriptive information about this Realm implementation.
151: */
152: protected static final String name = "JAASRealm";
153:
154: /**
155: * The list of role class names, split out for easy processing.
156: */
157: protected ArrayList roleClasses = new ArrayList();
158:
159: /**
160: * The string manager for this package.
161: */
162: protected static final StringManager sm = StringManager
163: .getManager(Constants.Package);
164:
165: /**
166: * The set of user class names, split out for easy processing.
167: */
168: protected ArrayList userClasses = new ArrayList();
169:
170: // ------------------------------------------------------------- Properties
171:
172: /**
173: * setter for the appName member variable
174: */
175: public void setAppName(String name) {
176: appName = name;
177: }
178:
179: /**
180: * getter for the appName member variable
181: */
182: public String getAppName() {
183: return appName;
184: }
185:
186: /**
187: * Comma-delimited list of <code>javax.security.Principal</code> classes
188: * that represent security roles.
189: */
190: protected String roleClassNames = null;
191:
192: public String getRoleClassNames() {
193: return (this .roleClassNames);
194: }
195:
196: public void setRoleClassNames(String roleClassNames) {
197: this .roleClassNames = roleClassNames;
198: roleClasses.clear();
199: String temp = this .roleClassNames;
200: if (temp == null) {
201: return;
202: }
203: while (true) {
204: int comma = temp.indexOf(',');
205: if (comma < 0) {
206: break;
207: }
208: roleClasses.add(temp.substring(0, comma).trim());
209: temp = temp.substring(comma + 1);
210: }
211: temp = temp.trim();
212: if (temp.length() > 0) {
213: roleClasses.add(temp);
214: }
215: }
216:
217: /**
218: * Comma-delimited list of <code>javax.security.Principal</code> classes
219: * that represent individual users.
220: */
221: protected String userClassNames = null;
222:
223: public String getUserClassNames() {
224: return (this .userClassNames);
225: }
226:
227: public void setUserClassNames(String userClassNames) {
228: this .userClassNames = userClassNames;
229: userClasses.clear();
230: String temp = this .userClassNames;
231: if (temp == null) {
232: return;
233: }
234: while (true) {
235: int comma = temp.indexOf(',');
236: if (comma < 0) {
237: break;
238: }
239: userClasses.add(temp.substring(0, comma).trim());
240: temp = temp.substring(comma + 1);
241: }
242: temp = temp.trim();
243: if (temp.length() > 0) {
244: userClasses.add(temp);
245: }
246: }
247:
248: // --------------------------------------------------------- Public Methods
249:
250: /**
251: * Return the Principal associated with the specified username and
252: * credentials, if there is one; otherwise return <code>null</code>.
253: *
254: * If there are any errors with the JDBC connection, executing
255: * the query or anything we return null (don't authenticate). This
256: * event is also logged, and the connection will be closed so that
257: * a subsequent request will automatically re-open it.
258: *
259: * @param username Username of the Principal to look up
260: * @param credentials Password or other credentials to use in
261: * authenticating this username
262: */
263: public Principal authenticate(String username, String credentials) {
264:
265: // Establish a LoginContext to use for authentication
266: LoginContext loginContext = null;
267: try {
268: loginContext = new LoginContext(
269: appName,
270: new JAASCallbackHandler(this , username, credentials));
271: } catch (LoginException e) {
272: log(sm.getString("jaasRealm.loginException", username), e);
273: return (null);
274: }
275:
276: // Negotiate a login via this LoginContext
277: Subject subject = null;
278: try {
279: loginContext.login();
280: subject = loginContext.getSubject();
281: if (subject == null) {
282: if (debug >= 2)
283: log(sm.getString("jaasRealm.failedLogin", username));
284: return (null);
285: }
286: } catch (AccountExpiredException e) {
287: if (debug >= 2)
288: log(sm.getString("jaasRealm.accountExpired", username));
289: return (null);
290: } catch (CredentialExpiredException e) {
291: if (debug >= 2)
292: log(sm.getString("jaasRealm.credentialExpired",
293: username));
294: return (null);
295: } catch (FailedLoginException e) {
296: if (debug >= 2)
297: log(sm.getString("jaasRealm.failedLogin", username));
298: return (null);
299: } catch (LoginException e) {
300: log(sm.getString("jaasRealm.loginException", username), e);
301: return (null);
302: }
303:
304: // Return the appropriate Principal for this authenticated Subject
305: Principal principal = createPrincipal(subject);
306: if (principal == null) {
307: log(sm.getString("jaasRealm.authenticateError", username));
308: return (null);
309: }
310: if (debug >= 2) {
311: log(sm.getString("jaasRealm.authenticateSuccess", username));
312: }
313: return (principal);
314:
315: }
316:
317: // -------------------------------------------------------- Package Methods
318:
319: // ------------------------------------------------------ Protected Methods
320:
321: /**
322: * Return a short name for this Realm implementation.
323: */
324: protected String getName() {
325:
326: return (this .name);
327:
328: }
329:
330: /**
331: * Return the password associated with the given principal's user name.
332: */
333: protected String getPassword(String username) {
334:
335: return (null);
336:
337: }
338:
339: /**
340: * Return the Principal associated with the given user name.
341: */
342: protected Principal getPrincipal(String username) {
343:
344: return (null);
345:
346: }
347:
348: /**
349: * Construct and return a <code>java.security.Principal</code> instance
350: * representing the authenticated user for the specified Subject. If no
351: * such Principal can be constructed, return <code>null</code>.
352: *
353: * @param subject The Subject representing the logged in user
354: */
355: protected Principal createPrincipal(Subject subject) {
356: // Prepare to scan the Principals for this Subject
357: String username = null;
358: String password = null; // Will not be carried forward
359: ArrayList roles = new ArrayList();
360:
361: // Scan the Principals for this Subject
362: Iterator principals = subject.getPrincipals().iterator();
363: while (principals.hasNext()) {
364: Principal principal = (Principal) principals.next();
365: String principalClass = principal.getClass().getName();
366: if ((username == null)
367: && userClasses.contains(principalClass)) {
368: username = principal.getName();
369: }
370: if (roleClasses.contains(principalClass)) {
371: roles.add(principal.getName());
372: }
373: }
374:
375: // Create the resulting Principal for our authenticated user
376: if (username != null) {
377: return (new GenericPrincipal(this , username, password,
378: roles));
379: } else {
380: return (null);
381: }
382:
383: }
384:
385: // ------------------------------------------------------ Lifecycle Methods
386:
387: /**
388: *
389: * Prepare for active use of the public methods of this Component.
390: *
391: * @exception LifecycleException if this component detects a fatal error
392: * that prevents it from being started
393: */
394: public void start() throws LifecycleException {
395:
396: // Perform normal superclass initialization
397: super .start();
398:
399: }
400:
401: /**
402: * Gracefully shut down active use of the public methods of this Component.
403: *
404: * @exception LifecycleException if this component detects a fatal error
405: * that needs to be reported
406: */
407: public void stop() throws LifecycleException {
408:
409: // Perform normal superclass finalization
410: super.stop();
411:
412: }
413:
414: }
|