Browse Source
A builder API provides a more convenient way to define a customized SshdSessionFactory by hiding the subclassing. Also provide a new interface SshConfigStore to abstract away the specifics of reading a ssh config file, and provide a way to customize the concrete ssh config implementation to be used. This facilitates using an alternate ssh config implementation that may or may not be based on files. Change-Id: Ib9038e8ff2a4eb3a9ce7b3554d1450befec8e1e1 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>master
Thomas Wolf
5 years ago
7 changed files with 746 additions and 52 deletions
@ -0,0 +1,163 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others |
||||||
|
* |
||||||
|
* This program and the accompanying materials are made available under the |
||||||
|
* terms of the Eclipse Distribution License v. 1.0 which is available at |
||||||
|
* https://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
|
* |
||||||
|
* SPDX-License-Identifier: BSD-3-Clause |
||||||
|
*/ |
||||||
|
package org.eclipse.jgit.transport.sshd; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.UncheckedIOException; |
||||||
|
import java.net.InetSocketAddress; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.security.GeneralSecurityException; |
||||||
|
import java.security.KeyPair; |
||||||
|
import java.security.PublicKey; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.apache.sshd.common.NamedResource; |
||||||
|
import org.apache.sshd.common.config.keys.KeyUtils; |
||||||
|
import org.apache.sshd.common.keyprovider.KeyIdentityProvider; |
||||||
|
import org.apache.sshd.common.session.SessionContext; |
||||||
|
import org.apache.sshd.common.util.net.SshdSocketAddress; |
||||||
|
import org.apache.sshd.common.util.security.SecurityUtils; |
||||||
|
import org.eclipse.jgit.lib.Constants; |
||||||
|
import org.eclipse.jgit.transport.CredentialsProvider; |
||||||
|
import org.eclipse.jgit.transport.SshSessionFactory; |
||||||
|
import org.eclipse.jgit.transport.ssh.SshTestHarness; |
||||||
|
import org.eclipse.jgit.util.FS; |
||||||
|
import org.junit.After; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test for using the SshdSessionFactory without files in ~/.ssh but with an |
||||||
|
* in-memory setup, creating the factory via the builder API. |
||||||
|
*/ |
||||||
|
public class NoFilesSshBuilderTest extends SshTestHarness { |
||||||
|
|
||||||
|
private PublicKey testServerKey; |
||||||
|
|
||||||
|
private KeyPair testUserKey; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected SshSessionFactory createSessionFactory() { |
||||||
|
return new SshdSessionFactoryBuilder() //
|
||||||
|
.setConfigStoreFactory((h, f, u) -> null) |
||||||
|
.setDefaultKeysProvider(f -> new KeyAuthenticator()) |
||||||
|
.setServerKeyDatabase((h, s) -> new ServerKeyDatabase() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<PublicKey> lookup(String connectAddress, |
||||||
|
InetSocketAddress remoteAddress, |
||||||
|
Configuration config) { |
||||||
|
return Collections.singletonList(testServerKey); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean accept(String connectAddress, |
||||||
|
InetSocketAddress remoteAddress, |
||||||
|
PublicKey serverKey, Configuration config, |
||||||
|
CredentialsProvider provider) { |
||||||
|
return KeyUtils.compareKeys(serverKey, testServerKey); |
||||||
|
} |
||||||
|
|
||||||
|
}) //
|
||||||
|
.setPreferredAuthentications("publickey") |
||||||
|
.setHomeDirectory(FS.DETECTED.userHome()) |
||||||
|
.setSshDirectory(sshDir) //
|
||||||
|
.build(new JGitKeyCache()); |
||||||
|
} |
||||||
|
|
||||||
|
private class KeyAuthenticator |
||||||
|
implements KeyIdentityProvider, Iterable<KeyPair> { |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterator<KeyPair> iterator() { |
||||||
|
// Should not be called. The use of the Iterable interface in
|
||||||
|
// SshdSessionFactory.getDefaultKeys() made sense in sshd 2.0.0,
|
||||||
|
// but sshd 2.2.0 added the SessionContext, which although good
|
||||||
|
// (without it we couldn't check here) breaks the Iterable analogy.
|
||||||
|
// But we're stuck now with that interface for getDefaultKeys, and
|
||||||
|
// so this override throwing an exception is unfortunately needed.
|
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterable<KeyPair> loadKeys(SessionContext session) |
||||||
|
throws IOException, GeneralSecurityException { |
||||||
|
if (!TEST_USER.equals(session.getUsername())) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
SshdSocketAddress remoteAddress = SshdSocketAddress |
||||||
|
.toSshdSocketAddress(session.getRemoteAddress()); |
||||||
|
switch (remoteAddress.getHostName()) { |
||||||
|
case "localhost": |
||||||
|
case "127.0.0.1": |
||||||
|
return Collections.singletonList(testUserKey); |
||||||
|
default: |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@After |
||||||
|
public void cleanUp() { |
||||||
|
testServerKey = null; |
||||||
|
testUserKey = null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void installConfig(String... config) { |
||||||
|
File configFile = new File(sshDir, Constants.CONFIG); |
||||||
|
if (config != null) { |
||||||
|
try { |
||||||
|
Files.write(configFile.toPath(), Arrays.asList(config)); |
||||||
|
} catch (IOException e) { |
||||||
|
throw new UncheckedIOException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private KeyPair load(Path path) throws Exception { |
||||||
|
try (InputStream in = Files.newInputStream(path)) { |
||||||
|
return SecurityUtils |
||||||
|
.loadKeyPairIdentities(null, |
||||||
|
NamedResource.ofName(path.toString()), in, null) |
||||||
|
.iterator().next(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testCloneWithBuiltInKeys() throws Exception { |
||||||
|
// This test should fail unless our in-memory setup is taken: no
|
||||||
|
// known_hosts file, and a config that specifies a non-existing key.
|
||||||
|
File newHostKey = new File(getTemporaryDirectory(), "newhostkey"); |
||||||
|
copyTestResource("id_ed25519", newHostKey); |
||||||
|
server.addHostKey(newHostKey.toPath(), true); |
||||||
|
testServerKey = load(newHostKey.toPath()).getPublic(); |
||||||
|
assertTrue(newHostKey.delete()); |
||||||
|
testUserKey = load(privateKey1.getAbsoluteFile().toPath()); |
||||||
|
assertNotNull(testServerKey); |
||||||
|
assertNotNull(testUserKey); |
||||||
|
cloneWith( |
||||||
|
"ssh://" + TEST_USER + "@localhost:" + testPort |
||||||
|
+ "/doesntmatter", |
||||||
|
new File(getTemporaryDirectory(), "cloned"), null, //
|
||||||
|
"Host localhost", //
|
||||||
|
"IdentityFile " |
||||||
|
+ new File(sshDir, "does_not_exist").getAbsolutePath()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,393 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others |
||||||
|
* |
||||||
|
* This program and the accompanying materials are made available under the |
||||||
|
* terms of the Eclipse Distribution License v. 1.0 which is available at |
||||||
|
* https://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
|
* |
||||||
|
* SPDX-License-Identifier: BSD-3-Clause |
||||||
|
*/ |
||||||
|
package org.eclipse.jgit.transport.sshd; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.security.KeyPair; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.function.BiFunction; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull; |
||||||
|
import org.eclipse.jgit.transport.CredentialsProvider; |
||||||
|
import org.eclipse.jgit.transport.SshConfigStore; |
||||||
|
import org.eclipse.jgit.util.StringUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* A builder API to configure {@link SshdSessionFactory SshdSessionFactories}. |
||||||
|
* |
||||||
|
* @since 5.8 |
||||||
|
*/ |
||||||
|
public final class SshdSessionFactoryBuilder { |
||||||
|
|
||||||
|
private final State state = new State(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the {@link ProxyDataFactory} to use for {@link SshdSessionFactory |
||||||
|
* SshdSessionFactories} created by {@link #build(KeyCache)}. |
||||||
|
* |
||||||
|
* @param proxyDataFactory |
||||||
|
* to use |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setProxyDataFactory( |
||||||
|
ProxyDataFactory proxyDataFactory) { |
||||||
|
this.state.proxyDataFactory = proxyDataFactory; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the home directory to use for {@link SshdSessionFactory |
||||||
|
* SshdSessionFactories} created by {@link #build(KeyCache)}. |
||||||
|
* |
||||||
|
* @param homeDirectory |
||||||
|
* to use; may be {@code null}, in which case the home directory |
||||||
|
* as defined by {@link org.eclipse.jgit.util.FS#userHome() |
||||||
|
* FS.userHome()} is assumed |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setHomeDirectory(File homeDirectory) { |
||||||
|
this.state.homeDirectory = homeDirectory; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the SSH directory to use for {@link SshdSessionFactory |
||||||
|
* SshdSessionFactories} created by {@link #build(KeyCache)}. |
||||||
|
* |
||||||
|
* @param sshDirectory |
||||||
|
* to use; may be {@code null}, in which case ".ssh" under the |
||||||
|
* {@link #setHomeDirectory(File) home directory} is assumed |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setSshDirectory(File sshDirectory) { |
||||||
|
this.state.sshDirectory = sshDirectory; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the default preferred authentication mechanisms to use for |
||||||
|
* {@link SshdSessionFactory SshdSessionFactories} created by |
||||||
|
* {@link #build(KeyCache)}. |
||||||
|
* |
||||||
|
* @param authentications |
||||||
|
* comma-separated list of authentication mechanism names; if |
||||||
|
* {@code null} or empty, the default as specified by |
||||||
|
* {@link SshdSessionFactory#getDefaultPreferredAuthentications()} |
||||||
|
* will be used |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setPreferredAuthentications( |
||||||
|
String authentications) { |
||||||
|
this.state.preferredAuthentications = authentications; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets a function that returns the SSH config file, given the SSH |
||||||
|
* directory. The function may return {@code null}, in which case no SSH |
||||||
|
* config file will be used. If a non-null file is returned, it will be used |
||||||
|
* when it exists. If no supplier has been set, or the supplier has been set |
||||||
|
* explicitly to {@code null}, by default a file named |
||||||
|
* {@link org.eclipse.jgit.transport.SshConstants#CONFIG |
||||||
|
* SshConstants.CONFIG} in the {@link #setSshDirectory(File) SSH directory} |
||||||
|
* is used. |
||||||
|
* |
||||||
|
* @param supplier |
||||||
|
* returning a {@link File} for the SSH config file to use, or |
||||||
|
* returning {@code null} if no config file is to be used |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setConfigFile( |
||||||
|
Function<File, File> supplier) { |
||||||
|
this.state.configFileFinder = supplier; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* A factory interface for creating a @link SshConfigStore}. |
||||||
|
*/ |
||||||
|
@FunctionalInterface |
||||||
|
public interface ConfigStoreFactory { |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a {@link SshConfigStore}. May return {@code null} if none is |
||||||
|
* to be used. |
||||||
|
* |
||||||
|
* @param homeDir |
||||||
|
* to use for ~-replacements |
||||||
|
* @param configFile |
||||||
|
* to use, may be {@code null} if none |
||||||
|
* @param localUserName |
||||||
|
* name of the current user in the local OS |
||||||
|
* @return the {@link SshConfigStore}, or {@code null} if none is to be |
||||||
|
* used |
||||||
|
*/ |
||||||
|
SshConfigStore create(@NonNull File homeDir, File configFile, |
||||||
|
String localUserName); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets a factory for the {@link SshConfigStore} to use. If not set or |
||||||
|
* explicitly set to {@code null}, the default as specified by |
||||||
|
* {@link SshdSessionFactory#createSshConfigStore(File, File, String)} is |
||||||
|
* used. |
||||||
|
* |
||||||
|
* @param factory |
||||||
|
* to set |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setConfigStoreFactory( |
||||||
|
ConfigStoreFactory factory) { |
||||||
|
this.state.configFactory = factory; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets a function that returns the default known hosts files, given the SSH |
||||||
|
* directory. If not set or explicitly set to {@code null}, the defaults as |
||||||
|
* specified by {@link SshdSessionFactory#getDefaultKnownHostsFiles(File)} |
||||||
|
* are used. |
||||||
|
* |
||||||
|
* @param supplier |
||||||
|
* to get the default known hosts files |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setDefaultKnownHostsFiles( |
||||||
|
Function<File, List<Path>> supplier) { |
||||||
|
this.state.knownHostsFileFinder = supplier; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets a function that returns the default private key files, given the SSH |
||||||
|
* directory. If not set or explicitly set to {@code null}, the defaults as |
||||||
|
* specified by {@link SshdSessionFactory#getDefaultIdentities(File)} are |
||||||
|
* used. |
||||||
|
* |
||||||
|
* @param supplier |
||||||
|
* to get the default private key files |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setDefaultIdentities( |
||||||
|
Function<File, List<Path>> supplier) { |
||||||
|
this.state.defaultKeyFileFinder = supplier; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets a function that returns the default private keys, given the SSH |
||||||
|
* directory. If not set or explicitly set to {@code null}, the defaults as |
||||||
|
* specified by {@link SshdSessionFactory#getDefaultKeys(File)} are used. |
||||||
|
* |
||||||
|
* @param provider |
||||||
|
* to get the default private key files |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setDefaultKeysProvider( |
||||||
|
Function<File, Iterable<KeyPair>> provider) { |
||||||
|
this.state.defaultKeysProvider = provider; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets a factory function to create a {@link KeyPasswordProvider}. If not |
||||||
|
* set or explicitly set to {@code null}, or if the factory returns |
||||||
|
* {@code null}, the default as specified by |
||||||
|
* {@link SshdSessionFactory#createKeyPasswordProvider(CredentialsProvider)} |
||||||
|
* is used. |
||||||
|
* |
||||||
|
* @param factory |
||||||
|
* to create a {@link KeyPasswordProvider} |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setKeyPasswordProvider( |
||||||
|
Function<CredentialsProvider, KeyPasswordProvider> factory) { |
||||||
|
this.state.passphraseProviderFactory = factory; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets a function that creates a new {@link ServerKeyDatabase}, given the |
||||||
|
* SSH and home directory. If not set or explicitly set to {@code null}, or |
||||||
|
* if the {@code factory} returns {@code null}, the default as specified by |
||||||
|
* {@link SshdSessionFactory#createServerKeyDatabase(File, File)} is used. |
||||||
|
* |
||||||
|
* @param factory |
||||||
|
* to create a {@link ServerKeyDatabase} |
||||||
|
* @return this {@link SshdSessionFactoryBuilder} |
||||||
|
*/ |
||||||
|
public SshdSessionFactoryBuilder setServerKeyDatabase( |
||||||
|
BiFunction<File, File, ServerKeyDatabase> factory) { |
||||||
|
this.state.serverKeyDatabaseCreator = factory; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a {@link SshdSessionFactory} as configured, using the given |
||||||
|
* {@link KeyCache} for caching keys. |
||||||
|
* <p> |
||||||
|
* Different {@link SshdSessionFactory SshdSessionFactories} should |
||||||
|
* <em>not</em> share the same {@link KeyCache} since the cache is |
||||||
|
* invalidated when the factory itself or when the last {@link SshdSession} |
||||||
|
* created from the factory is closed. |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @param cache |
||||||
|
* to use for caching ssh keys; may be {@code null} if no caching |
||||||
|
* is desired. |
||||||
|
* @return the {@link SshdSessionFactory} |
||||||
|
*/ |
||||||
|
public SshdSessionFactory build(KeyCache cache) { |
||||||
|
// Use a copy to avoid that subsequent calls to setters affect an
|
||||||
|
// already created SshdSessionFactory.
|
||||||
|
return state.copy().build(cache); |
||||||
|
} |
||||||
|
|
||||||
|
private static class State { |
||||||
|
|
||||||
|
ProxyDataFactory proxyDataFactory; |
||||||
|
|
||||||
|
File homeDirectory; |
||||||
|
|
||||||
|
File sshDirectory; |
||||||
|
|
||||||
|
String preferredAuthentications; |
||||||
|
|
||||||
|
Function<File, File> configFileFinder; |
||||||
|
|
||||||
|
ConfigStoreFactory configFactory; |
||||||
|
|
||||||
|
Function<CredentialsProvider, KeyPasswordProvider> passphraseProviderFactory; |
||||||
|
|
||||||
|
Function<File, List<Path>> knownHostsFileFinder; |
||||||
|
|
||||||
|
Function<File, List<Path>> defaultKeyFileFinder; |
||||||
|
|
||||||
|
Function<File, Iterable<KeyPair>> defaultKeysProvider; |
||||||
|
|
||||||
|
BiFunction<File, File, ServerKeyDatabase> serverKeyDatabaseCreator; |
||||||
|
|
||||||
|
State copy() { |
||||||
|
State c = new State(); |
||||||
|
c.proxyDataFactory = proxyDataFactory; |
||||||
|
c.homeDirectory = homeDirectory; |
||||||
|
c.sshDirectory = sshDirectory; |
||||||
|
c.preferredAuthentications = preferredAuthentications; |
||||||
|
c.configFileFinder = configFileFinder; |
||||||
|
c.configFactory = configFactory; |
||||||
|
c.passphraseProviderFactory = passphraseProviderFactory; |
||||||
|
c.knownHostsFileFinder = knownHostsFileFinder; |
||||||
|
c.defaultKeyFileFinder = defaultKeyFileFinder; |
||||||
|
c.defaultKeysProvider = defaultKeysProvider; |
||||||
|
c.serverKeyDatabaseCreator = serverKeyDatabaseCreator; |
||||||
|
return c; |
||||||
|
} |
||||||
|
|
||||||
|
SshdSessionFactory build(KeyCache cache) { |
||||||
|
SshdSessionFactory factory = new SessionFactory(cache, |
||||||
|
proxyDataFactory); |
||||||
|
factory.setHomeDirectory(homeDirectory); |
||||||
|
factory.setSshDirectory(sshDirectory); |
||||||
|
return factory; |
||||||
|
} |
||||||
|
|
||||||
|
private class SessionFactory extends SshdSessionFactory { |
||||||
|
|
||||||
|
public SessionFactory(KeyCache cache, |
||||||
|
ProxyDataFactory proxyDataFactory) { |
||||||
|
super(cache, proxyDataFactory); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected File getSshConfig(File sshDir) { |
||||||
|
if (configFileFinder != null) { |
||||||
|
return configFileFinder.apply(sshDir); |
||||||
|
} |
||||||
|
return super.getSshConfig(sshDir); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected List<Path> getDefaultKnownHostsFiles(File sshDir) { |
||||||
|
if (knownHostsFileFinder != null) { |
||||||
|
List<Path> result = knownHostsFileFinder.apply(sshDir); |
||||||
|
return result == null ? Collections.emptyList() : result; |
||||||
|
} |
||||||
|
return super.getDefaultKnownHostsFiles(sshDir); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected List<Path> getDefaultIdentities(File sshDir) { |
||||||
|
if (defaultKeyFileFinder != null) { |
||||||
|
List<Path> result = defaultKeyFileFinder.apply(sshDir); |
||||||
|
return result == null ? Collections.emptyList() : result; |
||||||
|
} |
||||||
|
return super.getDefaultIdentities(sshDir); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected String getDefaultPreferredAuthentications() { |
||||||
|
if (!StringUtils.isEmptyOrNull(preferredAuthentications)) { |
||||||
|
return preferredAuthentications; |
||||||
|
} |
||||||
|
return super.getDefaultPreferredAuthentications(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Iterable<KeyPair> getDefaultKeys(File sshDir) { |
||||||
|
if (defaultKeysProvider != null) { |
||||||
|
Iterable<KeyPair> result = defaultKeysProvider |
||||||
|
.apply(sshDir); |
||||||
|
return result == null ? Collections.emptyList() : result; |
||||||
|
} |
||||||
|
return super.getDefaultKeys(sshDir); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected KeyPasswordProvider createKeyPasswordProvider( |
||||||
|
CredentialsProvider provider) { |
||||||
|
if (passphraseProviderFactory != null) { |
||||||
|
KeyPasswordProvider result = passphraseProviderFactory |
||||||
|
.apply(provider); |
||||||
|
if (result != null) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
return super.createKeyPasswordProvider(provider); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected ServerKeyDatabase createServerKeyDatabase(File homeDir, |
||||||
|
File sshDir) { |
||||||
|
if (serverKeyDatabaseCreator != null) { |
||||||
|
ServerKeyDatabase result = serverKeyDatabaseCreator |
||||||
|
.apply(homeDir, sshDir); |
||||||
|
if (result != null) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
return super.createServerKeyDatabase(homeDir, sshDir); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected SshConfigStore createSshConfigStore(File homeDir, |
||||||
|
File configFile, String localUserName) { |
||||||
|
if (configFactory != null) { |
||||||
|
return configFactory.create(homeDir, configFile, |
||||||
|
localUserName); |
||||||
|
} |
||||||
|
return super.createSshConfigStore(homeDir, configFile, |
||||||
|
localUserName); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2020, Thomas Wolf <thomas.wolf@paranor.ch> and others |
||||||
|
* |
||||||
|
* This program and the accompanying materials are made available under the |
||||||
|
* terms of the Eclipse Distribution License v. 1.0 which is available at |
||||||
|
* https://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
|
* |
||||||
|
* SPDX-License-Identifier: BSD-3-Clause |
||||||
|
*/ |
||||||
|
package org.eclipse.jgit.transport; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull; |
||||||
|
|
||||||
|
/** |
||||||
|
* An abstraction for a SSH config storage, like the OpenSSH ~/.ssh/config file. |
||||||
|
* |
||||||
|
* @since 5.8 |
||||||
|
*/ |
||||||
|
public interface SshConfigStore { |
||||||
|
|
||||||
|
/** |
||||||
|
* Locate the configuration for a specific host request. |
||||||
|
* |
||||||
|
* @param hostName |
||||||
|
* to look up |
||||||
|
* @param port |
||||||
|
* the user supplied; <= 0 if none |
||||||
|
* @param userName |
||||||
|
* the user supplied, may be {@code null} or empty if none given |
||||||
|
* @return the configuration for the requested name. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
HostConfig lookup(@NonNull String hostName, int port, String userName); |
||||||
|
|
||||||
|
/** |
||||||
|
* A host entry from the ssh config. Any merging of global values and of |
||||||
|
* several matching host entries, %-substitutions, and ~ replacement have |
||||||
|
* all been done. |
||||||
|
*/ |
||||||
|
interface HostConfig { |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the value of a single-valued key, or the first if the key |
||||||
|
* has multiple values. Keys are case-insensitive, so |
||||||
|
* {@code getValue("HostName") == getValue("HOSTNAME")}. |
||||||
|
* |
||||||
|
* @param key |
||||||
|
* to get the value of |
||||||
|
* @return the value, or {@code null} if none |
||||||
|
*/ |
||||||
|
String getValue(String key); |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the values of a multi- or list-valued key. Keys are |
||||||
|
* case-insensitive, so |
||||||
|
* {@code getValue("HostName") == getValue("HOSTNAME")}. |
||||||
|
* |
||||||
|
* @param key |
||||||
|
* to get the values of |
||||||
|
* @return a possibly empty list of values |
||||||
|
*/ |
||||||
|
List<String> getValues(String key); |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves an unmodifiable map of all single-valued options, with |
||||||
|
* case-insensitive lookup by keys. |
||||||
|
* |
||||||
|
* @return all single-valued options |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Map<String, String> getOptions(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves an unmodifiable map of all multi- or list-valued options, |
||||||
|
* with case-insensitive lookup by keys. |
||||||
|
* |
||||||
|
* @return all multi-valued options |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Map<String, List<String>> getMultiValuedOptions(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* An empty {@link HostConfig}. |
||||||
|
*/ |
||||||
|
static final HostConfig EMPTY_CONFIG = new HostConfig() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getValue(String key) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> getValues(String key) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Map<String, String> getOptions() { |
||||||
|
return Collections.emptyMap(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Map<String, List<String>> getMultiValuedOptions() { |
||||||
|
return Collections.emptyMap(); |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
} |
Loading…
Reference in new issue