8048138: Tests for JAAS callbacks

Reviewed-by: weijun
This commit is contained in:
Artem Smotrakov 2015-04-23 18:01:01 +08:00
parent ff227ec11f
commit f3a11c507f
5 changed files with 570 additions and 0 deletions

View File

@ -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 {}
}

View File

@ -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);
}
}
}
}

View File

@ -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");
}
}
}
}
}

View File

@ -0,0 +1,4 @@
StandardCallbacks {
DefaultHandlerModule required;
CustomLoginModule required username="username" password="password";
};

View File

@ -0,0 +1,4 @@
SharedState {
SharedState$FirstModule required;
SharedState$SecondModule required;
};