From c2e1de507de42d236173692ae36a11c9405a43e9 Mon Sep 17 00:00:00 2001 From: Gert Driesen Date: Thu, 5 Oct 2017 21:44:32 +0200 Subject: [PATCH] Improve tests for partial success limit. --- ...ticationsHaveReachedPartialSuccessLimit.cs | 190 ++++++++++++++++++ ...eList_AuthenticationMethodNotConfigured.cs | 17 +- ...ticationsHaveReachedPartialSuccessLimit.cs | 14 -- ...lowedAuthenticationsAfterPartialSuccess.cs | 41 +++- ...achedFollowedByFailureInAlternateBranch.cs | 30 +-- ...chedFollowedByFailureInAlternateBranch2.cs | 2 +- ...mitReachedFollowedByFailureInSameBranch.cs | 61 +++--- ...achedFollowedBySuccessInAlternateBranch.cs | 185 +++++++++++++++++ ...mitReachedFollowedBySuccessInSameBranch.cs | 59 ++++-- ...llowedAuthenticationAfterPartialSuccess.cs | 27 +++ ...rtialSuccess_PartialSuccessLimitReached.cs | 92 ++++++--- .../Renci.SshNet.Tests.csproj | 3 +- 12 files changed, 609 insertions(+), 112 deletions(-) create mode 100644 src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs delete mode 100644 src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs create mode 100644 src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs new file mode 100644 index 000000000..bcf93a91f --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs @@ -0,0 +1,190 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; + +namespace Renci.SshNet.Tests.Classes +{ + /// + /// * 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) + /// + [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 + { + 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); + } + } +} \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs index 9a8921be9..72a9da49a 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs @@ -28,16 +28,17 @@ protected override void SetupMocks() ConnectionInfoMock.InSequence(seq).Setup(p => p.CreateNoneAuthenticationMethod()) .Returns(NoneAuthenticationMethodMock.Object); - NoneAuthenticationMethodMock.InSequence(seq).Setup(p => p.Authenticate(SessionMock.Object)) - .Returns(AuthenticationResult.Failure); + NoneAuthenticationMethodMock.InSequence(seq) + .Setup(p => p.Authenticate(SessionMock.Object)) + .Returns(AuthenticationResult.Failure); ConnectionInfoMock.InSequence(seq).Setup(p => p.AuthenticationMethods) - .Returns(new List - { - PublicKeyAuthenticationMethodMock.Object - }); + .Returns(new List + { + PublicKeyAuthenticationMethodMock.Object + }); NoneAuthenticationMethodMock.InSequence(seq) - .Setup(p => p.AllowedAuthentications) - .Returns(new[] { "password" }); + .Setup(p => p.AllowedAuthentications) + .Returns(new[] {"password"}); PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey"); diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs deleted file mode 100644 index 79797bf03..000000000 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Renci.SshNet.Tests.Classes -{ - [TestClass] - public class ClientAuthenticationTest_Success_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit - { - [TestMethod] - public void Test() - { - Assert.Fail(); - } - } -} diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs index 552f47bda..0f21f0bf5 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs @@ -4,6 +4,25 @@ namespace Renci.SshNet.Tests.Classes { + /// + /// * 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) + /// [TestClass] public class ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess : ClientAuthenticationTestBase { @@ -26,6 +45,8 @@ protected override void SetupMocks() 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) @@ -35,20 +56,34 @@ protected override void SetupMocks() 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")); diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs index 964fec71e..f4c49023e 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs @@ -11,24 +11,24 @@ namespace Renci.SshNet.Tests.Classes /// 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) | - /// +------------+ + /// +------------------------+ + /// | | + /// publickey keyboard-interactive + /// (2=PS) ^ (6=FAIL) + /// | | + /// password | + /// (3=PS) | + /// | | + /// password | + /// (4=PS) | + /// | | + /// password | + /// (5=SKIP) | + /// +---------------+ /// [TestClass] public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch : ClientAuthenticationTestBase diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs index f61b735f4..e8c0e4745 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs @@ -145,7 +145,7 @@ protected override void SetupMocks() PasswordAuthenticationMethodMock.InSequence(seq) .Setup(p => p.Name) - .Returns("password-partial1"); + .Returns("password-partial2"); /* 8 */ diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs index 0358c731a..be985a702 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs @@ -6,29 +6,25 @@ namespace Renci.SshNet.Tests.Classes { /// - /// ConnectionInfo provides 'keyboard-interactive', 'password', 'publickey' and authentication methods, and partial - /// success limit is set to 2. - /// - /// Authentication proceeds as follows: - /// - /// 1 x * Client performs 'none' authentication attempt. - /// * Server responds with 'failure', and 'password' allowed authentication method. - /// - /// 1 x * Client performs 'password' authentication attempt. - /// * Server responds with 'partial success', and 'password' & 'publickey' allowed authentication methods. - /// - /// 1 x * Client performs 'publickey' authentication attempt. - /// * Server responds with 'failure'. - /// - /// 1 x * Client performs 'password' authentication attempt. - /// * Server responds with 'partial success', and 'keyboard-interactive' allowed authentication methods. - /// - /// 1 x * Client performs 'keyboard-interactive' authentication attempt. - /// * Server responds with 'failure'. - /// - /// Since the server only ever allowed the 'password' authentication method, there are no - /// authentication methods left to try after reaching the partial success limit for 'password' - /// and as such authentication fails. + /// * 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) /// [TestClass] public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch : ClientAuthenticationTestBase @@ -53,6 +49,8 @@ protected override void SetupMocks() 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) @@ -67,9 +65,14 @@ protected override void SetupMocks() .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); @@ -77,9 +80,14 @@ protected override void SetupMocks() .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); @@ -87,6 +95,8 @@ protected override void SetupMocks() .Setup(p => p.Name) .Returns("publickey-failure"); + /* 4 */ + PasswordAuthenticationMethodMock.InSequence(seq) .Setup(p => p.Authenticate(SessionMock.Object)) .Returns(AuthenticationResult.PartialSuccess); @@ -94,9 +104,14 @@ protected override void SetupMocks() .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); diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs new file mode 100644 index 000000000..8c6413ef0 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs @@ -0,0 +1,185 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; + +namespace Renci.SshNet.Tests.Classes +{ + /// + /// * 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) | + /// +------------+ + /// + [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 + { + 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)); + } + } +} \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs index 35288a781..228ad23f5 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs @@ -5,25 +5,25 @@ namespace Renci.SshNet.Tests.Classes { /// - /// ConnectionInfo provides 'keyboard-interactive', 'password', 'publickey' and authentication methods, and partial - /// success limit is set to 2. - /// - /// Authentication proceeds as follows: - /// - /// 1 x * Client performs 'none' authentication attempt. - /// * Server responds with 'failure', and 'password' allowed authentication method. - /// - /// 1 x * Client performs 'password' authentication attempt. - /// * Server responds with 'partial success', and 'password' & 'publickey' allowed authentication methods. - /// - /// 1 x * Client performs 'publickey' authentication attempt. - /// * Server responds with 'failure'. - /// - /// 1 x * Client performs 'password' authentication attempt. - /// * Server responds with 'partial success', and 'keyboard-interactive' allowed authentication methods. - /// - /// 1 x * Client performs 'keyboard-interactive' authentication attempt. - /// * Server responds with 'success'. + /// * 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) /// [TestClass] public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch : ClientAuthenticationTestBase @@ -47,6 +47,8 @@ protected override void SetupMocks() 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) @@ -61,9 +63,14 @@ protected override void SetupMocks() .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); @@ -71,9 +78,14 @@ protected override void SetupMocks() .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); @@ -81,16 +93,23 @@ protected override void SetupMocks() .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" }); + .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); diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs index 4aff4ab39..e3f8ff3fb 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs @@ -4,6 +4,22 @@ namespace Renci.SshNet.Tests.Classes { + /// + /// * 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) + /// [TestClass] public class ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess : ClientAuthenticationTestBase { @@ -26,6 +42,8 @@ protected override void SetupMocks() 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) @@ -39,18 +57,27 @@ protected override void SetupMocks() .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.Success); diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs index 005968a1c..74261dc10 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs @@ -6,20 +6,25 @@ namespace Renci.SshNet.Tests.Classes { /// - /// ConnectionInfo provides 'password' and 'publickey' authentication methods, and partial success limit is - /// set to 3. - /// - /// Authentication proceeds as follows: - /// - /// 1 x * Client performs 'none' authentication attempt. - /// * Server responds with 'failure', and 'password' allowed authentication method. - /// - /// 3 x * Client performs 'password' authentication attempt. - /// * Server responds with 'partial success', and 'password' allowed authentication method - /// - /// Since the server only ever allowed the 'password' authentication method, there are no - /// authentication methods left to try after reaching the partial success limit for 'password' - /// and as such authentication fails. + /// * 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) /// [TestClass] public class ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached : ClientAuthenticationTestBase @@ -44,6 +49,7 @@ protected override void SetupMocks() 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) @@ -57,23 +63,55 @@ protected override void SetupMocks() .Setup(p => p.AllowedAuthentications) .Returns(new[] { "password" }); - for (var i = 0; i < _partialSuccessLimit; i++) - { - 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"}); - } + /* Enumerate supported authentication methods */ PublicKeyAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("publickey"); PasswordAuthenticationMethodMock.InSequence(seq).Setup(p => p.Name).Returns("password"); - // used to construct exception message + /* 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")); diff --git a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj index 393a110ee..61df8cdc3 100644 --- a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj +++ b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj @@ -139,11 +139,12 @@ - + +