8048138: Tests for JAAS callbacks
authorasmotrak
Thu, 23 Apr 2015 18:01:01 +0800
changeset 29985 9e7cc139fb5c
parent 29984 2d0af02e9add
child 29986 97167d851fc4
8048138: Tests for JAAS callbacks Reviewed-by: weijun
jdk/test/javax/security/auth/login/LoginContext/CustomLoginModule.java
jdk/test/javax/security/auth/login/LoginContext/SharedState.java
jdk/test/javax/security/auth/login/LoginContext/StandardCallbacks.java
jdk/test/javax/security/auth/login/LoginContext/custom.config
jdk/test/javax/security/auth/login/LoginContext/shared.config
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/security/auth/login/LoginContext/CustomLoginModule.java	Thu Apr 23 18:01:01 2015 +0800
@@ -0,0 +1,275 @@
+/*
+ * 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 java.io.IOException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.ChoiceCallback;
+import javax.security.auth.callback.ConfirmationCallback;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextInputCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+public class CustomLoginModule implements LoginModule {
+
+    static final String HELLO = "Hello";
+
+    private Subject subject;
+    private CallbackHandler callbackHandler;
+    private boolean loginSucceeded = false;
+    private String username;
+    private char[] password;
+
+    /*
+     * Initialize this LoginModule.
+     */
+    @Override
+    public void initialize(Subject subject, CallbackHandler callbackHandler,
+            Map<String, ?> sharedState, Map<String, ?> options) {
+        this.subject = subject;
+        this.callbackHandler = callbackHandler;
+
+        // check if custom parameter is passed from comfiguration
+        if (options == null) {
+            throw new RuntimeException("options is null");
+        }
+
+        // read username/password from configuration
+        Object o = options.get("username");
+        if (o == null) {
+            throw new RuntimeException("Custom parameter not passed");
+        }
+        if (!(o instanceof String)) {
+            throw new RuntimeException("Password is not a string");
+        }
+        username = (String) o;
+
+        o = options.get("password");
+        if (o == null) {
+            throw new RuntimeException("Custom parameter not passed");
+        }
+        if (!(o instanceof String)) {
+            throw new RuntimeException("Password is not a string");
+        }
+        password = ((String) o).toCharArray();
+    }
+
+    /*
+     * Authenticate the user.
+     */
+    @Override
+    public boolean login() throws LoginException {
+        // prompt for a user name and password
+        if (callbackHandler == null) {
+            throw new LoginException("No CallbackHandler available");
+        }
+
+        // standard callbacks
+        NameCallback name = new NameCallback("username: ", "default");
+        PasswordCallback passwd = new PasswordCallback("password: ", false);
+
+        LanguageCallback language = new LanguageCallback();
+
+        TextOutputCallback error = new TextOutputCallback(
+                TextOutputCallback.ERROR, "This is an error");
+        TextOutputCallback warning = new TextOutputCallback(
+                TextOutputCallback.WARNING, "This is a warning");
+        TextOutputCallback info = new TextOutputCallback(
+                TextOutputCallback.INFORMATION, "This is a FYI");
+
+        TextInputCallback text = new TextInputCallback("Please type " + HELLO,
+                "Bye");
+
+        ChoiceCallback choice = new ChoiceCallback("Choice: ",
+                new String[] { "pass", "fail" }, 1, true);
+
+        ConfirmationCallback confirmation = new ConfirmationCallback(
+                "confirmation: ", ConfirmationCallback.INFORMATION,
+                ConfirmationCallback.YES_NO_OPTION, ConfirmationCallback.NO);
+
+        CustomCallback custom = new CustomCallback();
+
+        Callback[] callbacks = new Callback[] {
+            choice, info, warning, error, name, passwd, text, language,
+            confirmation, custom
+        };
+
+        boolean uce = false;
+        try {
+            callbackHandler.handle(callbacks);
+        } catch (UnsupportedCallbackException e) {
+            Callback callback = e.getCallback();
+            if (custom.equals(callback)) {
+                uce = true;
+                System.out.println("CustomLoginModule: "
+                        + "custom callback not supported as expected");
+            } else {
+                throw new LoginException("Unsupported callback: " + callback);
+            }
+        } catch (IOException ioe) {
+            throw new LoginException(ioe.toString());
+        }
+
+        if (!uce) {
+            throw new RuntimeException("UnsupportedCallbackException "
+                    + "not thrown");
+        }
+
+        if (!HELLO.equals(text.getText())) {
+            System.out.println("Text: " + text.getText());
+            throw new FailedLoginException("No hello");
+        }
+
+        if (!Locale.GERMANY.equals(language.getLocale())) {
+            System.out.println("Selected locale: " + language.getLocale());
+            throw new FailedLoginException("Achtung bitte");
+        }
+
+        String readUsername = name.getName();
+        char[] readPassword = passwd.getPassword();
+        if (readPassword == null) {
+            // treat a NULL password as an empty password
+            readPassword = new char[0];
+        }
+        passwd.clearPassword();
+
+        // verify the username/password
+        if (!username.equals(readUsername)
+                || !Arrays.equals(password, readPassword)) {
+            loginSucceeded = false;
+            throw new FailedLoginException("Username/password is not correct");
+        }
+
+        // check chosen option
+        int[] selected = choice.getSelectedIndexes();
+        if (selected == null || selected.length == 0) {
+            throw new FailedLoginException("Nothing selected");
+        }
+
+        if (selected[0] != 0) {
+            throw new FailedLoginException("Wrong choice: " + selected[0]);
+        }
+
+        // check confirmation
+        if (confirmation.getSelectedIndex() != ConfirmationCallback.YES) {
+            throw new FailedLoginException("Not confirmed: "
+                    + confirmation.getSelectedIndex());
+        }
+
+        loginSucceeded = true;
+        System.out.println("CustomLoginModule: authentication succeeded");
+        return true;
+    }
+
+    /*
+     * This method is called if the LoginContext's overall authentication
+     * succeeded.
+     */
+    @Override
+    public boolean commit() throws LoginException {
+        if (loginSucceeded) {
+            // add a Principal to the Subject
+            Principal principal = new TestPrincipal(username);
+            if (!subject.getPrincipals().contains(principal)) {
+                subject.getPrincipals().add(principal);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    /*
+     * This method is called if the LoginContext's overall authentication
+     * failed.
+     */
+    @Override
+    public boolean abort() throws LoginException {
+        loginSucceeded = false;
+        return true;
+    }
+
+    /*
+     * Logout the user.
+     */
+    @Override
+    public boolean logout() throws LoginException {
+        loginSucceeded = false;
+        boolean removed = subject.getPrincipals().remove(
+                new TestPrincipal(username));
+        if (!removed) {
+            throw new LoginException("Coundn't remove a principal: "
+                    + username);
+        }
+        return true;
+    }
+
+    static class TestPrincipal implements Principal {
+
+        private final String name;
+
+        public TestPrincipal(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String toString() {
+            return("TestPrincipal [name =" + name + "]");
+        }
+
+        @Override
+        public int hashCode() {
+            return name.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null) {
+                return false;
+            }
+            if (!(o instanceof TestPrincipal)) {
+                return false;
+            }
+            TestPrincipal other = (TestPrincipal) o;
+            return name != null ? name.equals(other.name) : other.name == null;
+        }
+    }
+
+    static class CustomCallback implements Callback {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/security/auth/login/LoginContext/SharedState.java	Thu Apr 23 18:01:01 2015 +0800
@@ -0,0 +1,98 @@
+/*
+ * 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 java.util.Map;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+/**
+ * @test
+ * @bug 8048138
+ * @summary Check if shared state is passed to login module
+ * @run main/othervm SharedState
+ */
+public class SharedState {
+
+    static final String NAME = "name";
+    static final String VALUE = "shared";
+
+    public static void main(String[] args) throws LoginException {
+        System.setProperty("java.security.auth.login.config",
+                System.getProperty("test.src")
+                        + System.getProperty("file.separator")
+                        + "shared.config");
+
+        new LoginContext("SharedState").login();
+    }
+
+    public static abstract class Module implements LoginModule {
+
+        @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;
+        }
+    }
+
+    public static class FirstModule extends Module {
+
+        @Override
+        public void initialize(Subject subject, CallbackHandler callbackHandler,
+                            Map<String,?> sharedState, Map<String,?> options) {
+            ((Map)sharedState).put(NAME, VALUE);
+        }
+
+    }
+
+    public static class SecondModule extends Module {
+
+        @Override
+        public void initialize(Subject subject, CallbackHandler callbackHandler,
+                            Map<String,?> sharedState, Map<String,?> options) {
+            // check shared object
+            Object shared = sharedState.get(NAME);
+            if (!VALUE.equals(shared)) {
+                throw new RuntimeException("Unexpected shared object: "
+                        + shared);
+            }
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/security/auth/login/LoginContext/StandardCallbacks.java	Thu Apr 23 18:01:01 2015 +0800
@@ -0,0 +1,189 @@
+/*
+ * 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 java.security.Principal;
+import java.util.Arrays;
+import java.util.Locale;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.ChoiceCallback;
+import javax.security.auth.callback.ConfirmationCallback;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextInputCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+/*
+ * @test
+ * @bug 8048138
+ * @summary Checks if JAAS login works fine with standard callbacks
+ * @compile DefaultHandlerModule.java
+ * @run main/othervm StandardCallbacks
+ */
+public class StandardCallbacks {
+
+    private static final String USERNAME = "username";
+    private static final char[] PASSWORD = "password".toCharArray();
+
+    public static void main(String[] args) throws LoginException {
+        System.setProperty("java.security.auth.login.config",
+                System.getProperty("test.src")
+                        + System.getProperty("file.separator")
+                        + "custom.config");
+
+        CustomCallbackHandler handler = new CustomCallbackHandler(USERNAME);
+        LoginContext context = new LoginContext("StandardCallbacks", handler);
+
+        handler.setPassword(PASSWORD);
+        System.out.println("Try to login with correct password, "
+                + "successful authentication is expected");
+        context.login();
+        System.out.println("Authentication succeeded!");
+
+        Subject subject = context.getSubject();
+        System.out.println("Authenticated user has the following principals ["
+                + subject.getPrincipals().size() + " ]:");
+        boolean found = true;
+        for (Principal principal : subject.getPrincipals()) {
+            System.out.println("principal: " + principal);
+            if (principal instanceof CustomLoginModule.TestPrincipal) {
+                CustomLoginModule.TestPrincipal testPrincipal =
+                        (CustomLoginModule.TestPrincipal) principal;
+                if (USERNAME.equals(testPrincipal.getName())) {
+                    System.out.println("Found test principal: "
+                            + testPrincipal);
+                    found = true;
+                    break;
+                }
+            }
+        }
+
+        if (!found) {
+            throw new RuntimeException("TestPrincipal not found");
+        }
+
+        // check if all expected text output callbacks have been called
+        if (!handler.info) {
+            throw new RuntimeException("TextOutputCallback.INFO not called");
+        }
+
+        if (!handler.warning) {
+            throw new RuntimeException("TextOutputCallback.WARNING not called");
+        }
+
+        if (!handler.error) {
+            throw new RuntimeException("TextOutputCallback.ERROR not called");
+        }
+
+        System.out.println("Authenticated user has the following public "
+                + "credentials [" + subject.getPublicCredentials().size()
+                + "]:");
+        subject.getPublicCredentials().stream().
+                forEach((o) -> {
+                    System.out.println("public credential: " + o);
+        });
+
+        context.logout();
+
+        System.out.println("Test passed");
+    }
+
+    private static class CustomCallbackHandler implements CallbackHandler {
+
+        private final String username;
+        private char[] password;
+        private boolean info = false;
+        private boolean warning = false;
+        private boolean error = false;
+
+        CustomCallbackHandler(String username) {
+            this.username = username;
+        }
+
+        void setPassword(char[] password) {
+            this.password = password;
+        }
+
+        @Override
+        public void handle(Callback[] callbacks)
+                throws UnsupportedCallbackException {
+            for (Callback callback : callbacks) {
+                if (callback instanceof TextOutputCallback) {
+                    TextOutputCallback toc = (TextOutputCallback) callback;
+                    switch (toc.getMessageType()) {
+                        case TextOutputCallback.INFORMATION:
+                            System.out.println("INFO: " + toc.getMessage());
+                            info = true;
+                            break;
+                        case TextOutputCallback.ERROR:
+                            System.out.println("ERROR: " + toc.getMessage());
+                            error = true;
+                            break;
+                        case TextOutputCallback.WARNING:
+                            System.out.println("WARNING: " + toc.getMessage());
+                            warning = true;
+                            break;
+                        default:
+                            throw new UnsupportedCallbackException(toc,
+                                    "Unsupported message type: "
+                                            + toc.getMessageType());
+                    }
+                } else if (callback instanceof TextInputCallback) {
+                    TextInputCallback tic = (TextInputCallback) callback;
+                    System.out.println(tic.getPrompt());
+                    tic.setText(CustomLoginModule.HELLO);
+                } else if (callback instanceof LanguageCallback) {
+                    LanguageCallback lc = (LanguageCallback) callback;
+                    lc.setLocale(Locale.GERMANY);
+                } else if (callback instanceof ConfirmationCallback) {
+                    ConfirmationCallback cc = (ConfirmationCallback) callback;
+                    System.out.println(cc.getPrompt());
+                    cc.setSelectedIndex(ConfirmationCallback.YES);
+                } else if (callback instanceof ChoiceCallback) {
+                    ChoiceCallback cc = (ChoiceCallback) callback;
+                    System.out.println(cc.getPrompt()
+                            + Arrays.toString(cc.getChoices()));
+                    cc.setSelectedIndex(0);
+                } else if (callback instanceof NameCallback) {
+                    NameCallback nc = (NameCallback) callback;
+                    System.out.println(nc.getPrompt());
+                    nc.setName(username);
+                } else if (callback instanceof PasswordCallback) {
+                    PasswordCallback pc = (PasswordCallback) callback;
+                    System.out.println(pc.getPrompt());
+                    pc.setPassword(password);
+                } else {
+                    throw new UnsupportedCallbackException(callback,
+                            "Unknown callback");
+                }
+            }
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/security/auth/login/LoginContext/custom.config	Thu Apr 23 18:01:01 2015 +0800
@@ -0,0 +1,4 @@
+StandardCallbacks {
+    DefaultHandlerModule required;
+    CustomLoginModule required username="username" password="password";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/security/auth/login/LoginContext/shared.config	Thu Apr 23 18:01:01 2015 +0800
@@ -0,0 +1,4 @@
+SharedState {
+    SharedState$FirstModule required;
+    SharedState$SecondModule required;
+};
\ No newline at end of file