usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/RadLoginManager.java
changeset 645 13b54060dbf4
parent 644 91293d42f869
child 646 e1e91f5b0cb1
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/RadLoginManager.java	Wed Jan 26 10:36:11 2011 -0500
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/RadLoginManager.java	Fri Jan 28 16:07:43 2011 -0500
@@ -49,16 +49,41 @@
     // Inner classes
     //
 
-    private static interface AuthPrompter {
-	Block initiate(LoginRequest request, AuthenticatorMXBean auth)
-	    throws ActionAbortedException, ObjectException;
+    private static abstract class AuthPrompter {
+	//
+	// Instance data
+	//
+
+	private boolean acknowledged;
+
+	//
+	// AuthPrompter methods
+	//
 
-	void prompt(LoginRequest request, List<LoginProperty> properties,
-	    LoginData data, boolean isFirst) throws ActionAbortedException,
-	    ActionRegressedException;
+	/**
+	 * _,__/|
+	 *  `O.o'
+	 * =(_x_)=
+	 *    U
+	 */
+	protected void ack() {
+	    acknowledged = true;
+	}
+
+	public boolean isAcknowledged() {
+	    return acknowledged;
+	}
+
+	public abstract Block initiate(LoginRequest request,
+	    AuthenticatorMXBean auth) throws ActionAbortedException,
+	    ObjectException;
+
+	public abstract void prompt(LoginRequest request,
+	    List<LoginProperty> properties, boolean isFirst)
+	    throws ActionAbortedException, ActionRegressedException;
     }
 
-    private class LoginPrompter implements AuthPrompter {
+    private class LoginPrompter extends AuthPrompter {
 	@Override
 	public Block initiate(LoginRequest request,
 	    AuthenticatorMXBean auth) throws ActionAbortedException,
@@ -73,20 +98,20 @@
 
 	@Override
 	public void prompt(LoginRequest request, List<LoginProperty> properties,
-	    LoginData data, boolean isFirst) throws ActionAbortedException,
+	    boolean isFirst) throws ActionAbortedException,
 	    ActionRegressedException {
 
 	    try {
 		promptForAuth(request, properties, true, isFirst);
 	    } finally {
-		data.setUserAcknowledged(true);
+		ack();
 		request.getHost().setErrored(false);
 		request.getUser().setErrored(false);
 	    }
 	}
     }
 
-    private class RolePrompter implements AuthPrompter {
+    private class RolePrompter extends AuthPrompter {
 	@Override
 	public Block initiate(LoginRequest request,
 	    AuthenticatorMXBean auth) throws ActionAbortedException,
@@ -101,13 +126,13 @@
 
 	@Override
 	public void prompt(LoginRequest request, List<LoginProperty> properties,
-	    LoginData data, boolean isFirst) throws ActionAbortedException,
+	    boolean isFirst) throws ActionAbortedException,
 	    ActionRegressedException {
 
 	    try {
 		promptForAuth(request, properties, false, isFirst);
 	    } finally {
-		data.setRoleAcknowledged(true);
+		ack();
 		request.getRole().setErrored(false);
 	    }
 	}
@@ -118,83 +143,70 @@
 	// Instance data
 	//
 
-	private ConnectionInfo userInfo;
-	private ConnectionInfo roleInfo;
-	private boolean userAcknowledged;
-	private boolean roleAcknowledged;
+	private LinkedList<ConnectionInfo> depChain =
+	    new LinkedList<ConnectionInfo>();
+
+	private LinkedList<Boolean> acks = new LinkedList<Boolean>();
 
 	//
 	// LoginData methods
 	//
 
-	public AuthenticatorMXBean getAuthBean(LoginRequest request) {
-	    MBeanServerConnection mbsc = getMBeanServerConnection(request);
-	    if (mbsc == null) {
-		return null;
-	    }
-
-	    ObjectName oName = MBeanUtil.makeObjectName(
-		"org.opensolaris.os.rad", "authentication");
-
-	    return JMX.newMXBeanProxy(mbsc, oName, AuthenticatorMXBean.class);
+	public boolean isAcknowledged() {
+	    return acks.peek();
 	}
 
-	public ConnectionInfo getRoleConnectionInfo() {
-	    return roleInfo;
+	public List<ConnectionInfo> getDepChain() {
+	    return depChain;
 	}
 
-	public ConnectionInfo getUserConnectionInfo() {
-	    return userInfo;
-	}
-
-	public boolean isRoleAcknowledged() {
-	    return roleAcknowledged;
+	public ConnectionInfo peek() {
+	    return depChain.peek();
 	}
 
-	public boolean isUserAcknowledged() {
-	    return userAcknowledged;
-	}
-
-	public void setRoleAcknowledged(boolean roleAcknowledged) {
-	    this.roleAcknowledged = roleAcknowledged;
-	    if (roleAcknowledged) {
-		setUserAcknowledged(true);
-	    }
+	public void pop() {
+	    depChain.pop();
+	    acks.pop();
 	}
 
-	public void setRoleConnectionInfo(ConnectionInfo roleInfo) {
-	    this.roleInfo = roleInfo;
+	public void push(ConnectionInfo info, boolean acknowledged) {
+	    depChain.push(info);
+	    acks.push(acknowledged);
 	}
 
-	public void setUserAcknowledged(boolean userAcknowledged) {
-	    this.userAcknowledged = userAcknowledged;
-	    if (!userAcknowledged) {
-		setRoleAcknowledged(false);
-	    }
-	}
+	public void setDepChain(List<ConnectionInfo> depChain,
+	    boolean acknowledged) {
 
-	public void setUserConnectionInfo(ConnectionInfo userInfo) {
-	    this.userInfo = userInfo;
-	    setRoleConnectionInfo(null);
+	    assert compatible(depChain);
+
+	    this.depChain.clear();
+	    this.depChain.addAll(depChain);
+	    acks.push(acknowledged);
 	}
 
 	//
 	// Private methods
 	//
 
-	private MBeanServerConnection getMBeanServerConnection(
-	    LoginRequest request) {
+	private void clearConnectionsTo(int level) {
+	    int size = depChain.size();
+	    for (int i = size - 1; i >= level; i--) {
+		depChain.remove(i);
+	    }
+	}
 
-            ConnectionInfo info = roleInfo != null ? roleInfo : userInfo;
-	    try {
-		return info.getConnector().getMBeanServerConnection();
-	    } catch (IOException e) {
-		request.getMessages().add(new DialogMessage(
-		    Finder.getString("login.err.io",
-		    request.getHost().getValue()),
-		    JOptionPane.ERROR_MESSAGE));
+	private boolean compatible(List<ConnectionInfo> depChain) {
+	    boolean compatible = false;
+	    if (depChain.size() == this.depChain.size() + 1) {
+		compatible = true;
+		for (int i = 0, n = this.depChain.size(); i < n; i++) {
+		    if (!this.depChain.get(i).equals(depChain.get(i + 1))) {
+			compatible = false;
+			break;
+		    }
+		}
 	    }
-	    return null;
+	    return compatible;
 	}
     }
 
@@ -252,29 +264,29 @@
     }
 
     /**
-     * Opens a connection to the server.  This routine returns a two-element
-     * array consisting of:
+     * Guides the user through the login process, then returns a dependency
+     * chain of {@link ConnectionInfo}s.  The first element of the chain is the
+     * {@code ConnectionInfo} that satisfies the given request.  Each additional
+     * {@code ConnectionInfo} is a dependency of the previous {@code
+     * ConnectionInfo}.
      * <p/>
-     * <ol>
-     *	 <li>
-     *	   a (new or existing) {@link ConnectionInfo} for the {@code user@host}
-     *	   connection, and
-     *	 </li>
-     *	 <li>
-     *	   a (new or existing) {@link ConnectionInfo} for the {@code role@host
-     *	   (via user)} connection, or {@code null} if the user did not choose to
-     *	   assume a role
-     *	 </li>
-     * </ol>
+     * For example, a role-based connection ("root@nerd (via talley)", say)
+     * would have an dependency on the user-based connection ("talley@nerd")
+     * used to create it.  The returned chain would contain the role-based
+     * connection just before the user-based connection.
+     * <p/>
+     * This chain of {@code ConnectionInfo} dependencies can be {@link
+     * ConnectionManager#add added} to the {@code ConnectionManager} for
+     * automatic dependency-based management.
      *
      * @param	    request
      *		    the {@link LoginRequest} encapsulating the preset values and
      *		    editability of each core {@link LoginProperty}
      *
      * @param	    current
-     *		    if non-{@code null}, ensures that the user is aware of
-     *		    changes in host/user/role (preventing the use of cached
-     *		    connections without the user's knowledge)
+     *		    if non-{@code null}, ensures that the user is aware of any
+     *		    change in login, preventing the use of cached connections
+     *		    without the user's knowledge
      *
      * @exception   ActionAbortedException
      *		    if the user cancels the operation
@@ -283,10 +295,9 @@
      *		    if the given request fails
      */
     @SuppressWarnings({"fallthrough"})
-    public ConnectionInfo[] getConnectionInfo(LoginRequest request,
-	ConnectionInfo current) throws ActionAbortedException,
-	ActionFailedException {
-
+    public List<ConnectionInfo> getConnectionInfo(LoginRequest request,
+	LoginInfo current) throws ActionAbortedException, ActionFailedException
+    {
 	LoginData data = new LoginData();
 	for (int step = 0; ; ) {
 	    try {
@@ -305,27 +316,17 @@
 	    }
 	}
 
-	ConnectionInfo userInfo = data.getUserConnectionInfo();
-	ConnectionInfo roleInfo = data.getRoleConnectionInfo();
+	List<ConnectionInfo> depChain = data.getDepChain();
 
-	// To prevent rogue connections, if the chosen connection has a
-	// different host/user/role than the current connection, ensure that the
-	// user has already acknowledged it at some point in the authentication
-	// process.  If not, prompt for acknowledgement now.
-	if (current != null &&
-	    (roleInfo == null ?
-	    (!data.isUserAcknowledged() &&
-	    (!current.matchesHost(userInfo.getHost()) ||
-	    !current.matchesUser(userInfo.getUser()))) :
-	    (!data.isRoleAcknowledged() &&
-	    (!current.matchesHost(roleInfo.getHost()) ||
-	    !current.matchesUser(roleInfo.getUser()) ||
-	    !current.matchesRole(roleInfo.getRole()))))) {
-
+        // To prevent rogue connections, if the chosen connection differs from
+        // the current one, ensure that the user has acknowledged it at some
+        // point in the authentication process.  If not, prompt for
+        // acknowledgement now.
+	if (!data.isAcknowledged() && !depChain.get(0).matches(current)) {
 	    promptForAck(request);
 	}
 
-	return new ConnectionInfo[] { userInfo, roleInfo };
+	return depChain;
     }
 
     public ConnectionManager getConnectionManager() {
@@ -526,7 +527,7 @@
 
     @SuppressWarnings({"fallthrough"})
     private boolean authConverse(LoginRequest request, AuthenticatorMXBean auth,
-	AuthPrompter prompter, LoginData data) throws ActionAbortedException,
+	AuthPrompter prompter) throws ActionAbortedException,
 	ActionRegressedException {
 
 	List<DialogMessage> messages = request.getMessages();
@@ -546,7 +547,7 @@
 		case success:
 		    // Display any lingering messages from the server
 		    if (!messages.isEmpty()) {
-			prompter.prompt(request, properties, data, isFirst);
+			prompter.prompt(request, properties, isFirst);
 			isFirst = false;
 			messages.clear();
 		    }
@@ -586,7 +587,7 @@
 		    }
 
 		    if (!properties.isEmpty()) {
-			prompter.prompt(request, properties, data, isFirst);
+			prompter.prompt(request, properties, isFirst);
 			isFirst = false;
 			messages.clear();
 		    }
@@ -617,6 +618,20 @@
 	}
     }
 
+    private AuthenticatorMXBean createAuthBean(LoginRequest request,
+	ConnectionInfo info) {
+
+	MBeanServerConnection mbsc = getMBeanServerConnection(request, info);
+	if (mbsc == null) {
+	    return null;
+	}
+
+	ObjectName oName = MBeanUtil.makeObjectName(
+	    "org.opensolaris.os.rad", "authentication");
+
+	return JMX.newMXBeanProxy(mbsc, oName, AuthenticatorMXBean.class);
+    }
+
     private JMXConnector createConnector(String host)
 	throws KeyStoreException, NoSuchAlgorithmException,
 	CertificateException, MalformedURLException, IOException,
@@ -630,7 +645,7 @@
 	    for (String path : paths) {
 		JMXServiceURL url = null;
 		try {
-                    url = new JMXServiceURL(RadConnector.PROTOCOL_UDS, "", 0,
+		    url = new JMXServiceURL(RadConnector.PROTOCOL_UDS, "", 0,
 			path);
 		    return JMXConnectorFactory.connect(url);
 		} catch (IOException e) {
@@ -769,7 +784,9 @@
 
 	// Loop until connected to host and authenticated as user
 	while (true) {
-	    // Refresh each iteration in case host/user.isEditableOnError()
+	    boolean acknowledged = false;
+
+	    // Refresh each iteration in case a prop isEditableOnError()
 	    boolean hostEditable = host.isEditable();
 	    boolean userEditable = user.isEditable();
 
@@ -780,11 +797,11 @@
 		requestFailed(request);
 	    }
 
-            if (hostEditable || userEditable || !messages.isEmpty()) {
+	    if (hostEditable || userEditable || !messages.isEmpty()) {
 		try {
 		    promptForHostAndUser(request);
 		} finally {
-		    data.setUserAcknowledged(true);
+		    acknowledged = true;
 
 		    // User only needs to see any message once, presumably
 		    messages.clear();
@@ -802,48 +819,52 @@
 	    String userVal = user.getValue();
 
 	    // Search for existing connection
-	    ConnectionInfo info = getConnectionManager().getConnection(hostVal,
-		userVal, null);
-	    if (info != null) {
-		data.setUserConnectionInfo(info);
+	    List<ConnectionInfo> depChain =
+		getConnectionManager().getDepChain(hostVal, userVal, null);
+	    if (depChain != null) {
+		data.setDepChain(depChain, acknowledged);
 		return;
 	    }
 
 	    // Create connection, append to messages on error
 	    JMXConnector connector = createConnector(request);
 	    if (connector != null) {
-		info = new ConnectionInfo(hostVal, userVal, null, connector);
-		data.setUserConnectionInfo(info);
+		ConnectionInfo info = new ConnectionInfo(hostVal, userVal, null,
+		    connector);
 
 		// Get/create auth bean, append to messages on error
-		AuthenticatorMXBean auth = data.getAuthBean(request);
+		AuthenticatorMXBean auth = createAuthBean(request, info);
 		if (auth != null) {
 		    setLoginStatus(request,
 			Finder.getString("login.status.user"));
 
 		    if (userVal.equals(auth.getuser())) {
+			data.push(info, acknowledged);
 			return;
 		    }
 
 		    try {
 			AuthPrompter prompter = new LoginPrompter();
 			do {
-			    if (authConverse(request, auth, prompter, data)) {
+			    if (authConverse(request, auth, prompter)) {
+				acknowledged |= prompter.isAcknowledged();
+				data.push(info, acknowledged);
 				return;
 			    }
 
 			    // Authentication failed
 			    user.setErrored(true);
 
-			    // Add generic auth failure message if not already
-			    // provided by server
+                            // Add generic auth failure message if not already
+                            // provided by server
 			    if (messages.isEmpty()) {
-				messages.add(new DialogMessage(Finder.getString(
-				    "login.err.user.auth", hostVal, userVal),
+				messages.add(new DialogMessage(
+				    Finder.getString("login.err.user.auth",
+				    hostVal, userVal),
 				    JOptionPane.ERROR_MESSAGE));
 			    }
 
-			// No chance to edit host/user, so keep iterating here
+			// No chance to edit, so keep iterating here
 			} while (!host.isEditable() && !user.isEditable());
 
 		    // Thrown by authConverse
@@ -851,9 +872,6 @@
 		    }
 		}
 	    }
-
-	    // Could not create/authenticate connection -- reset and try again
-	    data.setUserConnectionInfo(null);
 	}
     }
 
@@ -862,7 +880,7 @@
 	ActionRegressedException {
 
 	// Get/create auth bean, append to messages on error
-	AuthenticatorMXBean userAuth = data.getAuthBean(request);
+	AuthenticatorMXBean userAuth = createAuthBean(request, data.peek());
 	if (userAuth == null) {
 	    // Not likely, but handle it anyway
 	    requestFailed(request);
@@ -888,6 +906,8 @@
 
 	// Loop until no role is chosen, or chosen role is authenticated
 	while (true) {
+	    boolean acknowledged = false;
+
 	    // Refresh each iteration in case role.isEditableOnError()
 	    boolean roleEditable = role.isEditable();
 
@@ -896,11 +916,11 @@
 		requestFailed(request);
 	    }
 
-            if ((roleEditable && !roles.isEmpty()) || !messages.isEmpty()) {
+	    if ((roleEditable && !roles.isEmpty()) || !messages.isEmpty()) {
 		try {
 		    promptForRole(request, roles);
 		} finally {
-		    data.setRoleAcknowledged(true);
+		    acknowledged = true;
 
 		    // User only needs to see any message once, presumably
 		    messages.clear();
@@ -920,10 +940,10 @@
 	    }
 
 	    // Search for existing connection
-	    ConnectionInfo info = getConnectionManager().getConnection(hostVal,
-		userVal, roleVal);
-	    if (info != null) {
-		data.setRoleConnectionInfo(info);
+	    List<ConnectionInfo> depChain =
+		getConnectionManager().getDepChain(hostVal, userVal, roleVal);
+	    if (depChain != null) {
+		data.setDepChain(depChain, acknowledged);
 		return;
 	    }
 
@@ -933,19 +953,20 @@
 		// Create connection, append to messages on error
 		JMXConnector connector = createConnector(request);
 		if (connector != null) {
-		    info = new ConnectionInfo(hostVal, userVal, roleVal,
-			connector);
-		    data.setRoleConnectionInfo(info);
+		    ConnectionInfo info = new ConnectionInfo(hostVal, userVal,
+			roleVal, connector);
 
 		    // Create auth bean, append to messages on error
-		    AuthenticatorMXBean roleAuth = data.getAuthBean(request);
+		    AuthenticatorMXBean roleAuth = createAuthBean(request,
+			info);
 		    if (roleAuth != null) {
 			roleAuth.redeemToken(userVal, token);
 
 			AuthPrompter prompter = new RolePrompter();
 			do {
-                            if (authConverse(request, roleAuth, prompter, data))
-                            {
+			    if (authConverse(request, roleAuth, prompter)) {
+				acknowledged |= prompter.isAcknowledged();
+				data.push(info, acknowledged);
 				return;
 			    }
 
@@ -974,10 +995,21 @@
 	    // Thrown by authConverse
 	    } catch (ActionRegressedException e) {
 	    }
+	}
+    }
 
-	    // Could not create/authenticate connection -- reset and try again
-	    data.setRoleConnectionInfo(null);
+    private MBeanServerConnection getMBeanServerConnection(LoginRequest request,
+	ConnectionInfo info) {
+
+	try {
+	    return info.getConnector().getMBeanServerConnection();
+	} catch (IOException e) {
+	    request.getMessages().add(new DialogMessage(
+		Finder.getString("login.err.io",
+		request.getHost().getValue()),
+		JOptionPane.ERROR_MESSAGE));
 	}
+	return null;
     }
 
     private <T> boolean inSet(LoginProperty<T> property, List<T> valid,
@@ -1017,7 +1049,7 @@
 
 	String value = property.getValue();
 	if (value == null || value.isEmpty()) {
-            request.getMessages().add(new DialogMessage(Finder.getString(
+	    request.getMessages().add(new DialogMessage(Finder.getString(
 		resource), JOptionPane.ERROR_MESSAGE));
 	    property.setErrored(true);
 	    return false;