|
|
@@ -6,6 +6,40 @@ namespace Renci.SshNet
|
|
|
{
|
|
|
internal class ClientAuthentication : IClientAuthentication
|
|
|
{
|
|
|
+ private readonly int _partialSuccessLimit;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new <see cref="ClientAuthentication"/> instance.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="partialSuccessLimit">The number of times an authentication attempt with any given <see cref="IAuthenticationMethod"/> can result in <see cref="AuthenticationResult.PartialSuccess"/> before it is disregarded.</param>
|
|
|
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="partialSuccessLimit"/> is less than one.</exception>
|
|
|
+ public ClientAuthentication(int partialSuccessLimit)
|
|
|
+ {
|
|
|
+ if (partialSuccessLimit < 1)
|
|
|
+ throw new ArgumentOutOfRangeException("partialSuccessLimit", "Cannot be less than one.");
|
|
|
+
|
|
|
+ _partialSuccessLimit = partialSuccessLimit;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the number of times an authentication attempt with any given <see cref="IAuthenticationMethod"/> can
|
|
|
+ /// result in <see cref="AuthenticationResult.PartialSuccess"/> before it is disregarded.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>
|
|
|
+ /// The number of times an authentication attempt with any given <see cref="IAuthenticationMethod"/> can result
|
|
|
+ /// in <see cref="AuthenticationResult.PartialSuccess"/> before it is disregarded.
|
|
|
+ /// </value>
|
|
|
+ internal int PartialSuccessLimit
|
|
|
+ {
|
|
|
+ get { return _partialSuccessLimit; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Attempts to authentication for a given <see cref="ISession"/> using the <see cref="IConnectionInfoInternal.AuthenticationMethods"/>
|
|
|
+ /// of the specified <see cref="IConnectionInfoInternal"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="connectionInfo">A <see cref="IConnectionInfoInternal"/> to use for authenticating.</param>
|
|
|
+ /// <param name="session">The <see cref="ISession"/> for which to perform authentication.</param>
|
|
|
public void Authenticate(IConnectionInfoInternal connectionInfo, ISession session)
|
|
|
{
|
|
|
if (connectionInfo == null)
|
|
|
@@ -44,10 +78,10 @@ namespace Renci.SshNet
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static bool TryAuthenticate(ISession session,
|
|
|
- AuthenticationState authenticationState,
|
|
|
- string[] allowedAuthenticationMethods,
|
|
|
- ref SshAuthenticationException authenticationException)
|
|
|
+ private bool TryAuthenticate(ISession session,
|
|
|
+ AuthenticationState authenticationState,
|
|
|
+ string[] allowedAuthenticationMethods,
|
|
|
+ ref SshAuthenticationException authenticationException)
|
|
|
{
|
|
|
if (allowedAuthenticationMethods.Length == 0)
|
|
|
{
|
|
|
@@ -58,40 +92,39 @@ namespace Renci.SshNet
|
|
|
// we want to try authentication methods in the order in which they were
|
|
|
// passed in the ctor, not the order in which the SSH server returns
|
|
|
// the allowed authentication methods
|
|
|
- var matchingAuthenticationMethods = GetAllowedAuthenticationMethodsThatAreSupported(authenticationState, allowedAuthenticationMethods);
|
|
|
+ var matchingAuthenticationMethods = authenticationState.GetSupportedAuthenticationMethods(allowedAuthenticationMethods);
|
|
|
if (matchingAuthenticationMethods.Count == 0)
|
|
|
{
|
|
|
- authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).", string.Join(",", allowedAuthenticationMethods)));
|
|
|
+ authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).",
|
|
|
+ string.Join(",", allowedAuthenticationMethods)));
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- foreach (var authenticationMethod in GetOrderedAuthenticationMethods(authenticationState, matchingAuthenticationMethods))
|
|
|
+ foreach (var authenticationMethod in authenticationState.GetActiveAuthenticationMethods(matchingAuthenticationMethods))
|
|
|
{
|
|
|
- if (authenticationState.FailedAuthenticationMethods.Contains(authenticationMethod))
|
|
|
- continue;
|
|
|
-
|
|
|
- // when the authentication method was previously executed, then skip the authentication
|
|
|
- // method as long as there's another authentication method to try; this is done to avoid
|
|
|
- // a stack overflow for servers that do not update the list of allowed authentication
|
|
|
+ // guard against a stack overlow for servers that do not update the list of allowed authentication
|
|
|
// methods after a partial success
|
|
|
-
|
|
|
- if (!authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod))
|
|
|
+ if (authenticationState.GetPartialSuccessCount(authenticationMethod) >= _partialSuccessLimit)
|
|
|
{
|
|
|
- // update state to reflect previosuly executed authentication methods
|
|
|
- authenticationState.ExecutedAuthenticationMethods.Add(authenticationMethod);
|
|
|
+ // TODO Get list of all authentication methods that have reached the partial success limit?
|
|
|
+
|
|
|
+ authenticationException = new SshAuthenticationException(string.Format("Reached authentication attempt limit for method ({0}).",
|
|
|
+ authenticationMethod.Name));
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
var authenticationResult = authenticationMethod.Authenticate(session);
|
|
|
switch (authenticationResult)
|
|
|
{
|
|
|
case AuthenticationResult.PartialSuccess:
|
|
|
+ authenticationState.RecordPartialSuccess(authenticationMethod);
|
|
|
if (TryAuthenticate(session, authenticationState, authenticationMethod.AllowedAuthentications, ref authenticationException))
|
|
|
{
|
|
|
authenticationResult = AuthenticationResult.Success;
|
|
|
}
|
|
|
break;
|
|
|
case AuthenticationResult.Failure:
|
|
|
- authenticationState.FailedAuthenticationMethods.Add(authenticationMethod);
|
|
|
+ authenticationState.RecordFailure(authenticationMethod);
|
|
|
authenticationException = new SshAuthenticationException(string.Format("Permission denied ({0}).", authenticationMethod.Name));
|
|
|
break;
|
|
|
case AuthenticationResult.Success:
|
|
|
@@ -106,85 +139,151 @@ namespace Renci.SshNet
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- private static List<IAuthenticationMethod> GetAllowedAuthenticationMethodsThatAreSupported(AuthenticationState authenticationState,
|
|
|
- string[] allowedAuthenticationMethods)
|
|
|
+ private class AuthenticationState
|
|
|
{
|
|
|
- var result = new List<IAuthenticationMethod>();
|
|
|
-
|
|
|
- foreach (var supportedAuthenticationMethod in authenticationState.SupportedAuthenticationMethods)
|
|
|
- {
|
|
|
- var nameOfSupportedAuthenticationMethod = supportedAuthenticationMethod.Name;
|
|
|
+ private readonly IList<IAuthenticationMethod> _supportedAuthenticationMethods;
|
|
|
|
|
|
- for (var i = 0; i < allowedAuthenticationMethods.Length; i++)
|
|
|
- {
|
|
|
- if (allowedAuthenticationMethods[i] == nameOfSupportedAuthenticationMethod)
|
|
|
- {
|
|
|
- result.Add(supportedAuthenticationMethod);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Records if a given <see cref="IAuthenticationMethod"/> has been tried, and how many times this resulted
|
|
|
+ /// in <see cref="AuthenticationResult.PartialSuccess"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// When there's no entry for a given <see cref="IAuthenticationMethod"/>, then it was never tried.
|
|
|
+ /// </remarks>
|
|
|
+ private readonly Dictionary<IAuthenticationMethod, int> _authenticationMethodPartialSuccessRegister;
|
|
|
|
|
|
- return result;
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Holds the list of authentications methods that failed.
|
|
|
+ /// </summary>
|
|
|
+ private readonly List<IAuthenticationMethod> _failedAuthenticationMethods;
|
|
|
|
|
|
- private static IEnumerable<IAuthenticationMethod> GetOrderedAuthenticationMethods(AuthenticationState authenticationState, List<IAuthenticationMethod> matchingAuthenticationMethods)
|
|
|
- {
|
|
|
- var skippedAuthenticationMethods = new List<IAuthenticationMethod>();
|
|
|
+ public AuthenticationState(IList<IAuthenticationMethod> supportedAuthenticationMethods)
|
|
|
+ {
|
|
|
+ _supportedAuthenticationMethods = supportedAuthenticationMethods;
|
|
|
+ _failedAuthenticationMethods = new List<IAuthenticationMethod>();
|
|
|
+ _authenticationMethodPartialSuccessRegister = new Dictionary<IAuthenticationMethod, int>();
|
|
|
+ }
|
|
|
|
|
|
- for (var i = 0; i < matchingAuthenticationMethods.Count; i++)
|
|
|
+ /// <summary>
|
|
|
+ /// Records a <see cref="AuthenticationResult.Failure"/> authentication attempt for the specified
|
|
|
+ /// <see cref="IAuthenticationMethod"/> .
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="authenticationMethod">An <see cref="IAuthenticationMethod"/> for which to record the result of an authentication attempt.</param>
|
|
|
+ public void RecordFailure(IAuthenticationMethod authenticationMethod)
|
|
|
{
|
|
|
- var authenticationMethod = matchingAuthenticationMethods[i];
|
|
|
+ _failedAuthenticationMethods.Add(authenticationMethod);
|
|
|
+ }
|
|
|
|
|
|
- if (authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod))
|
|
|
+ /// <summary>
|
|
|
+ /// Records a <see cref="AuthenticationResult.PartialSuccess"/> authentication attempt for the specified
|
|
|
+ /// <see cref="IAuthenticationMethod"/> .
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="authenticationMethod">An <see cref="IAuthenticationMethod"/> for which to record the result of an authentication attempt.</param>
|
|
|
+ public void RecordPartialSuccess(IAuthenticationMethod authenticationMethod)
|
|
|
+ {
|
|
|
+ int partialSuccessCount;
|
|
|
+ if (_authenticationMethodPartialSuccessRegister.TryGetValue(authenticationMethod, out partialSuccessCount))
|
|
|
{
|
|
|
- skippedAuthenticationMethods.Add(authenticationMethod);
|
|
|
- continue;
|
|
|
+ _authenticationMethodPartialSuccessRegister[authenticationMethod] = ++partialSuccessCount;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _authenticationMethodPartialSuccessRegister.Add(authenticationMethod, 1);
|
|
|
}
|
|
|
-
|
|
|
- yield return authenticationMethod;
|
|
|
}
|
|
|
|
|
|
- foreach (var authenticationMethod in skippedAuthenticationMethods)
|
|
|
- yield return authenticationMethod;
|
|
|
- }
|
|
|
-
|
|
|
- private class AuthenticationState
|
|
|
- {
|
|
|
- private readonly IList<IAuthenticationMethod> _supportedAuthenticationMethods;
|
|
|
-
|
|
|
- public AuthenticationState(IList<IAuthenticationMethod> supportedAuthenticationMethods)
|
|
|
+ /// <summary>
|
|
|
+ /// Returns the number of times an authentication attempt with the specified <see cref="IAuthenticationMethod"/>
|
|
|
+ /// has resulted in <see cref="AuthenticationResult.PartialSuccess"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="authenticationMethod">An <see cref="IAuthenticationMethod"/>.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// The number of times an authentication attempt with the specified <see cref="IAuthenticationMethod"/>
|
|
|
+ /// has resulted in <see cref="AuthenticationResult.PartialSuccess"/>.
|
|
|
+ /// </returns>
|
|
|
+ public int GetPartialSuccessCount(IAuthenticationMethod authenticationMethod)
|
|
|
{
|
|
|
- _supportedAuthenticationMethods = supportedAuthenticationMethods;
|
|
|
- ExecutedAuthenticationMethods = new List<IAuthenticationMethod>();
|
|
|
- FailedAuthenticationMethods = new List<IAuthenticationMethod>();
|
|
|
+ int partialSuccessCount;
|
|
|
+ if (_authenticationMethodPartialSuccessRegister.TryGetValue(authenticationMethod, out partialSuccessCount))
|
|
|
+ {
|
|
|
+ return partialSuccessCount;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets the list of authentication methods that were previously executed.
|
|
|
+ /// Returns a list of supported authentication methods that match one of the specified allowed authentication
|
|
|
+ /// methods.
|
|
|
/// </summary>
|
|
|
- /// <value>
|
|
|
- /// The list of authentication methods that were previously executed.
|
|
|
- /// </value>
|
|
|
- public IList<IAuthenticationMethod> ExecutedAuthenticationMethods { get; private set; }
|
|
|
+ /// <param name="allowedAuthenticationMethods">A list of allowed authentication methods.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// A list of supported authentication methods that match one of the specified allowed authentication methods.
|
|
|
+ /// </returns>
|
|
|
+ /// <remarks>
|
|
|
+ /// The authentication methods are returned in the order in which they were specified in the list that was
|
|
|
+ /// used to initialize the current <see cref="AuthenticationState"/> instance.
|
|
|
+ /// </remarks>
|
|
|
+ public List<IAuthenticationMethod> GetSupportedAuthenticationMethods(string[] allowedAuthenticationMethods)
|
|
|
+ {
|
|
|
+ var result = new List<IAuthenticationMethod>();
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Gets the list of authentications methods that failed.
|
|
|
- /// </summary>
|
|
|
- /// <value>
|
|
|
- /// The list of authentications methods that failed.
|
|
|
- /// </value>
|
|
|
- public IList<IAuthenticationMethod> FailedAuthenticationMethods { get; private set; }
|
|
|
+ foreach (var supportedAuthenticationMethod in _supportedAuthenticationMethods)
|
|
|
+ {
|
|
|
+ var nameOfSupportedAuthenticationMethod = supportedAuthenticationMethod.Name;
|
|
|
+
|
|
|
+ for (var i = 0; i < allowedAuthenticationMethods.Length; i++)
|
|
|
+ {
|
|
|
+ if (allowedAuthenticationMethods[i] == nameOfSupportedAuthenticationMethod)
|
|
|
+ {
|
|
|
+ result.Add(supportedAuthenticationMethod);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets the list of supported authentication methods.
|
|
|
+ /// Returns the authentication methods from the specified list that have not yet failed.
|
|
|
/// </summary>
|
|
|
- /// <value>
|
|
|
- /// The list of supported authentication methods.
|
|
|
- /// </value>
|
|
|
- public IList<IAuthenticationMethod> SupportedAuthenticationMethods
|
|
|
+ /// <param name="matchingAuthenticationMethods">A list of authentication methods.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// The authentication methods from <paramref name="matchingAuthenticationMethods"/> that have not yet failed.
|
|
|
+ /// </returns>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// This method first returns the authentication methods that have not yet been executed, and only then
|
|
|
+ /// returns those for which an authentication attempt resulted in a <see cref="AuthenticationResult.PartialSuccess"/>.
|
|
|
+ /// </para>
|
|
|
+ /// <para>
|
|
|
+ /// Any <see cref="IAuthenticationMethod"/> that has failed is skipped.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ public IEnumerable<IAuthenticationMethod> GetActiveAuthenticationMethods(List<IAuthenticationMethod> matchingAuthenticationMethods)
|
|
|
{
|
|
|
- get { return _supportedAuthenticationMethods; }
|
|
|
+ var skippedAuthenticationMethods = new List<IAuthenticationMethod>();
|
|
|
+
|
|
|
+ for (var i = 0; i < matchingAuthenticationMethods.Count; i++)
|
|
|
+ {
|
|
|
+ var authenticationMethod = matchingAuthenticationMethods[i];
|
|
|
+
|
|
|
+ // skip authentication methods that have already failed
|
|
|
+ if (_failedAuthenticationMethods.Contains(authenticationMethod))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ // delay use of authentication methods that had a PartialSuccess result
|
|
|
+ if (_authenticationMethodPartialSuccessRegister.ContainsKey(authenticationMethod))
|
|
|
+ {
|
|
|
+ skippedAuthenticationMethods.Add(authenticationMethod);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ yield return authenticationMethod;
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var authenticationMethod in skippedAuthenticationMethods)
|
|
|
+ yield return authenticationMethod;
|
|
|
}
|
|
|
}
|
|
|
}
|