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:
Gert Driesen
2017-10-05 22:02:15 +02:00
committed by GitHub
21 changed files with 1755 additions and 156 deletions
@@ -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();
}
@@ -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);
}
}
}
@@ -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)
@@ -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)
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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));
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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" />
+184 -85
View File
@@ -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;
}
}
}
+7 -1
View File
@@ -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>