001: /*
002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/realm/JAASMemoryLoginModule.java,v 1.1 2001/11/13 22:42:31 craigmcc Exp $
003: * $Revision: 1.1 $
004: * $Date: 2001/11/13 22:42:31 $
005: *
006: * ====================================================================
007: * The Apache Software License, Version 1.1
008: *
009: * Copyright (c) 1999 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.beans.PropertyChangeListener;
066: import java.io.File;
067: import java.io.IOException;
068: import java.util.ArrayList;
069: import java.util.HashMap;
070: import java.util.Map;
071: import java.security.Principal;
072: import java.security.cert.X509Certificate;
073: import javax.security.auth.Subject;
074: import javax.security.auth.callback.Callback;
075: import javax.security.auth.callback.CallbackHandler;
076: import javax.security.auth.callback.NameCallback;
077: import javax.security.auth.callback.PasswordCallback;
078: import javax.security.auth.callback.UnsupportedCallbackException;
079: import javax.security.auth.login.FailedLoginException;
080: import javax.security.auth.login.LoginException;
081: import javax.security.auth.spi.LoginModule;
082: import org.apache.catalina.Container;
083: import org.apache.catalina.Realm;
084: import org.apache.commons.digester.Digester;
085:
086: /**
087: * <p>Implementation of the JAAS <strong>LoginModule</strong> interface,
088: * primarily for use in testing <code>JAASRealm</code>. It utilizes an
089: * XML-format data file of username/password/role information identical to
090: * that supported by <code>org.apache.catalina.realm.MemoryRealm</code>
091: * (except that digested passwords are not supported).</p>
092: *
093: * <p>This class recognizes the following string-valued options, which are
094: * specified in the configuration file (and passed to our constructor in
095: * the <code>options</code> argument:</p>
096: * <ul>
097: * <li><strong>debug</strong> - Set to "true" to get debugging messages
098: * generated to System.out. The default value is <code>false</code>.</li>
099: * <li><strong>pathname</strong> - Relative (to the pathname specified by the
100: * "catalina.base" system property) or absolute pahtname to the
101: * XML file containing our user information, in the format supported by
102: * {@link MemoryRealm}. The default value matches the MemoryRealm
103: * default.</li>
104: * </ul>
105: *
106: * <p><strong>IMPLEMENTATION NOTE</strong> - This class implements
107: * <code>Realm</code> only to satisfy the calling requirements of the
108: * <code>GenericPrincipal</code> constructor. It does not actually perform
109: * the functionality required of a <code>Realm</code> implementation.</p>
110: *
111: * @author Craig R. McClanahan
112: * @version $Revision: 1.1 $ $Date: 2001/11/13 22:42:31 $
113: */
114:
115: public class JAASMemoryLoginModule implements LoginModule, Realm {
116:
117: // ----------------------------------------------------- Instance Variables
118:
119: /**
120: * The callback handler responsible for answering our requests.
121: */
122: protected CallbackHandler callbackHandler = null;
123:
124: /**
125: * Has our own <code>commit()</code> returned successfully?
126: */
127: protected boolean committed = false;
128:
129: /**
130: * Should we log debugging messages?
131: */
132: protected boolean debug = false;
133:
134: /**
135: * The configuration information for this <code>LoginModule</code>.
136: */
137: protected Map options = null;
138:
139: /**
140: * The absolute or relative pathname to the XML configuration file.
141: */
142: protected String pathname = "conf/tomcat-users.xml";
143:
144: /**
145: * The <code>Principal</code> identified by our validation, or
146: * <code>null</code> if validation falied.
147: */
148: protected Principal principal = null;
149:
150: /**
151: * The set of <code>Principals</code> loaded from our configuration file.
152: */
153: protected HashMap principals = new HashMap();
154:
155: /**
156: * The state information that is shared with other configured
157: * <code>LoginModule</code> instances.
158: */
159: protected Map sharedState = null;
160:
161: /**
162: * The subject for which we are performing authentication.
163: */
164: protected Subject subject = null;
165:
166: // --------------------------------------------------------- Public Methods
167:
168: /**
169: * Add a new user to the in-memory database.
170: *
171: * @param username User's username
172: * @param password User's password (clear text)
173: * @param roles Comma-delimited set of roles associated with this user
174: */
175: void addUser(String username, String password, String roles) {
176:
177: // Accumulate the list of roles for this user
178: ArrayList list = new ArrayList();
179: roles += ",";
180: while (true) {
181: int comma = roles.indexOf(',');
182: if (comma < 0)
183: break;
184: String role = roles.substring(0, comma).trim();
185: list.add(role);
186: roles = roles.substring(comma + 1);
187: }
188:
189: // Construct and cache the Principal for this user
190: GenericPrincipal principal = new GenericPrincipal(this ,
191: username, password, list);
192: principals.put(username, principal);
193:
194: }
195:
196: /**
197: * Phase 2 of authenticating a <code>Subject</code> when Phase 1
198: * fails. This method is called if the <code>LoginContext</code>
199: * failed somewhere in the overall authentication chain.
200: *
201: * @return <code>true</code> if this method succeeded, or
202: * <code>false</code> if this <code>LoginModule</code> should be
203: * ignored
204: *
205: * @exception LoginException if the abort fails
206: */
207: public boolean abort() throws LoginException {
208:
209: // If our authentication was not successful, just return false
210: if (principal == null)
211: return (false);
212:
213: // Clean up if overall authentication failed
214: if (committed)
215: logout();
216: else {
217: committed = false;
218: principal = null;
219: }
220: return (true);
221:
222: }
223:
224: /**
225: * Phase 2 of authenticating a <code>Subject</code> when Phase 1
226: * was successful. This method is called if the <code>LoginContext</code>
227: * succeeded in the overall authentication chain.
228: *
229: * @return <code>true</code> if the authentication succeeded, or
230: * <code>false</code> if this <code>LoginModule</code> should be
231: * ignored
232: *
233: * @exception LoginException if the commit fails
234: */
235: public boolean commit() throws LoginException {
236:
237: // If authentication was not successful, just return false
238: if (principal == null)
239: return (false);
240:
241: // Add our Principal to the Subject if needed
242: if (!subject.getPrincipals().contains(principal))
243: subject.getPrincipals().add(principal);
244: committed = true;
245: return (true);
246:
247: }
248:
249: /**
250: * Initialize this <code>LoginModule</code> with the specified
251: * configuration information.
252: *
253: * @param subject The <code>Subject</code> to be authenticated
254: * @param callbackHandler A <code>CallbackHandler</code> for communicating
255: * with the end user as necessary
256: * @param sharedState State information shared with other
257: * <code>LoginModule</code> instances
258: * @param options Configuration information for this specific
259: * <code>LoginModule</code> instance
260: */
261: public void initialize(Subject subject,
262: CallbackHandler callbackHandler, Map sharedState,
263: Map options) {
264:
265: // Save configuration values
266: this .subject = subject;
267: this .callbackHandler = callbackHandler;
268: this .sharedState = sharedState;
269: this .options = options;
270:
271: // Perform instance-specific initialization
272: this .debug = "true".equalsIgnoreCase((String) options
273: .get("debug"));
274: if (options.get("pathname") != null)
275: this .pathname = (String) options.get("pathname");
276:
277: // Load our defined Principals
278: load();
279:
280: }
281:
282: /**
283: * Phase 1 of authenticating a <code>Subject</code>.
284: *
285: * @return <code>true</code> if the authentication succeeded, or
286: * <code>false</code> if this <code>LoginModule</code> should be
287: * ignored
288: *
289: * @exception LoginException if the authentication fails
290: */
291: public boolean login() throws LoginException {
292:
293: // Set up our CallbackHandler requests
294: if (callbackHandler == null)
295: throw new LoginException("No CallbackHandler specified");
296: Callback callbacks[] = new Callback[2];
297: callbacks[0] = new NameCallback("Username: ");
298: callbacks[1] = new PasswordCallback("Password: ", false);
299:
300: // Interact with the user to retrieve the username and password
301: String username = null;
302: String password = null;
303: try {
304: callbackHandler.handle(callbacks);
305: username = ((NameCallback) callbacks[0]).getName();
306: password = new String(((PasswordCallback) callbacks[1])
307: .getPassword());
308: } catch (IOException e) {
309: throw new LoginException(e.toString());
310: } catch (UnsupportedCallbackException e) {
311: throw new LoginException(e.toString());
312: }
313:
314: // Validate the username and password we have received
315: principal = null; // FIXME - look up and check password
316:
317: // Report results based on success or failure
318: if (principal != null) {
319: return (true);
320: } else {
321: throw new FailedLoginException(
322: "Username or password is incorrect");
323: }
324:
325: }
326:
327: /**
328: * Log out this user.
329: *
330: * @return <code>true</code> in all cases because thie
331: * <code>LoginModule</code> should not be ignored
332: *
333: * @exception LoginException if logging out failed
334: */
335: public boolean logout() throws LoginException {
336:
337: subject.getPrincipals().remove(principal);
338: committed = false;
339: principal = null;
340: return (true);
341:
342: }
343:
344: // ---------------------------------------------------------- Realm Methods
345:
346: /**
347: * Return the Container with which this Realm has been associated.
348: */
349: public Container getContainer() {
350:
351: return (null);
352:
353: }
354:
355: /**
356: * Set the Container with which this Realm has been associated.
357: *
358: * @param container The associated Container
359: */
360: public void setContainer(Container container) {
361:
362: ;
363:
364: }
365:
366: /**
367: * Return descriptive information about this Realm implementation and
368: * the corresponding version number, in the format
369: * <code><description>/<version></code>.
370: */
371: public String getInfo() {
372:
373: return (null);
374:
375: }
376:
377: /**
378: * Add a property change listener to this component.
379: *
380: * @param listener The listener to add
381: */
382: public void addPropertyChangeListener(
383: PropertyChangeListener listener) {
384:
385: ;
386:
387: }
388:
389: /**
390: * Return the Principal associated with the specified username and
391: * credentials, if there is one; otherwise return <code>null</code>.
392: *
393: * @param username Username of the Principal to look up
394: * @param credentials Password or other credentials to use in
395: * authenticating this username
396: */
397: public Principal authenticate(String username, String credentials) {
398:
399: return (null);
400:
401: }
402:
403: /**
404: * Return the Principal associated with the specified username and
405: * credentials, if there is one; otherwise return <code>null</code>.
406: *
407: * @param username Username of the Principal to look up
408: * @param credentials Password or other credentials to use in
409: * authenticating this username
410: */
411: public Principal authenticate(String username, byte[] credentials) {
412:
413: return (null);
414:
415: }
416:
417: /**
418: * Return the Principal associated with the specified username, which
419: * matches the digest calculated using the given parameters using the
420: * method described in RFC 2069; otherwise return <code>null</code>.
421: *
422: * @param username Username of the Principal to look up
423: * @param digest Digest which has been submitted by the client
424: * @param nonce Unique (or supposedly unique) token which has been used
425: * for this request
426: * @param realm Realm name
427: * @param md5a2 Second MD5 digest used to calculate the digest :
428: * MD5(Method + ":" + uri)
429: */
430: public Principal authenticate(String username, String digest,
431: String nonce, String nc, String cnonce, String qop,
432: String realm, String md5a2) {
433:
434: return (null);
435:
436: }
437:
438: /**
439: * Return the Principal associated with the specified chain of X509
440: * client certificates. If there is none, return <code>null</code>.
441: *
442: * @param certs Array of client certificates, with the first one in
443: * the array being the certificate of the client itself.
444: */
445: public Principal authenticate(X509Certificate certs[]) {
446:
447: return (null);
448:
449: }
450:
451: /**
452: * Return <code>true</code> if the specified Principal has the specified
453: * security role, within the context of this Realm; otherwise return
454: * <code>false</code>.
455: *
456: * @param principal Principal for whom the role is to be checked
457: * @param role Security role to be checked
458: */
459: public boolean hasRole(Principal principal, String role) {
460:
461: return (false);
462:
463: }
464:
465: /**
466: * Remove a property change listener from this component.
467: *
468: * @param listener The listener to remove
469: */
470: public void removePropertyChangeListener(
471: PropertyChangeListener listener) {
472:
473: ;
474:
475: }
476:
477: // ------------------------------------------------------ Protected Methods
478:
479: /**
480: * Load the contents of our configuration file.
481: */
482: protected void load() {
483:
484: // Validate the existence of our configuration file
485: File file = new File(pathname);
486: if (!file.isAbsolute())
487: file = new File(System.getProperty("catalina.base"),
488: pathname);
489: if (!file.exists() || !file.canRead()) {
490: log("Cannot load configuration file "
491: + file.getAbsolutePath());
492: return;
493: }
494:
495: // Load the contents of our configuration file
496: Digester digester = new Digester();
497: digester.setValidating(false);
498: digester.addRuleSet(new MemoryRuleSet());
499: try {
500: digester.push(this );
501: digester.parse(file);
502: } catch (Exception e) {
503: log("Error processing configuration file "
504: + file.getAbsolutePath(), e);
505: return;
506: }
507:
508: }
509:
510: /**
511: * Log a message.
512: *
513: * @param message The message to be logged
514: */
515: protected void log(String message) {
516:
517: System.out.print("JAASMemoryLoginModule: ");
518: System.out.println(message);
519:
520: }
521:
522: /**
523: * Log a message and associated exception.
524: *
525: * @param message The message to be logged
526: * @param exception The associated exception
527: */
528: protected void log(String message, Throwable exception) {
529:
530: log(message);
531: exception.printStackTrace(System.out);
532:
533: }
534:
535: }
|