Преглед изворни кода

Improve tests for partial success limit.

Gert Driesen пре 8 година
родитељ
комит
c2e1de507d
12 измењених фајлова са 609 додато и 112 уклоњено
  1. 190 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs
  2. 9 8
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs
  3. 0 14
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs
  4. 38 3
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs
  5. 15 15
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch.cs
  6. 1 1
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs
  7. 38 23
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs
  8. 185 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs
  9. 39 20
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs
  10. 27 0
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs
  11. 65 27
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs
  12. 2 1
      src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

+ 190 - 0
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
+{
+    /// <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);
+        }
+    }
+}

+ 9 - 8
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs

@@ -28,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);
+            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
-                });
+                              .Returns(new List<IAuthenticationMethod>
+                                  {
+                                      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");
 

+ 0 - 14
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.cs

@@ -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();
-        }
-    }
-}

+ 38 - 3
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_DifferentAllowedAuthenticationsAfterPartialSuccess.cs

@@ -4,6 +4,25 @@ 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
     {
@@ -26,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)
@@ -35,20 +56,34 @@ 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"));

+ 15 - 15
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)            |
+    ///             +---------------+
     /// </summary>
     [TestClass]
     public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch : ClientAuthenticationTestBase

+ 1 - 1
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInAlternateBranch2.cs

@@ -145,7 +145,7 @@ namespace Renci.SshNet.Tests.Classes
 
             PasswordAuthenticationMethodMock.InSequence(seq)
                                             .Setup(p => p.Name)
-                                            .Returns("password-partial1");
+                                            .Returns("password-partial2");
 
             /* 8 */
 

+ 38 - 23
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch.cs

@@ -6,29 +6,25 @@ using Renci.SshNet.Common;
 namespace Renci.SshNet.Tests.Classes
 {
     /// <summary>
-    /// 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)
     /// </summary>
     [TestClass]
     public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedByFailureInSameBranch : ClientAuthenticationTestBase
@@ -53,6 +49,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)
@@ -67,9 +65,14 @@ namespace Renci.SshNet.Tests.Classes
                                         .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 @@ namespace Renci.SshNet.Tests.Classes
                                             .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 @@ namespace Renci.SshNet.Tests.Classes
                                              .Setup(p => p.Name)
                                              .Returns("publickey-failure");
 
+            /* 4 */
+
             PasswordAuthenticationMethodMock.InSequence(seq)
                                             .Setup(p => p.Authenticate(SessionMock.Object))
                                             .Returns(AuthenticationResult.PartialSuccess);
@@ -94,9 +104,14 @@ namespace Renci.SshNet.Tests.Classes
                                             .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);

+ 185 - 0
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
+{
+    /// <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));
+        }
+    }
+}

+ 39 - 20
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch.cs

@@ -5,25 +5,25 @@ using Moq;
 namespace Renci.SshNet.Tests.Classes
 {
     /// <summary>
-    /// 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)
     /// </summary>
     [TestClass]
     public class ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInSameBranch : ClientAuthenticationTestBase
@@ -47,6 +47,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)
@@ -61,9 +63,14 @@ namespace Renci.SshNet.Tests.Classes
                                         .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 @@ namespace Renci.SshNet.Tests.Classes
                                             .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 @@ namespace Renci.SshNet.Tests.Classes
                                              .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);

+ 27 - 0
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs

@@ -4,6 +4,22 @@ 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
     {
@@ -26,6 +42,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)
@@ -39,18 +57,27 @@ namespace Renci.SshNet.Tests.Classes
                                         .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);

+ 65 - 27
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached.cs

@@ -6,20 +6,25 @@ using Renci.SshNet.Common;
 namespace Renci.SshNet.Tests.Classes
 {
     /// <summary>
-    /// 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)
     /// </summary>
     [TestClass]
     public class ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess_PartialSuccessLimitReached : ClientAuthenticationTestBase
@@ -44,6 +49,7 @@ 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)
@@ -57,23 +63,55 @@ namespace Renci.SshNet.Tests.Classes
                                         .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"));

+ 2 - 1
src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -139,11 +139,12 @@
     <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_Success_MultiList_AllAllowedAuthenticationsHaveReachedPartialSuccessLimit.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" />