When setting up an SSH connection, use the caller supplied CredentialsProvider, if one has been given to the Transport or was defined as the default. The CredentialsProvider is re-wrapped as a JSch UserInfo, allowing the connection to use this for user interactive prompts. This give a unified API for authentication on any transport type. Change-Id: Id3b4cf5bfd27a23207cdfb188bae3b78e71e02c0 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>tags/v0.10.1
@@ -211,6 +211,60 @@ public abstract class CredentialItem { | |||
} | |||
} | |||
/** An item whose value is a boolean choice, presented as Yes/No. */ | |||
public static class YesNoType extends CredentialItem { | |||
private boolean value; | |||
/** | |||
* Initialize a prompt for a single boolean answer. | |||
* | |||
* @param promptText | |||
* prompt to display to the user alongside of the input | |||
* field. Should be sufficient text to indicate what to | |||
* supply for this item. | |||
*/ | |||
public YesNoType(String promptText) { | |||
super(promptText, false); | |||
} | |||
@Override | |||
public void clear() { | |||
value = false; | |||
} | |||
/** @return the current value */ | |||
public boolean getValue() { | |||
return value; | |||
} | |||
/** | |||
* Set the new value. | |||
* | |||
* @param newValue | |||
*/ | |||
public void setValue(boolean newValue) { | |||
value = newValue; | |||
} | |||
} | |||
/** An advice message presented to the user, with no response required. */ | |||
public static class InformationalMessage extends CredentialItem { | |||
/** | |||
* Initialize an informational message. | |||
* | |||
* @param messageText | |||
* message to display to the user. | |||
*/ | |||
public InformationalMessage(String messageText) { | |||
super(messageText, false); | |||
} | |||
@Override | |||
public void clear() { | |||
// Nothing to clear. | |||
} | |||
} | |||
/** Prompt for a username, which is not masked on input. */ | |||
public static class Username extends StringType { | |||
/** Initialize a new username item, with a default username prompt. */ |
@@ -80,6 +80,17 @@ public abstract class CredentialsProvider { | |||
defaultProvider = p; | |||
} | |||
/** | |||
* Check if the provider is interactive with the end-user. | |||
* | |||
* An interactive provider may try to open a dialog box, or prompt for input | |||
* on the terminal, and will wait for a user response. A non-interactive | |||
* provider will either populate CredentialItems, or fail. | |||
* | |||
* @return {@code true} if the provider is interactive with the end-user. | |||
*/ | |||
public abstract boolean isInteractive(); | |||
/** | |||
* Check if the provider can supply the necessary {@link CredentialItem}s. | |||
* |
@@ -0,0 +1,150 @@ | |||
/* | |||
* Copyright (C) 2010, Google Inc. | |||
* and other copyright owners as documented in the project's IP log. | |||
* | |||
* This program and the accompanying materials are made available | |||
* under the terms of the Eclipse Distribution License v1.0 which | |||
* accompanies this distribution, is reproduced below, and is | |||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||
* | |||
* All rights reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or | |||
* without modification, are permitted provided that the following | |||
* conditions are met: | |||
* | |||
* - Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* - Redistributions in binary form must reproduce the above | |||
* copyright notice, this list of conditions and the following | |||
* disclaimer in the documentation and/or other materials provided | |||
* with the distribution. | |||
* | |||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||
* names of its contributors may be used to endorse or promote | |||
* products derived from this software without specific prior | |||
* written permission. | |||
* | |||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
*/ | |||
package org.eclipse.jgit.transport; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import com.jcraft.jsch.Session; | |||
import com.jcraft.jsch.UIKeyboardInteractive; | |||
import com.jcraft.jsch.UserInfo; | |||
/** A JSch {@link UserInfo} adapter for a {@link CredentialsProvider}. */ | |||
public class CredentialsProviderUserInfo implements UserInfo, | |||
UIKeyboardInteractive { | |||
private final URIish uri; | |||
private final CredentialsProvider provider; | |||
private String password; | |||
private String passphrase; | |||
/** | |||
* Wrap a CredentialsProvider to make it suitable for use with JSch. | |||
* | |||
* @param session | |||
* the JSch session this UserInfo will support authentication on. | |||
* @param credentialsProvider | |||
* the provider that will perform the authentication. | |||
*/ | |||
public CredentialsProviderUserInfo(Session session, | |||
CredentialsProvider credentialsProvider) { | |||
this.uri = createURI(session); | |||
this.provider = credentialsProvider; | |||
} | |||
private static URIish createURI(Session session) { | |||
URIish uri = new URIish(); | |||
uri = uri.setScheme("ssh"); | |||
uri = uri.setUser(session.getUserName()); | |||
uri = uri.setHost(session.getHost()); | |||
uri = uri.setPort(session.getPort()); | |||
return uri; | |||
} | |||
public String getPassword() { | |||
return password; | |||
} | |||
public String getPassphrase() { | |||
return passphrase; | |||
} | |||
public boolean promptPassphrase(String msg) { | |||
CredentialItem.StringType v = newPrompt(msg); | |||
if (provider.get(uri, v)) { | |||
passphrase = v.getValue(); | |||
return true; | |||
} else { | |||
passphrase = null; | |||
return false; | |||
} | |||
} | |||
public boolean promptPassword(String msg) { | |||
CredentialItem.StringType v = newPrompt(msg); | |||
if (provider.get(uri, v)) { | |||
password = v.getValue(); | |||
return true; | |||
} else { | |||
password = null; | |||
return false; | |||
} | |||
} | |||
private CredentialItem.StringType newPrompt(String msg) { | |||
return new CredentialItem.StringType(msg, true); | |||
} | |||
public boolean promptYesNo(String msg) { | |||
CredentialItem.YesNoType v = new CredentialItem.YesNoType(msg); | |||
return provider.get(uri, v) && v.getValue(); | |||
} | |||
public void showMessage(String msg) { | |||
provider.get(uri, new CredentialItem.InformationalMessage(msg)); | |||
} | |||
public String[] promptKeyboardInteractive(String destination, String name, | |||
String instruction, String[] prompt, boolean[] echo) { | |||
CredentialItem.StringType[] v = new CredentialItem.StringType[prompt.length]; | |||
for (int i = 0; i < prompt.length; i++) | |||
v[i] = new CredentialItem.StringType(prompt[i], !echo[i]); | |||
List<CredentialItem> items = new ArrayList<CredentialItem>(); | |||
if (instruction != null && instruction.length() > 0) | |||
items.add(new CredentialItem.InformationalMessage(instruction)); | |||
items.addAll(Arrays.asList(v)); | |||
if (!provider.get(uri, items)) | |||
return null; // cancel | |||
String[] result = new String[v.length]; | |||
for (int i = 0; i < v.length; i++) | |||
result[i] = v[i].getValue(); | |||
return result; | |||
} | |||
} |
@@ -83,7 +83,8 @@ public abstract class SshConfigSessionFactory extends SshSessionFactory { | |||
@Override | |||
public synchronized Session getSession(String user, String pass, | |||
String host, int port, FS fs) throws JSchException { | |||
String host, int port, CredentialsProvider credentialsProvider, | |||
FS fs) throws JSchException { | |||
if (config == null) | |||
config = OpenSshConfig.get(fs); | |||
@@ -105,6 +106,11 @@ public abstract class SshConfigSessionFactory extends SshSessionFactory { | |||
final String pauth = hc.getPreferredAuthentications(); | |||
if (pauth != null) | |||
session.setConfig("PreferredAuthentications", pauth); | |||
if (credentialsProvider != null | |||
&& (!hc.isBatchMode() || !credentialsProvider.isInteractive())) { | |||
session.setUserInfo(new CredentialsProviderUserInfo(session, | |||
credentialsProvider)); | |||
} | |||
configure(hc, session); | |||
return session; | |||
} |
@@ -111,6 +111,8 @@ public abstract class SshSessionFactory { | |||
* @param port | |||
* port number the server is listening for connections on. May be <= | |||
* 0 to indicate the IANA registered port of 22 should be used. | |||
* @param credentialsProvider | |||
* provider to support authentication, may be null. | |||
* @param fs | |||
* the file system abstraction which will be necessary to | |||
* perform certain file system operations. | |||
@@ -119,14 +121,16 @@ public abstract class SshSessionFactory { | |||
* the session could not be created. | |||
*/ | |||
public abstract Session getSession(String user, String pass, String host, | |||
int port, FS fs) throws JSchException; | |||
int port, CredentialsProvider credentialsProvider, FS fs) | |||
throws JSchException; | |||
/** | |||
* Close (or recycle) a session to a host. | |||
* | |||
* @param session | |||
* a session previously obtained from this factory's | |||
* {@link #getSession(String,String, String, int, FS)} method.s | |||
* {@link #getSession(String,String, String, int, CredentialsProvider, FS)} | |||
* method. | |||
*/ | |||
public void releaseSession(final Session session) { | |||
if (session.isConnected()) |
@@ -128,7 +128,8 @@ public abstract class SshTransport extends TcpTransport { | |||
final String host = uri.getHost(); | |||
final int port = uri.getPort(); | |||
try { | |||
sock = sch.getSession(user, pass, host, port, local.getFS()); | |||
sock = sch.getSession(user, pass, host, port, | |||
getCredentialsProvider(), local.getFS()); | |||
if (!sock.isConnected()) | |||
sock.connect(tms); | |||
} catch (JSchException je) { |
@@ -76,6 +76,11 @@ public class UsernamePasswordCredentialsProvider extends CredentialsProvider { | |||
this.password = password; | |||
} | |||
@Override | |||
public boolean isInteractive() { | |||
return false; | |||
} | |||
@Override | |||
public boolean supports(CredentialItem... items) { | |||
for (CredentialItem i : items) { |