1 /*
2  * @(#) $Id: JSTKLoginModule.java,v 1.2 2003/07/08 08:13:53 pankaj Exp $
3  *
4  * Copyright (c) 2002-03 by Pankaj Kumar (http://www.pankaj-k.net). 
5  * All rights reserved.
6  *
7  * The license governing the use of this file can be found in the 
8  * root directory of the containing software.
9  */
10package org.jstk.uam;
11
12import java.util.*;
13import java.io.IOException;
14import java.security.Principal;
15import java.security.AccessController;
16import javax.security.auth.*;
17import javax.security.auth.callback.*;
18import javax.security.auth.login.*;
19import javax.security.auth.spi.*;
20
21public class JSTKLoginModule implements LoginModule {
22
23    private UserAccountManager uam;
24    private boolean initStatus;
25    // initial state
26    private Subject subject;
27    private CallbackHandler callbackHandler;
28    private Map sharedState;
29    private Map options;
30
31    // configurable option
32    private boolean debug = false;
33
34    // the authentication status
35    private boolean succeeded = false;
36    private boolean commitSucceeded = false;
37
38    // username and password
39    private String username;
40    private char[] password;
41
42    // testUser's SamplePrincipal
43    private Principal userPrincipal;
44    private Vector rolePrincipals = null;
45
46    /**
47     * Initialize this <code>LoginModule</code>.
48     *
49     * <p>
50     *
51     * @param subject the <code>Subject</code> to be authenticated. <p>
52     *
53     * @param callbackHandler a <code>CallbackHandler</code> for communicating
54     *          with the end user (prompting for user names and
55     *          passwords, for example). <p>
56     *
57     * @param sharedState shared <code>LoginModule</code> state. <p>
58     *
59     * @param options options specified in the login
60     *          <code>Configuration</code> for this particular
61     *          <code>LoginModule</code>.
62     */
63    public void initialize(Subject subject, CallbackHandler callbackHandler,
64            Map sharedState, Map options) {
65
66        this.subject = subject;
67        this.callbackHandler = callbackHandler;
68        this.sharedState = sharedState;
69        this.options = options;
70
71        debug = "true".equalsIgnoreCase((String)options.get("debug"));
72        String uamfile = (String)options.get("uamfile");
73        if (debug)
74            System.out.println("\t\t[JSTKLoginModule] uamfile = " + uamfile);
75        if (uamfile != null){
76            DefaultUAMPersistenceManager pm = new DefaultUAMPersistenceManager(uamfile);
77            try {
78                uam = UserAccountManager.getInstance(pm);
79                initStatus = true;
80            } catch (Exception e){
81                initStatus = false;
82            }
83        } else {
84            initStatus = false;
85        }
86    }
87
88    /**
89     * Authenticate the user by prompting for a user name and password.
90     *
91     * <p>
92     *
93     * @return true in all cases since this <code>LoginModule</code>
94     *      should not be ignored.
95     *
96     * @exception FailedLoginException if the authentication fails. <p>
97     *
98     * @exception LoginException if this <code>LoginModule</code>
99     *      is unable to perform the authentication.
00     */
01    public boolean login() throws LoginException {
02
03        if (!initStatus)
04            throw new LoginException("Error: JSTKLoginModule initialization failed ");
05
06        // prompt for a user name and password
07        if (callbackHandler == null)
08            throw new LoginException("Error: no CallbackHandler available " +
09                "to garner authentication information from the user");
10
11        Callback[] callbacks = new Callback[2];
12        callbacks[0] = new NameCallback("login: ");
13        callbacks[1] = new PasswordCallback("password: ", false);
14
15        try {
16            callbackHandler.handle(callbacks);
17            username = ((NameCallback)callbacks[0]).getName();
18            char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
19            if (tmpPassword == null) {
20                // treat a NULL password as an empty password
21                tmpPassword = new char[0];
22            }
23            password = new char[tmpPassword.length];
24            System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
25            ((PasswordCallback)callbacks[1]).clearPassword();
26
27        } catch (java.io.IOException ioe) {
28            throw new LoginException(ioe.toString());
29        } catch (UnsupportedCallbackException uce) {
30            throw new LoginException("Error: " + uce.getCallback().toString() +
31                " not available to garner authentication information " +
32                "from the user");
33        }
34
35        // print debugging information
36        if (debug) {
37            System.out.println("\t\t[JSTKLoginModule] " +
38                "user entered user name: " +
39                username);
40            System.out.print("\t\t[JSTKLoginModule] " +
41                "user entered password: ");
42            for (int i = 0; i < password.length; i++)
43                System.out.print(password[i]);
44            System.out.println();
45        }
46
47        // verify the username/password
48        boolean usernameCorrect = true;
49        boolean passwordCorrect = true;
50        try {
51            uam.validate(username, new String(password));
52        } catch (UserAccountManager.NoSuchUserException e){
53            usernameCorrect = false;
54        } catch (UserAccountManager.InvalidPasswordException e){
55            passwordCorrect = false;
56        }
57        if (!usernameCorrect || !passwordCorrect){
58            // authentication failed -- clean out state
59            if (debug)
60                System.out.println("\t\t[JSTKLoginModule] " +
61                    "authentication failed");
62            succeeded = false;
63            username = null;
64            for (int i = 0; i < password.length; i++)
65                password[i] = ' ';
66            password = null;
67            if (!usernameCorrect) {
68                throw new FailedLoginException("User Name Incorrect");
69            } else {
70                throw new FailedLoginException("Password Incorrect");
71            }
72        }
73        if (debug)
74        System.out.println("\t\t[SampleLoginModule] " +
75                "authentication succeeded");
76        succeeded = true;
77        return true;
78    }
79
80    /**
81     * <p> This method is called if the LoginContext's
82     * overall authentication succeeded
83     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
84     * succeeded).
85     *
86     * <p> If this LoginModule's own authentication attempt
87     * succeeded (checked by retrieving the private state saved by the
88     * <code>login</code> method), then this method associates a
89     * <code>SamplePrincipal</code>
90     * with the <code>Subject</code> located in the
91     * <code>LoginModule</code>.  If this LoginModule's own
92     * authentication attempted failed, then this method removes
93     * any state that was originally saved.
94     *
95     * <p>
96     *
97     * @exception LoginException if the commit fails.
98     *
99     * @return true if this LoginModule's own login and commit
00     *      attempts succeeded, or false otherwise.
01     */
02    public boolean commit() throws LoginException {
03    if (succeeded == false) {
04        return false;
05    } else {
06        // add a Principal (authenticated identity)
07        // to the Subject
08
09        // assume the user we authenticated is the SamplePrincipal
10        userPrincipal = uam.getUser(username);
11        if (!subject.getPrincipals().contains(userPrincipal)){
12            subject.getPrincipals().add(userPrincipal);
13            if (debug) {
14                System.out.println("\t\t[JSTKLoginModule] " +
15                    "added user Principal Subject: " + userPrincipal);
16            }
17        }
18        try {
19            Iterator itr = uam.userRoles(username);
20            rolePrincipals = new Vector();
21            while (itr.hasNext()){
22                Principal rolePrincipal = (Principal)itr.next();
23                if (!subject.getPrincipals().contains(rolePrincipal)){
24                    subject.getPrincipals().add(rolePrincipal);
25                    rolePrincipals.add(rolePrincipal);
26                    if (debug) {
27                        System.out.println("\t\t[JSTKLoginModule] " +
28                            "added role Principal to Subject: " + rolePrincipal);
29                    }
30                }
31            }
32        } catch (UserAccountManager.NoSuchUserException e){
33            // Just go on.
34        }
35
36
37
38        // in any case, clean out state
39        username = null;
40        for (int i = 0; i < password.length; i++)
41        password[i] = ' ';
42        password = null;
43
44        commitSucceeded = true;
45        return true;
46    }
47    }
48
49    /**
50     * <p> This method is called if the LoginContext's
51     * overall authentication failed.
52     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
53     * did not succeed).
54     *
55     * <p> If this LoginModule's own authentication attempt
56     * succeeded (checked by retrieving the private state saved by the
57     * <code>login</code> and <code>commit</code> methods),
58     * then this method cleans up any state that was originally saved.
59     *
60     * <p>
61     *
62     * @exception LoginException if the abort fails.
63     *
64     * @return false if this LoginModule's own login and/or commit attempts
65     *      failed, and true otherwise.
66     */
67    public boolean abort() throws LoginException {
68    if (succeeded == false) {
69        return false;
70    } else if (succeeded == true && commitSucceeded == false) {
71        // login succeeded but overall authentication failed
72        succeeded = false;
73        username = null;
74        if (password != null) {
75        for (int i = 0; i < password.length; i++)
76            password[i] = ' ';
77        password = null;
78        }
79        userPrincipal = null;
80        rolePrincipals = null;
81    } else {
82        // overall authentication succeeded and commit succeeded,
83        // but someone else's commit failed
84        logout();
85    }
86    return true;
87    }
88
89    /**
90     * Logout the user.
91     *
92     * <p> This method removes the <code>SamplePrincipal</code>
93     * that was added by the <code>commit</code> method.
94     *
95     * <p>
96     *
97     * @exception LoginException if the logout fails.
98     *
99     * @return true in all cases since this <code>LoginModule</code>
00     *          should not be ignored.
01     */
02    public boolean logout() throws LoginException {
03
04    subject.getPrincipals().remove(userPrincipal);
05    Iterator itr = rolePrincipals.iterator();
06    while (itr.hasNext()){
07        Principal rolePrincipal = (Principal)itr.next();
08        subject.getPrincipals().remove(rolePrincipal);
09    }
10
11    succeeded = false;
12    succeeded = commitSucceeded;
13    username = null;
14    if (password != null) {
15        for (int i = 0; i < password.length; i++)
16        password[i] = ' ';
17        password = null;
18    }
19    userPrincipal = null;
20    rolePrincipals = null;
21    return true;
22    }
23
24    private static void printPrincipals(Subject sub, String label){
25        System.out.println(label);
26        if (sub == null)
27            return;
28        Iterator itr = sub.getPrincipals().iterator();
29        int index = 0;
30        while (itr.hasNext()){
31            System.out.println("Principal# " + index++ + ": " + itr.next());
32        }
33    }
34
35    public static void main(String[] args) throws Exception {
36        System.out.println("------ JSTKLoginModule Test ------");
37
38        LoginContext lc = new LoginContext("Test", new DefaultCallbackHandler());
39        printPrincipals(lc.getSubject(), "Subject Principals before login:");
40        lc.login();
41        printPrincipals(lc.getSubject(), "Subject Principals after login:");
42        lc.logout();
43        printPrincipals(lc.getSubject(), "Subject Principals after logout:");
44    }
45}
46