summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java124
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java68
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java69
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java41
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java18
5 files changed, 225 insertions, 95 deletions
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
index 420a1d16eb..bf891742b7 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
@@ -18,16 +18,22 @@ import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import org.apache.sshd.client.ClientFactoryManager;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSessionImpl;
+import org.apache.sshd.common.AttributeRepository;
import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.PropertyResolver;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.config.keys.KeyUtils;
@@ -419,4 +425,122 @@ public class JGitClientSession extends ClientSessionImpl {
return b.toString();
}
+ @Override
+ public <T> T getAttribute(AttributeKey<T> key) {
+ T value = super.getAttribute(key);
+ if (value == null) {
+ IoSession ioSession = getIoSession();
+ if (ioSession != null) {
+ Object obj = ioSession.getAttribute(AttributeRepository.class);
+ if (obj instanceof AttributeRepository) {
+ AttributeRepository sessionAttributes = (AttributeRepository) obj;
+ value = sessionAttributes.resolveAttribute(key);
+ }
+ }
+ }
+ return value;
+ }
+
+ @Override
+ public PropertyResolver getParentPropertyResolver() {
+ IoSession ioSession = getIoSession();
+ if (ioSession != null) {
+ Object obj = ioSession.getAttribute(AttributeRepository.class);
+ if (obj instanceof PropertyResolver) {
+ return (PropertyResolver) obj;
+ }
+ }
+ return super.getParentPropertyResolver();
+ }
+
+ /**
+ * An {@link AttributeRepository} that chains together two other attribute
+ * sources in a hierarchy.
+ */
+ public static class ChainingAttributes implements AttributeRepository {
+
+ private final AttributeRepository delegate;
+
+ private final AttributeRepository parent;
+
+ /**
+ * Create a new {@link ChainingAttributes} attribute source.
+ *
+ * @param self
+ * to search for attributes first
+ * @param parent
+ * to search for attributes if not found in {@code self}
+ */
+ public ChainingAttributes(AttributeRepository self,
+ AttributeRepository parent) {
+ this.delegate = self;
+ this.parent = parent;
+ }
+
+ @Override
+ public int getAttributesCount() {
+ return delegate.getAttributesCount();
+ }
+
+ @Override
+ public <T> T getAttribute(AttributeKey<T> key) {
+ return delegate.getAttribute(Objects.requireNonNull(key));
+ }
+
+ @Override
+ public Collection<AttributeKey<?>> attributeKeys() {
+ return delegate.attributeKeys();
+ }
+
+ @Override
+ public <T> T resolveAttribute(AttributeKey<T> key) {
+ T value = getAttribute(Objects.requireNonNull(key));
+ if (value == null) {
+ return parent.getAttribute(key);
+ }
+ return value;
+ }
+ }
+
+ /**
+ * A {@link ChainingAttributes} repository that doubles as a
+ * {@link PropertyResolver}. The property map can be set via the attribute
+ * key {@link SessionAttributes#PROPERTIES}.
+ */
+ public static class SessionAttributes extends ChainingAttributes
+ implements PropertyResolver {
+
+ /** Key for storing a map of properties in the attributes. */
+ public static final AttributeKey<Map<String, Object>> PROPERTIES = new AttributeKey<>();
+
+ private final PropertyResolver parentProperties;
+
+ /**
+ * Creates a new {@link SessionAttributes} attribute and property
+ * source.
+ *
+ * @param self
+ * to search for attributes first
+ * @param parent
+ * to search for attributes if not found in {@code self}
+ * @param parentProperties
+ * to search for properties if not found in {@code self}
+ */
+ public SessionAttributes(AttributeRepository self,
+ AttributeRepository parent, PropertyResolver parentProperties) {
+ super(self, parent);
+ this.parentProperties = parentProperties;
+ }
+
+ @Override
+ public PropertyResolver getParentPropertyResolver() {
+ return parentProperties;
+ }
+
+ @Override
+ public Map<String, Object> getProperties() {
+ Map<String, Object> props = getAttribute(PROPERTIES);
+ return props == null ? Collections.emptyMap() : props;
+ }
+ }
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
index b8dd60fb10..1825fb37b2 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 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
@@ -23,12 +23,16 @@ import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Collectors;
+import org.apache.sshd.client.ClientAuthenticationManager;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.future.ConnectFuture;
@@ -45,6 +49,8 @@ import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.session.helpers.AbstractSession;
import org.apache.sshd.common.util.ValidateUtils;
+import org.eclipse.jgit.internal.transport.sshd.JGitClientSession.ChainingAttributes;
+import org.eclipse.jgit.internal.transport.sshd.JGitClientSession.SessionAttributes;
import org.eclipse.jgit.internal.transport.sshd.proxy.HttpClientConnector;
import org.eclipse.jgit.internal.transport.sshd.proxy.Socks5ClientConnector;
import org.eclipse.jgit.transport.CredentialsProvider;
@@ -52,6 +58,7 @@ import org.eclipse.jgit.transport.SshConstants;
import org.eclipse.jgit.transport.sshd.KeyCache;
import org.eclipse.jgit.transport.sshd.ProxyData;
import org.eclipse.jgit.transport.sshd.ProxyDataFactory;
+import org.eclipse.jgit.util.StringUtils;
/**
* Customized {@link SshClient} for JGit. It creates specialized
@@ -100,35 +107,55 @@ public class JGitSshClient extends SshClient {
int port = hostConfig.getPort();
ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port); //$NON-NLS-1$
String userName = hostConfig.getUsername();
+ AttributeRepository attributes = chain(context, this);
InetSocketAddress address = new InetSocketAddress(host, port);
ConnectFuture connectFuture = new DefaultConnectFuture(
userName + '@' + address, null);
SshFutureListener<IoConnectFuture> listener = createConnectCompletionListener(
connectFuture, userName, address, hostConfig);
- // sshd needs some entries from the host config already in the
- // constructor of the session. Put those as properties on this client,
- // where it will find them. We can set the host config only once the
- // session object has been created.
- copyProperty(
- hostConfig.getProperty(SshConstants.PREFERRED_AUTHENTICATIONS,
- getAttribute(PREFERRED_AUTHENTICATIONS)),
- PREFERRED_AUTHS);
- setAttribute(HOST_CONFIG_ENTRY, hostConfig);
- setAttribute(ORIGINAL_REMOTE_ADDRESS, address);
+ attributes = sessionAttributes(attributes, hostConfig, address);
// Proxy support
ProxyData proxy = getProxyData(address);
if (proxy != null) {
address = configureProxy(proxy, address);
proxy.clearPassword();
}
- connector.connect(address, this, localAddress).addListener(listener);
+ connector.connect(address, attributes, localAddress)
+ .addListener(listener);
return connectFuture;
}
- private void copyProperty(String value, String key) {
- if (value != null && !value.isEmpty()) {
- getProperties().put(key, value);
+ private AttributeRepository chain(AttributeRepository self,
+ AttributeRepository parent) {
+ if (self == null) {
+ return Objects.requireNonNull(parent);
+ }
+ if (parent == null || parent == self) {
+ return self;
+ }
+ return new ChainingAttributes(self, parent);
+ }
+
+ private AttributeRepository sessionAttributes(AttributeRepository parent,
+ HostConfigEntry hostConfig, InetSocketAddress originalAddress) {
+ // sshd needs some entries from the host config already in the
+ // constructor of the session. Put those into a dedicated
+ // AttributeRepository for the new session where it will find them.
+ // We can set the host config only once the session object has been
+ // created.
+ Map<AttributeKey<?>, Object> data = new HashMap<>();
+ data.put(HOST_CONFIG_ENTRY, hostConfig);
+ data.put(ORIGINAL_REMOTE_ADDRESS, originalAddress);
+ String preferredAuths = hostConfig.getProperty(
+ SshConstants.PREFERRED_AUTHENTICATIONS,
+ resolveAttribute(PREFERRED_AUTHENTICATIONS));
+ if (!StringUtils.isEmptyOrNull(preferredAuths)) {
+ data.put(SessionAttributes.PROPERTIES,
+ Collections.singletonMap(PREFERRED_AUTHS, preferredAuths));
}
+ return new SessionAttributes(
+ AttributeRepository.ofAttributesMap(data),
+ parent, this);
}
private ProxyData getProxyData(InetSocketAddress remoteAddress) {
@@ -219,11 +246,6 @@ public class JGitSshClient extends SshClient {
int numberOfPasswordPrompts = getNumberOfPasswordPrompts(hostConfig);
session.getProperties().put(PASSWORD_PROMPTS,
Integer.valueOf(numberOfPasswordPrompts));
- FilePasswordProvider passwordProvider = getFilePasswordProvider();
- if (passwordProvider instanceof RepeatingFilePasswordProvider) {
- ((RepeatingFilePasswordProvider) passwordProvider)
- .setAttempts(numberOfPasswordPrompts);
- }
List<Path> identities = hostConfig.getIdentities().stream()
.map(s -> {
try {
@@ -237,6 +259,7 @@ public class JGitSshClient extends SshClient {
.collect(Collectors.toList());
CachingKeyPairProvider ourConfiguredKeysProvider = new CachingKeyPairProvider(
identities, keyCache);
+ FilePasswordProvider passwordProvider = getFilePasswordProvider();
ourConfiguredKeysProvider.setPasswordFinder(passwordProvider);
if (hostConfig.isIdentitiesOnly()) {
session.setKeyIdentityProvider(ourConfiguredKeysProvider);
@@ -265,9 +288,7 @@ public class JGitSshClient extends SshClient {
log.warn(format(SshdText.get().configInvalidPositive,
SshConstants.NUMBER_OF_PASSWORD_PROMPTS, prompts));
}
- // Default for NumberOfPasswordPrompts according to
- // https://man.openbsd.org/ssh_config
- return 3;
+ return ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS;
}
/**
@@ -408,6 +429,5 @@ public class JGitSshClient extends SshClient {
};
}
-
}
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
index 5b48a8cf99..078e411f29 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 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
@@ -16,8 +16,12 @@ import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+import org.apache.sshd.client.ClientAuthenticationManager;
+import org.apache.sshd.common.AttributeRepository.AttributeKey;
import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.session.SessionContext;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.transport.CredentialsProvider;
@@ -25,39 +29,61 @@ import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.sshd.KeyPasswordProvider;
/**
- * A bridge from sshd's {@link RepeatingFilePasswordProvider} to our
+ * A bridge from sshd's {@link FilePasswordProvider} to our per-session
* {@link KeyPasswordProvider} API.
*/
-public class PasswordProviderWrapper implements RepeatingFilePasswordProvider {
+public class PasswordProviderWrapper implements FilePasswordProvider {
- private final KeyPasswordProvider delegate;
+ private static final AttributeKey<PerSessionState> STATE = new AttributeKey<>();
- private Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
+ private static class PerSessionState {
+
+ Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
+
+ KeyPasswordProvider delegate;
- /**
- * @param delegate
- */
- public PasswordProviderWrapper(@NonNull KeyPasswordProvider delegate) {
- this.delegate = delegate;
}
- @Override
- public void setAttempts(int numberOfPasswordPrompts) {
- delegate.setAttempts(numberOfPasswordPrompts);
+ private final Supplier<KeyPasswordProvider> factory;
+
+ /**
+ * Creates a new {@link PasswordProviderWrapper}.
+ *
+ * @param factory
+ * to use to create per-session {@link KeyPasswordProvider}s
+ */
+ public PasswordProviderWrapper(
+ @NonNull Supplier<KeyPasswordProvider> factory) {
+ this.factory = factory;
}
- @Override
- public int getAttempts() {
- return delegate.getAttempts();
+ private PerSessionState getState(SessionContext context) {
+ PerSessionState state = context.getAttribute(STATE);
+ if (state == null) {
+ state = new PerSessionState();
+ state.delegate = factory.get();
+ Integer maxNumberOfAttempts = context
+ .getInteger(ClientAuthenticationManager.PASSWORD_PROMPTS);
+ if (maxNumberOfAttempts != null
+ && maxNumberOfAttempts.intValue() > 0) {
+ state.delegate.setAttempts(maxNumberOfAttempts.intValue());
+ } else {
+ state.delegate.setAttempts(
+ ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS);
+ }
+ context.setAttribute(STATE, state);
+ }
+ return state;
}
@Override
public String getPassword(SessionContext session, NamedResource resource,
int attemptIndex) throws IOException {
String key = resource.getName();
- int attempt = counts
+ PerSessionState state = getState(session);
+ int attempt = state.counts
.computeIfAbsent(key, k -> new AtomicInteger()).get();
- char[] passphrase = delegate.getPassphrase(toUri(key), attempt);
+ char[] passphrase = state.delegate.getPassphrase(toUri(key), attempt);
if (passphrase == null) {
return null;
}
@@ -74,18 +100,19 @@ public class PasswordProviderWrapper implements RepeatingFilePasswordProvider {
String password, Exception err)
throws IOException, GeneralSecurityException {
String key = resource.getName();
- AtomicInteger count = counts.get(key);
+ PerSessionState state = getState(session);
+ AtomicInteger count = state.counts.get(key);
int numberOfAttempts = count == null ? 0 : count.incrementAndGet();
ResourceDecodeResult result = null;
try {
- if (delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
+ if (state.delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
result = ResourceDecodeResult.RETRY;
} else {
result = ResourceDecodeResult.TERMINATE;
}
} finally {
if (result != ResourceDecodeResult.RETRY) {
- counts.remove(key);
+ state.counts.remove(key);
}
}
return result;
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java
deleted file mode 100644
index 86f0fe7b60..0000000000
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2018, 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.internal.transport.sshd;
-
-import org.apache.sshd.common.config.keys.FilePasswordProvider;
-
-/**
- * A {@link FilePasswordProvider} augmented to support repeatedly asking for
- * passwords.
- *
- */
-public interface RepeatingFilePasswordProvider extends FilePasswordProvider {
-
- /**
- * Define the maximum number of attempts to get a password that should be
- * attempted for one identity resource through this provider.
- *
- * @param numberOfPasswordPrompts
- * number of times to ask for a password;
- * {@link IllegalArgumentException} may be thrown if <= 0
- */
- void setAttempts(int numberOfPasswordPrompts);
-
- /**
- * Gets the maximum number of attempts to get a password that should be
- * attempted for one identity resource through this provider.
- *
- * @return the maximum number of attempts to try, always >= 1.
- */
- default int getAttempts() {
- return 1;
- }
-
-}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
index bb4e49be8e..0f7ab849f5 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, 2019 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 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
@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.sshd.client.ClientBuilder;
@@ -194,12 +195,11 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
home, sshDir);
KeyIdentityProvider defaultKeysProvider = toKeyIdentityProvider(
getDefaultKeys(sshDir));
- KeyPasswordProvider passphrases = createKeyPasswordProvider(
- credentialsProvider);
SshClient client = ClientBuilder.builder()
.factory(JGitSshClient::new)
- .filePasswordProvider(
- createFilePasswordProvider(passphrases))
+ .filePasswordProvider(createFilePasswordProvider(
+ () -> createKeyPasswordProvider(
+ credentialsProvider)))
.hostConfigEntryResolver(configFile)
.serverKeyVerifier(new JGitServerKeyVerifier(
getServerKeyDatabase(home, sshDir)))
@@ -536,14 +536,14 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
/**
* Creates a {@link FilePasswordProvider} for a new session.
*
- * @param provider
- * the {@link KeyPasswordProvider} to delegate to
+ * @param providerFactory
+ * providing the {@link KeyPasswordProvider} to delegate to
* @return a new {@link FilePasswordProvider}
*/
@NonNull
private FilePasswordProvider createFilePasswordProvider(
- KeyPasswordProvider provider) {
- return new PasswordProviderWrapper(provider);
+ Supplier<KeyPasswordProvider> providerFactory) {
+ return new PasswordProviderWrapper(providerFactory);
}
/**