18722 - Add tests for authentication and PAM conversation processing.
authorShadrack Kilemba <Shadrack.Kilemba@Oracle.COM>
Fri, 29 Jul 2011 17:37:59 -0400
changeset 758 06c57694c78d
parent 757 1eb5766869d6
child 759 0aa521216e66
18722 - Add tests for authentication and PAM conversation processing.
usr/src/Makefile.env
usr/src/Makefile.targ
usr/src/cmd/conv_test/Makefile
usr/src/cmd/conv_test/rad-test
usr/src/cmd/rad/daemon/rad-test.xml
usr/src/lib/conv_test/common/conv_test.c
usr/src/pkg/manifests_dev/system-management-rad-test.p5m
usr/src/test/java/Makefile
usr/src/test/java/build.xml
usr/src/test/java/src/client/PAMAuthTest.java
usr/src/test/java/src/client/PAMConvTest.java
usr/src/test/java/src/client/PAMTestBase.java
usr/src/test/java/src/client/RadRequestBase.java
usr/src/test/radtest/java.py
--- a/usr/src/Makefile.env	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/Makefile.env	Fri Jul 29 17:37:59 2011 -0400
@@ -43,6 +43,7 @@
 PROTO_AUTOSTART   = $(ROOT)/usr/share/gnome/autostart
 PROTO_BIN         = $(ROOT)/usr/bin
 PROTO_DOC         = $(ROOT)/usr/share/lib/java/javadoc
+PROTO_ETC_UATTR.D = $(ROOT)/etc/user_attr.d
 PROTO_ETC_VPANELS = $(ROOT)/etc/vpanels
 PROTO_INCLUDE     = $(ROOT)/usr/include
 PROTO_LIB         = $(ROOT)/usr/lib
--- a/usr/src/Makefile.targ	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/Makefile.targ	Fri Jul 29 17:37:59 2011 -0400
@@ -67,6 +67,7 @@
 # Proto files, remove CDDL
 $(PROTO_BIN)/% $(PROTO_RAD)/% := FILEMODE = 0555
 $(PROTO_BIN)/% \
+	$(PROTO_ETC_UATTR.D)/% \
 	$(PROTO_ETC_VPANELS)/% \
 	$(PROTO_MFS_NETWORK)/% \
 	$(PROTO_MFS_SYSTEM)/% \
--- a/usr/src/cmd/conv_test/Makefile	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/cmd/conv_test/Makefile	Fri Jul 29 17:37:59 2011 -0400
@@ -28,6 +28,7 @@
 $(PROTO_MTD)/rad_conv_test := FILEMODE = 0555
 
 install: $(PROTO_MTD)/rad_conv_test \
-	$(PROTO_MFS_SYSTEM)/rad-conv-test.xml
+	$(PROTO_MFS_SYSTEM)/rad-conv-test.xml \
+	$(PROTO_ETC_UATTR.D)/rad-test
 
 include $(SRC)/Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/conv_test/rad-test	Fri Jul 29 17:37:59 2011 -0400
@@ -0,0 +1,27 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+radtest_rolepass::::type=role
+radtest_rolenopass::::type=role
+radtest_hasroles::::type=normal;roles=radtest_rolepass,radtest_rolenopass
--- a/usr/src/cmd/rad/daemon/rad-test.xml	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/cmd/rad/daemon/rad-test.xml	Fri Jul 29 17:37:59 2011 -0400
@@ -89,7 +89,7 @@
 			</astring_list>
 		</property>
 		<propval name='debug' type='boolean' value='false' />
-		<propval name='timeout' type='integer' value='180' />
+		<propval name='timeout' type='integer' value='3' />
 		<propval name='pam_service' type='astring' value='radtest' />
 	</property_group>
 
--- a/usr/src/lib/conv_test/common/conv_test.c	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/lib/conv_test/common/conv_test.c	Fri Jul 29 17:37:59 2011 -0400
@@ -40,10 +40,14 @@
 #define	USER_TRAILING	"radtest_trailing"
 #define	USER_SERIAL	"radtest_serial"
 #define	USER_PARALLEL	"radtest_parallel"
+#define	USER_HASROLES	"radtest_hasroles"
+
+#define	ROLE_PASS	"radtest_rolepass"
+#define	ROLE_NOPASS	"radtest_rolenopass"
 
 #define	PROMPT_SIMPLE	"Password:"
 #define	PROMPT_TRAILING	"Thank you!"
-#define	PROMPT_NEW	"New password:"
+#define	PROMPT_NEW	"New Password:"
 #define	PROMPT_A	"Password A:"
 #define	PROMPT_B	"Password B:"
 
@@ -53,13 +57,16 @@
 #define	PASS_LAME	"lamepass"
 #define	PASS_A		"Apass"
 #define	PASS_B		"Bpass"
+#define	PASS_ROLE	"rolepass"
 
+/*ARGSUSED*/
 int
 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
 {
 	return (PAM_IGNORE);
 }
 
+/*ARGSUSED*/
 int
 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
 {
@@ -73,11 +80,25 @@
 	    pam_get_item(pamh, PAM_USER, (void **)&username) != PAM_SUCCESS)
 		return (PAM_AUTH_ERR);
 
-	if (strcmp(username, USER_NOPASS) == 0) {
+	if (strcmp(username, USER_NOPASS) == 0 ||
+	    strcmp(username, ROLE_NOPASS) == 0) {
 		return (PAM_SUCCESS);
+	} else if (strcmp(username, ROLE_PASS) == 0) {
+		msg[0].msg_style = PAM_PROMPT_ECHO_ON;
+		msg[0].msg = PROMPT_SIMPLE;
+		int err = conv->conv(1, &pmsg, &res, conv->appdata_ptr);
+		if (res != NULL) {
+			if (err == PAM_SUCCESS && res->resp) {
+				if (strcmp(res->resp, PASS_ROLE) == 0)
+					result = PAM_SUCCESS;
+				free(res->resp);
+			}
+			free(res);
+		}
 	} else if (strcmp(username, USER_SIMPLE) == 0 ||
 	    strcmp(username, USER_STALE) == 0 ||
-	    strcmp(username, USER_EXPIRED) == 0) {
+	    strcmp(username, USER_EXPIRED) == 0 ||
+	    strcmp(username, USER_HASROLES) == 0) {
 		msg[0].msg_style = PAM_PROMPT_ECHO_ON;
 		msg[0].msg = PROMPT_SIMPLE;
 		int err = conv->conv(1, &pmsg, &res, conv->appdata_ptr);
@@ -102,12 +123,12 @@
 			free(res);
 		}
 		if (result == PAM_SUCCESS) {
-			result == PAM_AUTH_ERR;
+			result = PAM_AUTH_ERR;
 			msg[0].msg_style = PAM_TEXT_INFO;
 			msg[0].msg = PROMPT_TRAILING;
 			err = conv->conv(1, &pmsg, &res, conv->appdata_ptr);
 			if (err == PAM_SUCCESS)
-				result == PAM_SUCCESS;
+				result = PAM_SUCCESS;
 		}
 	} else if (strcmp(username, USER_SERIAL) == 0) {
 		msg[0].msg_style = PAM_PROMPT_ECHO_ON;
@@ -122,7 +143,7 @@
 			free(res);
 		}
 		if (result == PAM_SUCCESS) {
-			result == PAM_AUTH_ERR;
+			result = PAM_AUTH_ERR;
 			msg[0].msg_style = PAM_PROMPT_ECHO_ON;
 			msg[0].msg = PROMPT_B;
 			err = conv->conv(1, &pmsg, &res, conv->appdata_ptr);
@@ -156,6 +177,7 @@
 	return (result);
 }
 
+/*ARGSUSED*/
 int
 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
 {
@@ -170,6 +192,7 @@
 	return (PAM_SUCCESS);
 }
 
+/*ARGSUSED*/
 int
 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
 {
@@ -184,7 +207,7 @@
 		return (PAM_AUTH_ERR);
 
 	msg[0].msg_style = PAM_PROMPT_ECHO_ON;
-	msg[0].msg = PASS_NEW;
+	msg[0].msg = PROMPT_NEW;
 	int err = conv->conv(1, &pmsg, &res, conv->appdata_ptr);
 	if (res != NULL) {
 		if (err == PAM_SUCCESS && res->resp) {
--- a/usr/src/pkg/manifests_dev/system-management-rad-test.p5m	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/pkg/manifests_dev/system-management-rad-test.p5m	Fri Jul 29 17:37:59 2011 -0400
@@ -37,6 +37,13 @@
 user username=radtest_trailing ftpuser=false gcos-field="rad test" group=other
 user username=radtest_serial ftpuser=false gcos-field="rad test" group=other
 user username=radtest_parallel ftpuser=false gcos-field="rad test" group=other
+user username=radtest_rolepass ftpuser=false gcos-field="rad test" group=other
+user username=radtest_rolenopass ftpuser=false gcos-field="rad test" group=other
+user username=radtest_hasroles ftpuser=false gcos-field="rad test" group=other
+
+dir path=/etc group=sys
+dir path=/etc/user_attr.d group=sys
+file path=/etc/user_attr.d/rad-test group=sys mode=0644
 
 dir path=lib
 dir path=lib/svc
--- a/usr/src/test/java/Makefile	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/test/java/Makefile	Fri Jul 29 17:37:59 2011 -0400
@@ -32,6 +32,7 @@
 DEPENDENCY_JARS = $(PROTO_RAD_JAVA)/rad.jar
 DEPENDENCY_JARS += $(PROTO_RAD_JAVA)/adr.jar
 DEPENDENCY_JARS += $(PROTO_RAD_JAVA)/afunix.jar
+DEPENDENCY_JARS += $(PROTO_VP_DIR)/scf-common.jar
 DEPENDENCY_JARS += /usr/share/lib/java/junit.jar
 
 JAVA_OPTS = -g -Xlint -Xlint:-serial
--- a/usr/src/test/java/build.xml	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/test/java/build.xml	Fri Jul 29 17:37:59 2011 -0400
@@ -37,7 +37,8 @@
 	<property name="path.adr" location="${path.rad.java}/adr.jar" />
 	<property name="path.rad" location="${path.rad.java}/rad.jar" />
 	<property name="path.util" location="${path.rad.java}/afunix.jar" />
-
+	<property name="path.scf"
+		location="${path.proto}/usr/share/vpanels/scf-common.jar" />
 	<path id="srcroots">
 		<pathelement location="${path.src}" />
 		<pathelement location="${path.gen}" />
@@ -47,6 +48,7 @@
 		<pathelement location="${path.adr}" />
 		<pathelement location="${path.rad}" />
 		<pathelement location="${path.util}" />
+		<pathelement location="${path.scf}" />
 		<pathelement location="/usr/share/lib/java/junit.jar" />
 	</path>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/java/src/client/PAMAuthTest.java	Fri Jul 29 17:37:59 2011 -0400
@@ -0,0 +1,51 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ */
+
+package client;
+
+import org.junit.Before;
+import org.junit.Test;
+import testutil.Desc;
+
+import static org.junit.Assert.*;
+
+public class PAMAuthTest extends PAMTestBase {
+    @Before
+    public void setUp() throws Exception {
+	assertTrue(isRadReady(true)); // auth
+    }
+
+    @Test
+    @Desc("Connecting to the container with authentication correctly" +
+	  " reports the user you authenticated as.")
+    public void authUserCorrect() throws Exception {
+	String username = System.getProperty("user.name");
+	String serverUserName = bean_.getuser();
+
+	assertNotNull(username);
+	assertNotNull(serverUserName);
+	assertEquals(username, serverUserName);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/java/src/client/PAMConvTest.java	Fri Jul 29 17:37:59 2011 -0400
@@ -0,0 +1,639 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ */
+
+package client;
+
+import java.io.IOException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.opensolaris.os.rad.ObjectException;
+import org.opensolaris.os.rad.test.RadRequestMXBean;
+import org.opensolaris.os.rad.api.pam.Block;
+import org.opensolaris.os.rad.api.pam.BlockType;
+import org.opensolaris.os.rad.api.pam.Message;
+import org.opensolaris.os.rad.api.pam.MsgType;
+import testutil.Desc;
+
+import static org.junit.Assert.*;
+
+public class PAMConvTest extends PAMTestBase {
+    static final String USCE_CLASSNAME =
+	"com.oracle.solaris.afunix.UnixSocketClosedException";
+    static final int CONN_TO = 3;
+
+    @Before
+    public void setUp() throws Exception {
+	assertTrue(isRadReady(false)); // unauth
+    }
+
+    @Test
+    @Desc("Connecting to the container without authentication correctly" +
+	  " reports that you aren't authenticated.")
+    public void authUserUserNone() throws Exception {
+	String serverUserName = bean_.getuser();
+
+	assertNull(serverUserName);
+    }
+
+    @Test
+    @Desc("Connecting to the container without authentication correcctly" +
+	  " reports you have no roles availables.")
+    public void authRolesNone() throws Exception {
+	List<String>roles = bean_.getroles();
+
+	assertNotNull(roles);
+	assertEquals(0, roles.size());
+    }
+
+    @Test
+    @Desc("Connecting to the container with authentication correctly" +
+	  " resports the roles available to the authenticated user.")
+    public void authRolesCorrect() throws Exception {
+	String username = "radtest_hasroles";
+	String prompt = "Password:";
+	String password = "simplepass";
+	String myroles[] =
+	    new String[] {"radtest_rolepass", "radtest_rolenopass"};
+
+	Block block = bean_.login(locale_, username);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message>messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(1, messages.size());
+	assertEquals(prompt, messages.get(0).getMessage());
+
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add(password.toCharArray());
+
+	block = bean_.submit(resp);
+	assertNotNull(block);
+	assertEquals(BlockType.success, block.getType());
+
+	List<String>roles = bean_.getroles();
+	assertNotNull(roles);
+	assertEquals(myroles.length, roles.size());
+
+	// the rolenames returned are the expected ones.
+	assertTrue(roles.contains(myroles[0]) && roles.contains(myroles[1]));
+    }
+
+    @Test
+    @Desc("Authentication as a bogus user failes immediately.")
+    public void authLoginBogusUser() throws Exception {
+	String username = "bogususer";
+	Block block = bean_.login(locale_, username);
+
+	assertNotNull(block);
+	assertEquals(BlockType.error, block.getType());
+	assertNull(bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authentication as user that doesn't have password completes" +
+	  " successfully.")
+    public void authLoginNoPassword() throws Exception {
+	String username = "radtest_nopass";
+	Block block = bean_.login(locale_, username);
+
+	assertNotNull(block);
+	assertEquals(BlockType.success, block.getType());
+	assertEquals(username, bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authentication as user that has password completes successfully")
+    public void authLoginPassword() throws Exception {
+	String username = "radtest_simple";
+	String prompt = "Password:";
+	String password = "simplepass";
+
+	Block block = bean_.login(locale_, username);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message> messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(1, messages.size());
+	assertEquals(prompt, messages.get(0).getMessage());
+
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add(password.toCharArray());
+
+	block = bean_.submit(resp);
+	assertNotNull(block);
+	assertEquals(BlockType.success, block.getType());
+
+	assertEquals(username, bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authntication as user that has password fails when bad password" +
+	  " is provided")
+    public void authLoginPasswordFail() throws Exception {
+	String username = "radtest_simple";
+	String prompt = "Password:";
+	String password = "badpassword";
+
+	Block block = bean_.login(locale_, username);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message> messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(1, messages.size());
+	assertEquals(prompt, messages.get(0).getMessage());
+
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add(password.toCharArray());
+
+	block = bean_.submit(resp);
+	assertNotNull(block);
+	assertEquals(BlockType.error, block.getType());
+	assertNull(bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authentication as user that has password completes successfully," +
+	  " with trailing message")
+    public void authLoginPasswordTrailing() throws Exception {
+	String username = "radtest_trailing";
+	String prompt = "Password:";
+	String password = "simplepass";
+	String trailingPrompt = "Thank you!";
+
+	Block block = bean_.login(locale_, username);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message> messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(1, messages.size());
+	assertEquals(prompt, messages.get(0).getMessage());
+
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add(password.toCharArray());
+
+	block = bean_.submit(resp);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(1, messages.size());
+
+	assertEquals(MsgType.text_info, messages.get(0).getStyle());
+	assertEquals(trailingPrompt, messages.get(0).getMessage());
+
+	resp = new ArrayList<char []>();
+	block = bean_.submit(resp);
+	assertNotNull(block);
+	assertEquals(BlockType.success, block.getType());
+	assertEquals(username, bean_.getuser());
+    }
+
+    /**
+     * authenticate a user using multiple prompts in serial.
+     * @param aPass - password for the first prompt.
+     * @param bPass - password for the second prompt.
+     * @return boolean - true if successully authenticated, otherwise false.
+     */
+    private boolean authLoginMultipleSerial(String aPass, String bPass)
+	throws Exception {
+	String username = "radtest_serial";
+	String promptA = "Password A:";
+	String promptB = "Password B:";
+
+	Block block = bean_.login(locale_, username);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message> messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(1, messages.size());
+	assertEquals(promptA, messages.get(0).getMessage());
+
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add(aPass.toCharArray());
+
+	block = bean_.submit(resp);
+	assertNotNull(block);
+	if (block.getType() != BlockType.conv)
+	    return false;
+
+	messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(1, messages.size());
+	assertEquals(promptB, messages.get(0).getMessage());
+
+	resp = new ArrayList<char[]>();
+	resp.add(bPass.toCharArray());
+	block = bean_.submit(resp);
+	assertNotNull(block);
+
+	return (block.getType() == BlockType.success);
+    }
+
+    @Test
+    @Desc("Authentication as user using mulitple prompts in serial completes" +
+	  " successfully.")
+    public void authLoginMultipleSerial() throws Exception {
+	assertTrue(authLoginMultipleSerial("Apass", "Bpass"));
+	assertEquals("radtest_serial", bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authentication as user using multiple prompts in serial fails" +
+	  " when the first password is wrong")
+    public void authLoginMutipleSerialA() throws Exception {
+	assertFalse(authLoginMultipleSerial("ABadpass", "Bpass"));
+	assertNull(bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authentication as user using multiple prompts in serial fails" +
+	  " when the second password is wrong")
+    public void authLoginMultipleSerialB() throws Exception {
+	assertFalse(authLoginMultipleSerial("Apass", "BBadpass"));
+	assertNull(bean_.getuser());
+    }
+
+    /**
+     * authenticate a user using multiple prompts in parallel.
+     * @param passowrd - a list a passwords to used as response.
+     * @return boolean - true if successful, otherwise false.
+     */
+    private boolean authLoginMultipleParallel(List<char[]>passList)
+	throws Exception {
+	String username = "radtest_parallel";
+	String [] prompts = new String[] {"Password A:", "Password B:"};
+
+	Block block = bean_.login(locale_, username);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message>messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(2, messages.size());
+	assertEquals(prompts[0], messages.get(0).getMessage());
+	assertEquals(prompts[1], messages.get(1).getMessage());
+
+	block = bean_.submit(passList);
+	assertNotNull(block);
+
+	return (block.getType() == BlockType.success);
+    }
+
+    @Test
+    @Desc("Authnetication as user using multiple prompts simultaneously" +
+	  " completes successfully.")
+    public void authLoginMultipleParallel() throws Exception {
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add("Apass".toCharArray());
+	resp.add("Bpass".toCharArray());
+
+	assertTrue(authLoginMultipleParallel(resp));
+	assertEquals("radtest_parallel", bean_.getuser());
+    }
+
+    @Test
+    @Desc("Autentication as user using multiple prompts simultaneously" +
+	  " fails when passwords are wrong.")
+    public void authLoginMultipleParallelFail() throws Exception {
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add("ABadPass".toCharArray());
+	resp.add("BBadPass".toCharArray());
+
+	assertFalse(authLoginMultipleParallel(resp));
+	assertNull(bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authentication as user using multiple prompts simultaneously" +
+	  " fails when only one password is provided.")
+    public void authLoginMultipleParallelFailShort() throws Exception {
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add("Apass".toCharArray());
+
+	boolean successful = false;
+	try {
+	    authLoginMultipleParallel(resp);
+	} catch (ObjectException oe) {
+	    // We expect an ObjectException to be thrown
+	    successful = true;
+	}
+
+	assertNull(bean_.getuser());
+	assertTrue(successful);
+    }
+
+    @Test
+    @Desc("Authentication as user using multiple prompts simultaneously" +
+	  " fails when too many passwords are provided.")
+    public void authLoginMultipleParallelFailLong() throws Exception {
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add("Apass".toCharArray());
+	resp.add("Bpass".toCharArray());
+	resp.add("OneTooManyPass".toCharArray());
+
+	boolean successful = false;
+	try {
+	    authLoginMultipleParallel(resp);
+	} catch (ObjectException oe) {
+	    // We expect an ObjectException to be thrown
+	    successful = true;
+	}
+
+	assertNull(bean_.getuser());
+	assertTrue(successful);
+    }
+
+    @Test
+    @Desc("Authentication as expired user fails.")
+    public void authLoginExpired() throws Exception {
+	String username = "radtest_expired";
+	String prompt = "Password:";
+	String password = "simplepss";
+
+	Block block = bean_.login(locale_, username);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message>messages = block.getMessages();
+	assertNotNull(messages);
+	assertEquals(1, messages.size());
+	assertEquals(prompt, messages.get(0).getMessage());
+
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add(password.toCharArray());
+	block = bean_.submit(resp);
+
+	assertEquals(BlockType.error, block.getType());
+	assertNull(bean_.getuser());
+    }
+
+    /**
+     * common method used by the following three 'change password' tests.
+     *
+     * @param prompt [] - exptected prompts from the server.
+     * @param response [] - proper responses to the prompts above.
+     * @param BlockType [] - expected server responses to the responses above.
+     */
+    private void authLoginChangePasswordBase(String [] prompt,
+        String [] response, BlockType [] blockType) throws Exception {
+
+	String username = "radtest_stale";
+
+	Block block = bean_.login(locale_, username);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+	for (int i = 0; i < prompt.length; i++) {
+	    List<Message>messages = block.getMessages();
+	    assertNotNull(messages);
+	    assertEquals(1, messages.size());
+	    assertEquals(prompt[i], messages.get(0).getMessage());
+
+	    List<char []>resp = new ArrayList<char []>();
+	    if (response[i] != null) {
+		resp.add(response[i].toCharArray());
+	    }
+	    block = bean_.submit(resp);
+	    assertNotNull(block);
+	    assertEquals(blockType[i], block.getType());
+	}
+    }
+
+    @Test
+    @Desc("Authentication as user with stale password is able to provide" +
+	  " a new one.")
+    public void authLoginChangePassword() throws Exception {
+	String [] prompt = new String [] {"Password:",
+					  "Your password has expired.",
+					  "New Password:",
+					  "New Password:"};
+	String [] response = new String [] {"simplepass",
+					    null,
+					    "newpass",
+					    "newpass"};
+	BlockType [] blockType =  new BlockType [] {BlockType.conv,
+						    BlockType.conv,
+						    BlockType.conv,
+						    BlockType.success};
+
+	authLoginChangePasswordBase(prompt, response, blockType);
+	assertEquals("radtest_stale", bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authentication as user with statel password is prompted for new" +
+	  " one until successful.")
+    public void authLoginChangePasswordRetry() throws Exception {
+	String [] prompt =  new String [] {"Password:",
+					   "Your password has expired.",
+					   "New Password:",
+					   "New Password:",
+					   "New Password:"};
+	String [] response = new String [] {"simplepass",
+					    null,
+					    "lamepass",
+					    "newpass",
+					    "newpass"};
+	BlockType [] blockType = new BlockType [] {BlockType.conv,
+						   BlockType.conv,
+						   BlockType.conv,
+						   BlockType.conv,
+						   BlockType.success};
+
+	authLoginChangePasswordBase(prompt, response, blockType);
+	assertEquals("radtest_stale", bean_.getuser());
+    }
+
+    @Test
+    @Desc("Authentication as user with stale password is prompted for new" +
+	  " one but fails")
+    public void authLoginChangePasswordFail() throws Exception {
+	String [] prompt =  new String [] {"Password:",
+					   "Your password has expired.",
+					   "New Password:",
+					   "New Password:",
+					   "New Password:"};
+	String [] response = new String [] {"simplepass",
+					    null,
+					    "firstbadpass",
+					    "secondbadpass",
+					    "thirdbadpass"};
+	BlockType [] blockType = new BlockType [] {BlockType.conv,
+						   BlockType.conv,
+						   BlockType.conv,
+						   BlockType.conv,
+						   BlockType.error};
+
+	authLoginChangePasswordBase(prompt, response, blockType);
+	assertNull(bean_.getuser());
+    }
+
+    @Test
+    @Desc("User is able to assume role without password")
+    public void authAssumeNoPassword() throws Exception {
+	// login as 'radtest_hasroles'
+	authRolesCorrect();
+
+	String rolename = "radtest_rolenopass";
+	Block block = bean_.assume(locale_, rolename);
+	assertNotNull(block);
+	assertEquals(BlockType.success, block.getType());
+	assertEquals(rolename, bean_.getuser());
+    }
+
+    @Test
+    @Desc("User is able to assume a role with a password")
+    public void authAssumePassword() throws Exception {
+	String rolename = "radtest_rolepass";
+	String prompt = "Password:";
+	String rolepass = "rolepass";
+
+	// login as 'radtest_hasroles'
+	authRolesCorrect();
+	Block block = bean_.assume(locale_, rolename);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message>messages = block.getMessages();
+	assertEquals(1, messages.size());
+	assertEquals(prompt, messages.get(0).getMessage());
+
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add(rolepass.toCharArray());
+
+	block = bean_.submit(resp);
+	assertNotNull(block);
+	assertEquals(BlockType.success, block.getType());
+	assertEquals(rolename,  bean_.getuser());
+    }
+
+    @Test
+    @Desc("Attempt to assume a bad role fails.")
+    public void authAssumeBadRole() throws Exception {
+	String rolename = "badrole";
+
+	// login as 'radtest_hasroles'
+	authRolesCorrect();
+	Block block = bean_.assume(locale_, rolename);
+	assertNotNull(block);
+	assertEquals(BlockType.error, block.getType());
+	assertEquals("radtest_hasroles", bean_.getuser());
+    }
+
+    @Test
+    @Desc("Attempt to assume a role fails due to bad password.")
+    public void authAssumeBadPassword() throws Throwable {
+	String rolename = "radtest_rolepass";
+	String prompt = "Password:";
+	String password = "badrolepass";
+
+	// login as 'radtest_hasroles'
+	authRolesCorrect();
+	Block block = bean_.assume(locale_, rolename);
+	assertNotNull(block);
+	assertEquals(BlockType.conv, block.getType());
+
+	List<Message>messages = block.getMessages();
+	assertEquals(1, messages.size());
+	assertEquals(prompt, messages.get(0).getMessage());
+
+	List<char[]>resp = new ArrayList<char[]>();
+	resp.add(password.toCharArray());
+
+	block = bean_.submit(resp);
+	assertNotNull(block);
+	assertEquals(BlockType.error, block.getType());
+	assertEquals("radtest_hasroles", bean_.getuser());
+    }
+
+    @Test
+    @Desc("Connection times out if no requests are made following connection.")
+    public void authTimeoutExpireInitial() throws Throwable {
+	// pause a second logger to force a timeout.
+	Thread.sleep((CONN_TO + 1) * 1000);
+
+	try {
+	    int to = bean_.getconnectionTimeout();
+	    assertEquals(CONN_TO, to);
+	} catch (UndeclaredThrowableException ute) {
+	    Throwable t = ute.getUndeclaredThrowable();
+	    if (USCE_CLASSNAME.equals(t.getClass().getName()))
+		return;
+
+	    throw t;
+	}
+
+	fail("Connection did not timeout as expected.");
+    }
+
+    @Test
+    @Desc("Connection times out after a request is made.")
+    public void authTimeoutExpireResponse() throws Throwable {
+	int to = bean_.getconnectionTimeout();
+	assertEquals(CONN_TO, to);
+
+	// pause to force a timeout
+	Thread.sleep((CONN_TO + 1) * 1000);
+	try {
+	    to = bean_.getconnectionTimeout();
+	    assertEquals(CONN_TO, to);
+	} catch (UndeclaredThrowableException ute) {
+	    Throwable t = ute.getUndeclaredThrowable();
+	    if (USCE_CLASSNAME.equals(t.getClass().getName()))
+		return;
+
+	    throw t;
+	}
+	fail("Connection did not timeout as expected.");
+    }
+
+    @Test
+    @Desc("Connection about to timeout is correctly extended following" +
+	  " response")
+    public void authTimeoutExtension() throws Exception {
+	Thread.sleep((CONN_TO - 1) * 1000);
+	int to = bean_.getconnectionTimeout();
+	assertEquals(CONN_TO, to);
+
+	// start login process with 1 second left
+	Block block = bean_.login(locale_, "radtest_nopass");
+
+	// pause past the would have been remaining 1 second
+	Thread.sleep(1500);
+
+	// should have got another 'CONN_TO' worth of time. It should now
+	// be CONN_TO - 1.5 seconds before the next timeout.
+	to = bean_.getconnectionTimeout();
+	assertEquals(CONN_TO, to);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/test/java/src/client/PAMTestBase.java	Fri Jul 29 17:37:59 2011 -0400
@@ -0,0 +1,128 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ */
+
+package client;
+
+import com.oracle.solaris.smf.Instance;
+import com.oracle.solaris.smf.MasterMXBean;
+import com.oracle.solaris.smf.SMFState;
+import common.MBeanTestCommon;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import org.junit.After;
+import org.junit.Before;
+import org.opensolaris.os.adr.Stability;
+import org.opensolaris.os.rad.jmx.RadJMX;
+import org.opensolaris.os.rad.api.pam.AuthenticatorMXBean;
+import org.opensolaris.os.rad.api.pam.Block;
+import org.opensolaris.os.rad.api.pam.Message;
+
+import static org.junit.Assert.*;
+
+public abstract class PAMTestBase extends MBeanTestCommon {
+    public static final String RADTEST_SVC = "svc:/system/rad-test";
+    public static final String RADCONVTEST_SVC = "svc:/system/rad-conv-test";
+    public static final String PATH_AUTH = "/var/run/test-radsocket";
+    public static final String PATH_UNAUTH = "/var/run/test-radsocket-unauth";
+
+    private JMXConnector conn_;
+    protected AuthenticatorMXBean bean_;
+    protected String locale_ = Locale.getDefault().getLanguage();
+
+    /**
+     * determine if the rad-test and rad-conv-test services are runnning.
+     */
+    protected void checkRequiredServices() throws Exception {
+	setUpCommon("/usr/lib/rad/module/mod_smf.so");
+
+	final String name = "com.oracle.solaris.smf:type=master";
+	MasterMXBean smfBean = RadJMX.newMXBeanProxy(getMBSC(), strToON(name),
+            MasterMXBean.class, Stability.PRIVATE);
+	if (smfBean == null)
+	    throw new Exception("Unable to create SMF Master MX Bean.");
+
+	List<Instance> instances = smfBean.getinstances();
+	boolean radtest = false;
+	boolean radconvtest = false;
+
+	for (Instance instance : instances) {
+	    if (instance.getFmri().startsWith(RADTEST_SVC))
+		radtest = instance.getState() == SMFState.ONLINE;
+	    if (instance.getFmri().startsWith(RADCONVTEST_SVC))
+		radconvtest = instance.getState() == SMFState.ONLINE;
+	}
+
+	if (!radtest)
+	    throw new Exception("The Service '" + RADTEST_SVC +
+		"' Required by this test is not running.");
+	if (!radconvtest)
+	    throw new Exception("The Service '" + RADCONVTEST_SVC +
+		"' Required by this test is not running.");
+
+	super.tearDown();
+    }
+
+    /**
+     * connect to the rad-test instance.
+     */
+    protected boolean isRadReady(boolean auth) throws Exception {
+	checkRequiredServices();
+
+	String path = auth ? PATH_AUTH : PATH_UNAUTH;
+
+	final String url = "service:jmx:radunix://" + path;
+	final String name = "org.opensolaris.os.rad:type=authentication";
+
+	conn_ = JMXConnectorFactory.connect(new JMXServiceURL(url));
+	MBeanServerConnection mbsc = conn_.getMBeanServerConnection();
+	if (mbsc == null) {
+	    conn_.close();
+	    return false;
+	}
+
+	bean_ = RadJMX.newMXBeanProxy(mbsc, new ObjectName(name),
+	    AuthenticatorMXBean.class, Stability.PRIVATE);
+	if (bean_ == null) {
+	    conn_.close();
+	    return false;
+	}
+
+	return true;
+    }
+
+    @After
+    public void tearDown() throws IOException {
+	if (conn_ != null)
+	    conn_.close();
+    }
+}
--- a/usr/src/test/java/src/client/RadRequestBase.java	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/test/java/src/client/RadRequestBase.java	Fri Jul 29 17:37:59 2011 -0400
@@ -108,7 +108,8 @@
     @After
     @Override
     public void tearDown() throws IOException {
-	connector_.close();
+	if (connector_ != null)
+	    connector_.close();
 	super.tearDown();
     }
 
--- a/usr/src/test/radtest/java.py	Thu Jul 28 15:54:26 2011 -0700
+++ b/usr/src/test/radtest/java.py	Fri Jul 29 17:37:59 2011 -0400
@@ -34,6 +34,7 @@
 
 _VP_DIR = "usr/lib/rad/java"
 _JUNIT_JAR = "/usr/share/lib/java/junit.jar"
+_SCF_JAR = "usr/share/vpanels/scf-common.jar"
 
 class JavaExecutor(fw.ExtTestFinder):
 	""" Finds tests matching a particular pattern and executes them """
@@ -47,9 +48,11 @@
 	def make_classpath(self, root, dir):
 		vproot = os.path.join(root, _VP_DIR)
 		vpjars = [ "rad.jar", "adr.jar", "afunix.jar" ]
+		scfjar = os.path.join(root, _SCF_JAR)
 
 		classpathents = map(lambda x: os.path.join(vproot, x), vpjars)
 		classpathents.append(_JUNIT_JAR)
+		classpathents.append(scfjar)
 		classpathents.append(dir)
 		return ":".join(classpathents)