package dk.topsecurity;
import java.util.*;
import java.io.IOException;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
/**
* <p> LoginModule authenticates users, using a password.
*
* <p> Includes a sequence (arrays) of users and their passwords. Indexes in the
* arrays are the same for corresponding user and password.
*
* <p> On successfull authentication, a <code>TopsecurityPrincipal</code> object
* with the user name is added to the Subject.
*/
public class TopsecurityLoginModule implements LoginModule {
/**
* <p> LoginModule recognizes the debug option (in the 'initialize' method).
* If initialized true in the login Configuration, debug will be output to System.out.
*/
private boolean debug = false;
// validation objects
private Subject subject;
private TopsecurityPrincipal entity;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
// tracking authentication status
private static final int NOT = 0, OK = 1, COMMIT = 2;
private int status;
// login info
private static final String[] userNames = { "guest", "user1", "user2" };
private static final String[] passwords = { "guest", "pass1", "pass2" };
// current user
private String username;
private char[] password;
/**
* Initializes the LoginModule.<p>
*
* @param subject the Subject to be authenticated as provided through the JAAS interface. <p>
* @param callbackHandler a CallbackHandler for retrieving username and password from the user <p>
* @param sharedState shared LoginModule state. <p>
* @param options options specified in the login Configuration for this particular LoginModule
* (in the java.security.auth.login.config file).
*/
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
status = NOT;
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
// sets options according to the java.security.auth.login.config file
TopsecurityPrincipal.setDebug( "true".equalsIgnoreCase((String)options.get("debug")) );
}//end initialize()
/**
* Simple console trace to system.out for debug purposes only.
* <p>
*
* @param msg the message to be printed to the console
*
*/
private void debugOut(String msg) {
if( debug )
System.out.println("\t[TopsecurityLoginModule] " + msg);
}
/**
* Authenticate the user based on a username and password.
* If the combination of user name/password occurs anywhere in the list,
* authentication succeeds - otherwise not.
*
* <p>
*
* @return true in all cases since this LoginModule should not be ignored.
* @exception FailedLoginException if authentication fails. <p>
* @exception LoginException if this LoginModule is unable to perform the authentication.
*/
public boolean login() throws LoginException {
// prompt for a user name and password
if( callbackHandler == null )
throw new LoginException( "Error: no CallbackHandler available to retrieve user credentials");
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback( "\nuser name: " );
callbacks[1] = new PasswordCallback( "password: ", false );
try {
callbackHandler.handle(callbacks); //get the user credentials
username = ((NameCallback)callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
if (tmpPassword == null) // treat a NULL password as an empty password
tmpPassword = new char[0];
password = new char[tmpPassword.length];
System.arraycopy( tmpPassword, 0,password, 0, tmpPassword.length );
((PasswordCallback)callbacks[1]).clearPassword(); //wipe out occurrences in memory
} catch( java.io.IOException ioe ) {
throw new LoginException(ioe.toString());
} catch( UnsupportedCallbackException uce ) {
throw new LoginException( "Error: " + uce.getCallback().toString() +
" not available to authenticate user." );
}//end try/catch
// verify the username/password
String passwordString = new String( password );
debugOut("user entered user name: " + username );
debugOut("user entered password: " + passwordString + "\n");
for( int i = 0; i < userNames.length; i++ ) {
if( username.equals( userNames[i] ) && passwordString.equals( passwords[i] ) ) {
// because the username/passwords matches in list, authentication succeeded!!!
debugOut("User #" + i + " authentication succeeded." );
status = OK;
return true;
} else {
// authentication failed -- clean out state
debugOut("User #" + i + " authentication failed." );
}//end if/else
}//end for(;;)
return false;
}//end login()
/**
* <p> Method called if the LoginContext's
* overall authentication succeeded (the relevant REQUIRED,
* REQUISITE, SUFFICIENT and OPTIONAL LoginModules
* mentioned in file java.security.auth.login.config.. did succeed).
*
* <p> If this LoginModule's authentication succeeded
* (status stored in the variable 'status' by the
* .login() method), then the .commit() method associates a
* TopsecurityPrincipal with the Subject located in the
* LoginModule. If this LoginModule's authentication failed,
* any state originally saved is removed.
*
* <p>
*
* @exception LoginException if the commit fails.
* @return true if this LoginModule's own .login() and .commit()
* attempts succeeded, false otherwise.
*/
public boolean commit() throws LoginException {
if(status == NOT || subject == null) {
return false;
} else {
// add a Principal (authenticated identity) to the Subject
// assume the user we authenticated is the TopsecurityPrincipal
entity = new TopsecurityPrincipal(username);
Set entities = subject.getPrincipals();
if( !entities.contains(entity) )
entities.add(entity);
debugOut("added TopsecurityPrincipal to Subject");
// in any case, clean out state
username = null;
password = null;
status = COMMIT;
return true;
}//end if
}//end commit()
/**
* <p> This method is called if the LoginContext's
* overall authentication failed.
* (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
* mentioned in file java.security.auth.login.config.. did not succeed).
*
* <p> If this LoginModule's authentication succeeded
* (status stored in the variable 'status' by the
* .login() method and .commit() methods),
* then the .abort() method cleans up any state that was originally saved.
*
* <p>
*
* @exception LoginException if the abort fails.
* @return false if this LoginModule's own login and/or commit attempts
* failed, true otherwise.
*/
public boolean abort() throws LoginException {
if(status == NOT) {
return false;
} else if( status == OK ) {
// login succeeded but overall authentication failed
username = null;
if( password != null ) password = null;
entity = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}//end if/else
status = NOT;
return true;
}//end abort()
/**
* Logout of the user.
*
* <p>Removes the TopsecurityPrincipal added by the .commit() method.<p>
*
* @exception LoginException if the logout fails.
* @return true in all cases since this LoginModule should not be ignored.
*/
public boolean logout() throws LoginException {
subject.getPrincipals().remove(entity);
status = NOT;
username = null;
if( password != null ) password = null;
entity = null;
return true;
}//end logout()
}//end class TopsecurityLoginModule