8047789: auth.login.LoginContext needs to be updated to work with modules
Reviewed-by: mullan, mchung, alanb
diff --git a/jdk/src/java.base/share/classes/javax/security/auth/login/LoginContext.java b/jdk/src/java.base/share/classes/javax/security/auth/login/LoginContext.java
index d86a914..e266c47 100644
--- a/jdk/src/java.base/share/classes/javax/security/auth/login/LoginContext.java
+++ b/jdk/src/java.base/share/classes/javax/security/auth/login/LoginContext.java
@@ -25,18 +25,18 @@
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.security.AccessController;
+import java.security.PrivilegedAction;
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 javax.security.auth.spi.LoginModule;
import java.security.AccessControlContext;
+import java.util.ServiceLoader;
+
import sun.security.util.PendingException;
import sun.security.util.ResourcesMgr;
@@ -192,7 +192,6 @@
*/
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";
@@ -210,7 +209,6 @@
private AccessControlContext creatorAcc = null; // customized config only
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
@@ -678,64 +676,64 @@
for (int i = moduleIndex; i < moduleStack.length; i++, moduleIndex++) {
try {
- int mIndex = 0;
- Method[] methods = null;
+ if (moduleStack[i].module == null) {
- if (moduleStack[i].module != null) {
- methods = moduleStack[i].module.getClass().getMethods();
- } else {
-
- // instantiate the LoginModule
+ // locate and instantiate the LoginModule
//
- // Allow any object to be a LoginModule as long as it
- // conforms to the interface.
- Class<?> c = Class.forName(
- moduleStack[i].entry.getLoginModuleName(),
- true,
- contextClassLoader);
-
- Constructor<?> constructor = c.getConstructor(PARAMS);
- Object[] args = { };
- moduleStack[i].module = constructor.newInstance(args);
-
- // call the LoginModule's initialize method
- methods = moduleStack[i].module.getClass().getMethods();
- for (mIndex = 0; mIndex < methods.length; mIndex++) {
- if (methods[mIndex].getName().equals(INIT_METHOD)) {
+ String name = moduleStack[i].entry.getLoginModuleName();
+ ServiceLoader<LoginModule> sc = AccessController.doPrivileged(
+ (PrivilegedAction<ServiceLoader<LoginModule>>)
+ () -> ServiceLoader.load(
+ LoginModule.class, contextClassLoader));
+ for (LoginModule m: sc) {
+ if (m.getClass().getName().equals(name)) {
+ moduleStack[i].module = m;
+ if (debug != null) {
+ debug.println(name + " loaded as a service");
+ }
break;
}
}
- Object[] initArgs = {subject,
- callbackHandler,
- state,
- moduleStack[i].entry.getOptions() };
+ if (moduleStack[i].module == null) {
+ try {
+ moduleStack[i].module = (LoginModule) Class.forName(
+ name, false, contextClassLoader).newInstance();
+ if (debug != null) {
+ debug.println(name + " loaded via reflection");
+ }
+ } catch (ClassNotFoundException e) {
+ throw new LoginException("No LoginModule found for "
+ + name);
+ }
+ }
+
// invoke the LoginModule initialize method
- //
- // Throws ArrayIndexOutOfBoundsException if no such
- // method defined. May improve to use LoginException in
- // the future.
- methods[mIndex].invoke(moduleStack[i].module, initArgs);
+ moduleStack[i].module.initialize(subject,
+ callbackHandler,
+ state,
+ moduleStack[i].entry.getOptions());
}
// find the requested method in the LoginModule
- for (mIndex = 0; mIndex < methods.length; mIndex++) {
- if (methods[mIndex].getName().equals(methodName)) {
+ boolean status;
+ switch (methodName) {
+ case LOGIN_METHOD:
+ status = moduleStack[i].module.login();
break;
- }
+ case COMMIT_METHOD:
+ status = moduleStack[i].module.commit();
+ break;
+ case LOGOUT_METHOD:
+ status = moduleStack[i].module.logout();
+ break;
+ case ABORT_METHOD:
+ status = moduleStack[i].module.abort();
+ break;
+ default:
+ throw new AssertionError("Unknown method " + methodName);
}
- // set up the arguments to be passed to the LoginModule method
- Object[] args = { };
-
- // invoke the LoginModule method
- //
- // Throws ArrayIndexOutOfBoundsException if no such
- // method defined. May improve to use LoginException in
- // the future.
- boolean status = ((Boolean)methods[mIndex].invoke
- (moduleStack[i].module, args)).booleanValue();
-
if (status == true) {
// if SUFFICIENT, return if no prior REQUIRED errors
@@ -760,31 +758,12 @@
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) {
+ } catch (Exception ite) {
// failure cases
-
LoginException le;
- if (ite.getCause() instanceof PendingException &&
+ if (ite instanceof PendingException &&
methodName.equals(LOGIN_METHOD)) {
// XXX
@@ -808,13 +787,13 @@
// the only time that is not true is in this case -
// do not call throwException here.
- throw (PendingException)ite.getCause();
+ throw (PendingException)ite;
- } else if (ite.getCause() instanceof LoginException) {
+ } else if (ite instanceof LoginException) {
- le = (LoginException)ite.getCause();
+ le = (LoginException)ite;
- } else if (ite.getCause() instanceof SecurityException) {
+ } else if (ite instanceof SecurityException) {
// do not want privacy leak
// (e.g., sensitive file path in exception msg)
@@ -826,14 +805,14 @@
("original security exception with detail msg " +
"replaced by new exception with empty detail msg");
debug.println("original security exception: " +
- ite.getCause().toString());
+ ite.toString());
}
} else {
// capture an unexpected LoginModule exception
java.io.StringWriter sw = new java.io.StringWriter();
- ite.getCause().printStackTrace
- (new java.io.PrintWriter(sw));
+ ite.printStackTrace
+ (new java.io.PrintWriter(sw));
sw.flush();
le = new LoginException(sw.toString());
}
@@ -938,9 +917,9 @@
*/
private static class ModuleInfo {
AppConfigurationEntry entry;
- Object module;
+ LoginModule module;
- ModuleInfo(AppConfigurationEntry newEntry, Object newModule) {
+ ModuleInfo(AppConfigurationEntry newEntry, LoginModule newModule) {
this.entry = newEntry;
this.module = newModule;
}
diff --git a/jdk/src/java.base/share/classes/javax/security/auth/spi/LoginModule.java b/jdk/src/java.base/share/classes/javax/security/auth/spi/LoginModule.java
index 871bbbc..3206b5a 100644
--- a/jdk/src/java.base/share/classes/javax/security/auth/spi/LoginModule.java
+++ b/jdk/src/java.base/share/classes/javax/security/auth/spi/LoginModule.java
@@ -32,10 +32,9 @@
import java.util.Map;
/**
- * <p> {@code LoginModule} describes the interface
- * implemented by authentication technology providers. LoginModules
- * are plugged in under applications to provide a particular type of
- * authentication.
+ * <p> Service-provider interface for authentication technology providers.
+ * LoginModules are plugged in under applications to provide a particular
+ * type of authentication.
*
* <p> While applications write to the {@code LoginContext} API,
* authentication technology providers implement the
diff --git a/jdk/test/javax/security/auth/spi/FirstLoginModule.java b/jdk/test/javax/security/auth/spi/FirstLoginModule.java
new file mode 100644
index 0000000..60112ae
--- /dev/null
+++ b/jdk/test/javax/security/auth/spi/FirstLoginModule.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.util.Map;
+
+public class FirstLoginModule implements LoginModule {
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options) {
+ // Nothing
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ return true;
+ }
+}
diff --git a/jdk/test/javax/security/auth/spi/Loader.java b/jdk/test/javax/security/auth/spi/Loader.java
new file mode 100644
index 0000000..e2bac97
--- /dev/null
+++ b/jdk/test/javax/security/auth/spi/Loader.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+import javax.security.auth.login.LoginContext;
+import java.io.File;
+
+/*
+ * @test
+ * @bug 8047789
+ * @summary auth.login.LoginContext needs to be updated to work with modules
+ * @build FirstLoginModule SecondLoginModule
+ * @run main/othervm Loader
+ */
+public class Loader {
+
+ public static void main(String[] args) throws Exception {
+
+ System.setProperty("java.security.auth.login.config",
+ new File(System.getProperty("test.src"), "sl.conf").toString());
+ LoginContext lc = new LoginContext("me");
+
+ if (SecondLoginModule.isLoaded) {
+ throw new Exception();
+ }
+
+ lc.login();
+
+ // Although only FirstLoginModule is specified in the JAAS login
+ // config file, LoginContext will first create all LoginModule
+ // implementations that are registered as services, which include
+ // SecondLoginModule.
+ if (!SecondLoginModule.isLoaded) {
+ throw new Exception();
+ }
+ }
+}
diff --git a/jdk/test/javax/security/auth/spi/META-INF/services/javax.security.auth.spi.LoginModule b/jdk/test/javax/security/auth/spi/META-INF/services/javax.security.auth.spi.LoginModule
new file mode 100644
index 0000000..b618924
--- /dev/null
+++ b/jdk/test/javax/security/auth/spi/META-INF/services/javax.security.auth.spi.LoginModule
@@ -0,0 +1 @@
+SecondLoginModule
diff --git a/jdk/test/javax/security/auth/spi/SecondLoginModule.java b/jdk/test/javax/security/auth/spi/SecondLoginModule.java
new file mode 100644
index 0000000..c6c8587
--- /dev/null
+++ b/jdk/test/javax/security/auth/spi/SecondLoginModule.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.util.Map;
+
+public class SecondLoginModule implements LoginModule {
+
+ public static boolean isLoaded;
+
+ public SecondLoginModule() {
+ isLoaded = true;
+ }
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options) {
+ // Nothing
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ return true;
+ }
+}
diff --git a/jdk/test/javax/security/auth/spi/sl.conf b/jdk/test/javax/security/auth/spi/sl.conf
new file mode 100644
index 0000000..e14400e
--- /dev/null
+++ b/jdk/test/javax/security/auth/spi/sl.conf
@@ -0,0 +1,3 @@
+me {
+ FirstLoginModule required;
+};