SshdSessionFactoryBuilder.java

  1. /*
  2.  * Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.transport.sshd;

  11. import java.io.File;
  12. import java.nio.file.Path;
  13. import java.security.KeyPair;
  14. import java.util.Collections;
  15. import java.util.List;
  16. import java.util.function.BiFunction;
  17. import java.util.function.Function;

  18. import org.eclipse.jgit.annotations.NonNull;
  19. import org.eclipse.jgit.transport.CredentialsProvider;
  20. import org.eclipse.jgit.transport.SshConfigStore;
  21. import org.eclipse.jgit.util.StringUtils;

  22. /**
  23.  * A builder API to configure {@link SshdSessionFactory SshdSessionFactories}.
  24.  *
  25.  * @since 5.8
  26.  */
  27. public final class SshdSessionFactoryBuilder {

  28.     private final State state = new State();

  29.     /**
  30.      * Sets the {@link ProxyDataFactory} to use for {@link SshdSessionFactory
  31.      * SshdSessionFactories} created by {@link #build(KeyCache)}.
  32.      *
  33.      * @param proxyDataFactory
  34.      *            to use
  35.      * @return this {@link SshdSessionFactoryBuilder}
  36.      */
  37.     public SshdSessionFactoryBuilder setProxyDataFactory(
  38.             ProxyDataFactory proxyDataFactory) {
  39.         this.state.proxyDataFactory = proxyDataFactory;
  40.         return this;
  41.     }

  42.     /**
  43.      * Sets the home directory to use for {@link SshdSessionFactory
  44.      * SshdSessionFactories} created by {@link #build(KeyCache)}.
  45.      *
  46.      * @param homeDirectory
  47.      *            to use; may be {@code null}, in which case the home directory
  48.      *            as defined by {@link org.eclipse.jgit.util.FS#userHome()
  49.      *            FS.userHome()} is assumed
  50.      * @return this {@link SshdSessionFactoryBuilder}
  51.      */
  52.     public SshdSessionFactoryBuilder setHomeDirectory(File homeDirectory) {
  53.         this.state.homeDirectory = homeDirectory;
  54.         return this;
  55.     }

  56.     /**
  57.      * Sets the SSH directory to use for {@link SshdSessionFactory
  58.      * SshdSessionFactories} created by {@link #build(KeyCache)}.
  59.      *
  60.      * @param sshDirectory
  61.      *            to use; may be {@code null}, in which case ".ssh" under the
  62.      *            {@link #setHomeDirectory(File) home directory} is assumed
  63.      * @return this {@link SshdSessionFactoryBuilder}
  64.      */
  65.     public SshdSessionFactoryBuilder setSshDirectory(File sshDirectory) {
  66.         this.state.sshDirectory = sshDirectory;
  67.         return this;
  68.     }

  69.     /**
  70.      * Sets the default preferred authentication mechanisms to use for
  71.      * {@link SshdSessionFactory SshdSessionFactories} created by
  72.      * {@link #build(KeyCache)}.
  73.      *
  74.      * @param authentications
  75.      *            comma-separated list of authentication mechanism names; if
  76.      *            {@code null} or empty, the default as specified by
  77.      *            {@link SshdSessionFactory#getDefaultPreferredAuthentications()}
  78.      *            will be used
  79.      * @return this {@link SshdSessionFactoryBuilder}
  80.      */
  81.     public SshdSessionFactoryBuilder setPreferredAuthentications(
  82.             String authentications) {
  83.         this.state.preferredAuthentications = authentications;
  84.         return this;
  85.     }

  86.     /**
  87.      * Sets a function that returns the SSH config file, given the SSH
  88.      * directory. The function may return {@code null}, in which case no SSH
  89.      * config file will be used. If a non-null file is returned, it will be used
  90.      * when it exists. If no supplier has been set, or the supplier has been set
  91.      * explicitly to {@code null}, by default a file named
  92.      * {@link org.eclipse.jgit.transport.SshConstants#CONFIG
  93.      * SshConstants.CONFIG} in the {@link #setSshDirectory(File) SSH directory}
  94.      * is used.
  95.      *
  96.      * @param supplier
  97.      *            returning a {@link File} for the SSH config file to use, or
  98.      *            returning {@code null} if no config file is to be used
  99.      * @return this {@link SshdSessionFactoryBuilder}
  100.      */
  101.     public SshdSessionFactoryBuilder setConfigFile(
  102.             Function<File, File> supplier) {
  103.         this.state.configFileFinder = supplier;
  104.         return this;
  105.     }

  106.     /**
  107.      * A factory interface for creating a @link SshConfigStore}.
  108.      */
  109.     @FunctionalInterface
  110.     public interface ConfigStoreFactory {

  111.         /**
  112.          * Creates a {@link SshConfigStore}. May return {@code null} if none is
  113.          * to be used.
  114.          *
  115.          * @param homeDir
  116.          *            to use for ~-replacements
  117.          * @param configFile
  118.          *            to use, may be {@code null} if none
  119.          * @param localUserName
  120.          *            name of the current user in the local OS
  121.          * @return the {@link SshConfigStore}, or {@code null} if none is to be
  122.          *         used
  123.          */
  124.         SshConfigStore create(@NonNull File homeDir, File configFile,
  125.                 String localUserName);
  126.     }

  127.     /**
  128.      * Sets a factory for the {@link SshConfigStore} to use. If not set or
  129.      * explicitly set to {@code null}, the default as specified by
  130.      * {@link SshdSessionFactory#createSshConfigStore(File, File, String)} is
  131.      * used.
  132.      *
  133.      * @param factory
  134.      *            to set
  135.      * @return this {@link SshdSessionFactoryBuilder}
  136.      */
  137.     public SshdSessionFactoryBuilder setConfigStoreFactory(
  138.             ConfigStoreFactory factory) {
  139.         this.state.configFactory = factory;
  140.         return this;
  141.     }

  142.     /**
  143.      * Sets a function that returns the default known hosts files, given the SSH
  144.      * directory. If not set or explicitly set to {@code null}, the defaults as
  145.      * specified by {@link SshdSessionFactory#getDefaultKnownHostsFiles(File)}
  146.      * are used.
  147.      *
  148.      * @param supplier
  149.      *            to get the default known hosts files
  150.      * @return this {@link SshdSessionFactoryBuilder}
  151.      */
  152.     public SshdSessionFactoryBuilder setDefaultKnownHostsFiles(
  153.             Function<File, List<Path>> supplier) {
  154.         this.state.knownHostsFileFinder = supplier;
  155.         return this;
  156.     }

  157.     /**
  158.      * Sets a function that returns the default private key files, given the SSH
  159.      * directory. If not set or explicitly set to {@code null}, the defaults as
  160.      * specified by {@link SshdSessionFactory#getDefaultIdentities(File)} are
  161.      * used.
  162.      *
  163.      * @param supplier
  164.      *            to get the default private key files
  165.      * @return this {@link SshdSessionFactoryBuilder}
  166.      */
  167.     public SshdSessionFactoryBuilder setDefaultIdentities(
  168.             Function<File, List<Path>> supplier) {
  169.         this.state.defaultKeyFileFinder = supplier;
  170.         return this;
  171.     }

  172.     /**
  173.      * Sets a function that returns the default private keys, given the SSH
  174.      * directory. If not set or explicitly set to {@code null}, the defaults as
  175.      * specified by {@link SshdSessionFactory#getDefaultKeys(File)} are used.
  176.      *
  177.      * @param provider
  178.      *            to get the default private key files
  179.      * @return this {@link SshdSessionFactoryBuilder}
  180.      */
  181.     public SshdSessionFactoryBuilder setDefaultKeysProvider(
  182.             Function<File, Iterable<KeyPair>> provider) {
  183.         this.state.defaultKeysProvider = provider;
  184.         return this;
  185.     }

  186.     /**
  187.      * Sets a factory function to create a {@link KeyPasswordProvider}. If not
  188.      * set or explicitly set to {@code null}, or if the factory returns
  189.      * {@code null}, the default as specified by
  190.      * {@link SshdSessionFactory#createKeyPasswordProvider(CredentialsProvider)}
  191.      * is used.
  192.      *
  193.      * @param factory
  194.      *            to create a {@link KeyPasswordProvider}
  195.      * @return this {@link SshdSessionFactoryBuilder}
  196.      */
  197.     public SshdSessionFactoryBuilder setKeyPasswordProvider(
  198.             Function<CredentialsProvider, KeyPasswordProvider> factory) {
  199.         this.state.passphraseProviderFactory = factory;
  200.         return this;
  201.     }

  202.     /**
  203.      * Sets a function that creates a new {@link ServerKeyDatabase}, given the
  204.      * SSH and home directory. If not set or explicitly set to {@code null}, or
  205.      * if the {@code factory} returns {@code null}, the default as specified by
  206.      * {@link SshdSessionFactory#createServerKeyDatabase(File, File)} is used.
  207.      *
  208.      * @param factory
  209.      *            to create a {@link ServerKeyDatabase}
  210.      * @return this {@link SshdSessionFactoryBuilder}
  211.      */
  212.     public SshdSessionFactoryBuilder setServerKeyDatabase(
  213.             BiFunction<File, File, ServerKeyDatabase> factory) {
  214.         this.state.serverKeyDatabaseCreator = factory;
  215.         return this;
  216.     }

  217.     /**
  218.      * Builds a {@link SshdSessionFactory} as configured, using the given
  219.      * {@link KeyCache} for caching keys.
  220.      * <p>
  221.      * Different {@link SshdSessionFactory SshdSessionFactories} should
  222.      * <em>not</em> share the same {@link KeyCache} since the cache is
  223.      * invalidated when the factory itself or when the last {@link SshdSession}
  224.      * created from the factory is closed.
  225.      * </p>
  226.      *
  227.      * @param cache
  228.      *            to use for caching ssh keys; may be {@code null} if no caching
  229.      *            is desired.
  230.      * @return the {@link SshdSessionFactory}
  231.      */
  232.     public SshdSessionFactory build(KeyCache cache) {
  233.         // Use a copy to avoid that subsequent calls to setters affect an
  234.         // already created SshdSessionFactory.
  235.         return state.copy().build(cache);
  236.     }

  237.     private static class State {

  238.         ProxyDataFactory proxyDataFactory;

  239.         File homeDirectory;

  240.         File sshDirectory;

  241.         String preferredAuthentications;

  242.         Function<File, File> configFileFinder;

  243.         ConfigStoreFactory configFactory;

  244.         Function<CredentialsProvider, KeyPasswordProvider> passphraseProviderFactory;

  245.         Function<File, List<Path>> knownHostsFileFinder;

  246.         Function<File, List<Path>> defaultKeyFileFinder;

  247.         Function<File, Iterable<KeyPair>> defaultKeysProvider;

  248.         BiFunction<File, File, ServerKeyDatabase> serverKeyDatabaseCreator;

  249.         State copy() {
  250.             State c = new State();
  251.             c.proxyDataFactory = proxyDataFactory;
  252.             c.homeDirectory = homeDirectory;
  253.             c.sshDirectory = sshDirectory;
  254.             c.preferredAuthentications = preferredAuthentications;
  255.             c.configFileFinder = configFileFinder;
  256.             c.configFactory = configFactory;
  257.             c.passphraseProviderFactory = passphraseProviderFactory;
  258.             c.knownHostsFileFinder = knownHostsFileFinder;
  259.             c.defaultKeyFileFinder = defaultKeyFileFinder;
  260.             c.defaultKeysProvider = defaultKeysProvider;
  261.             c.serverKeyDatabaseCreator = serverKeyDatabaseCreator;
  262.             return c;
  263.         }

  264.         SshdSessionFactory build(KeyCache cache) {
  265.             SshdSessionFactory factory = new SessionFactory(cache,
  266.                     proxyDataFactory);
  267.             factory.setHomeDirectory(homeDirectory);
  268.             factory.setSshDirectory(sshDirectory);
  269.             return factory;
  270.         }

  271.         private class SessionFactory extends SshdSessionFactory {

  272.             public SessionFactory(KeyCache cache,
  273.                     ProxyDataFactory proxyDataFactory) {
  274.                 super(cache, proxyDataFactory);
  275.             }

  276.             @Override
  277.             protected File getSshConfig(File sshDir) {
  278.                 if (configFileFinder != null) {
  279.                     return configFileFinder.apply(sshDir);
  280.                 }
  281.                 return super.getSshConfig(sshDir);
  282.             }

  283.             @Override
  284.             protected List<Path> getDefaultKnownHostsFiles(File sshDir) {
  285.                 if (knownHostsFileFinder != null) {
  286.                     List<Path> result = knownHostsFileFinder.apply(sshDir);
  287.                     return result == null ? Collections.emptyList() : result;
  288.                 }
  289.                 return super.getDefaultKnownHostsFiles(sshDir);
  290.             }

  291.             @Override
  292.             protected List<Path> getDefaultIdentities(File sshDir) {
  293.                 if (defaultKeyFileFinder != null) {
  294.                     List<Path> result = defaultKeyFileFinder.apply(sshDir);
  295.                     return result == null ? Collections.emptyList() : result;
  296.                 }
  297.                 return super.getDefaultIdentities(sshDir);
  298.             }

  299.             @Override
  300.             protected String getDefaultPreferredAuthentications() {
  301.                 if (!StringUtils.isEmptyOrNull(preferredAuthentications)) {
  302.                     return preferredAuthentications;
  303.                 }
  304.                 return super.getDefaultPreferredAuthentications();
  305.             }

  306.             @Override
  307.             protected Iterable<KeyPair> getDefaultKeys(File sshDir) {
  308.                 if (defaultKeysProvider != null) {
  309.                     Iterable<KeyPair> result = defaultKeysProvider
  310.                             .apply(sshDir);
  311.                     return result == null ? Collections.emptyList() : result;
  312.                 }
  313.                 return super.getDefaultKeys(sshDir);
  314.             }

  315.             @Override
  316.             protected KeyPasswordProvider createKeyPasswordProvider(
  317.                     CredentialsProvider provider) {
  318.                 if (passphraseProviderFactory != null) {
  319.                     KeyPasswordProvider result = passphraseProviderFactory
  320.                             .apply(provider);
  321.                     if (result != null) {
  322.                         return result;
  323.                     }
  324.                 }
  325.                 return super.createKeyPasswordProvider(provider);
  326.             }

  327.             @Override
  328.             protected ServerKeyDatabase createServerKeyDatabase(File homeDir,
  329.                     File sshDir) {
  330.                 if (serverKeyDatabaseCreator != null) {
  331.                     ServerKeyDatabase result = serverKeyDatabaseCreator
  332.                             .apply(homeDir, sshDir);
  333.                     if (result != null) {
  334.                         return result;
  335.                     }
  336.                 }
  337.                 return super.createServerKeyDatabase(homeDir, sshDir);
  338.             }

  339.             @Override
  340.             protected SshConfigStore createSshConfigStore(File homeDir,
  341.                     File configFile, String localUserName) {
  342.                 if (configFactory != null) {
  343.                     return configFactory.create(homeDir, configFile,
  344.                             localUserName);
  345.                 }
  346.                 return super.createSshConfigStore(homeDir, configFile,
  347.                         localUserName);
  348.             }
  349.         }
  350.     }
  351. }