]> source.dussan.org Git - jgit.git/commitdiff
Support CredentialsProvider for SSH connections 80/1880/1
authorShawn O. Pearce <spearce@spearce.org>
Wed, 10 Nov 2010 22:18:46 +0000 (14:18 -0800)
committerShawn O. Pearce <spearce@spearce.org>
Wed, 10 Nov 2010 23:00:13 +0000 (15:00 -0800)
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>
org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java

index eacc1ddec04c04836e7cb964cc379a73328798e2..55ce4db6268785ee27c091d824f5334f0fda74f2 100644 (file)
@@ -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. */
index ca83c690427dad550a5d0fd4882e7866dfa640af..194268f1f94a0327ac5c6c1521afbe24aecdde76 100644 (file)
@@ -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.
         *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java
new file mode 100644 (file)
index 0000000..8f259c6
--- /dev/null
@@ -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;
+       }
+}
index daa6f4ca2f9fb67442295fd31d027b7129ee7876..99e7b83335442670577369f8dbb4f1aed779cfc2 100644 (file)
@@ -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;
        }
index d10010fcf6894c45de1b597b3c1f1f0ee85ce99b..34aa3dbd24ba1239a72cd2248070b5baa6538839 100644 (file)
@@ -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())
index f642ac1ea841c96fb45304e2c618f2032584513a..81d233f1ed883da1a6cb0b78126a62a4f1450780 100644 (file)
@@ -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) {
index 68cf96cf379dc8e8a29227c4770ff7e0094b0aa2..235e4b4b971ddcf3f50b6f5762626821e4b3ec4a 100644 (file)
@@ -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) {