components/visual-panels/core/src/java/vpanels/client/com/oracle/solaris/vp/client/common/RadLoginManager.java
changeset 3553 f1d133b09a8c
parent 3552 077ebe3d0d24
child 3554 ef58713bafc4
equal deleted inserted replaced
3552:077ebe3d0d24 3553:f1d133b09a8c
     1 /*
       
     2  * CDDL HEADER START
       
     3  *
       
     4  * The contents of this file are subject to the terms of the
       
     5  * Common Development and Distribution License (the "License").
       
     6  * You may not use this file except in compliance with the License.
       
     7  *
       
     8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
     9  * or http://www.opensolaris.org/os/licensing.
       
    10  * See the License for the specific language governing permissions
       
    11  * and limitations under the License.
       
    12  *
       
    13  * When distributing Covered Code, include this CDDL HEADER in each
       
    14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    15  * If applicable, add the following below this CDDL HEADER, with the
       
    16  * fields enclosed by brackets "[]" replaced with your own identifying
       
    17  * information: Portions Copyright [yyyy] [name of copyright owner]
       
    18  *
       
    19  * CDDL HEADER END
       
    20  */
       
    21 
       
    22 /*
       
    23  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
       
    24  */
       
    25 
       
    26 package com.oracle.solaris.vp.client.common;
       
    27 
       
    28 import java.io.*;
       
    29 import java.net.*;
       
    30 import java.security.*;
       
    31 import java.security.cert.*;
       
    32 import java.security.cert.Certificate;
       
    33 import java.util.*;
       
    34 import java.util.logging.*;
       
    35 import java.net.InetAddress;
       
    36 import javax.swing.JOptionPane;
       
    37 import com.oracle.solaris.rad.client.ProxyInterface;
       
    38 import com.oracle.solaris.rad.client.RadObjectException;
       
    39 import com.oracle.solaris.rad.connect.Connection;
       
    40 import com.oracle.solaris.rad.pam.*;
       
    41 import com.oracle.solaris.rad.transports.RadTrustManager;
       
    42 import com.oracle.solaris.rad.zonesbridge.*;
       
    43 import com.oracle.solaris.vp.panel.common.*;
       
    44 import com.oracle.solaris.vp.panel.common.action.*;
       
    45 import com.oracle.solaris.vp.util.misc.*;
       
    46 import com.oracle.solaris.vp.util.misc.finder.Finder;
       
    47 
       
    48 public abstract class RadLoginManager {
       
    49     public static final String RAD_PATH_AFUNIX_AUTH =
       
    50 	"/system/volatile/rad/radsocket";
       
    51     public static final String RAD_PATH_AFUNIX_UNAUTH =
       
    52 	"/system/volatile/rad/radsocket-unauth";
       
    53 
       
    54     //
       
    55     // Inner classes
       
    56     //
       
    57 
       
    58     private static abstract class AuthPrompter {
       
    59 	//
       
    60 	// Instance data
       
    61 	//
       
    62 
       
    63 	private boolean acknowledged;
       
    64 
       
    65 	//
       
    66 	// AuthPrompter methods
       
    67 	//
       
    68 
       
    69 	/**
       
    70 	 * _,__/|
       
    71 	 *  `O.o'
       
    72 	 * =(_x_)=
       
    73 	 *    U
       
    74 	 */
       
    75 	protected void ack() {
       
    76 	    acknowledged = true;
       
    77 	}
       
    78 
       
    79 	public boolean isAcknowledged() {
       
    80 	    return acknowledged;
       
    81 	}
       
    82 
       
    83 	public abstract Block initiate(LoginRequest request,
       
    84 	    Authentication auth) throws ActionAbortedException,
       
    85 	    RadObjectException;
       
    86 
       
    87 	public abstract void prompt(LoginRequest request,
       
    88 	    List<LoginProperty> properties, boolean isFirst)
       
    89 	    throws ActionAbortedException, ActionRegressedException;
       
    90     }
       
    91 
       
    92     private class UserPrompter extends AuthPrompter {
       
    93 	@Override
       
    94 	public Block initiate(LoginRequest request,
       
    95 	    Authentication auth) throws ActionAbortedException,
       
    96 	    RadObjectException {
       
    97 
       
    98 	    setLoginStatus(request, Finder.getString("login.status.login",
       
    99 		request.getUser().getValue()));
       
   100 
       
   101 	    return auth.login(Locale.getDefault().getLanguage(),
       
   102 		request.getUser().getValue());
       
   103 	}
       
   104 
       
   105 	@Override
       
   106 	public void prompt(LoginRequest request, List<LoginProperty> properties,
       
   107 	    boolean isFirst) throws ActionAbortedException,
       
   108 	    ActionRegressedException {
       
   109 
       
   110 	    try {
       
   111 		promptForAuth(request, properties, false, true, isFirst);
       
   112 	    } finally {
       
   113 		ack();
       
   114 		request.getHost().setErrored(false);
       
   115 		request.getUser().setErrored(false);
       
   116 	    }
       
   117 	}
       
   118     }
       
   119 
       
   120     private class RolePrompter extends AuthPrompter {
       
   121 	@Override
       
   122 	public Block initiate(LoginRequest request,
       
   123 	    Authentication auth) throws ActionAbortedException,
       
   124 	    RadObjectException {
       
   125 
       
   126 	    setLoginStatus(request, Finder.getString("login.status.assume",
       
   127 		request.getRole().getValue()));
       
   128 
       
   129 	    return auth.assume(Locale.getDefault().getLanguage(),
       
   130 		request.getRole().getValue());
       
   131 	}
       
   132 
       
   133 	@Override
       
   134 	public void prompt(LoginRequest request, List<LoginProperty> properties,
       
   135 	    boolean isFirst) throws ActionAbortedException,
       
   136 	    ActionRegressedException {
       
   137 
       
   138 	    try {
       
   139 		promptForAuth(request, properties, false, false, isFirst);
       
   140 	    } finally {
       
   141 		ack();
       
   142 		request.getRole().setErrored(false);
       
   143 	    }
       
   144 	}
       
   145     }
       
   146 
       
   147     private class ZoneUserPrompter extends AuthPrompter {
       
   148 	@Override
       
   149 	public Block initiate(LoginRequest request,
       
   150 	    Authentication auth) throws ActionAbortedException,
       
   151 	    RadObjectException {
       
   152 
       
   153 	    return auth.login(Locale.getDefault().getLanguage(),
       
   154 		request.getZoneUser().getValue());
       
   155 	}
       
   156 
       
   157 	@Override
       
   158 	public void prompt(LoginRequest request, List<LoginProperty> properties,
       
   159 	    boolean isFirst) throws ActionAbortedException,
       
   160 	    ActionRegressedException {
       
   161 
       
   162 	    try {
       
   163 		promptForAuth(request, properties, true, true, isFirst);
       
   164 	    } finally {
       
   165 		ack();
       
   166 		request.getZone().setErrored(false);
       
   167 		request.getZoneUser().setErrored(false);
       
   168 	    }
       
   169 	}
       
   170     }
       
   171 
       
   172     private class ZoneRolePrompter extends AuthPrompter {
       
   173 	@Override
       
   174 	public Block initiate(LoginRequest request,
       
   175 	    Authentication auth) throws ActionAbortedException,
       
   176 	    RadObjectException {
       
   177 
       
   178 	    return auth.assume(Locale.getDefault().getLanguage(),
       
   179 		request.getZoneRole().getValue());
       
   180 	}
       
   181 
       
   182 	@Override
       
   183 	public void prompt(LoginRequest request, List<LoginProperty> properties,
       
   184 	    boolean isFirst) throws ActionAbortedException,
       
   185 	    ActionRegressedException {
       
   186 
       
   187 	    try {
       
   188 		promptForAuth(request, properties, true, false, isFirst);
       
   189 	    } finally {
       
   190 		ack();
       
   191 		request.getZoneRole().setErrored(false);
       
   192 	    }
       
   193 	}
       
   194     }
       
   195 
       
   196     private static class LoginData {
       
   197 	//
       
   198 	// Instance data
       
   199 	//
       
   200 
       
   201 	private LinkedList<ConnectionInfo> depChain =
       
   202 	    new LinkedList<ConnectionInfo>();
       
   203 
       
   204 	private LinkedList<Boolean> acks = new LinkedList<Boolean>();
       
   205 
       
   206 	//
       
   207 	// LoginData methods
       
   208 	//
       
   209 
       
   210 	public boolean isAcknowledged() {
       
   211 	    return acks.peek();
       
   212 	}
       
   213 
       
   214 	public List<ConnectionInfo> getDepChain() {
       
   215 	    return depChain;
       
   216 	}
       
   217 
       
   218 	public ConnectionInfo peek(int offset) {
       
   219 	    return depChain.get(offset);
       
   220 	}
       
   221 
       
   222 	public void pop() {
       
   223 	    depChain.pop();
       
   224 	    acks.pop();
       
   225 	}
       
   226 
       
   227 	public void push(ConnectionInfo info, boolean acknowledged) {
       
   228 	    depChain.push(info);
       
   229 	    acks.push(acknowledged);
       
   230 	}
       
   231 
       
   232 	public void setDepChain(List<ConnectionInfo> depChain,
       
   233 	    boolean acknowledged) {
       
   234 
       
   235 	    assert compatible(depChain);
       
   236 
       
   237 	    this.depChain.clear();
       
   238 	    acks.clear();
       
   239 
       
   240 	    for (int i = depChain.size() - 1; i >= 0; i--) {
       
   241 		push(depChain.get(i), acknowledged);
       
   242 	    }
       
   243 	}
       
   244 
       
   245 	//
       
   246 	// Object methods
       
   247 	//
       
   248 
       
   249 	@Override
       
   250 	public String toString() {
       
   251 	    StringBuilder buffer = new StringBuilder();
       
   252 	    for (int i = depChain.size() - 1; i >= 0; i--) {
       
   253 		buffer.append(String.format("%d. %s (%s)\n", i,
       
   254 		    depChain.get(i), acks.get(i) ? "acknowledged" :
       
   255 		    "not acknowledged"));
       
   256 	    }
       
   257 	    return buffer.toString();
       
   258 	}
       
   259 
       
   260 	//
       
   261 	// Private methods
       
   262 	//
       
   263 
       
   264 	private void clearConnectionsTo(int level) {
       
   265 	    int size = depChain.size();
       
   266 	    for (int i = size - 1; i >= level; i--) {
       
   267 		depChain.remove(i);
       
   268 	    }
       
   269 	}
       
   270 
       
   271 	private boolean compatible(List<ConnectionInfo> depChain) {
       
   272 	    boolean compatible = false;
       
   273 	    if (depChain.size() == this.depChain.size() + 1) {
       
   274 		compatible = true;
       
   275 		for (int i = 0, n = this.depChain.size(); i < n; i++) {
       
   276 		    if (!this.depChain.get(i).equals(depChain.get(i + 1))) {
       
   277 			compatible = false;
       
   278 			break;
       
   279 		    }
       
   280 		}
       
   281 	    }
       
   282 	    return compatible;
       
   283 	}
       
   284     }
       
   285 
       
   286     //
       
   287     // Static data
       
   288     //
       
   289 
       
   290     public static final String TRUSTSTORE_PASSWORD = "trustpass";
       
   291     public static final String LOCAL_USER = System.getProperty("user.name");
       
   292     public static final String LOCAL_HOST = "localhost";
       
   293 
       
   294     //
       
   295     // Instance data
       
   296     //
       
   297 
       
   298     private ConnectionManager connManager;
       
   299 
       
   300     //
       
   301     // Constructors
       
   302     //
       
   303 
       
   304     public RadLoginManager(ConnectionManager connManager) {
       
   305 	this.connManager = connManager;
       
   306     }
       
   307 
       
   308     //
       
   309     // RadLoginManager methods
       
   310     //
       
   311 
       
   312     /**
       
   313      * Creates an empty truststore file.
       
   314      */
       
   315     protected void createTrustStore(File truststore) throws KeyStoreException,
       
   316 	IOException, NoSuchAlgorithmException, CertificateException {
       
   317 
       
   318 	File truststoreDir = truststore.getParentFile();
       
   319 
       
   320 	if (!truststoreDir.exists()) {
       
   321 	    if (!truststoreDir.mkdirs()) {
       
   322 		throw new IOException(
       
   323 		    "could not create truststore directory: " +
       
   324 		    truststoreDir.getAbsolutePath());
       
   325 	    }
       
   326 	}
       
   327 
       
   328 	KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
       
   329 	char[] password = getTrustStorePassword().toCharArray();
       
   330 
       
   331 	// Create empty keystore
       
   332 	keyStore.load(null, password);
       
   333 
       
   334 	FileOutputStream fos = new FileOutputStream(truststore);
       
   335 	keyStore.store(fos, password);
       
   336 	fos.close();
       
   337     }
       
   338 
       
   339     /**
       
   340      * Guides the user through the login process, then returns a dependency
       
   341      * chain of {@link ConnectionInfo}s.  The first element of the chain is the
       
   342      * {@code ConnectionInfo} that satisfies the given request.  Each additional
       
   343      * {@code ConnectionInfo} is a dependency of the previous {@code
       
   344      * ConnectionInfo}.
       
   345      * <p/>
       
   346      * For example, a role-based connection ("root@nerd (via talley)", say)
       
   347      * would have an dependency on the user-based connection ("talley@nerd")
       
   348      * used to create it.  The returned chain would contain the role-based
       
   349      * connection just before the user-based connection.
       
   350      * <p/>
       
   351      * This chain of {@code ConnectionInfo} dependencies can be {@link
       
   352      * ConnectionManager#add added} to the {@code ConnectionManager} for
       
   353      * automatic dependency-based management.
       
   354      *
       
   355      * @param	    request
       
   356      *		    the {@link LoginRequest} encapsulating the preset values and
       
   357      *		    editability of each core {@link LoginProperty}
       
   358      *
       
   359      * @param	    current
       
   360      *		    if non-{@code null}, ensures that the user is aware of any
       
   361      *		    change in login, preventing the use of cached connections
       
   362      *		    without the user's knowledge
       
   363      *
       
   364      * @exception   ActionAbortedException
       
   365      *		    if the user cancels the operation
       
   366      *
       
   367      * @exception   ActionFailedException
       
   368      *		    if the given request fails
       
   369      */
       
   370     @SuppressWarnings({"fallthrough"})
       
   371     public List<ConnectionInfo> getConnectionInfo(LoginRequest request,
       
   372 	LoginInfo current) throws ActionAbortedException, ActionFailedException
       
   373     {
       
   374 	LoginData data = new LoginData();
       
   375 	Boolean doZone;
       
   376 	for (int step = 0; ; ) {
       
   377 	    try {
       
   378 		switch (step) {
       
   379 		case 0:
       
   380 		    gatherHostAndUserData(request, data);
       
   381 		    step++;
       
   382 
       
   383 		case 1:
       
   384 		    gatherRoleData(request, data);
       
   385 		    step++;
       
   386 		case 2:
       
   387 		    doZone = request.getZonePrompt().getValue();
       
   388 		    if (doZone != null && doZone) {
       
   389 			gatherZoneHostAndUserData(request, data);
       
   390 		    }
       
   391 		    step++;
       
   392 
       
   393 		case 3:
       
   394 		    doZone = request.getZonePrompt().getValue();
       
   395 		    if (doZone != null && doZone) {
       
   396 			gatherZoneRoleData(request, data);
       
   397 		    }
       
   398 		    step++;
       
   399 		}
       
   400 		break;
       
   401 	    } catch (ActionRegressedException e) {
       
   402 		step--;
       
   403 	    }
       
   404 	}
       
   405 
       
   406 	List<ConnectionInfo> depChain = data.getDepChain();
       
   407 
       
   408 	// To prevent rogue connections, if the chosen connection differs from
       
   409 	// the current one, ensure that the user has acknowledged it at some
       
   410 	// point in the authentication process.  If not, prompt for
       
   411 	// acknowledgement now.
       
   412 	if (!data.isAcknowledged() && !depChain.get(0).matches(current)) {
       
   413 	    promptForAck(request);
       
   414 	}
       
   415 
       
   416 	return depChain;
       
   417     }
       
   418 
       
   419     public ConnectionManager getConnectionManager() {
       
   420 	return connManager;
       
   421     }
       
   422 
       
   423     /**
       
   424      * Gets the truststore file.
       
   425      */
       
   426     public abstract File getTrustStoreFile();
       
   427 
       
   428     /**
       
   429      * Gets the truststore password.  This default implementation returns
       
   430      * "{@code trustpass}".
       
   431      */
       
   432     public String getTrustStorePassword() {
       
   433 	return TRUSTSTORE_PASSWORD;
       
   434     }
       
   435 
       
   436     protected boolean handleCertFailure(String host, File truststore,
       
   437 	Certificate certificate) throws ActionAbortedException,
       
   438 	KeyStoreException, IOException, NoSuchAlgorithmException,
       
   439 	CertificateException {
       
   440 
       
   441 	KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
       
   442 	char[] password = getTrustStorePassword().toCharArray();
       
   443 
       
   444 	// Load truststore
       
   445 	FileInputStream fis = new FileInputStream(truststore);
       
   446 	keyStore.load(fis, password);
       
   447 	fis.close();
       
   448 
       
   449 	// Does the truststore already contain the certificate?
       
   450 	String alias = keyStore.getCertificateAlias(certificate);
       
   451 	if (alias != null) {
       
   452 	    return false;
       
   453 	}
       
   454 
       
   455 	boolean acceptNeeded = true;
       
   456 
       
   457 	if (NetUtil.isLocalAddress(host)) {
       
   458 	    FileInputStream certFileIn = null;
       
   459 	    try {
       
   460 		File certFile = new File("/etc/rad/cert.pem");
       
   461 		certFileIn = new FileInputStream(certFile);
       
   462 		Certificate localCert = CertificateFactory.
       
   463 		    getInstance("X.509").generateCertificate(certFileIn);
       
   464 
       
   465 		if (localCert.equals(certificate)) {
       
   466 		    acceptNeeded = false;
       
   467 		}
       
   468 	    } catch (Throwable ignore) {
       
   469 	    } finally {
       
   470 		IOUtil.closeIgnore(certFileIn);
       
   471 	    }
       
   472 	}
       
   473 
       
   474 	if (acceptNeeded) {
       
   475 	    // Display the certificate, prompt user to accept
       
   476 	    promptForCertificate(host, certificate);
       
   477 	}
       
   478 
       
   479 	// Add certificate
       
   480 	alias = ((X509Certificate)certificate).getIssuerDN().toString();
       
   481 	KeyStore.Entry entry =
       
   482 	    new KeyStore.TrustedCertificateEntry(certificate);
       
   483 	keyStore.setEntry(alias, entry, null);
       
   484 
       
   485 	FileOutputStream fos = new FileOutputStream(truststore);
       
   486 	keyStore.store(fos, password);
       
   487 	fos.close();
       
   488 
       
   489 	return true;
       
   490     }
       
   491 
       
   492     /**
       
   493      * Prompt the user to acknowledge or reject the imminent completion of the
       
   494      * given request.
       
   495      *
       
   496      * @param	    request
       
   497      *		    the {@link LoginRequest} encapsulating the preset values of
       
   498      *		    each core {@link LoginProperty}
       
   499      *
       
   500      * @exception   ActionAbortedException
       
   501      *		    if the user cancels the operation
       
   502      */
       
   503     protected abstract void promptForAck(LoginRequest request)
       
   504 	throws ActionAbortedException;
       
   505 
       
   506     /**
       
   507      * Prompts the user to enter data for each of the given {@link
       
   508      * LoginProperty}s required for user or role authentication by the server.
       
   509      *
       
   510      * @param	    request
       
   511      *		    the {@link LoginRequest} encapsulating the preset values and
       
   512      *		    editability of each core {@link LoginProperty}
       
   513      *
       
   514      * @param	    properties
       
   515      *		    a {@code List} of {@link LoginProperty}s from the server
       
   516      *		    ({@code LoginProperty<String>} or {@code
       
   517      *		    PasswordLoginProperty} only)
       
   518      *
       
   519      * @exception   ActionAbortedException
       
   520      *		    if the user cancels the operation
       
   521      *
       
   522      * @exception   ActionRegressedException
       
   523      *		    if the chooses to re-edit the host or user fields
       
   524      *
       
   525      * @param	    isZone
       
   526      *		    {@code true} if prompting for a zone user or role
       
   527      *		    authentication
       
   528      *
       
   529      * @param	    isUserAuth
       
   530      *		    {@code true} if for user authentication, {@code false} if
       
   531      *		    for role authentication
       
   532      *
       
   533      * @param	    isFirst
       
   534      *		    {@code true} if this is the first prompt in the
       
   535      *		    authentication conversation
       
   536      */
       
   537     protected abstract void promptForAuth(LoginRequest request,
       
   538 	List<LoginProperty> properties, boolean isZone, boolean isUserAuth,
       
   539 	boolean isFirst) throws ActionAbortedException,
       
   540 	ActionRegressedException;
       
   541 
       
   542     /**
       
   543      * Display the given {@code Certificate} details and prompt for user
       
   544      * confirmation to add it to the truststore.
       
   545      *
       
   546      * @param	    host
       
   547      *		    the owner of the {@code Certificate}
       
   548      *
       
   549      * @param	    certificate
       
   550      *		    the {@code Certificate} to verify
       
   551      *
       
   552      * @exception   ActionAbortedException
       
   553      *		    if the user cancels the operation
       
   554      */
       
   555     protected abstract void promptForCertificate(String host,
       
   556 	Certificate certificate) throws ActionAbortedException;
       
   557 
       
   558     /**
       
   559      * Prompts the user to acknowledge failure of the given request.
       
   560      *
       
   561      * @param	    request
       
   562      *		    the {@link LoginRequest} encapsulating the values and
       
   563      *		    error status of each core {@link LoginProperty}
       
   564      */
       
   565     protected abstract void promptForFailedRequest(LoginRequest request);
       
   566 
       
   567     /**
       
   568      * Prompt the user to enter host/user data, subject to the editability and
       
   569      * preset values of the host and user {@link LoginProperty}s of the given
       
   570      * request.
       
   571      *
       
   572      * @param	    request
       
   573      *		    the {@link LoginRequest} encapsulating the preset values and
       
   574      *		    editability of each core {@link LoginProperty}
       
   575      *
       
   576      * @exception   ActionAbortedException
       
   577      *		    if the user cancels the operation
       
   578      */
       
   579     protected abstract void promptForHostAndUser(LoginRequest request)
       
   580 	throws ActionAbortedException;
       
   581 
       
   582     /**
       
   583      * Prompt the user to select a role, subject to the editability and preset
       
   584      * value of the role {@link LoginProperty} of the given request.
       
   585      *
       
   586      * @param	    request
       
   587      *		    the {@link LoginRequest} encapsulating the preset values and
       
   588      *		    editability of each core {@link LoginProperty}
       
   589      *
       
   590      * @param	    roles
       
   591      *		    a list of valid roles for the selected user
       
   592      *
       
   593      * @param	    isZone
       
   594      *		    {@code true} if prompting for a zone role
       
   595      *
       
   596      * @exception   ActionAbortedException
       
   597      *		    if the user cancels the operation
       
   598      *
       
   599      * @exception   ActionRegressedException
       
   600      *		    if the chooses to re-edit the host or user fields
       
   601      */
       
   602     protected abstract void promptForRole(LoginRequest request,
       
   603 	List<String> roles, boolean isZone) throws ActionAbortedException,
       
   604 	ActionRegressedException;
       
   605 
       
   606     /**
       
   607      * Prompt the user to enter zone/user data, subject to the editability and
       
   608      * preset values of the zone and user {@link LoginProperty}s of the given
       
   609      * request.
       
   610      *
       
   611      * @param	    request
       
   612      *		    the {@link LoginRequest} encapsulating the preset values and
       
   613      *		    editability of each core {@link LoginProperty}
       
   614      *
       
   615      * @param	    zones
       
   616      *		    a list of valid zones for the selected host
       
   617      *
       
   618      * @exception   ActionAbortedException
       
   619      *		    if the user cancels the operation
       
   620      *
       
   621      * @exception   ActionRegressedException
       
   622      *		    if the chooses to re-edit the host or user fields
       
   623      */
       
   624     protected abstract void promptForZoneAndUser(LoginRequest request,
       
   625 	List<String> zones) throws ActionAbortedException,
       
   626 	ActionRegressedException;
       
   627 
       
   628     /**
       
   629      * Set login status.
       
   630      *
       
   631      * @param	    request
       
   632      *		    the {@link LoginRequest} encapsulating the preset values of
       
   633      *		    each core {@link LoginProperty}
       
   634      *
       
   635      * @param	    status
       
   636      *		    the login status
       
   637      */
       
   638     protected abstract void setLoginStatus(LoginRequest request, String status);
       
   639 
       
   640     //
       
   641     // Private methods
       
   642     //
       
   643 
       
   644     @SuppressWarnings({"fallthrough"})
       
   645     private boolean authConverse(LoginRequest request,
       
   646 	Authentication auth, AuthPrompter prompter)
       
   647 	throws ActionAbortedException, ActionRegressedException {
       
   648 
       
   649 	List<DialogMessage> messages = request.getMessages();
       
   650 	boolean isFirst = true;
       
   651 
       
   652 	try {
       
   653 	    Block answer = prompter.initiate(request, auth);
       
   654 
       
   655 	    List<LoginProperty> properties =
       
   656 		new LinkedList<LoginProperty>();
       
   657 
       
   658 	    BlockType type;
       
   659 	    while (true) {
       
   660 		properties.clear();
       
   661 
       
   662 		switch (type = answer.getType()) {
       
   663 		case SUCCESS:
       
   664 		    // Display any lingering messages from the server
       
   665 		    if (!messages.isEmpty()) {
       
   666 			prompter.prompt(request, properties, isFirst);
       
   667 			isFirst = false;
       
   668 			messages.clear();
       
   669 		    }
       
   670 
       
   671 		    auth.complete();
       
   672 		    return true;
       
   673 
       
   674 		case ERROR:
       
   675 		    return false;
       
   676 
       
   677 		default:
       
   678 		    assert type == BlockType.CONV;
       
   679 		case CONV:
       
   680 		    for (Message m : answer.getMessages()) {
       
   681 			String text = m.getMessage();
       
   682 			switch (m.getStyle()) {
       
   683 			case PROMPT_ECHO_OFF:
       
   684 			    properties.add(new PasswordLoginProperty(
       
   685 				text, new char[0], true));
       
   686 			    break;
       
   687 
       
   688 			case PROMPT_ECHO_ON:
       
   689 			    properties.add(new LoginProperty<String>(
       
   690 				text, "", true));
       
   691 			    break;
       
   692 
       
   693 			case ERROR_MSG:
       
   694 			    messages.add(new DialogMessage(text,
       
   695 				JOptionPane.ERROR_MESSAGE));
       
   696 			    break;
       
   697 
       
   698 			case TEXT_INFO:
       
   699 			    messages.add(new DialogMessage(text,
       
   700 				JOptionPane.INFORMATION_MESSAGE));
       
   701 			    break;
       
   702 			}
       
   703 		    }
       
   704 
       
   705 		    if (!properties.isEmpty()) {
       
   706 			prompter.prompt(request, properties, isFirst);
       
   707 			isFirst = false;
       
   708 			messages.clear();
       
   709 		    }
       
   710 
       
   711 		    List<String> response = new LinkedList<String>();
       
   712 		    for (LoginProperty property : properties) {
       
   713 			Object value = property.getValue();
       
   714 			if (value != null) {
       
   715 			    response.add(value instanceof char[] ?
       
   716 				new String((char[])value) : (String)value);
       
   717 			}
       
   718 		    }
       
   719 		    answer = auth.submit(response);
       
   720 		    // clear out passwords
       
   721 		    for (String s : response) {
       
   722 			s = null;
       
   723 		    }
       
   724 		}
       
   725 	    }
       
   726 	} catch (RadObjectException e) {
       
   727 	    messages.add(new DialogMessage(
       
   728 		Finder.getString("login.err.io",
       
   729 		request.getHost().getValue()),
       
   730 		JOptionPane.ERROR_MESSAGE));
       
   731 	    return false;
       
   732 	}
       
   733     }
       
   734 
       
   735     private <T> T createProxy(LoginRequest request, ConnectionInfo info,
       
   736 	Class<T> ifaceClass, String domain, String name)
       
   737 	throws ActionFailedException {
       
   738 
       
   739 	try {
       
   740 	    ProxyInterface proxy = (ProxyInterface) ifaceClass.newInstance();
       
   741 	    return info.getConnection().getObject(proxy);
       
   742 	} catch (RadObjectException e) {
       
   743 	    List<DialogMessage> messages = request.getMessages();
       
   744 	    messages.add(new DialogMessage(Finder.getString(
       
   745 		"proxy.error.general", ifaceClass.getSimpleName()),
       
   746 		JOptionPane.ERROR_MESSAGE));
       
   747 	    requestFailed(request);
       
   748 	} catch (IOException e) {
       
   749 	    List<DialogMessage> messages = request.getMessages();
       
   750 	    messages.add(new DialogMessage(Finder.getString(
       
   751 		"proxy.error.io", ifaceClass.getSimpleName()),
       
   752 		JOptionPane.ERROR_MESSAGE));
       
   753 	    requestFailed(request);
       
   754 	} catch (Exception e) {
       
   755 	    List<DialogMessage> messages = request.getMessages();
       
   756 	    messages.add(new DialogMessage(Finder.getString(
       
   757 		"proxy.error.general", ifaceClass.getSimpleName()),
       
   758 		JOptionPane.ERROR_MESSAGE));
       
   759 	    requestFailed(request);
       
   760 	}
       
   761 	return null;
       
   762     }
       
   763 
       
   764     private Authentication createAuthBean(LoginRequest request,
       
   765 	ConnectionInfo info) throws ActionFailedException {
       
   766 	return createProxy(request, info, Authentication.class,
       
   767 	    "com.oracle.solaris.rad.pam", "Authentication");
       
   768     }
       
   769 
       
   770     private Connection createConnector(String host)
       
   771 	throws KeyStoreException, ActionAbortedException, CertificateException,
       
   772 	UnknownHostException, NoSuchAlgorithmException, IOException {
       
   773 
       
   774 	if (NetUtil.isLocalAddress(host)) {
       
   775 	    String[] paths = {
       
   776 		RAD_PATH_AFUNIX_AUTH,
       
   777 		RAD_PATH_AFUNIX_UNAUTH
       
   778 	    };
       
   779 
       
   780 	    for (String path : paths) {
       
   781 		try {
       
   782 		    return Connection.connectUnix(new File(path));
       
   783 		} catch (IOException e) {
       
   784 		    // Not necessarily an error
       
   785 		    Logger.getLogger(getClass().getName()).log(Level.CONFIG,
       
   786 			"unable to utilize local AF_UNIX connection: " +
       
   787 			path, e);
       
   788 		}
       
   789 	    }
       
   790 	}
       
   791 
       
   792 	InetAddress addr = InetAddress.getByName(host);
       
   793 
       
   794 	File truststore = getTrustStoreFile();
       
   795 	if (!truststore.exists()) {
       
   796 	    createTrustStore(truststore);
       
   797 	}
       
   798 
       
   799 	String tsloc = truststore.getAbsolutePath();
       
   800 	String tspass = getTrustStorePassword();
       
   801 	if (tsloc == null || tspass == null)
       
   802 	    throw new IOException("Must supply truststore and password");
       
   803 	RadTrustManager mtm = new RadTrustManager();
       
   804 
       
   805 	KeyStore ks;
       
   806 
       
   807 	try {
       
   808 	    ks = KeyStore.getInstance("JKS");
       
   809 	    ks.load(new FileInputStream(tsloc), tspass.toCharArray());
       
   810 	} catch (Exception e) {
       
   811 	    throw new IOException(e);
       
   812 	}
       
   813 
       
   814 	Connection conn = null;
       
   815 	for (;;) {
       
   816 	    try {
       
   817 		conn = Connection.connectTLS(addr, host, ks, mtm);
       
   818 		break;
       
   819 	    } catch (IOException e) {
       
   820 		X509Certificate[] chain = mtm.getBadChain();
       
   821 		if (chain == null) {
       
   822 		    throw e;
       
   823 		}
       
   824 
       
   825 		if (!handleCertFailure(host, truststore, chain[0])) {
       
   826 		    throw e;
       
   827 		}
       
   828 	    }
       
   829 	}
       
   830 	return conn;
       
   831     }
       
   832 
       
   833     private Connection createConnector(LoginRequest request)
       
   834 	throws ActionAbortedException {
       
   835 
       
   836 	Connection connector = null;
       
   837 	LoginProperty<String> host = request.getHost();
       
   838 	String hostVal = host.getValue();
       
   839 	List<DialogMessage> messages = request.getMessages();
       
   840 	boolean success = false;
       
   841 
       
   842 	try {
       
   843 	    setLoginStatus(request, Finder.getString("login.status.host",
       
   844 		hostVal));
       
   845 
       
   846 	    connector = createConnector(hostVal);
       
   847 	    success = true;
       
   848 	} catch (UnknownHostException e) {
       
   849 	    messages.add(new DialogMessage(Finder.getString(
       
   850 		"login.err.host.unknown", hostVal), JOptionPane.ERROR_MESSAGE));
       
   851 	} catch (SecurityException e) {
       
   852 	    messages.add(new DialogMessage(Finder.getString(
       
   853 		"login.err.security", hostVal),
       
   854 		JOptionPane.ERROR_MESSAGE));
       
   855 	} catch (KeyStoreException e) {
       
   856 	    messages.add(new DialogMessage(Finder.getString(
       
   857 		"login.err.keystore", e.getMessage()),
       
   858 		JOptionPane.ERROR_MESSAGE));
       
   859 	// Thrown by createTrustStore
       
   860 	} catch (NoSuchAlgorithmException e) {
       
   861 	    messages.add(new DialogMessage(Finder.getString(
       
   862 		"login.err.keystore", e.getMessage()),
       
   863 		JOptionPane.ERROR_MESSAGE));
       
   864 
       
   865 	// Thrown by getDaemonCertificateChain
       
   866 	} catch (CertificateException e) {
       
   867 	    messages.add(new DialogMessage(Finder.getString(
       
   868 		"login.err.nocerts", hostVal),
       
   869 		JOptionPane.ERROR_MESSAGE));
       
   870 
       
   871 	// Thrown by RAD java client
       
   872 	} catch (IOException e) {
       
   873 	    messages.add(new DialogMessage(Finder.getString(
       
   874 		"login.err.io", hostVal),
       
   875 		JOptionPane.ERROR_MESSAGE));
       
   876 
       
   877 	} finally {
       
   878 	    if (!success) {
       
   879 		host.setErrored(true);
       
   880 	    }
       
   881 	}
       
   882 
       
   883 	return connector;
       
   884     }
       
   885 
       
   886     private IO createZonesBridgeBean(LoginRequest request,
       
   887 	ConnectionInfo info) throws ActionFailedException {
       
   888 	return createProxy(request, info, IO.class,
       
   889 	    "com.oracle.solaris.rad.zonesbridge", "IO");
       
   890     }
       
   891 
       
   892 
       
   893     private Util createZonesUtilBean(LoginRequest request,
       
   894 	ConnectionInfo info) throws ActionFailedException {
       
   895 	return createProxy(request, info, Util.class,
       
   896 	    "com.oracle.solaris.rad.zonesbridge", "Util");
       
   897     }
       
   898 
       
   899 
       
   900     private Connection createZoneConnector(LoginRequest request,
       
   901 	IO bean) throws ActionAbortedException {
       
   902 
       
   903 	Connection conn = null;
       
   904 	LoginProperty<String> host = request.getHost();
       
   905 	String hostVal = host.getValue();
       
   906 	LoginProperty<String> zone = request.getZone();
       
   907 	String zoneVal = zone.getValue();
       
   908 	String zoneUserVal = request.getZoneUser().getValue();
       
   909 	List<DialogMessage> messages = request.getMessages();
       
   910 	boolean success = false;
       
   911 
       
   912 	try {
       
   913 	    Connection rc = createConnector(hostVal);
       
   914 	    conn = Connection.connectZone(rc, zoneVal, zoneUserVal, null);
       
   915 	    success = true;
       
   916 	} catch (SecurityException e) {
       
   917 	    messages.add(new DialogMessage(Finder.getString(
       
   918 		"login.err.zone.security", zoneVal, zoneUserVal),
       
   919 		JOptionPane.ERROR_MESSAGE));
       
   920 	} catch (KeyStoreException e) {
       
   921 	    messages.add(new DialogMessage(Finder.getString(
       
   922 		"login.err.keystore", e.getMessage()),
       
   923 		JOptionPane.ERROR_MESSAGE));
       
   924 	// Thrown by createTrustStore
       
   925 	} catch (NoSuchAlgorithmException e) {
       
   926 	    messages.add(new DialogMessage(Finder.getString(
       
   927 		"login.err.keystore", e.getMessage()),
       
   928 		JOptionPane.ERROR_MESSAGE));
       
   929 
       
   930 	// Thrown by getDaemonCertificateChain
       
   931 	} catch (CertificateException e) {
       
   932 	    messages.add(new DialogMessage(Finder.getString(
       
   933 		"login.err.nocerts", hostVal),
       
   934 		JOptionPane.ERROR_MESSAGE));
       
   935 
       
   936 	} catch (IOException e) {
       
   937 	    messages.add(new DialogMessage(Finder.getString(
       
   938 		"login.err.io", zoneVal), JOptionPane.ERROR_MESSAGE));
       
   939 
       
   940 	} finally {
       
   941 	    if (!success) {
       
   942 		zone.setErrored(true);
       
   943 	    }
       
   944 	}
       
   945 	return conn;
       
   946     }
       
   947 
       
   948     private void gatherHostAndUserData(LoginRequest request, LoginData data)
       
   949 	throws ActionAbortedException, ActionFailedException {
       
   950 
       
   951 	LoginProperty<String> host = request.getHost();
       
   952 	LoginProperty<String> user = request.getUser();
       
   953 	LoginProperty<Boolean> zonePrompt = request.getZonePrompt();
       
   954 	List<DialogMessage> messages = request.getMessages();
       
   955 
       
   956 	// Validate any preset values prior to prompting user
       
   957 	if (host.getValue() != null || !host.isEditable()) {
       
   958 	    isHostValid(request);
       
   959 	}
       
   960 	if (user.getValue() != null || !user.isEditable()) {
       
   961 	    isUserValid(request);
       
   962 	}
       
   963 	if (zonePrompt.getValue() != null || !zonePrompt.isEditable()) {
       
   964 	    isPropertyValid(zonePrompt, request);
       
   965 	}
       
   966 
       
   967 	// Loop until connected to host and authenticated as user
       
   968 	while (true) {
       
   969 	    boolean acknowledged = false;
       
   970 
       
   971 	    // Refresh each iteration in case a prop isEditableOnError()
       
   972 	    boolean hostEditable = host.isEditable();
       
   973 	    boolean userEditable = user.isEditable();
       
   974 	    boolean zonePromptEditable = zonePrompt.isEditable();
       
   975 
       
   976 	    // If an error cannot be fixed by the user...
       
   977 	    if ((!zonePromptEditable && zonePrompt.isErrored()) ||
       
   978 		(!hostEditable && (host.isErrored() ||
       
   979 		(!userEditable && user.isErrored())))) {
       
   980 
       
   981 		requestFailed(request);
       
   982 	    }
       
   983 
       
   984 	    if (hostEditable || userEditable || zonePromptEditable ||
       
   985 		!messages.isEmpty()) {
       
   986 
       
   987 		try {
       
   988 		    promptForHostAndUser(request);
       
   989 		} finally {
       
   990 		    acknowledged = true;
       
   991 
       
   992 		    // User only needs to see any message once, presumably
       
   993 		    messages.clear();
       
   994 
       
   995 		    host.setErrored(false);
       
   996 		    user.setErrored(false);
       
   997 		    zonePrompt.setErrored(false);
       
   998 		}
       
   999 
       
  1000 		if (!isHostValid(request) || !isUserValid(request) ||
       
  1001 		    !isPropertyValid(zonePrompt, request)) {
       
  1002 		    continue;
       
  1003 		}
       
  1004 	    }
       
  1005 
       
  1006 	    String hostVal = host.getValue();
       
  1007 	    String userVal = user.getValue();
       
  1008 
       
  1009 	    // Search for existing connection
       
  1010 	    List<ConnectionInfo> depChain =
       
  1011 		getConnectionManager().getDepChain(hostVal, userVal, null, null,
       
  1012 		null, null);
       
  1013 	    if (depChain != null) {
       
  1014 		data.setDepChain(depChain, acknowledged);
       
  1015 		return;
       
  1016 	    }
       
  1017 
       
  1018 	    // Create connection, append to messages on error
       
  1019 	    Connection connection = createConnector(request);
       
  1020 	    if (connection != null) {
       
  1021 		ConnectionInfo info = new ConnectionInfo(hostVal, userVal, null,
       
  1022 		    connection);
       
  1023 
       
  1024 		// Get/create auth bean, append to messages on error
       
  1025 		Authentication auth = createAuthBean(request, info);
       
  1026 		if (auth != null) {
       
  1027 		    setLoginStatus(request,
       
  1028 			Finder.getString("login.status.user"));
       
  1029 
       
  1030 		    if (userVal.equals(auth.getuser())) {
       
  1031 			data.push(info, acknowledged);
       
  1032 			return;
       
  1033 		    }
       
  1034 
       
  1035 		    try {
       
  1036 			AuthPrompter prompter = new UserPrompter();
       
  1037 			do {
       
  1038 			    if (authConverse(request, auth, prompter)) {
       
  1039 				acknowledged |= prompter.isAcknowledged();
       
  1040 				data.push(info, acknowledged);
       
  1041 				return;
       
  1042 			    }
       
  1043 
       
  1044 			    // Authentication failed
       
  1045 			    user.setErrored(true);
       
  1046 
       
  1047 			    // Add generic auth failure message if not already
       
  1048 			    // provided by server
       
  1049 			    if (messages.isEmpty()) {
       
  1050 				messages.add(new DialogMessage(
       
  1051 				    Finder.getString("login.err.user.auth",
       
  1052 				    hostVal, userVal),
       
  1053 				    JOptionPane.ERROR_MESSAGE));
       
  1054 			    }
       
  1055 
       
  1056 			// No chance to edit, so keep iterating here
       
  1057 			} while (!host.isEditable() && !user.isEditable());
       
  1058 
       
  1059 		    // Thrown by authConverse
       
  1060 		    } catch (ActionRegressedException e) {
       
  1061 		    }
       
  1062 		}
       
  1063 	    }
       
  1064 	}
       
  1065     }
       
  1066 
       
  1067     private void gatherRoleData(LoginRequest request, LoginData data)
       
  1068 	throws ActionAbortedException, ActionFailedException,
       
  1069 	ActionRegressedException {
       
  1070 
       
  1071 	LoginProperty<String> host = request.getHost();
       
  1072 	String hostVal = host.getValue();
       
  1073 
       
  1074 	LoginProperty<String> user = request.getUser();
       
  1075 	String userVal = user.getValue();
       
  1076 
       
  1077 	LoginProperty<String> zone = request.getZone();
       
  1078 	String zoneVal = zone.getValue();
       
  1079 
       
  1080 	LoginProperty<String> zoneUser = request.getZoneUser();
       
  1081 	String zoneUserVal = zoneUser.getValue();
       
  1082 
       
  1083 	// Get/create auth bean, append to messages on error
       
  1084 	Authentication userAuth = createAuthBean(request, data.peek(0));
       
  1085 	if (userAuth == null) {
       
  1086 	    // Not likely, but handle it anyway
       
  1087 	    requestFailed(request);
       
  1088 	}
       
  1089 
       
  1090 	LoginProperty<String> role = request.getRole();
       
  1091 	List<DialogMessage> messages = request.getMessages();
       
  1092 
       
  1093 	setLoginStatus(request, Finder.getString("login.status.roles",
       
  1094 	    request.getUser().getValue()));
       
  1095 	List<String> roles = userAuth.getroles();
       
  1096 
       
  1097 	// Validate any preset value prior to prompting user
       
  1098 	if (role.getValue() != null || !role.isEditable()) {
       
  1099 	    isRoleValid(request, roles, false);
       
  1100 	}
       
  1101 
       
  1102 	// Loop until no role is chosen, or chosen role is authenticated
       
  1103 	while (true) {
       
  1104 	    boolean acknowledged = false;
       
  1105 
       
  1106 	    // Refresh each iteration in case role.isEditableOnError()
       
  1107 	    boolean roleEditable = role.isEditable();
       
  1108 
       
  1109 	    // If an error cannot be fixed by the user...
       
  1110 	    if (!roleEditable && role.isErrored()) {
       
  1111 		requestFailed(request);
       
  1112 	    }
       
  1113 
       
  1114 	    if ((roleEditable && !roles.isEmpty()) || !messages.isEmpty()) {
       
  1115 		try {
       
  1116 		    promptForRole(request, roles, false);
       
  1117 		} finally {
       
  1118 		    acknowledged = true;
       
  1119 
       
  1120 		    // User only needs to see any message once, presumably
       
  1121 		    messages.clear();
       
  1122 
       
  1123 		    role.setErrored(false);
       
  1124 		}
       
  1125 
       
  1126 		if (!isRoleValid(request, roles, false)) {
       
  1127 		    continue;
       
  1128 		}
       
  1129 	    }
       
  1130 
       
  1131 	    String roleVal = role.getValue();
       
  1132 	    if (roleVal == null) {
       
  1133 		// No need to keep going
       
  1134 		return;
       
  1135 	    }
       
  1136 
       
  1137 	    // Search for existing connection
       
  1138 	    List<ConnectionInfo> depChain = getConnectionManager().getDepChain(
       
  1139 		hostVal, userVal, roleVal, null, null, null);
       
  1140 	    if (depChain != null) {
       
  1141 		data.setDepChain(depChain, acknowledged);
       
  1142 		return;
       
  1143 	    }
       
  1144 
       
  1145 	    try {
       
  1146 		byte[] token = userAuth.createToken();
       
  1147 
       
  1148 		// Create connection, append to messages on error
       
  1149 		Connection connector = createConnector(request);
       
  1150 		if (connector != null) {
       
  1151 		    ConnectionInfo info = new ConnectionInfo(hostVal, userVal,
       
  1152 			roleVal, connector);
       
  1153 
       
  1154 		    // Create auth bean, append to messages on error
       
  1155 		    Authentication roleAuth = createAuthBean(request,
       
  1156 			info);
       
  1157 		    if (roleAuth != null) {
       
  1158 			    roleAuth.redeemToken(userVal, token);
       
  1159 
       
  1160 			AuthPrompter prompter = new RolePrompter();
       
  1161 			do {
       
  1162 			    if (authConverse(request, roleAuth, prompter)) {
       
  1163 				acknowledged |= prompter.isAcknowledged();
       
  1164 				data.push(info, acknowledged);
       
  1165 				return;
       
  1166 			    }
       
  1167 
       
  1168 			    // Authentication failed
       
  1169 			    role.setErrored(true);
       
  1170 
       
  1171 			    // Add generic auth failure message if not already
       
  1172 			    // provided by server
       
  1173 			    if (messages.isEmpty()) {
       
  1174 				messages.add(new DialogMessage(
       
  1175 				    Finder.getString("login.err.role.auth",
       
  1176 				    hostVal, userVal, roleVal),
       
  1177 				    JOptionPane.ERROR_MESSAGE));
       
  1178 			    }
       
  1179 
       
  1180 			// No chance to edit role, so keep iterating here
       
  1181 			} while (!role.isEditable());
       
  1182 		    }
       
  1183 		}
       
  1184 
       
  1185 	    // Thrown by createToken/redeemToken
       
  1186 	    } catch (RadObjectException e) {
       
  1187 		messages.add(new DialogMessage(Finder.getString(
       
  1188 		    "login.err.io", hostVal), JOptionPane.ERROR_MESSAGE));
       
  1189 
       
  1190 	    // Thrown by authConverse
       
  1191 	    } catch (ActionRegressedException e) {
       
  1192 	    }
       
  1193 	}
       
  1194     }
       
  1195 
       
  1196     private void gatherZoneHostAndUserData(LoginRequest request, LoginData data)
       
  1197 	throws ActionAbortedException, ActionFailedException,
       
  1198 	ActionRegressedException {
       
  1199 
       
  1200 	IO zcon = createZonesBridgeBean(request, data.peek(0));
       
  1201 	Util zutil = createZonesUtilBean(request, data.peek(0));
       
  1202 	if (zcon == null || zutil == null) {
       
  1203 	    requestFailed(request);
       
  1204 	}
       
  1205 
       
  1206 	LoginProperty<String> zone = request.getZone();
       
  1207 	LoginProperty<String> zoneUser = request.getZoneUser();
       
  1208 	List<DialogMessage> messages = request.getMessages();
       
  1209 
       
  1210 	List<String> zones = null;
       
  1211 	try {
       
  1212 	    zones = zutil.getZones(ZoneState.RUNNING);
       
  1213 	} catch (RadObjectException e) {
       
  1214 	    messages.add(new DialogMessage(Finder.getString(
       
  1215                 "login.err.io", request.getHost().getValue()),
       
  1216                 JOptionPane.ERROR_MESSAGE));
       
  1217 	    requestFailed(request);
       
  1218 	}
       
  1219 
       
  1220 	// Validate any preset value prior to prompting user
       
  1221 	if (zone.getValue() != null || !zone.isEditable()) {
       
  1222 	    isZoneValid(request, zones);
       
  1223 	}
       
  1224 	if (zoneUser.getValue() != null || !zoneUser.isEditable()) {
       
  1225 	    isZoneUserValid(request);
       
  1226 	}
       
  1227 
       
  1228 	// Loop until connected to zone and authenticated as zoneUser
       
  1229 	while (true) {
       
  1230 	    boolean acknowledged = false;
       
  1231 
       
  1232 	    // Refresh each iteration in case zone/zoneUser.isEditableOnError()
       
  1233 	    boolean zoneEditable = zone.isEditable();
       
  1234 	    boolean zoneUserEditable = zoneUser.isEditable();
       
  1235 
       
  1236 	    // If an error cannot be fixed by the user...
       
  1237 	    if (!zoneEditable && (zone.isErrored() ||
       
  1238 		(!zoneUserEditable && zoneUser.isErrored()))) {
       
  1239 
       
  1240 		requestFailed(request);
       
  1241 	    }
       
  1242 
       
  1243 	    if (zoneEditable || zoneUserEditable || !messages.isEmpty()) {
       
  1244 		try {
       
  1245 		    promptForZoneAndUser(request, zones);
       
  1246 		} finally {
       
  1247 		    acknowledged = true;
       
  1248 
       
  1249 		    // User only needs to see any message once, presumably
       
  1250 		    messages.clear();
       
  1251 
       
  1252 		    zone.setErrored(false);
       
  1253 		    zoneUser.setErrored(false);
       
  1254 		}
       
  1255 
       
  1256 		if (!isZoneValid(request, zones) || !isZoneUserValid(request)) {
       
  1257 		    continue;
       
  1258 		}
       
  1259 	    }
       
  1260 
       
  1261 	    String zoneVal = zone.getValue();
       
  1262 	    if (zoneVal == null) {
       
  1263 		// No need to keep going
       
  1264 		return;
       
  1265 	    }
       
  1266 
       
  1267 	    String zoneUserVal = zoneUser.getValue();
       
  1268 
       
  1269 	    // Search for existing connection
       
  1270 	    List<ConnectionInfo> depChain =
       
  1271 		getConnectionManager().getDepChain(
       
  1272 		request.getHost().getValue(), request.getUser().getValue(),
       
  1273 		request.getRole().getValue(), zoneVal, zoneUserVal, null);
       
  1274 	    if (depChain != null) {
       
  1275 		data.setDepChain(depChain, acknowledged);
       
  1276 		return;
       
  1277 	    }
       
  1278 
       
  1279 	    Connection connector = createZoneConnector(request, zcon);
       
  1280 	    if (connector != null) {
       
  1281 		ConnectionInfo info = new ConnectionInfo(
       
  1282 		    request.getHost().getValue(), request.getUser().getValue(),
       
  1283 		    request.getRole().getValue(), zoneVal, zoneUserVal, null,
       
  1284 		    connector);
       
  1285 
       
  1286 		// Get/create auth bean, append to messages on error
       
  1287 		Authentication auth = createAuthBean(request, info);
       
  1288 		if (auth != null) {
       
  1289 		    if (zoneUserVal.equals(auth.getuser())) {
       
  1290 			data.push(info, acknowledged);
       
  1291 			return;
       
  1292 		    }
       
  1293 
       
  1294 		    try {
       
  1295 			AuthPrompter prompter = new ZoneUserPrompter();
       
  1296 			do {
       
  1297 			    if (authConverse(request, auth, prompter)) {
       
  1298 				acknowledged |= prompter.isAcknowledged();
       
  1299 				data.push(info, acknowledged);
       
  1300 				return;
       
  1301 			    }
       
  1302 
       
  1303 			    // Authentication failed
       
  1304 			    zoneUser.setErrored(true);
       
  1305 
       
  1306 			    // Add generic auth failure message if not already
       
  1307 			    // provided by server
       
  1308 			    if (messages.isEmpty()) {
       
  1309 				messages.add(new DialogMessage(
       
  1310 				    Finder.getString("login.err.user.auth",
       
  1311 				    zoneVal, zoneUserVal),
       
  1312 				    JOptionPane.ERROR_MESSAGE));
       
  1313 			    }
       
  1314 
       
  1315 			// No chance to edit, so keep iterating here
       
  1316 			} while (!zone.isEditable() && !zoneUser.isEditable());
       
  1317 
       
  1318 		    // Thrown by authConverse
       
  1319 		    } catch (ActionRegressedException e) {
       
  1320 		    }
       
  1321 		}
       
  1322 	    }
       
  1323 	}
       
  1324     }
       
  1325 
       
  1326     private void gatherZoneRoleData(LoginRequest request, LoginData data)
       
  1327 	throws ActionAbortedException, ActionFailedException,
       
  1328 	ActionRegressedException {
       
  1329 
       
  1330 	LoginProperty<String> host = request.getHost();
       
  1331 	String hostVal = host.getValue();
       
  1332 
       
  1333 	LoginProperty<String> user = request.getUser();
       
  1334 	String userVal = user.getValue();
       
  1335 
       
  1336 	LoginProperty<String> role = request.getRole();
       
  1337 	String roleVal = role.getValue();
       
  1338 
       
  1339 	LoginProperty<String> zone = request.getZone();
       
  1340 	String zoneVal = zone.getValue();
       
  1341 
       
  1342 	LoginProperty<String> zoneUser = request.getZoneUser();
       
  1343 	String zoneUserVal = zoneUser.getValue();
       
  1344 
       
  1345 	// Get/create auth bean, append to messages on error
       
  1346 	Authentication userAuth = createAuthBean(request, data.peek(0));
       
  1347 	if (userAuth == null) {
       
  1348 	    // Not likely, but handle it anyway
       
  1349 	    requestFailed(request);
       
  1350 	}
       
  1351 
       
  1352 	LoginProperty<String> zoneRole = request.getZoneRole();
       
  1353 	List<DialogMessage> messages = request.getMessages();
       
  1354 
       
  1355 	setLoginStatus(request, Finder.getString("login.status.roles",
       
  1356 	    request.getZoneUser().getValue()));
       
  1357 	List<String> roles = userAuth.getroles();
       
  1358 
       
  1359 	// Validate any preset value prior to prompting user
       
  1360 	if (zoneRole.getValue() != null || !zoneRole.isEditable()) {
       
  1361 	    isRoleValid(request, roles, true);
       
  1362 	}
       
  1363 
       
  1364 	IO zcon = null;
       
  1365 
       
  1366 	// Loop until no role is chosen, or chosen role is authenticated
       
  1367 	while (true) {
       
  1368 	    boolean acknowledged = false;
       
  1369 
       
  1370 	    // Refresh each iteration in case zoneRole.isEditableOnError()
       
  1371 	    boolean zoneRoleEditable = zoneRole.isEditable();
       
  1372 
       
  1373 	    // If an error cannot be fixed by the user...
       
  1374 	    if (!zoneRoleEditable && zoneRole.isErrored()) {
       
  1375 		requestFailed(request);
       
  1376 	    }
       
  1377 
       
  1378 	    if ((zoneRoleEditable && !roles.isEmpty()) || !messages.isEmpty()) {
       
  1379 		try {
       
  1380 		    promptForRole(request, roles, true);
       
  1381 		} finally {
       
  1382 		    acknowledged = true;
       
  1383 
       
  1384 		    // User only needs to see any message once, presumably
       
  1385 		    messages.clear();
       
  1386 
       
  1387 		    zoneRole.setErrored(false);
       
  1388 		}
       
  1389 
       
  1390 		if (!isRoleValid(request, roles, true)) {
       
  1391 		    continue;
       
  1392 		}
       
  1393 	    }
       
  1394 
       
  1395 	    String zoneRoleVal = zoneRole.getValue();
       
  1396 	    if (zoneRoleVal == null) {
       
  1397 		// No need to keep going
       
  1398 		return;
       
  1399 	    }
       
  1400 
       
  1401 	    // Search for existing connection
       
  1402 	    List<ConnectionInfo> depChain = getConnectionManager().getDepChain(
       
  1403 		hostVal, userVal, roleVal, zoneVal, zoneUserVal, zoneRoleVal);
       
  1404 	    if (depChain != null) {
       
  1405 		data.setDepChain(depChain, acknowledged);
       
  1406 		return;
       
  1407 	    }
       
  1408 
       
  1409 	    try {
       
  1410 		byte[] token = userAuth.createToken();
       
  1411 
       
  1412 		if (zcon == null) {
       
  1413 		    // Peek back to the host-based connection
       
  1414 		    zcon = createZonesBridgeBean(request, data.peek(1));
       
  1415 		    if (zcon == null) {
       
  1416 			requestFailed(request);
       
  1417 		    }
       
  1418 		}
       
  1419 
       
  1420 		// Create connection, append to messages on error
       
  1421 		Connection connector = createZoneConnector(request, zcon);
       
  1422 		if (connector != null) {
       
  1423 		    ConnectionInfo info = new ConnectionInfo(hostVal, userVal,
       
  1424 			roleVal, zoneVal, zoneUserVal, zoneRoleVal, connector);
       
  1425 
       
  1426 		    // Create auth bean, append to messages on error
       
  1427 		    Authentication roleAuth = createAuthBean(request,
       
  1428 			info);
       
  1429 		    if (roleAuth != null) {
       
  1430 			    roleAuth.redeemToken(userVal, token);
       
  1431 
       
  1432 			AuthPrompter prompter = new ZoneRolePrompter();
       
  1433 			do {
       
  1434 			    if (authConverse(request, roleAuth, prompter)) {
       
  1435 				acknowledged |= prompter.isAcknowledged();
       
  1436 				data.push(info, acknowledged);
       
  1437 				return;
       
  1438 			    }
       
  1439 
       
  1440 			    // Authentication failed
       
  1441 			    zoneRole.setErrored(true);
       
  1442 
       
  1443 			    // Add generic auth failure message if not already
       
  1444 			    // provided by server
       
  1445 			    if (messages.isEmpty()) {
       
  1446 				messages.add(new DialogMessage(
       
  1447 				    Finder.getString("login.err.zonerole.auth",
       
  1448 				    hostVal, userVal, roleVal, zoneVal,
       
  1449 				    zoneUserVal, zoneRoleVal),
       
  1450 				    JOptionPane.ERROR_MESSAGE));
       
  1451 			    }
       
  1452 
       
  1453 			// No chance to edit role, so keep iterating here
       
  1454 			} while (!zoneRole.isEditable());
       
  1455 		    }
       
  1456 		}
       
  1457 
       
  1458 	    // Thrown by createToken/redeemToken
       
  1459 	    } catch (RadObjectException e) {
       
  1460 		messages.add(new DialogMessage(Finder.getString(
       
  1461 		    "login.err.io", hostVal), JOptionPane.ERROR_MESSAGE));
       
  1462 
       
  1463 	    // Thrown by authConverse
       
  1464 	    } catch (ActionRegressedException e) {
       
  1465 	    }
       
  1466 	}
       
  1467     }
       
  1468 
       
  1469     private <T> boolean inSet(LoginProperty<T> property, List<T> valid,
       
  1470 	String resource, LoginRequest request) {
       
  1471 
       
  1472 	if (!valid.contains(property.getValue())) {
       
  1473 	    String message = Finder.getString(resource,
       
  1474 		request.getHost().getValue(),
       
  1475 		request.getUser().getValue(),
       
  1476 		request.getRole().getValue(),
       
  1477 		request.getZone().getValue(),
       
  1478 		request.getZoneUser().getValue(),
       
  1479 		request.getZoneRole().getValue());
       
  1480 
       
  1481 	    request.getMessages().add(new DialogMessage(message,
       
  1482 		JOptionPane.ERROR_MESSAGE));
       
  1483 
       
  1484 	    property.setErrored(true);
       
  1485 
       
  1486 	    if (property.isEditable()) {
       
  1487 		property.setValue(null);
       
  1488 	    }
       
  1489 
       
  1490 	    return false;
       
  1491 	}
       
  1492 
       
  1493 	return true;
       
  1494     }
       
  1495 
       
  1496     private boolean isHostValid(LoginRequest request)
       
  1497 	throws ActionFailedException {
       
  1498 
       
  1499 	LoginProperty<String> host = request.getHost();
       
  1500 	return isPropertyNonEmpty(host, request, "login.err.host.empty") &&
       
  1501 	    isPropertyValid(host, request);
       
  1502     }
       
  1503 
       
  1504     private boolean isPropertyNonEmpty(LoginProperty<String> property,
       
  1505 	LoginRequest request, String resource) {
       
  1506 
       
  1507 	String value = property.getValue();
       
  1508 	if (value == null || value.isEmpty()) {
       
  1509 	    request.getMessages().add(new DialogMessage(Finder.getString(
       
  1510 		resource), JOptionPane.ERROR_MESSAGE));
       
  1511 	    property.setErrored(true);
       
  1512 	    return false;
       
  1513 	}
       
  1514 
       
  1515 	return true;
       
  1516     }
       
  1517 
       
  1518     private <T> boolean isPropertyValid(LoginProperty<T> property,
       
  1519 	LoginRequest request, T... valid) throws ActionFailedException {
       
  1520 
       
  1521 	try {
       
  1522 	    property.validate(request, valid);
       
  1523 	    return true;
       
  1524 	} catch (LoginPropertyException e) {
       
  1525 	    property.setErrored(true);
       
  1526 	    request.getMessages().add(e.getDialogMessage());
       
  1527 	    if (e.isFatal()) {
       
  1528 		requestFailed(request);
       
  1529 	    }
       
  1530 	    return false;
       
  1531 	}
       
  1532     }
       
  1533 
       
  1534     private boolean isRoleValid(LoginRequest request, List<String> roles,
       
  1535 	boolean isZone) throws ActionFailedException {
       
  1536 
       
  1537 	LoginProperty<String> role = isZone ? request.getZoneRole() :
       
  1538 	    request.getRole();
       
  1539 
       
  1540 	return (role.getValue() == null ||
       
  1541 	    inSet(role, roles, "login.err.role.invalid", request)) &&
       
  1542 	    isPropertyValid(role, request,
       
  1543 	    roles.toArray(new String[roles.size()]));
       
  1544     }
       
  1545 
       
  1546     private boolean isUserValid(LoginRequest request)
       
  1547 	throws ActionFailedException {
       
  1548 
       
  1549 	LoginProperty<String> user = request.getUser();
       
  1550 	return isPropertyNonEmpty(user, request, "login.err.user.empty") &&
       
  1551 	    isPropertyValid(user, request);
       
  1552     }
       
  1553 
       
  1554     private boolean isZoneValid(LoginRequest request, List<String> zones)
       
  1555 	throws ActionFailedException {
       
  1556 
       
  1557 	LoginProperty<String> zone = request.getZone();
       
  1558 	if (zone.getValue() == null) {
       
  1559 	    request.getMessages().add(new DialogMessage(Finder.getString(
       
  1560 		"login.err.zone.empty"), JOptionPane.ERROR_MESSAGE));
       
  1561 	    zone.setErrored(true);
       
  1562 	    return false;
       
  1563 	}
       
  1564 
       
  1565 	String resource = "login.err.zone.invalid";
       
  1566 	if (request.getRole().getValue() != null) {
       
  1567 	    resource += ".role";
       
  1568 	}
       
  1569 
       
  1570 	return inSet(zone, zones, resource, request) &&
       
  1571 	    isPropertyValid(zone, request,
       
  1572 	    zones.toArray(new String[zones.size()]));
       
  1573     }
       
  1574 
       
  1575     private boolean isZoneUserValid(LoginRequest request)
       
  1576 	throws ActionFailedException {
       
  1577 
       
  1578 	LoginProperty<String> zoneUser = request.getZoneUser();
       
  1579 	return isPropertyNonEmpty(zoneUser, request,
       
  1580 	    "login.err.zoneuser.empty") && isPropertyValid(zoneUser, request);
       
  1581     }
       
  1582 
       
  1583     private void requestFailed(LoginRequest request)
       
  1584 	throws ActionFailedException {
       
  1585 
       
  1586 	promptForFailedRequest(request);
       
  1587 
       
  1588 	List<DialogMessage> messages = request.getMessages();
       
  1589 	throw new ActionFailedException(messages.isEmpty() ? null :
       
  1590 	    messages.get(0).getText());
       
  1591     }
       
  1592 }