mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 01:05:42 +00:00
Add support for partial success limit to prevent stack overflow on badly behaving servers
Add support for partial success limit to prevent stack overflow on badly behaving servers
This commit is contained in:
@@ -12,9 +12,64 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestInitialize]
|
||||
public void Init()
|
||||
{
|
||||
_clientAuthentication = new ClientAuthentication();
|
||||
_clientAuthentication = new ClientAuthentication(1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ctor_PartialSuccessLimit_Zero()
|
||||
{
|
||||
const int partialSuccessLimit = 0;
|
||||
|
||||
try
|
||||
{
|
||||
new ClientAuthentication(partialSuccessLimit);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual(string.Format("Cannot be less than one.{0}Parameter name: {1}", Environment.NewLine, ex.ParamName), ex.Message);
|
||||
Assert.AreEqual("partialSuccessLimit", ex.ParamName);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ctor_PartialSuccessLimit_Negative()
|
||||
{
|
||||
var partialSuccessLimit = new Random().Next(int.MinValue, -1);
|
||||
|
||||
try
|
||||
{
|
||||
new ClientAuthentication(partialSuccessLimit);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual(string.Format("Cannot be less than one.{0}Parameter name: {1}", Environment.NewLine, ex.ParamName), ex.Message);
|
||||
Assert.AreEqual("partialSuccessLimit", ex.ParamName);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ctor_PartialSuccessLimit_One()
|
||||
{
|
||||
const int partialSuccessLimit = 1;
|
||||
|
||||
var clientAuthentication = new ClientAuthentication(partialSuccessLimit);
|
||||
Assert.AreEqual(partialSuccessLimit, clientAuthentication.PartialSuccessLimit);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ctor_PartialSuccessLimit_MaxValue()
|
||||
{
|
||||
const int partialSuccessLimit = int.MaxValue;
|
||||
|
||||
var clientAuthentication = new ClientAuthentication(partialSuccessLimit);
|
||||
Assert.AreEqual(partialSuccessLimit, clientAuthentication.PartialSuccessLimit);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateShouldThrowArgumentNullExceptionWhenConnectionInfoIsNull()
|
||||
{
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace Renci.SshNet.Tests.Classes
|
||||
internal Mock<IAuthenticationMethod> PasswordAuthenticationMethodMock { get; private set; }
|
||||
internal Mock<IAuthenticationMethod> PublicKeyAuthenticationMethodMock { get; private set; }
|
||||
internal Mock<IAuthenticationMethod> KeyboardInteractiveAuthenticationMethodMock { get; private set; }
|
||||
internal ClientAuthentication ClientAuthentication { get; private set; }
|
||||
|
||||
protected abstract void SetupData();
|
||||
|
||||
protected void CreateMocks()
|
||||
{
|
||||
@@ -27,18 +28,20 @@ namespace Renci.SshNet.Tests.Classes
|
||||
|
||||
protected abstract void SetupMocks();
|
||||
|
||||
protected virtual void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
}
|
||||
|
||||
protected abstract void Act();
|
||||
|
||||
protected override void OnInit()
|
||||
protected sealed override void OnInit()
|
||||
{
|
||||
base.OnInit();
|
||||
|
||||
// Arrange
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
ClientAuthentication = new ClientAuthentication();
|
||||
|
||||
// Act
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o publickey
|
||||
/// o password
|
||||
/// * Partial success limit is 2
|
||||
///
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// +------------------------+------------------------+
|
||||
/// | | |
|
||||
/// password ◄--\ publickey keyboard-interactive
|
||||
/// (7=SKIP) | (2=PS)
|
||||
/// | |
|
||||
/// | password
|
||||
/// | (3=PS)
|
||||
/// | |
|
||||
/// | password
|
||||
/// | (4=PS)
|
||||
/// | |
|
||||
/// | publickey
|
||||
/// | (5=PS)
|
||||
/// | |
|
||||
/// \---- publickey
|
||||
/// (6=SKIP)
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
private SshAuthenticationException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 2;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password", "publickey", "keyboard-interactive"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 2 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 3 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 4 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"publickey"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 5 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"publickey"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 6: Record partial success limit reached exception, and skip password authentication method */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("publickey-partial1");
|
||||
|
||||
/* 7: Record partial success limit reached exception, and skip password authentication method */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("password-partial1");
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
try
|
||||
{
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshAuthenticationException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnPasswordAuthenticationMethodShouldHaveBeenInvokedTwice()
|
||||
{
|
||||
PasswordAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnPublicKeyAuthenticationMethodShouldHaveBeenInvokedTwice()
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateShouldThrowSshAuthenticationException()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.IsNull(_actualException.InnerException);
|
||||
Assert.AreEqual("Reached authentication attempt limit for method (password-partial1).", _actualException.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-13
@@ -8,8 +8,15 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
private SshAuthenticationException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 1;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
@@ -21,23 +28,26 @@ namespace Renci.SshNet.Tests.Classes
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
// obtain name for inclusion in SshAuthenticationException
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
@@ -46,11 +56,18 @@ namespace Renci.SshNet.Tests.Classes
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
try
|
||||
{
|
||||
ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshAuthenticationException ex)
|
||||
|
||||
+25
-10
@@ -8,8 +8,15 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotSupported : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
private SshAuthenticationException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 1;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
@@ -21,16 +28,17 @@ namespace Renci.SshNet.Tests.Classes
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
@@ -39,11 +47,18 @@ namespace Renci.SshNet.Tests.Classes
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
try
|
||||
{
|
||||
ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshAuthenticationException ex)
|
||||
|
||||
+54
-4
@@ -4,9 +4,36 @@ using Moq;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o password
|
||||
/// o publickey
|
||||
/// o keyboard-interactive
|
||||
/// * Partial success limit is 1
|
||||
/// * Scenario:
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// +------------------------------+
|
||||
/// | |
|
||||
/// publickey password
|
||||
/// (2=PARTIAL)
|
||||
/// *----------------------*
|
||||
/// | |
|
||||
/// keyboard-interactive publickey
|
||||
/// (3=SUCCESS)
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 1;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
@@ -18,6 +45,8 @@ namespace Renci.SshNet.Tests.Classes
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
|
||||
@@ -27,29 +56,50 @@ namespace Renci.SshNet.Tests.Classes
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
KeyboardInteractiveAuthenticationMethodMock.Object,
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications).Returns(new[] { "publickey", "password" });
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"publickey", "password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 2 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "keyboard-interactive", "publickey" });
|
||||
.Returns(new[] {"keyboard-interactive", "publickey"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)).Returns(AuthenticationResult.Success);
|
||||
/* 3 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Success);
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o password
|
||||
/// o publickey
|
||||
/// o keyboard-interactive
|
||||
/// * Partial success limit is 2
|
||||
/// * Scenario:
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// +------------------------+
|
||||
/// | |
|
||||
/// publickey keyboard-interactive
|
||||
/// (2=PS) ^ (6=FAIL)
|
||||
/// | |
|
||||
/// password |
|
||||
/// (3=PS) |
|
||||
/// | |
|
||||
/// password |
|
||||
/// (4=PS) |
|
||||
/// | |
|
||||
/// password |
|
||||
/// (5=SKIP) |
|
||||
/// +---------------+
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
private SshAuthenticationException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 2;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PasswordAuthenticationMethodMock.Object,
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
KeyboardInteractiveAuthenticationMethodMock.Object,
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"publickey", "keyboard-interactive"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 2 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 3 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 4 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 5: Record partial success limit reached exception, and skip password authentication method */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("password-partial");
|
||||
|
||||
/* 6 */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive-failure");
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
try
|
||||
{
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshAuthenticationException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateShouldThrowSshAuthenticationException()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.IsNull(_actualException.InnerException);
|
||||
Assert.AreEqual("Permission denied (keyboard-interactive-failure).", _actualException.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o password
|
||||
/// o publickey
|
||||
/// o keyboard-interactive
|
||||
/// * Partial success limit is 2
|
||||
///
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// +-------------------+
|
||||
/// | |
|
||||
/// publickey keyboard-interactive
|
||||
/// (2=PS) ^ (6=PS)
|
||||
/// | | |
|
||||
/// password | +-----------+
|
||||
/// (3=PS) | | |
|
||||
/// | | password publickey
|
||||
/// password | (7=SKIP) (8=FAIL)
|
||||
/// (4=PS) |
|
||||
/// | |
|
||||
/// password |
|
||||
/// (5=SKIP) |
|
||||
/// +------------+
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2 : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
private SshAuthenticationException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 2;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PasswordAuthenticationMethodMock.Object,
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
KeyboardInteractiveAuthenticationMethodMock.Object,
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "publickey", "keyboard-interactive" });
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 2 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 3 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 4 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 5: Record partial success limit reached exception, and skip password authentication method */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("password-partial1");
|
||||
|
||||
/* 6 */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password", "publickey"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 7: Record partial success limit reached exception, and skip password authentication method */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("password-partial2");
|
||||
|
||||
/* 8 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
try
|
||||
{
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshAuthenticationException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnPublicKeyAuthenticationMethodShouldHaveBeenInvokedTwice()
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateShouldThrowSshAuthenticationException()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.IsNull(_actualException.InnerException);
|
||||
Assert.AreEqual("Permission denied (publickey).", _actualException.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o keyboard-interactive
|
||||
/// o password
|
||||
/// o publickey
|
||||
/// * Partial success limit is 2
|
||||
/// * Scenario:
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// password
|
||||
/// (2=PARTIAL)
|
||||
/// |
|
||||
/// +------------------------------+
|
||||
/// | |
|
||||
/// password publickey
|
||||
/// (4=PARTIAL) (3=FAILURE)
|
||||
/// |
|
||||
/// keyboard-interactive
|
||||
/// (5=FAILURE)
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
private SshAuthenticationException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 2;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object,
|
||||
PublicKeyAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
/* 2 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password", "publickey"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
/* 3 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("publickey-failure");
|
||||
|
||||
/* 4 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"keyboard-interactive"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
/* 5 */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive-failure");
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
try
|
||||
{
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshAuthenticationException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateShouldThrowSshAuthenticationException()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.IsNull(_actualException.InnerException);
|
||||
Assert.AreEqual("Permission denied (keyboard-interactive-failure).", _actualException.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o password
|
||||
/// o publickey
|
||||
/// o keyboard-interactive
|
||||
/// * Partial success limit is 2
|
||||
///
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// +-------------------+
|
||||
/// | |
|
||||
/// publickey keyboard-interactive
|
||||
/// (2=PS) ^ (6=PS)
|
||||
/// | | |
|
||||
/// password | +-----------+
|
||||
/// (3=PS) | | |
|
||||
/// | | password publickey
|
||||
/// password | (7=SKIP) (8=SUCCESS)
|
||||
/// (4=PS) |
|
||||
/// | |
|
||||
/// password |
|
||||
/// (5=SKIP) |
|
||||
/// +------------+
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
private SshAuthenticationException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 2;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PasswordAuthenticationMethodMock.Object,
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
KeyboardInteractiveAuthenticationMethodMock.Object,
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"publickey", "keyboard-interactive"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 2 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 3 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 4 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 5: Record partial success limit reached exception, and skip password authentication method */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("password-partial1");
|
||||
|
||||
/* 6 */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password", "publickey"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
|
||||
/* 7: Record partial success limit reached exception, and skip password authentication method */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("password-partial2");
|
||||
|
||||
/* 8 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Success);
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnPublicKeyAuthenticationMethodShouldHaveBeenInvokedTwice()
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o keyboard-interactive
|
||||
/// o password
|
||||
/// o publickey
|
||||
/// * Partial success limit is 2
|
||||
/// * Scenario:
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// password
|
||||
/// (2=PARTIAL)
|
||||
/// |
|
||||
/// +------------------------------+
|
||||
/// | |
|
||||
/// password publickey
|
||||
/// (4=PARTIAL) (3=FAILURE)
|
||||
/// |
|
||||
/// keyboard-interactive
|
||||
/// (5=FAILURE)
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 2;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object,
|
||||
PublicKeyAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
/* 2 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password", "publickey"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
/* 3 */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("publickey-failure");
|
||||
|
||||
/* 4 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"keyboard-interactive"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
/* 5 */
|
||||
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Success);
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateOnKeyboardInteractiveAuthenticationMethodShouldHaveBeenInvokedOnce()
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
-18
@@ -7,6 +7,14 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_PostponePartialAccessAuthenticationMethod : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 3;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
@@ -16,42 +24,59 @@ namespace Renci.SshNet.Tests.Classes
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object,
|
||||
PublicKeyAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
KeyboardInteractiveAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object,
|
||||
PublicKeyAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications).Returns(new[] { "password" });
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password", "publickey" });
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password", "publickey"});
|
||||
KeyboardInteractiveAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("keyboard-interactive");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)).Returns(AuthenticationResult.Failure);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)).Returns(AuthenticationResult.Success);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Name)
|
||||
.Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Success);
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-1
@@ -7,6 +7,14 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 1;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
@@ -53,9 +61,16 @@ namespace Renci.SshNet.Tests.Classes
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-1
@@ -7,6 +7,14 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_MultiList_SkipFailedAuthenticationMethod : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 1;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
@@ -47,9 +55,16 @@ namespace Renci.SshNet.Tests.Classes
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+62
-13
@@ -4,9 +4,33 @@ using Moq;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o keyboard-interactive
|
||||
/// o password
|
||||
/// o publickey
|
||||
/// * Partial success limit is 2
|
||||
/// * Scenario:
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// password
|
||||
/// (2=PARTIAL)
|
||||
/// |
|
||||
/// password
|
||||
/// (3=SUCCESS)
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 2;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
@@ -18,36 +42,61 @@ namespace Renci.SshNet.Tests.Classes
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications).Returns(new[] { "password" });
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
/* 2 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)).Returns(AuthenticationResult.Success);
|
||||
/* 3 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Success);
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
ClientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// * ConnectionInfo provides the following authentication methods (in order):
|
||||
/// o publickey
|
||||
/// o password
|
||||
/// * Partial success limit is 3
|
||||
/// * Scenario:
|
||||
/// none
|
||||
/// (1=FAIL)
|
||||
/// |
|
||||
/// password
|
||||
/// (2=PARTIAL)
|
||||
/// |
|
||||
/// password
|
||||
/// (3=PARTIAL)
|
||||
/// |
|
||||
/// password
|
||||
/// (4=PARTIAL)
|
||||
/// |
|
||||
/// password
|
||||
/// (5=SKIP)
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached : ClientAuthenticationTestBase
|
||||
{
|
||||
private int _partialSuccessLimit;
|
||||
private ClientAuthentication _clientAuthentication;
|
||||
private SshAuthenticationException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
_partialSuccessLimit = 3;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.RegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
|
||||
ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod())
|
||||
.Returns(NoneAuthenticationMethodMock.Object);
|
||||
|
||||
/* 1 */
|
||||
NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.Failure);
|
||||
ConnectionInfoMock.InSequence(seq)
|
||||
.Setup(p => p.AuthenticationMethods)
|
||||
.Returns(new List<IAuthenticationMethod>
|
||||
{
|
||||
PublicKeyAuthenticationMethodMock.Object,
|
||||
PasswordAuthenticationMethodMock.Object
|
||||
});
|
||||
NoneAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 2 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] {"password"});
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 3 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 4 */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.Authenticate(SessionMock.Object))
|
||||
.Returns(AuthenticationResult.PartialSuccess);
|
||||
PasswordAuthenticationMethodMock.InSequence(seq)
|
||||
.Setup(p => p.AllowedAuthentications)
|
||||
.Returns(new[] { "password" });
|
||||
|
||||
/* Enumerate supported authentication methods */
|
||||
|
||||
PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey");
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password");
|
||||
|
||||
/* 5: Record partial success limit reached exception, and skip password authentication method */
|
||||
|
||||
PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("x_password_x");
|
||||
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS"));
|
||||
SessionMock.InSequence(seq).Setup(p => p.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER"));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_clientAuthentication = new ClientAuthentication(_partialSuccessLimit);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
try
|
||||
{
|
||||
_clientAuthentication.Authenticate(ConnectionInfoMock.Object, SessionMock.Object);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshAuthenticationException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AuthenticateShouldThrowSshAuthenticationException()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.IsNull(_actualException.InnerException);
|
||||
Assert.AreEqual("Reached authentication attempt limit for method (x_password_x).",_actualException.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateClientAuthentication
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private IClientAuthentication _actual;
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateClientAuthentication();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateClientAuthenticationShouldNotReturnNull()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ClientAuthenticationShouldHavePartialSuccessLimitOf5()
|
||||
{
|
||||
var clientAuthentication = _actual as ClientAuthentication;
|
||||
Assert.IsNotNull(clientAuthentication);
|
||||
Assert.AreEqual(5, clientAuthentication.PartialSuccessLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -90,6 +90,5 @@ namespace Renci.SshNet.Tests.Classes
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,11 +139,18 @@
|
||||
<Compile Include="Classes\ClientAuthenticationTestBase.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_PostponePartialAccessAuthenticationMethod.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_SkipFailedAuthenticationMethod.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs" />
|
||||
<Compile Include="Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs" />
|
||||
<Compile Include="Classes\Common\CountdownEventTest.cs" />
|
||||
<Compile Include="Classes\Common\CountdownEventTest_Dispose_NotSet.cs" />
|
||||
<Compile Include="Classes\Common\CountdownEventTest_Dispose_Set.cs" />
|
||||
@@ -263,6 +270,7 @@
|
||||
<Compile Include="Classes\Common\PortForwardEventArgsTest.cs" />
|
||||
<Compile Include="Classes\Compression\ZlibTest.cs" />
|
||||
<Compile Include="Classes\ConnectionInfoTest.cs" />
|
||||
<Compile Include="Classes\ServiceFactoryTest_CreateClientAuthentication.cs" />
|
||||
<Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_EndLStatThrowsSshException.cs" />
|
||||
<Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsAlmostSixTimesGreaterThanChunkSize.cs" />
|
||||
<Compile Include="Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsEqualToChunkSize.cs" />
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
var result = new List<IAuthenticationMethod>();
|
||||
|
||||
foreach (var supportedAuthenticationMethod in authenticationState.SupportedAuthenticationMethods)
|
||||
{
|
||||
var nameOfSupportedAuthenticationMethod = supportedAuthenticationMethod.Name;
|
||||
|
||||
for (var i = 0; i < allowedAuthenticationMethods.Length; i++)
|
||||
{
|
||||
if (allowedAuthenticationMethods[i] == nameOfSupportedAuthenticationMethod)
|
||||
{
|
||||
result.Add(supportedAuthenticationMethod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<IAuthenticationMethod> GetOrderedAuthenticationMethods(AuthenticationState authenticationState, List<IAuthenticationMethod> matchingAuthenticationMethods)
|
||||
{
|
||||
var skippedAuthenticationMethods = new List<IAuthenticationMethod>();
|
||||
|
||||
for (var i = 0; i < matchingAuthenticationMethods.Count; i++)
|
||||
{
|
||||
var authenticationMethod = matchingAuthenticationMethods[i];
|
||||
|
||||
if (authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod))
|
||||
{
|
||||
skippedAuthenticationMethods.Add(authenticationMethod);
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return authenticationMethod;
|
||||
}
|
||||
|
||||
foreach (var authenticationMethod in skippedAuthenticationMethods)
|
||||
yield return authenticationMethod;
|
||||
}
|
||||
|
||||
private class AuthenticationState
|
||||
{
|
||||
private readonly IList<IAuthenticationMethod> _supportedAuthenticationMethods;
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the list of authentications methods that failed.
|
||||
/// </summary>
|
||||
private readonly List<IAuthenticationMethod> _failedAuthenticationMethods;
|
||||
|
||||
public AuthenticationState(IList<IAuthenticationMethod> supportedAuthenticationMethods)
|
||||
{
|
||||
_supportedAuthenticationMethods = supportedAuthenticationMethods;
|
||||
ExecutedAuthenticationMethods = new List<IAuthenticationMethod>();
|
||||
FailedAuthenticationMethods = new List<IAuthenticationMethod>();
|
||||
_failedAuthenticationMethods = new List<IAuthenticationMethod>();
|
||||
_authenticationMethodPartialSuccessRegister = new Dictionary<IAuthenticationMethod, int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of authentication methods that were previously executed.
|
||||
/// Records a <see cref="AuthenticationResult.Failure"/> authentication attempt for the specified
|
||||
/// <see cref="IAuthenticationMethod"/> .
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The list of authentication methods that were previously executed.
|
||||
/// </value>
|
||||
public IList<IAuthenticationMethod> ExecutedAuthenticationMethods { get; private set; }
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of supported authentication methods.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The list of supported authentication methods.
|
||||
/// </value>
|
||||
public IList<IAuthenticationMethod> SupportedAuthenticationMethods
|
||||
/// <param name="authenticationMethod">An <see cref="IAuthenticationMethod"/> for which to record the result of an authentication attempt.</param>
|
||||
public void RecordFailure(IAuthenticationMethod authenticationMethod)
|
||||
{
|
||||
get { return _supportedAuthenticationMethods; }
|
||||
_failedAuthenticationMethods.Add(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))
|
||||
{
|
||||
_authenticationMethodPartialSuccessRegister[authenticationMethod] = ++partialSuccessCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
_authenticationMethodPartialSuccessRegister.Add(authenticationMethod, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
int partialSuccessCount;
|
||||
if (_authenticationMethodPartialSuccessRegister.TryGetValue(authenticationMethod, out partialSuccessCount))
|
||||
{
|
||||
return partialSuccessCount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of supported authentication methods that match one of the specified allowed authentication
|
||||
/// methods.
|
||||
/// </summary>
|
||||
/// <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>();
|
||||
|
||||
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>
|
||||
/// Returns the authentication methods from the specified list that have not yet failed.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ namespace Renci.SshNet
|
||||
/// </summary>
|
||||
internal partial class ServiceFactory : IServiceFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines 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>
|
||||
private static int PartialSuccessLimit = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IClientAuthentication"/>.
|
||||
/// </summary>
|
||||
@@ -23,7 +29,7 @@ namespace Renci.SshNet
|
||||
/// </returns>
|
||||
public IClientAuthentication CreateClientAuthentication()
|
||||
{
|
||||
return new ClientAuthentication();
|
||||
return new ClientAuthentication(PartialSuccessLimit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user