/*
* Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.security.auth.login;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import java.text.MessageFormat;
import javax.security.auth.Subject;
import javax.security.auth.AuthPermission;
import javax.security.auth.callback.*;
import java.security.AccessController;
import java.security.AccessControlContext;
import sun.security.util.PendingException;
import sun.security.util.ResourcesMgr;
/**
* <p> The <code>LoginContext</code> class describes the basic methods used
* to authenticate Subjects and provides a way to develop an
* application independent of the underlying authentication technology.
* A <code>Configuration</code> specifies the authentication technology, or
* <code>LoginModule</code>, to be used with a particular application.
* Different LoginModules can be plugged in under an application
* without requiring any modifications to the application itself.
*
* <p> In addition to supporting <i>pluggable</i> authentication, this class
* also supports the notion of <i>stacked</i> authentication.
* Applications may be configured to use more than one
* LoginModule. For example, one could
* configure both a Kerberos LoginModule and a smart card
* LoginModule under an application.
*
* <p> A typical caller instantiates a LoginContext with
* a <i>name</i> and a <code>CallbackHandler</code>.
* LoginContext uses the <i>name</i> as the index into a
* Configuration to determine which LoginModules should be used,
* and which ones must succeed in order for the overall authentication to
* succeed. The <code>CallbackHandler</code> is passed to the underlying
* LoginModules so they may communicate and interact with users
* (prompting for a username and password via a graphical user interface,
* for example).
*
* <p> Once the caller has instantiated a LoginContext,
* it invokes the <code>login</code> method to authenticate
* a <code>Subject</code>. The <code>login</code> method invokes
* the configured modules to perform their respective types of authentication
* (username/password, smart card pin verification, etc.).
* Note that the LoginModules will not attempt authentication retries nor
* introduce delays if the authentication fails.
* Such tasks belong to the LoginContext caller.
*
* <p> If the <code>login</code> method returns without
* throwing an exception, then the overall authentication succeeded.
* The caller can then retrieve
* the newly authenticated Subject by invoking the
* <code>getSubject</code> method. Principals and Credentials associated
* with the Subject may be retrieved by invoking the Subject's
* respective <code>getPrincipals</code>, <code>getPublicCredentials</code>,
* and <code>getPrivateCredentials</code> methods.
*
* <p> To logout the Subject, the caller calls
* the <code>logout</code> method. As with the <code>login</code>
* method, this <code>logout</code> method invokes the <code>logout</code>
* method for the configured modules.
*
* <p> A LoginContext should not be used to authenticate
* more than one Subject. A separate LoginContext
* should be used to authenticate each different Subject.
*
* <p> The following documentation applies to all LoginContext constructors:
* <ol>
*
* <li> <code>Subject</code>
* <ul>
* <li> If the constructor has a Subject
* input parameter, the LoginContext uses the caller-specified
* Subject object.
* <p>
* <li> If the caller specifies a <code>null</code> Subject
* and a <code>null</code> value is permitted,
* the LoginContext instantiates a new Subject.
* <p>
* <li> If the constructor does <b>not</b> have a Subject
* input parameter, the LoginContext instantiates a new Subject.
* <p>
* </ul>
*
* <li> <code>Configuration</code>
* <ul>
* <li> If the constructor has a Configuration
* input parameter and the caller specifies a non-null Configuration,
* the LoginContext uses the caller-specified Configuration.
* <p>
* If the constructor does <b>not</b> have a Configuration
* input parameter, or if the caller specifies a <code>null</code>
* Configuration object, the constructor uses the following call to
* get the installed Configuration:
* <pre>
* config = Configuration.getConfiguration();
* </pre>
* For both cases,
* the <i>name</i> argument given to the constructor is passed to the
* <code>Configuration.getAppConfigurationEntry</code> method.
* If the Configuration has no entries for the specified <i>name</i>,
* then the <code>LoginContext</code> calls
* <code>getAppConfigurationEntry</code> with the name, "<i>other</i>"
* (the default entry name). If there is no entry for "<i>other</i>",
* then a <code>LoginException</code> is thrown.
* <p>
* <li> When LoginContext uses the installed Configuration, the caller
* requires the createLoginContext.<em>name</em> and possibly
* createLoginContext.other AuthPermissions. Furthermore, the
* LoginContext will invoke configured modules from within an
* <code>AccessController.doPrivileged</code> call so that modules that
* perform security-sensitive tasks (such as connecting to remote hosts,
* and updating the Subject) will require the respective permissions, but
* the callers of the LoginContext will not require those permissions.
* <p>
* <li> When LoginContext uses a caller-specified Configuration, the caller
* does not require any createLoginContext AuthPermission. The LoginContext
* saves the <code>AccessControlContext</code> for the caller,
* and invokes the configured modules from within an
* <tt>AccessController.doPrivileged</tt> call constrained by that context.
* This means the caller context (stored when the LoginContext was created)
* must have sufficient permissions to perform any security-sensitive tasks
* that the modules may perform.
* <p>
* </ul>
*
* <li> <code>CallbackHandler</code>
* <ul>
* <li> If the constructor has a CallbackHandler
* input parameter, the LoginContext uses the caller-specified
* CallbackHandler object.
* <p>
* <li> If the constructor does <b>not</b> have a CallbackHandler
* input parameter, or if the caller specifies a <code>null</code>
* CallbackHandler object (and a <code>null</code> value is permitted),
* the LoginContext queries the
* <i>auth.login.defaultCallbackHandler</i> security property
* for the fully qualified class name of a default handler implementation.
* If the security property is not set,
* then the underlying modules will not have a
* CallbackHandler for use in communicating
* with users. The caller thus assumes that the configured
* modules have alternative means for authenticating the user.
*
* <p>
* <li> When the LoginContext uses the installed Configuration (instead of
* a caller-specified Configuration, see above),
* then this LoginContext must wrap any
* caller-specified or default CallbackHandler implementation
* in a new CallbackHandler implementation
* whose <code>handle</code> method implementation invokes the
* specified CallbackHandler's <code>handle</code> method in a
* <code>java.security.AccessController.doPrivileged</code> call
* constrained by the caller's current <code>AccessControlContext</code>.
* </ul>
* </ol>
*
* <p> Note that Security Properties
* (such as <code>auth.login.defaultCallbackHandler</code>)
* can be set programmatically via the
* <code>java.security.Security</code> class,
* or statically in the Java security properties file located in the
* file named &lt;JAVA_HOME&gt;/lib/security/java.security.
* &lt;JAVA_HOME&gt; refers to the value of the java.home system property,
* and specifies the directory where the JRE is installed.
*
* @see java.security.Security
* @see javax.security.auth.AuthPermission
* @see javax.security.auth.Subject
* @see javax.security.auth.callback.CallbackHandler
* @see javax.security.auth.login.Configuration
* @see javax.security.auth.spi.LoginModule
*/
public class LoginContext {
private static final String INIT_METHOD = "initialize";
private static final String LOGIN_METHOD = "login";
private static final String COMMIT_METHOD = "commit";
private static final String ABORT_METHOD = "abort";
private static final String LOGOUT_METHOD = "logout";
private static final String OTHER = "other";
private static final String DEFAULT_HANDLER =
"auth.login.defaultCallbackHandler";
private Subject subject = null;
private boolean subjectProvided = false;
private boolean loginSucceeded = false;
private CallbackHandler callbackHandler;
private Map state = new HashMap();
private Configuration config;
private boolean configProvided = false;
private AccessControlContext creatorAcc = null;
private ModuleInfo[] moduleStack;
private ClassLoader contextClassLoader = null;
private static final Class[] PARAMS = { };
// state saved in the event a user-specified asynchronous exception
// was specified and thrown
private int moduleIndex = 0;
private LoginException firstError = null;
private LoginException firstRequiredError = null;
private boolean success = false;
private static final sun.security.util.Debug debug =
sun.security.util.Debug.getInstance("logincontext", "\t[LoginContext]");
private void init(String name) throws LoginException {
SecurityManager sm = System.getSecurityManager();
if (sm != null && !configProvided) {
sm.checkPermission(new AuthPermission
("createLoginContext." + name));
}
if (name == null)
throw new LoginException
(ResourcesMgr.getString("Invalid.null.input.name"));
// get the Configuration
if (config == null) {
config = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<Configuration>() {
public Configuration run() {
return Configuration.getConfiguration();
}
});
}
// get the LoginModules configured for this application
AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name);
if (entries == null) {
if (sm != null && !configProvided) {
sm.checkPermission(new AuthPermission
("createLoginContext." + OTHER));
}
entries = config.getAppConfigurationEntry(OTHER);
if (entries == null) {
MessageFormat form = new MessageFormat(ResourcesMgr.getString
("No.LoginModules.configured.for.name"));
Object[] source = {name};
throw new LoginException(form.format(source));
}
}
moduleStack = new ModuleInfo[entries.length];
for (int i = 0; i < entries.length; i++) {
// clone returned array
moduleStack[i] = new ModuleInfo
(new AppConfigurationEntry
(entries[i].getLoginModuleName(),
entries[i].getControlFlag(),
entries[i].getOptions()),
null);
}
contextClassLoader = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return Thread.currentThread().getContextClassLoader();
}
});
}
private void loadDefaultCallbackHandler() throws LoginException {
// get the default handler class
try {
final ClassLoader finalLoader = contextClassLoader;
this.callbackHandler = java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<CallbackHandler>() {
public CallbackHandler run() throws Exception {
String defaultHandler = java.security.Security.getProperty
(DEFAULT_HANDLER);
if (defaultHandler == null || defaultHandler.length() == 0)
return null;
Class c = Class.forName(defaultHandler,
true,
finalLoader);
return (CallbackHandler)c.newInstance();
}
});
} catch (java.security.PrivilegedActionException pae) {
throw new LoginException(pae.getException().toString());
}
// secure it with the caller's ACC
if (this.callbackHandler != null && !configProvided) {
this.callbackHandler = new SecureCallbackHandler
(java.security.AccessController.getContext(),
this.callbackHandler);
}
}
/**
* Instantiate a new <code>LoginContext</code> object with a name.
*
* @param name the name used as the index into the
* <code>Configuration</code>.
*
* @exception LoginException if the caller-specified <code>name</code>
* does not appear in the <code>Configuration</code>
* and there is no <code>Configuration</code> entry
* for "<i>other</i>", or if the
* <i>auth.login.defaultCallbackHandler</i>
* security property was set, but the implementation
* class could not be loaded.
* <p>
* @exception SecurityException if a SecurityManager is set and
* the caller does not have
* AuthPermission("createLoginContext.<i>name</i>"),
* or if a configuration entry for <i>name</i> does not exist and
* the caller does not additionally have
* AuthPermission("createLoginContext.other")
*/
public LoginContext(String name) throws LoginException {
init(name);
loadDefaultCallbackHandler();
}
/**
* Instantiate a new <code>LoginContext</code> object with a name
* and a <code>Subject</code> object.
*
* <p>
*
* @param name the name used as the index into the
* <code>Configuration</code>. <p>
*
* @param subject the <code>Subject</code> to authenticate.
*
* @exception LoginException if the caller-specified <code>name</code>
* does not appear in the <code>Configuration</code>
* and there is no <code>Configuration</code> entry
* for "<i>other</i>", if the caller-specified <code>subject</code>
* is <code>null</code>, or if the
* <i>auth.login.defaultCallbackHandler</i>
* security property was set, but the implementation
* class could not be loaded.
* <p>
* @exception SecurityException if a SecurityManager is set and
* the caller does not have
* AuthPermission("createLoginContext.<i>name</i>"),
* or if a configuration entry for <i>name</i> does not exist and
* the caller does not additionally have
* AuthPermission("createLoginContext.other")
*/
public LoginContext(String name, Subject subject)
throws LoginException {
init(name);
if (subject == null)
throw new LoginException
(ResourcesMgr.getString("invalid.null.Subject.provided"));
this.subject = subject;
subjectProvided = true;
loadDefaultCallbackHandler();
}
/**
* Instantiate a new <code>LoginContext</code> object with a name
* and a <code>CallbackHandler</code> object.
*
* <p>
*
* @param name the name used as the index into the
* <code>Configuration</code>. <p>
*
* @param callbackHandler the <code>CallbackHandler</code> object used by
* LoginModules to communicate with the user.
*
* @exception LoginException if the caller-specified <code>name</code>
* does not appear in the <code>Configuration</code>
* and there is no <code>Configuration</code> entry
* for "<i>other</i>", or if the caller-specified
* <code>callbackHandler</code> is <code>null</code>.
* <p>
* @exception SecurityException if a SecurityManager is set and
* the caller does not have
* AuthPermission("createLoginContext.<i>name</i>"),
* or if a configuration entry for <i>name</i> does not exist and
* the caller does not additionally have
* AuthPermission("createLoginContext.other")
*/
public LoginContext(String name, CallbackHandler callbackHandler)
throws LoginException {
init(name);
if (callbackHandler == null)
throw new LoginException(ResourcesMgr.getString
("invalid.null.CallbackHandler.provided"));
this.callbackHandler = new SecureCallbackHandler
(java.security.AccessController.getContext(),
callbackHandler);
}
/**
* Instantiate a new <code>LoginContext</code> object with a name,
* a <code>Subject</code> to be authenticated, and a
* <code>CallbackHandler</code> object.
*
* <p>
*
* @param name the name used as the index into the
* <code>Configuration</code>. <p>
*
* @param subject the <code>Subject</code> to authenticate. <p>
*
* @param callbackHandler the <code>CallbackHandler</code> object used by
* LoginModules to communicate with the user.
*
* @exception LoginException if the caller-specified <code>name</code>
* does not appear in the <code>Configuration</code>
* and there is no <code>Configuration</code> entry
* for "<i>other</i>", or if the caller-specified
* <code>subject</code> is <code>null</code>,
* or if the caller-specified
* <code>callbackHandler</code> is <code>null</code>.
* <p>
* @exception SecurityException if a SecurityManager is set and
* the caller does not have
* AuthPermission("createLoginContext.<i>name</i>"),
* or if a configuration entry for <i>name</i> does not exist and
* the caller does not additionally have
* AuthPermission("createLoginContext.other")
*/
public LoginContext(String name, Subject subject,
CallbackHandler callbackHandler) throws LoginException {
this(name, subject);
if (callbackHandler == null)
throw new LoginException(ResourcesMgr.getString
("invalid.null.CallbackHandler.provided"));
this.callbackHandler = new SecureCallbackHandler
(java.security.AccessController.getContext(),
callbackHandler);
}
/**
* Instantiate a new <code>LoginContext</code> object with a name,
* a <code>Subject</code> to be authenticated,
* a <code>CallbackHandler</code> object, and a login
* <code>Configuration</code>.
*
* <p>
*
* @param name the name used as the index into the caller-specified
* <code>Configuration</code>. <p>
*
* @param subject the <code>Subject</code> to authenticate,
* or <code>null</code>. <p>
*
* @param callbackHandler the <code>CallbackHandler</code> object used by
* LoginModules to communicate with the user, or <code>null</code>.
* <p>
*
* @param config the <code>Configuration</code> that lists the
* login modules to be called to perform the authentication,
* or <code>null</code>.
*
* @exception LoginException if the caller-specified <code>name</code>
* does not appear in the <code>Configuration</code>
* and there is no <code>Configuration</code> entry
* for "<i>other</i>".
* <p>
* @exception SecurityException if a SecurityManager is set,
* <i>config</i> is <code>null</code>,
* and either the caller does not have
* AuthPermission("createLoginContext.<i>name</i>"),
* or if a configuration entry for <i>name</i> does not exist and
* the caller does not additionally have
* AuthPermission("createLoginContext.other")
*
* @since 1.5
*/
public LoginContext(String name, Subject subject,
CallbackHandler callbackHandler,
Configuration config) throws LoginException {
this.config = config;
configProvided = (config != null) ? true : false;
if (configProvided) {
creatorAcc = java.security.AccessController.getContext();
}
init(name);
if (subject != null) {
this.subject = subject;
subjectProvided = true;
}
if (callbackHandler == null) {
loadDefaultCallbackHandler();
} else if (!configProvided) {
this.callbackHandler = new SecureCallbackHandler
(java.security.AccessController.getContext(),
callbackHandler);
} else {
this.callbackHandler = callbackHandler;
}
}
/**
* Perform the authentication.
*
* <p> This method invokes the <code>login</code> method for each
* LoginModule configured for the <i>name</i> specified to the
* <code>LoginContext</code> constructor, as determined by the login
* <code>Configuration</code>. Each <code>LoginModule</code>
* then performs its respective type of authentication
* (username/password, smart card pin verification, etc.).
*
* <p> This method completes a 2-phase authentication process by
* calling each configured LoginModule's <code>commit</code> method
* if the overall authentication succeeded (the relevant REQUIRED,
* REQUISITE, SUFFICIENT, and OPTIONAL LoginModules succeeded),
* or by calling each configured LoginModule's <code>abort</code> method
* if the overall authentication failed. If authentication succeeded,
* each successful LoginModule's <code>commit</code> method associates
* the relevant Principals and Credentials with the <code>Subject</code>.
* If authentication failed, each LoginModule's <code>abort</code> method
* removes/destroys any previously stored state.
*
* <p> If the <code>commit</code> phase of the authentication process
* fails, then the overall authentication fails and this method
* invokes the <code>abort</code> method for each configured
* <code>LoginModule</code>.
*
* <p> If the <code>abort</code> phase
* fails for any reason, then this method propagates the
* original exception thrown either during the <code>login</code> phase
* or the <code>commit</code> phase. In either case, the overall
* authentication fails.
*
* <p> In the case where multiple LoginModules fail,
* this method propagates the exception raised by the first
* <code>LoginModule</code> which failed.
*
* <p> Note that if this method enters the <code>abort</code> phase
* (either the <code>login</code> or <code>commit</code> phase failed),
* this method invokes all LoginModules configured for the
* application regardless of their respective <code>Configuration</code>
* flag parameters. Essentially this means that <code>Requisite</code>
* and <code>Sufficient</code> semantics are ignored during the
* <code>abort</code> phase. This guarantees that proper cleanup
* and state restoration can take place.
*
* <p>
*
* @exception LoginException if the authentication fails.
*/
public void login() throws LoginException {
loginSucceeded = false;
if (subject == null) {
subject = new Subject();
}
try {
if (configProvided) {
// module invoked in doPrivileged with creatorAcc
invokeCreatorPriv(LOGIN_METHOD);
invokeCreatorPriv(COMMIT_METHOD);
} else {
// module invoked in doPrivileged
invokePriv(LOGIN_METHOD);
invokePriv(COMMIT_METHOD);
}
loginSucceeded = true;
} catch (LoginException le) {
try {
if (configProvided) {
invokeCreatorPriv(ABORT_METHOD);
} else {
invokePriv(ABORT_METHOD);
}
} catch (LoginException le2) {
throw le;
}
throw le;
}
}
/**
* Logout the <code>Subject</code>.
*
* <p> This method invokes the <code>logout</code> method for each
* <code>LoginModule</code> configured for this <code>LoginContext</code>.
* Each <code>LoginModule</code> performs its respective logout procedure
* which may include removing/destroying
* <code>Principal</code> and <code>Credential</code> information
* from the <code>Subject</code> and state cleanup.
*
* <p> Note that this method invokes all LoginModules configured for the
* application regardless of their respective
* <code>Configuration</code> flag parameters. Essentially this means
* that <code>Requisite</code> and <code>Sufficient</code> semantics are
* ignored for this method. This guarantees that proper cleanup
* and state restoration can take place.
*
* <p>
*
* @exception LoginException if the logout fails.
*/
public void logout() throws LoginException {
if (subject == null) {
throw new LoginException(ResourcesMgr.getString
("null.subject.logout.called.before.login"));
}
if (configProvided) {
// module invoked in doPrivileged with creatorAcc
invokeCreatorPriv(LOGOUT_METHOD);
} else {
// module invoked in doPrivileged
invokePriv(LOGOUT_METHOD);
}
}
/**
* Return the authenticated Subject.
*
* <p>
*
* @return the authenticated Subject. If the caller specified a
* Subject to this LoginContext's constructor,
* this method returns the caller-specified Subject.
* If a Subject was not specified and authentication succeeds,
* this method returns the Subject instantiated and used for
* authentication by this LoginContext.
* If a Subject was not specified, and authentication fails or
* has not been attempted, this method returns null.
*/
public Subject getSubject() {
if (!loginSucceeded && !subjectProvided)
return null;
return subject;
}
private void clearState() {
moduleIndex = 0;
firstError = null;
firstRequiredError = null;
success = false;
}
private void throwException(LoginException originalError, LoginException le)
throws LoginException {
// first clear state
clearState();
// throw the exception
LoginException error = (originalError != null) ? originalError : le;
throw error;
}
/**
* Invokes the login, commit, and logout methods
* from a LoginModule inside a doPrivileged block.
*
* This version is called if the caller did not instantiate
* the LoginContext with a Configuration object.
*/
private void invokePriv(final String methodName) throws LoginException {
try {
java.security.AccessController.doPrivileged
(new java.security.PrivilegedExceptionAction<Void>() {
public Void run() throws LoginException {
invoke(methodName);
return null;
}
});
} catch (java.security.PrivilegedActionException pae) {
throw (LoginException)pae.getException();
}
}
/**
* Invokes the login, commit, and logout methods
* from a LoginModule inside a doPrivileged block restricted
* by creatorAcc
*
* This version is called if the caller instantiated
* the LoginContext with a Configuration object.
*/
private void invokeCreatorPriv(final String methodName)
throws LoginException {
try {
java.security.AccessController.doPrivileged
(new java.security.PrivilegedExceptionAction<Void>() {
public Void run() throws LoginException {
invoke(methodName);
return null;
}
}, creatorAcc);
} catch (java.security.PrivilegedActionException pae) {
throw (LoginException)pae.getException();
}
}
private void invoke(String methodName) throws LoginException {
// start at moduleIndex
// - this can only be non-zero if methodName is LOGIN_METHOD
for (int i = moduleIndex; i < moduleStack.length; i++, moduleIndex++) {
try {
int mIndex = 0;
Method[] methods = null;
if (moduleStack[i].module != null) {
methods = moduleStack[i].module.getClass().getMethods();
} else {
// instantiate the LoginModule
Class c = Class.forName
(moduleStack[i].entry.getLoginModuleName(),
true,
contextClassLoader);
Constructor constructor = c.getConstructor(PARAMS);
Object[] args = { };
// allow any object to be a LoginModule
// as long as it conforms to the interface
moduleStack[i].module = constructor.newInstance(args);
methods = moduleStack[i].module.getClass().getMethods();
// call the LoginModule's initialize method
for (mIndex = 0; mIndex < methods.length; mIndex++) {
if (methods[mIndex].getName().equals(INIT_METHOD))
break;
}
Object[] initArgs = {subject,
callbackHandler,
state,
moduleStack[i].entry.getOptions() };
// invoke the LoginModule initialize method
methods[mIndex].invoke(moduleStack[i].module, initArgs);
}
// find the requested method in the LoginModule
for (mIndex = 0; mIndex < methods.length; mIndex++) {
if (methods[mIndex].getName().equals(methodName))
break;
}
// set up the arguments to be passed to the LoginModule method
Object[] args = { };
// invoke the LoginModule method
boolean status = ((Boolean)methods[mIndex].invoke
(moduleStack[i].module, args)).booleanValue();
if (status == true) {
// if SUFFICIENT, return if no prior REQUIRED errors
if (!methodName.equals(ABORT_METHOD) &&
!methodName.equals(LOGOUT_METHOD) &&
moduleStack[i].entry.getControlFlag() ==
AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT &&
firstRequiredError == null) {
// clear state
clearState();
if (debug != null)
debug.println(methodName + " SUFFICIENT success");
return;
}
if (debug != null)
debug.println(methodName + " success");
success = true;
} else {
if (debug != null)
debug.println(methodName + " ignored");
}
} catch (NoSuchMethodException nsme) {
MessageFormat form = new MessageFormat(ResourcesMgr.getString
("unable.to.instantiate.LoginModule.module.because.it.does.not.provide.a.no.argument.constructor"));
Object[] source = {moduleStack[i].entry.getLoginModuleName()};
throwException(null, new LoginException(form.format(source)));
} catch (InstantiationException ie) {
throwException(null, new LoginException(ResourcesMgr.getString
("unable.to.instantiate.LoginModule.") +
ie.getMessage()));
} catch (ClassNotFoundException cnfe) {
throwException(null, new LoginException(ResourcesMgr.getString
("unable.to.find.LoginModule.class.") +
cnfe.getMessage()));
} catch (IllegalAccessException iae) {
throwException(null, new LoginException(ResourcesMgr.getString
("unable.to.access.LoginModule.") +
iae.getMessage()));
} catch (InvocationTargetException ite) {
// failure cases
LoginException le;
if (ite.getCause() instanceof PendingException &&
methodName.equals(LOGIN_METHOD)) {
// XXX
//
// if a module's LOGIN_METHOD threw a PendingException
// then immediately throw it.
//
// when LoginContext is called again,
// the module that threw the exception is invoked first
// (the module list is not invoked from the start).
// previously thrown exception state is still present.
//
// it is assumed that the module which threw
// the exception can have its
// LOGIN_METHOD invoked twice in a row
// without any commit/abort in between.
//
// in all cases when LoginContext returns
// (either via natural return or by throwing an exception)
// we need to call clearState before returning.
// the only time that is not true is in this case -
// do not call throwException here.
throw (PendingException)ite.getCause();
} else if (ite.getCause() instanceof LoginException) {
le = (LoginException)ite.getCause();
} else if (ite.getCause() instanceof SecurityException) {
// do not want privacy leak
// (e.g., sensitive file path in exception msg)
le = new LoginException("Security Exception");
le.initCause(new SecurityException());
if (debug != null) {
debug.println
("original security exception with detail msg " +
"replaced by new exception with empty detail msg");
debug.println("original security exception: " +
ite.getCause().toString());
}
} else {
// capture an unexpected LoginModule exception
java.io.StringWriter sw = new java.io.StringWriter();
ite.getCause().printStackTrace
(new java.io.PrintWriter(sw));
sw.flush();
le = new LoginException(sw.toString());
}
if (moduleStack[i].entry.getControlFlag() ==
AppConfigurationEntry.LoginModuleControlFlag.REQUISITE) {
if (debug != null)
debug.println(methodName + " REQUISITE failure");
// if REQUISITE, then immediately throw an exception
if (methodName.equals(ABORT_METHOD) ||
methodName.equals(LOGOUT_METHOD)) {
if (firstRequiredError == null)
firstRequiredError = le;
} else {
throwException(firstRequiredError, le);
}
} else if (moduleStack[i].entry.getControlFlag() ==
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED) {
if (debug != null)
debug.println(methodName + " REQUIRED failure");
// mark down that a REQUIRED module failed
if (firstRequiredError == null)
firstRequiredError = le;
} else {
if (debug != null)
debug.println(methodName + " OPTIONAL failure");
// mark down that an OPTIONAL module failed
if (firstError == null)
firstError = le;
}
}
}
// we went thru all the LoginModules.
if (firstRequiredError != null) {
// a REQUIRED module failed -- return the error
throwException(firstRequiredError, null);
} else if (success == false && firstError != null) {
// no module succeeded -- return the first error
throwException(firstError, null);
} else if (success == false) {
// no module succeeded -- all modules were IGNORED
throwException(new LoginException
(ResourcesMgr.getString("Login.Failure.all.modules.ignored")),
null);
} else {
// success
clearState();
return;
}
}
/**
* Wrap the caller-specified CallbackHandler in our own
* and invoke it within a privileged block, constrained by
* the caller's AccessControlContext.
*/
private static class SecureCallbackHandler implements CallbackHandler {
private final java.security.AccessControlContext acc;
private final CallbackHandler ch;
SecureCallbackHandler(java.security.AccessControlContext acc,
CallbackHandler ch) {
this.acc = acc;
this.ch = ch;
}
public void handle(final Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException {
try {
java.security.AccessController.doPrivileged
(new java.security.PrivilegedExceptionAction<Void>() {
public Void run() throws java.io.IOException,
UnsupportedCallbackException {
ch.handle(callbacks);
return null;
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
if (pae.getException() instanceof java.io.IOException) {
throw (java.io.IOException)pae.getException();
} else {
throw (UnsupportedCallbackException)pae.getException();
}
}
}
}
/**
* LoginModule information -
* incapsulates Configuration info and actual module instances
*/
private static class ModuleInfo {
AppConfigurationEntry entry;
Object module;
ModuleInfo(AppConfigurationEntry newEntry, Object newModule) {
this.entry = newEntry;
this.module = newModule;
}
}
}