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