]> source.dussan.org Git - jgit.git/commitdiff
[sshd agent] Introduce ConnectorDescriptor 73/187573/2
authorThomas Wolf <thomas.wolf@paranor.ch>
Wed, 27 Oct 2021 12:03:10 +0000 (14:03 +0200)
committerThomas Wolf <thomas.wolf@paranor.ch>
Wed, 10 Nov 2021 18:57:01 +0000 (13:57 -0500)
Once a factory supports different SSH agents on the same platform,
which is planned for Windows once we use Apache MINA sshd 2.8.0,
client code may need to have a way to specify which SSH agent shall
be used when the SSH config doesn't define anything.

Add a mechanism by which a ConnectorFactory can tell what Connectors
it may provide. Client code can use this to set the identityAgent
parameter of ConnectorFactory.create() to the wanted default if it
would be null otherwise.

A ConnectorDescriptor is a pair of strings: an internal name, and a
display name. The latter is included because client code might want to
communicate agent names to the user, be it in error messages or in some
chooser dialog where a user could define which of several alternative
SSH agents should be used as default. The internal name is intended to
be used in the IdentityAgent directive in ~/.ssh/config.

Also make the ConnectorFactory discovered via the ServiceLoader
accessible and overrideable. Provide static get/setDefault() methods,
similar to the SshSessionFactory itself.

Change-Id: Ie3d077395d32dfddc72bc8627e92b23636938182
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties
org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java
org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java
org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java
org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java

index f884adc089bcdab6c2fe031c94667d2164c43dfa..6fce083668613265ddac5ed3d0777208e037e88a 100644 (file)
@@ -13,4 +13,5 @@ msgSendFailed=Sending {0} bytes to SSH agent failed; {0} bytes not written
 msgSendFailed2=Sending {0} bytes to SSH agent failed: {1} - {2}
 msgSharedMemoryFailed=Could not set up shared memory for communicating with Pageant
 msgShortRead=Short read from SSH agent, expected {0} bytes, got {1} bytes; last read() returned {2}
-
+pageant=Pageant
+unixDefaultAgent=ssh-agent
index 1cbf0e41b67542fb0907d35c6093229d801767a8..d7409b0c3c430e9bde6d4948bf2b8872b12c3e70 100644 (file)
@@ -11,6 +11,8 @@ package org.eclipse.jgit.internal.transport.sshd.agent.connector;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
 
 import org.eclipse.jgit.transport.sshd.agent.Connector;
 import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
@@ -41,4 +43,26 @@ public class Factory implements ConnectorFactory {
        public String getName() {
                return NAME;
        }
+
+       /**
+        * {@inheritDoc}
+        * <p>
+        * This factory returns on Windows a
+        * {@link org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor
+        * ConnectorDescriptor} for the internal name "pageant"; on Unix one for
+        * "SSH_AUTH_SOCK".
+        * </p>
+        */
+       @Override
+       public Collection<ConnectorDescriptor> getSupportedConnectors() {
+               return Collections.singleton(getDefaultConnector());
+       }
+
+       @Override
+       public ConnectorDescriptor getDefaultConnector() {
+               if (SystemReader.getInstance().isWindows()) {
+                       return PageantConnector.DESCRIPTOR;
+               }
+               return UnixDomainSocketConnector.DESCRIPTOR;
+       }
 }
index 59fe2fc246958b01073634598d5b95b283baf969..b0e3bce724abcd151b566d894bfb6080f1af67d6 100644 (file)
@@ -12,12 +12,29 @@ package org.eclipse.jgit.internal.transport.sshd.agent.connector;
 import java.io.IOException;
 
 import org.eclipse.jgit.transport.sshd.agent.AbstractConnector;
+import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor;
 
 /**
  * A connector using Pageant's shared memory IPC mechanism.
  */
 public class PageantConnector extends AbstractConnector {
 
+       /**
+        * {@link ConnectorDescriptor} for the {@link PageantConnector}.
+        */
+       public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor() {
+
+               @Override
+               public String getIdentityAgent() {
+                       return "pageant"; //$NON-NLS-1$
+               }
+
+               @Override
+               public String getDisplayName() {
+                       return Texts.get().pageant;
+               }
+       };
+
        private final PageantLibrary lib;
 
        /**
index 6b662615466e537940f2185c07762d01d8f4cb4f..fb45b30dd21121b095c118631dc2355b1e3f1df1 100644 (file)
@@ -42,5 +42,7 @@ public final class Texts extends TranslationBundle {
        /***/ public String msgSendFailed2;
        /***/ public String msgSharedMemoryFailed;
        /***/ public String msgShortRead;
+       /***/ public String pageant;
+       /***/ public String unixDefaultAgent;
 
 }
index 4c698d974a4113531584ac760dc06ccb33f2570b..3b75f3a7da0bf55a798d14d6215d77d9c3bce523 100644 (file)
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.sshd.common.SshException;
 import org.eclipse.jgit.transport.sshd.agent.AbstractConnector;
+import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor;
 import org.eclipse.jgit.util.StringUtils;
 import org.eclipse.jgit.util.SystemReader;
 import org.slf4j.Logger;
@@ -38,6 +39,22 @@ import com.sun.jna.platform.unix.LibCAPI;
  */
 public class UnixDomainSocketConnector extends AbstractConnector {
 
+       /**
+        * {@link ConnectorDescriptor} for the {@link UnixDomainSocketConnector}.
+        */
+       public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor() {
+
+               @Override
+               public String getIdentityAgent() {
+                       return ENV_SSH_AUTH_SOCK;
+               }
+
+               @Override
+               public String getDisplayName() {
+                       return Texts.get().unixDefaultAgent;
+               }
+       };
+
        private static final Logger LOG = LoggerFactory
                        .getLogger(UnixDomainSocketConnector.class);
 
index 9984f99763a0c0a52c256ba166407be8fd17cb02..aba7a76459def837393e2a413a85f720c26443b3 100644 (file)
@@ -19,7 +19,7 @@ import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
  */
 public final class ConnectorFactoryProvider {
 
-       private static final ConnectorFactory FACTORY = loadDefaultFactory();
+       private static volatile ConnectorFactory INSTANCE = loadDefaultFactory();
 
        private static ConnectorFactory loadDefaultFactory() {
                ServiceLoader<ConnectorFactory> loader = ServiceLoader
@@ -35,17 +35,27 @@ public final class ConnectorFactoryProvider {
 
        }
 
-       private ConnectorFactoryProvider() {
-               // No instantiation
-       }
-
        /**
-        * Retrieves the default {@link ConnectorFactory} obtained via the
-        * {@link ServiceLoader}.
+        * Retrieves the currently set default {@link ConnectorFactory}.
         *
         * @return the {@link ConnectorFactory}, or {@code null} if none.
         */
        public static ConnectorFactory getDefaultFactory() {
-               return FACTORY;
+               return INSTANCE;
+       }
+
+       /**
+        * Sets the default {@link ConnectorFactory}.
+        *
+        * @param factory
+        *            {@link ConnectorFactory} to use, or {@code null} to use the
+        *            factory discovered via the {@link ServiceLoader}.
+        */
+       public static void setDefaultFactory(ConnectorFactory factory) {
+               INSTANCE = factory == null ? loadDefaultFactory() : factory;
+       }
+
+       private ConnectorFactoryProvider() {
+               // No instantiation
        }
 }
index 3364180099022e37d3ef32aac43b69a4c607641a..58cf8e1ddd065b7fb83dc58c7f2572392d039fc5 100644 (file)
@@ -56,7 +56,6 @@ import org.eclipse.jgit.internal.transport.sshd.JGitUserInteraction;
 import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase;
 import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper;
 import org.eclipse.jgit.internal.transport.sshd.SshdText;
-import org.eclipse.jgit.internal.transport.sshd.agent.ConnectorFactoryProvider;
 import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.SshConfigStore;
@@ -456,12 +455,15 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
        /**
         * Gets a {@link ConnectorFactory}. If this returns {@code null}, SSH agents
         * are not supported.
+        * <p>
+        * The default implementation uses {@link ConnectorFactory#getDefault()}
+        * </p>
         *
         * @return the factory, or {@code null} if no SSH agent support is desired
         * @since 6.0
         */
        protected ConnectorFactory getConnectorFactory() {
-               return ConnectorFactoryProvider.getDefaultFactory();
+               return ConnectorFactory.getDefault();
        }
 
        /**
index b351d89ef53fa8cdaa70627aa32628c4f6bda40d..da98ea7fe04f64a1401cc80f64c456d4693e3df0 100644 (file)
@@ -11,8 +11,10 @@ package org.eclipse.jgit.transport.sshd.agent;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collection;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.transport.sshd.agent.ConnectorFactoryProvider;
 
 /**
  * A factory for creating {@link Connector}s. This is a service provider
@@ -24,14 +26,45 @@ import org.eclipse.jgit.annotations.NonNull;
  */
 public interface ConnectorFactory {
 
+       /**
+        * Retrieves the currently set default {@link ConnectorFactory}. This is the
+        * factory that is used unless overridden by the
+        * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}.
+        *
+        * @return the current default factory; may be {@code null} if none is set
+        *         and the {@link java.util.ServiceLoader} cannot find any suitable
+        *         implementation
+        */
+       static ConnectorFactory getDefault() {
+               return ConnectorFactoryProvider.getDefaultFactory();
+       }
+
+       /**
+        * Sets a default {@link ConnectorFactory}. This is the factory that is used
+        * unless overridden by the
+        * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}.
+        * <p>
+        * If no default factory is set programmatically, an implementation is
+        * discovered via the {@link java.util.ServiceLoader}.
+        * </p>
+        *
+        * @param factory
+        *            {@link ConnectorFactory} to set, or {@code null} to revert to
+        *            the default behavior of using the
+        *            {@link java.util.ServiceLoader}.
+        */
+       static void setDefault(ConnectorFactory factory) {
+               ConnectorFactoryProvider.setDefaultFactory(factory);
+       }
+
        /**
         * Creates a new {@link Connector}.
         *
         * @param identityAgent
         *            identifies the wanted agent connection; if {@code null}, the
         *            factory is free to provide a {@link Connector} to a default
-        *            agent. The value will typically come from the IdentityAgent
-        *            setting in ~/.ssh/config.
+        *            agent. The value will typically come from the
+        *            {@code IdentityAgent} setting in {@code ~/.ssh/config}.
         * @param homeDir
         *            the current local user's home directory as configured in the
         *            {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}
@@ -58,4 +91,83 @@ public interface ConnectorFactory {
         */
        String getName();
 
+       /**
+        * {@link ConnectorDescriptor}s describe available {@link Connector}s a
+        * {@link ConnectorFactory} may provide.
+        * <p>
+        * A {@link ConnectorFactory} may support connecting to different SSH
+        * agents. Agents are identified by name; a user can choose a specific agent
+        * for instance via the {@code IdentityAgent} setting in
+        * {@code ~/.ssh/config}.
+        * </p>
+        * <p>
+        * OpenSSH knows two built-in names: "none" for not using any agent, and
+        * "SSH_AUTH_SOCK" for using an agent that communicates over a Unix domain
+        * socket given by the value of environment variable {@code SSH_AUTH_SOCK}.
+        * Other agents can be specified in OpenSSH by specifying the socket file
+        * directly. (The "standard" OpenBSD OpenSSH knows only this communication
+        * mechanism.) "SSH_AUTH_SOCK" is also the default in OpenBSD OpenSSH if
+        * nothing is configured.
+        * </p>
+        * <p>
+        * A particular {@link ConnectorFactory} may support more communication
+        * mechanisms or different agents. For instance, a factory on Windows might
+        * support Pageant, Win32-OpenSSH, or even git bash ssh-agent, and might
+        * accept internal names like "pageant", "openssh", "SSH_AUTH_SOCK" in
+        * {@link ConnectorFactory#create(String, File)} to choose among them.
+        * </p>
+        * The {@link ConnectorDescriptor} interface and the
+        * {@link ConnectorFactory#getSupportedConnectors()} and
+        * {@link ConnectorFactory#getDefaultConnector()} methods provide a way for
+        * code using a {@link ConnectorFactory} to learn what the factory supports
+        * and thus implement some way by which a user can influence the default
+        * behavior if {@code IdentityAgent} is not set or
+        * {@link ConnectorFactory#create(String, File)} is called with
+        * {@code identityAgent == null}.
+        */
+       interface ConnectorDescriptor {
+
+               /**
+                * Retrieves the internal name of a supported {@link Connector}. The
+                * internal name is the one a user can specify for instance in the
+                * {@code IdentityAgent} setting in {@code ~/.ssh/config} to select the
+                * connector.
+                *
+                * @return the internal name; not empty
+                */
+               @NonNull
+               String getIdentityAgent();
+
+               /**
+                * Retrieves a display name for a {@link Connector}, suitable for
+                * showing in a UI.
+                *
+                * @return the display name; properly localized and not empty
+                */
+               @NonNull
+               String getDisplayName();
+       }
+
+       /**
+        * Tells which kinds of SSH agents this {@link ConnectorFactory} supports.
+        * <p>
+        * An implementation of this method should document the possible values it
+        * returns.
+        * </p>
+        *
+        * @return an immutable collection of {@link ConnectorDescriptor}s,
+        *         including {@link #getDefaultConnector()} and not including a
+        *         descriptor for internal name "none"
+        */
+       @NonNull
+       Collection<ConnectorDescriptor> getSupportedConnectors();
+
+       /**
+        * Tells what kind of {@link Connector} this {@link ConnectorFactory}
+        * creates if {@link ConnectorFactory#create(String, File)} is called with
+        * {@code identityAgent == null}.
+        *
+        * @return a {@link ConnectorDescriptor} for the default connector
+        */
+       ConnectorDescriptor getDefaultConnector();
 }
index e216a56ac6409de738c4f84d217920bc8637bdbf..1e98a56f7982bc0cc442ba970c0ca99dbca88ae4 100644 (file)
@@ -36,7 +36,7 @@ import org.eclipse.jgit.util.SystemReader;
  */
 public abstract class SshSessionFactory {
 
-       private static SshSessionFactory INSTANCE = loadSshSessionFactory();
+       private static volatile SshSessionFactory INSTANCE = loadSshSessionFactory();
 
        private static SshSessionFactory loadSshSessionFactory() {
                ServiceLoader<SshSessionFactory> loader = ServiceLoader.load(SshSessionFactory.class);