7947 vp should fail if unable to connect to requested host
authorStephen Talley <stephen.talley@oracle.com>
Wed, 10 Nov 2010 10:03:10 -0500
changeset 601 9d4e3e0ee603
parent 600 c16a7e34499d
child 602 c60ad6a81113
7947 vp should fail if unable to connect to requested host
usr/src/java/util/org/opensolaris/os/vp/util/swing/SettingsButtonBar.java
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/RadLoginManager.java
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/resources/Resources.properties
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/App.java
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppInstance.java
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppLoginManager.java
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/LoginPane.java
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/resources/Resources.properties
usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/ClientContext.java
usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/ConnectionInfo.java
usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/LoginInfo.java
usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/LoginProperty.java
usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/swing/control/PanelFrameControl.java
usr/src/java/vpanels/panels/hypervisor/org/opensolaris/os/vp/panels/hypervisor/client/swing/help/advanced.html
--- a/usr/src/java/util/org/opensolaris/os/vp/util/swing/SettingsButtonBar.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/util/org/opensolaris/os/vp/util/swing/SettingsButtonBar.java	Wed Nov 10 10:03:10 2010 -0500
@@ -117,6 +117,8 @@
     private SettingsButton okayButton;
     private SettingsButton closeButton;
     protected List<SettingsButton> buttons = new LinkedList<SettingsButton>();
+    private List<SettingsButton> roButtons =
+	Collections.unmodifiableList(buttons);
 
     //
     // Constructors
@@ -202,6 +204,14 @@
     // SettingsButtonBar methods
     //
 
+    /**
+     * Gets an unmodifiable list of the buttons supported by this
+     * {@code SettingsButtonBar}.
+     */
+    public List<SettingsButton> getButtons() {
+	return roButtons;
+    }
+
     public SettingsButton getApplyButton() {
 	return applyButton;
     }
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/RadLoginManager.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/RadLoginManager.java	Wed Nov 10 10:03:10 2010 -0500
@@ -124,23 +124,26 @@
 		roleInfo != null ? roleInfo.getConnector() :
 		userInfo != null ? userInfo.getConnector() : null;
 
-	    if (authConnector != connector) {
+	    if (authConnector != connector || auth == null) {
 		authConnector = connector;
+		auth = null;
 
-		try {
-		    MBeanServerConnection mbsc =
-			connector.getMBeanServerConnection();
+		if (connector != null) {
+		    try {
+			MBeanServerConnection mbsc =
+			    connector.getMBeanServerConnection();
 
-		    ObjectName oName = MBeanUtil.makeObjectName(
-			"org.opensolaris.os.rad", "authentication");
+			ObjectName oName = MBeanUtil.makeObjectName(
+			    "org.opensolaris.os.rad", "authentication");
 
-		    auth = JMX.newMXBeanProxy(mbsc, oName,
-			AuthenticatorMXBean.class);
-		} catch (IOException e) {
-		    request.getMessages().add(new DialogMessage(
-			Finder.getString("login.err.io",
-			request.getHost().getValue()),
-			JOptionPane.ERROR_MESSAGE));
+			auth = JMX.newMXBeanProxy(mbsc, oName,
+			    AuthenticatorMXBean.class);
+		    } catch (IOException e) {
+			request.getMessages().add(new DialogMessage(
+			    Finder.getString("login.err.io",
+			    request.getHost().getValue()),
+			    JOptionPane.ERROR_MESSAGE));
+		    }
 		}
 	    }
 
@@ -296,10 +299,14 @@
      *		    connections without the user's knowledge)
      *
      * @exception   ActionAbortedException
-     *		    if the action is aborted by the user
+     *		    if the user cancels the operation
+     *
+     * @exception   ActionFailedException
+     *		    if the given request fails
      */
     public ConnectionInfo[] getConnectionInfo(LoginRequest request,
-	ConnectionInfo current) throws ActionAbortedException {
+	ConnectionInfo current) throws ActionAbortedException,
+	ActionFailedException {
 
 	LoginData data = new LoginData();
 
@@ -446,6 +453,15 @@
 	Certificate certificate) throws ActionAbortedException;
 
     /**
+     * Prompts the user to acknowledge failure of the given request.
+     *
+     * @param	    request
+     *		    the {@link LoginRequest} encapsulating the values and
+     *		    error status of each core {@link LoginProperty}
+     */
+    protected abstract void promptForFailedRequest(LoginRequest request);
+
+    /**
      * Prompt the user to enter host/user data, subject to the editability and
      * preset values of the host and user {@link LoginProperty}s of the given
      * request.
@@ -735,68 +751,85 @@
     }
 
     private void gatherHostAndUserData(LoginRequest request, LoginData data)
-	throws ActionAbortedException {
+	throws ActionAbortedException, ActionFailedException {
 
 	StringLoginProperty host = request.getHost();
 	StringLoginProperty user = request.getUser();
 	List<DialogMessage> messages = request.getMessages();
 
-	// Loop until the specified user is authenticated on the specified host
+	// If the user property is in error, indicates whether the user could
+	// not be authorized (true), or whether the username was syntactically
+	// invalid (false).  Note that we cannot tell the difference between a
+	// valid user with a bad password and an invalid user.
+	boolean authError = false;
+
+	// Loop until connected to host and authenticated as user
 	while (true) {
+	    // Refresh each iteration in case host/user.isEditableOnError
+	    boolean hostEditable = host.isEditable();
+	    boolean userEditable = user.isEditable();
+
+	    // If host or user is in error, and cannot be fixed by the user...
+	    if (!hostEditable && (host.isErrored() ||
+		(!userEditable && user.isErrored() && !authError))) {
+
+		promptForFailedRequest(request);
+		throw new ActionFailedException(
+		    messages.isEmpty() ? null : messages.get(0).getText());
+	    }
+
+	    authError = false;
+
+	    if (hostEditable || userEditable || !messages.isEmpty()) {
+		promptForHostAndUser(request);
+		data.setUserAcknowledged(true);
+
+		// User only needs to see any message once, presumably
+		messages.clear();
+
+		host.setErrored(false);
+		user.setErrored(false);
+	    }
+
 	    String hostVal = host.getValue();
 	    String userVal = user.getValue();
 
-	    if (host.isEditable() || user.isEditable()) {
-		do {
-		    promptForHostAndUser(request);
-		    data.setUserAcknowledged(true);
-
-		    // User only needs to see any message once, presumably
-		    messages.clear();
-
-		    host.setErrored(false);
-		    user.setErrored(false);
-
-		    if (host.isEditable() &&
-			((hostVal = host.getValue()) == null ||
-			hostVal.isEmpty())) {
+	    if (hostVal == null || hostVal.isEmpty()) {
+		messages.add(new DialogMessage(
+		    Finder.getString("login.err.host.empty"),
+		    JOptionPane.ERROR_MESSAGE));
+		host.setErrored(true);
+		continue;
 
-			messages.add(new DialogMessage(
-			    Finder.getString("login.err.host.empty"),
-			    JOptionPane.ERROR_MESSAGE));
-			host.setErrored(true);
-
-		    } else if (user.isEditable() &&
-			((userVal = user.getValue()) == null ||
-			userVal.isEmpty())) {
-
-			messages.add(new DialogMessage(
-			    Finder.getString("login.err.user.empty"),
-			    JOptionPane.ERROR_MESSAGE));
-			user.setErrored(true);
-		    }
-		} while (!messages.isEmpty());
+	    } else if (userVal == null || userVal.isEmpty()) {
+		messages.add(new DialogMessage(
+		    Finder.getString("login.err.user.empty"),
+		    JOptionPane.ERROR_MESSAGE));
+		user.setErrored(true);
+		continue;
 	    }
 
-	    ConnectionInfo info = getConnectionManager().getConnection(
-		hostVal, userVal, null);
+	    // Is there an existing ConnectionInfo for this host & user?
+	    ConnectionInfo info = getConnectionManager().getConnection(hostVal,
+		userVal, null);
 	    if (info != null) {
 		data.setUserConnectionInfo(info);
 		return;
 	    }
 
-	    // No existing ConnectionInfo for this host & user
+	    // Create connection, append to messages on error
 	    ConnectorData conData = createConnection(request);
 	    if (conData != null) {
-		JMXConnector connector = conData.getConnector();
-		info = new ConnectionInfo(hostVal, userVal, null, connector);
+		info = new ConnectionInfo(hostVal, userVal, null,
+		    conData.getConnector());
 		data.setUserConnectionInfo(info);
 
 		// No need to authenticate if local user on local host
-		if ((conData.isLocal() && userVal.equals(LOCAL_USER))) {
+		if (conData.isLocal() && userVal.equals(LOCAL_USER)) {
 		    return;
 		}
 
+		// Get/create auth bean, append to messages on error
 		AuthenticatorMXBean auth = data.getAuthBean(request);
 		if (auth != null) {
 		    try {
@@ -806,9 +839,8 @@
 			}
 
 			// Authentication failed
-			if (user.isEditable() || user.isEditableOnError()) {
-			    user.setErrored(true);
-			}
+			user.setErrored(true);
+			authError = true;
 
 			// Add generic auth failure message if not already
 			// provided by server
@@ -818,7 +850,7 @@
 				JOptionPane.ERROR_MESSAGE));
 			}
 
-		    // Thrown by promptForUserAuth
+		    // Thrown by authConverse
 		    } catch (ActionRegressedException e) {
 			host.setErrored(false);
 			user.setErrored(false);
@@ -833,7 +865,8 @@
     }
 
     private void gatherRoleData(LoginRequest request, LoginData data)
-	throws ActionAbortedException, ActionRegressedException {
+	throws ActionAbortedException, ActionFailedException,
+	ActionRegressedException {
 
 	StringLoginProperty host = request.getHost();
 	String hostVal = host.getValue();
@@ -844,34 +877,56 @@
 	StringLoginProperty role = request.getRole();
 	List<DialogMessage> messages = request.getMessages();
 
-	// Loop until the specified role is authenticated
+	// Loop until no role is chosen, or chosen role is authenticated
 	while (true) {
-	    if (role.isEditable()) {
-		List<String> roles = Collections.emptyList();
-		AuthenticatorMXBean auth = data.getAuthBean(request);
-		if (auth != null) {
-		    roles = auth.getroles();
-		}
+	    // Get/create auth bean, append to messages on error
+	    AuthenticatorMXBean auth = data.getAuthBean(request);
+	    if (auth == null) {
+		// Not likely, but handle it anyway
+		promptForFailedRequest(request);
+		throw new ActionRegressedException();
+	    }
+
+	    // Get valid roles for this host/user
+	    List<String> roles = auth.getroles();
 
-		if (roles.isEmpty() && auth != null) {
+	    String roleVal = role.getValue();
+
+	    if (roleVal != null && !roles.contains(roleVal)) {
+		String message = Finder.getString("login.err.role.invalid",
+		    hostVal, userVal, roleVal);
+
+		messages.add(new DialogMessage(message,
+		    JOptionPane.ERROR_MESSAGE));
+
+		role.setErrored(true);
+
+		if (role.isEditable()) {
 		    role.setValue(null);
 		} else {
-		    promptForRole(request, roles);
-		    data.setRoleAcknowledged(true);
-
-		    // User only needs to see any message once, presumably
-		    messages.clear();
+		    promptForFailedRequest(request);
+		    throw new ActionFailedException(message);
 		}
 	    }
 
-	    String roleVal = role.getValue();
+	    if ((role.isEditable() && !roles.isEmpty()) || !messages.isEmpty())
+	    {
+		promptForRole(request, roles);
+		data.setRoleAcknowledged(true);
+
+		// User only needs to see any message once, presumably
+		messages.clear();
+
+		role.setErrored(false);
+		roleVal = role.getValue();
+	    }
 
 	    if (roleVal == null) {
 		// No need to keep going
 		return;
 	    }
 
-	    // Search for existing connection again now that roleVal is definite
+	    // Search for existing connection now that roleVal is definite
 	    ConnectionInfo info = getConnectionManager().getConnection(hostVal,
 		userVal, roleVal);
 	    if (info != null) {
@@ -879,54 +934,50 @@
 		return;
 	    }
 
-	    AuthenticatorMXBean auth = data.getAuthBean(request);
-	    if (auth != null) {
-		try {
-		    byte[] token = auth.createToken();
+	    try {
+		byte[] token = auth.createToken();
 
-		    ConnectorData conData = createConnection(request);
-		    if (conData != null) {
-			JMXConnector connector = conData.getConnector();
-			info = new ConnectionInfo(hostVal, userVal, roleVal,
-			    connector);
-			data.setRoleConnectionInfo(info);
+		// Create connection, append to messages on error
+		ConnectorData conData = createConnection(request);
+		if (conData != null) {
+		    JMXConnector connector = conData.getConnector();
+		    info = new ConnectionInfo(hostVal, userVal, roleVal,
+			connector);
+		    data.setRoleConnectionInfo(info);
 
-			auth = data.getAuthBean(request);
-			if (auth != null) {
-			    auth.redeemToken(userVal, token);
-			    if (authConverse(request, auth, new RolePrompter(),
-				data)) {
-				return;
-			    }
+		    // Create auth bean, append to messages on error
+		    auth = data.getAuthBean(request);
+		    if (auth != null) {
+			auth.redeemToken(userVal, token);
+			if (authConverse(request, auth, new RolePrompter(),
+			    data)) {
+			    return;
+			}
 
-			    // Authentication failed
-			    if (role.isEditable() || role.isEditableOnError()) {
-				role.setErrored(true);
-			    }
+			// Authentication failed
+			role.setErrored(true);
 
-			    // Add generic auth failure message if not already
-			    // provided by server
-			    if (messages.isEmpty()) {
-				messages.add(new DialogMessage(
-				    Finder.getString("login.err.role.auth",
-				    hostVal, userVal, roleVal),
-				    JOptionPane.ERROR_MESSAGE));
-			    }
+			// Add generic auth failure message if not already
+			// provided by server
+			if (messages.isEmpty()) {
+			    messages.add(new DialogMessage(
+				Finder.getString("login.err.role.auth", hostVal,
+				userVal, roleVal), JOptionPane.ERROR_MESSAGE));
 			}
 		    }
+		}
 
-		// Thrown by createToken/redeemToken
-		} catch (ObjectException e) {
-		    messages.add(new DialogMessage(Finder.getString(
-			"login.err.io", hostVal), JOptionPane.ERROR_MESSAGE));
+	    // Thrown by createToken/redeemToken
+	    } catch (ObjectException e) {
+		messages.add(new DialogMessage(Finder.getString(
+		    "login.err.io", hostVal), JOptionPane.ERROR_MESSAGE));
 
-		// Thrown by promptForRoleAuth
-		} catch (ActionRegressedException e) {
-		    host.setErrored(false);
-		    user.setErrored(false);
-		    role.setErrored(false);
-		    messages.clear();
-		}
+	    // Thrown by authConverse
+	    } catch (ActionRegressedException e) {
+		host.setErrored(false);
+		user.setErrored(false);
+		role.setErrored(false);
+		messages.clear();
 	    }
 
 	    // Could not create/authenticate connection -- reset and try again
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/resources/Resources.properties	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/common/resources/Resources.properties	Wed Nov 10 10:03:10 2010 -0500
@@ -20,8 +20,7 @@
 #
 
 #
-# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 #
 
 login.status.field.validation = Validating login fields...
@@ -30,12 +29,13 @@
 login.status.host.connecting = Connecting to {0}...
 login.status.loggingin = Logging into {0} as {1}...
 
-login.err.host.unknown = Host {0} is unknown.
-login.err.host.refused = Connection to host {0} was refused.
+login.err.host.unknown = Host "{0}" is unknown.
+login.err.host.refused = Connection to host "{0}" was refused.
 login.err.host.empty = Please specify a host to manage.
 login.err.user.empty = Please specify a username.
-login.err.user.auth = User {1} could not be authenticated.
-login.err.role.auth = Role {2} could not be authenticated.
+login.err.user.auth = User "{1}" could not be authenticated.
+login.err.role.invalid = Role "{2}" is not available to user "{1}" on host "{0}".
+login.err.role.auth = Role "{2}" could not be authenticated.
 login.err.url.invalid = The information entered resulted in an invalid URL.
 login.err.io = An error occurred when contacting {0}.
 login.err.nocerts = An error occurred when contacting {0}.
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/App.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/App.java	Wed Nov 10 10:03:10 2010 -0500
@@ -32,7 +32,7 @@
 import org.opensolaris.os.uds.*;
 import org.opensolaris.os.vp.client.common.*;
 import org.opensolaris.os.vp.panel.common.*;
-import org.opensolaris.os.vp.panel.common.action.ActionAbortedException;
+import org.opensolaris.os.vp.panel.common.action.*;
 import org.opensolaris.os.vp.panel.common.control.*;
 import org.opensolaris.os.vp.util.cli.*;
 import org.opensolaris.os.vp.util.misc.*;
@@ -394,7 +394,7 @@
 	exit();
     }
 
-    public void exit() {
+    public void exit(int exitCode) {
 	try {
 	    if (server != null) {
 		server.close();
@@ -409,7 +409,17 @@
 		"could not write preferences", e);
 	}
 
-	System.exit(0);
+	System.exit(exitCode);
+    }
+
+    public void exit() {
+	exit(0);
+    }
+
+    private void exitIfNoIntances(int exitCode) {
+	if (instances.isEmpty()) {
+	    exit(exitCode);
+	}
     }
 
     public ConnectionManager getConnectionManager() {
@@ -422,8 +432,8 @@
 
     protected void instanceClosed(AppInstance instance) {
 	synchronized (instances) {
-	    if (instances.remove(instance) && instances.isEmpty()) {
-		exit();
+	    if (instances.remove(instance)) {
+		exitIfNoIntances(0);
 	    }
 	}
     }
@@ -462,6 +472,9 @@
 	// User is aware of this because he explicitly caused it
 	} catch (ActionAbortedException e) {
 
+	// User has been advised of this by RadLoginManager
+	} catch (ActionFailedException e) {
+
 	// User has been advised of this by SwingNavigator
 	} catch (NavigationException e) {
 
@@ -645,6 +658,7 @@
 
 	App app = new App();
 	app.newInstance(properties, uri);
+	app.exitIfNoIntances(1);
 
 	if (!bean.getNewjvm()) {
 	    // Start daemon thread listening for connections
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppInstance.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppInstance.java	Wed Nov 10 10:03:10 2010 -0500
@@ -25,10 +25,9 @@
 
 package org.opensolaris.os.vp.client.swing;
 
+import java.awt.event.*;
 import java.awt.Window;
-import java.awt.event.*;
 import java.net.URL;
-import java.beans.*;
 import java.security.*;
 import java.util.*;
 import java.util.logging.*;
@@ -36,7 +35,7 @@
 import javax.swing.*;
 import org.opensolaris.os.vp.client.common.*;
 import org.opensolaris.os.vp.panel.common.*;
-import org.opensolaris.os.vp.panel.common.action.ActionAbortedException;
+import org.opensolaris.os.vp.panel.common.action.*;
 import org.opensolaris.os.vp.panel.common.control.*;
 import org.opensolaris.os.vp.panel.common.control.InvalidParameterException;
 import org.opensolaris.os.vp.panel.common.view.*;
@@ -121,7 +120,7 @@
     }
 
     public AppInstance(App app, Properties hints, LoginRequest request)
-	throws ActionAbortedException {
+	throws ActionAbortedException, ActionFailedException {
 
 	this(app, hints);
 
@@ -240,10 +239,14 @@
 
     @Override
     public ClientContext login(final LoginRequest request,
-	final boolean forceNewContext) throws ActionAbortedException {
+        final boolean forceNewContext) throws ActionAbortedException,
+        ActionFailedException {
+
 	// Using PrivilegedExceptionAction offers no compile-time checking of
 	// thrown Exceptions, so use this mildly awkward method instead
-	final ActionAbortedException[] err = new ActionAbortedException[1];
+
+	final ActionAbortedException[] abort = new ActionAbortedException[1];
+	final ActionFailedException[] fail = new ActionFailedException[1];
 
 	ClientContext context = AccessController.doPrivileged(
 	    new PrivilegedAction<ClientContext>() {
@@ -252,14 +255,20 @@
 		    try {
 			return loginImp(request, forceNewContext);
 		    } catch (ActionAbortedException e) {
-			err[0] = e;
+			abort[0] = e;
+		    } catch (ActionFailedException e) {
+			fail[0] = e;
 		    }
 		    return null;
 		}
 	    });
 
-	if (err[0] != null) {
-	    throw err[0];
+	if (abort[0] != null) {
+	    throw abort[0];
+	}
+
+	if (fail[0] != null) {
+	    throw fail[0];
 	}
 
 	return context;
@@ -408,7 +417,8 @@
     }
 
     private ClientContext loginImp(LoginRequest request,
-	boolean forceNewContext) throws ActionAbortedException {
+        boolean forceNewContext) throws ActionAbortedException,
+        ActionFailedException {
 
 	if (request == null) {
 	    StringLoginProperty host =
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppLoginManager.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppLoginManager.java	Wed Nov 10 10:03:10 2010 -0500
@@ -20,8 +20,7 @@
  */
 
 /*
- * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 package org.opensolaris.os.vp.client.swing;
@@ -60,7 +59,8 @@
 
     @Override
     public ConnectionInfo[] getConnectionInfo(LoginRequest request,
-	ConnectionInfo current) throws ActionAbortedException {
+        ConnectionInfo current) throws ActionAbortedException,
+        ActionFailedException {
 
 	final Window owner = hasWindow == null ?
 	    null : hasWindow.getComponent();
@@ -121,6 +121,11 @@
     }
 
     @Override
+    protected void promptForFailedRequest(LoginRequest request) {
+	dialog.getLoginPane().promptForFailedRequest(request);
+    }
+
+    @Override
     protected void promptForHostAndUser(LoginRequest request)
 	throws ActionAbortedException {
 
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/LoginPane.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/LoginPane.java	Wed Nov 10 10:03:10 2010 -0500
@@ -149,6 +149,7 @@
 
 	public LoginComboBox(String nullText) {
 	    this.nullText = nullText;
+	    updateLabel();
 	}
 
 	//
@@ -263,6 +264,9 @@
 	buttonBar.remove(cancel);
 	buttonBar.add(cancel, 0);
 
+	JButton close = buttonBar.getCloseButton();
+	close.addActionListener(setClickedButtonActionListener);
+
 	JButton back = buttonBar.getBackButton();
 	back.addActionListener(setClickedButtonActionListener);
 
@@ -317,6 +321,36 @@
 	}
     }
 
+    public void promptForFailedRequest(final LoginRequest request) {
+	assert !EventQueue.isDispatchThread();
+
+	final StringLoginProperty host = request.getHost();
+	final StringLoginProperty user = request.getUser();
+	final StringLoginProperty role = request.getRole();
+
+	GUIUtil.invokeAndWait(
+	    new Runnable() {
+		@Override
+		public void run() {
+		    DialogMessage help = new DialogMessage(Finder.getString(
+                        "login.message.fail", host.getValue(), user.getValue(),
+                        role.getValue()));
+
+		    List<DialogMessage> messages = request.getMessages();
+		    messages.add(help);
+		    getMessagePanel().setMessages(messages);
+
+		    setHostInUI(host, true);
+		    setUserInUI(user, true);
+		    setRoleInUI(role, true);
+
+		    clearAuthFields();
+		}
+	});
+
+	awaitClose();
+    }
+
     public void promptForHostAndUser(final LoginRequest request)
 	throws ActionAbortedException {
 
@@ -486,14 +520,41 @@
     // Private methods
     //
 
+    private void awaitClose() {
+	SettingsButtonBar bar = getButtonBar();
+	AbstractButton closeButton = bar.getCloseButton();
+
+	for (AbstractButton button : bar.getButtons()) {
+	    button.setVisible(button == closeButton);
+	}
+
+	while (awaitClickedButton() != closeButton);
+    }
+
     private void awaitForward() throws ActionAbortedException,
 	ActionRegressedException {
 
-	JButton clicked = awaitClickedButton();
-	if (clicked == getButtonBar().getBackButton()) {
-	    throw new ActionRegressedException();
-	} else if (clicked != getButtonBar().getForwardButton()) {
-	    throw new ActionAbortedException();
+	SettingsButtonBar bar = getButtonBar();
+	AbstractButton backButton = bar.getBackButton();
+	AbstractButton forwardButton = bar.getForwardButton();
+	AbstractButton cancelButton = bar.getCancelButton();
+
+	for (AbstractButton button : bar.getButtons()) {
+	    button.setVisible(button == backButton ||
+		button == forwardButton || button == cancelButton);
+	}
+
+	JButton clicked = null;
+	while (clicked != forwardButton) {
+	    clicked = awaitClickedButton();
+
+	    if (clicked == backButton) {
+		throw new ActionRegressedException();
+	    }
+
+	    if (clicked == cancelButton) {
+		throw new ActionAbortedException();
+	    }
 	}
     }
 
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/resources/Resources.properties	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/resources/Resources.properties	Wed Nov 10 10:03:10 2010 -0500
@@ -33,7 +33,8 @@
 # 1: user
 # 2: role
 login.message.ack = Proceed with the following connection?
-login.message.hostuser = User {1} will administer host {0}.
+login.message.fail = The requested login has failed.
+login.message.hostuser = Review the selected host to connect to and user to administer it.
 login.message.hostuser.host = Select a host to administer as user {1}.
 login.message.hostuser.host.user = Select a host to connect to and a user to administer it.
 login.message.hostuser.user = Select a user to administer host {0}.
--- a/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/ClientContext.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/ClientContext.java	Wed Nov 10 10:03:10 2010 -0500
@@ -27,7 +27,7 @@
 
 import java.util.Properties;
 import javax.help.HelpBroker;
-import org.opensolaris.os.vp.panel.common.action.ActionAbortedException;
+import org.opensolaris.os.vp.panel.common.action.*;
 import org.opensolaris.os.vp.panel.common.control.Navigator;
 import org.opensolaris.os.vp.panel.common.view.HasBusyIndicator;
 
@@ -103,9 +103,15 @@
      *
      * @return	    the {@link ClientContext} for the new instance, if created,
      *		    or this {@link ClientContext} otherwise
+     *
+     * @exception   ActionAbortedException
+     *		    if the user cancels the operation
+     *
+     * @exception   ActionFailedException
+     *		    if the given request fails
      */
     ClientContext login(LoginRequest request, boolean forceNewContext)
-	throws ActionAbortedException;
+	throws ActionAbortedException, ActionFailedException;
 
     /**
      * Removes a {@link ConnectionListener} from notification.
--- a/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/ConnectionInfo.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/ConnectionInfo.java	Wed Nov 10 10:03:10 2010 -0500
@@ -58,6 +58,35 @@
     }
 
     //
+    // LoginInfo methods
+    //
+
+    @Override
+    public String getHost() {
+	return host;
+    }
+
+    @Override
+    public String getRole() {
+	return role;
+    }
+
+    @Override
+    public String getUser() {
+	return user;
+    }
+
+    @Override
+    public boolean matches(String host, String user, String role) {
+	return matchesUser(user) && matchesRole(role) && matchesHost(host);
+    }
+
+    @Override
+    public boolean matches(LoginInfo info) {
+	return matches(info.getHost(), info.getUser(), info.getRole());
+    }
+
+    //
     // Object methods
     //
 
@@ -110,7 +139,7 @@
 		    }
 		}
 	    }
-	} catch (UnknownHostException ignore) {
+	} catch (UnknownHostException stop) {
 	}
 	return false;
     }
@@ -123,33 +152,4 @@
     public boolean matchesUser(String user) {
 	return user.equals(getUser());
     }
-
-    //
-    // LoginInfo methods
-    //
-
-    @Override
-    public String getHost() {
-	return host;
-    }
-
-    @Override
-    public String getRole() {
-	return role;
-    }
-
-    @Override
-    public String getUser() {
-	return user;
-    }
-
-    @Override
-    public boolean matches(String host, String user, String role) {
-	return matchesUser(user) && matchesRole(role) && matchesHost(host);
-    }
-
-    @Override
-    public boolean matches(LoginInfo info) {
-	return matches(info.getHost(), info.getUser(), info.getRole());
-    }
 }
--- a/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/LoginInfo.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/LoginInfo.java	Wed Nov 10 10:03:10 2010 -0500
@@ -33,25 +33,25 @@
     /**
      * Gets the host for this login.
      */
-    public String getHost();
+    String getHost();
 
     /**
      * Gets the role for this login.
      */
-    public String getRole();
+    String getRole();
 
     /**
      * Gets the user for this login.
      */
-    public String getUser();
+    String getUser();
 
     /**
      * Test if given properties match this instance.
      */
-    public boolean matches(String host, String user, String role);
+    boolean matches(String host, String user, String role);
 
     /**
      * Test if given {@link LoginInfo} matches this instance.
      */
-    public boolean matches(LoginInfo info);
+    boolean matches(LoginInfo info);
 }
--- a/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/LoginProperty.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/common/LoginProperty.java	Wed Nov 10 10:03:10 2010 -0500
@@ -55,8 +55,20 @@
     }
 
     //
+    // Object methods
+    //
+
+    @Override
+    public String toString() {
+	return String.format(
+            "name=%s, value=%s, editable=%b, editableOnError=%b, errored=%b",
+            name, value, editable, editableOnError, errored);
+    }
+
+    //
     // LoginProperty methods
     //
+
     /**
      * Gets the name of this {@code LoginProperty}.
      *
@@ -123,12 +135,13 @@
      * Sets the value of this {@code LoginProperty}.
      *
      * @throws	    IllegalStateException
-     *		    if this {@code LoginProperty} is editable
+     *		    if this {@code LoginProperty} is not editable
      */
-    public void setValue(T v) {
-	if (!editable)
+    public void setValue(T value) {
+	if (!editable) {
 	    throw new IllegalStateException();
+	}
 
-	    value = v;
+	this.value = value;
     }
 }
--- a/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/swing/control/PanelFrameControl.java	Tue Nov 09 20:47:10 2010 -0500
+++ b/usr/src/java/vpanels/panel/org/opensolaris/os/vp/panel/swing/control/PanelFrameControl.java	Wed Nov 10 10:03:10 2010 -0500
@@ -31,10 +31,10 @@
 import java.util.*;
 import java.util.logging.*;
 import javax.swing.*;
+import javax.swing.border.Border;
 import javax.swing.event.*;
-import javax.swing.border.Border;
 import org.opensolaris.os.vp.panel.common.*;
-import org.opensolaris.os.vp.panel.common.action.ActionAbortedException;
+import org.opensolaris.os.vp.panel.common.action.*;
 import org.opensolaris.os.vp.panel.common.control.*;
 import org.opensolaris.os.vp.panel.common.model.*;
 import org.opensolaris.os.vp.panel.swing.view.*;
@@ -557,7 +557,7 @@
 				array);
 			}
 		    } catch (ActionAbortedException ignore) {
-
+		    } catch (ActionFailedException ignore) {
 		    } catch (Throwable e) {
 			Logger.getLogger(getClass().getPackage().getName()).log(
 			    Level.WARNING, "unable to log in as " +
Binary file usr/src/java/vpanels/panels/hypervisor/org/opensolaris/os/vp/panels/hypervisor/client/swing/help/advanced.html has changed