diff options
author | Filip Navara <navara@emclient.com> | 2022-06-16 09:45:07 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-16 09:45:07 +0300 |
commit | 3c33b4293107c607ae8669545114d6f9891432cb (patch) | |
tree | 48bb56dbc737c96c6165d92c177d13d5cf8b7e89 | |
parent | 24bbfbfb925d1d71d6c2f52d521aab00c65446a9 (diff) |
Add end-to-end test for NTLM/Negotiate authentication against fake server (#70630)
* Add end-to-end test for NTLM/Negotiate authentication against fake server
* Simplify the test since HttpClientHandler is always SocketsHttpHandler for the test environment
* Fix test condition
* Remove extra comment
-rw-r--r-- | src/libraries/Common/tests/System/Net/Security/FakeNegotiateServer.cs (renamed from src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNegotiateServer.cs) | 0 | ||||
-rw-r--r-- | src/libraries/Common/tests/System/Net/Security/FakeNtlmServer.cs (renamed from src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNtlmServer.cs) | 0 | ||||
-rw-r--r-- | src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs | 140 | ||||
-rw-r--r-- | src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj | 12 | ||||
-rw-r--r-- | src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj | 6 |
5 files changed, 156 insertions, 2 deletions
diff --git a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNegotiateServer.cs b/src/libraries/Common/tests/System/Net/Security/FakeNegotiateServer.cs index 91149e7779f..91149e7779f 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNegotiateServer.cs +++ b/src/libraries/Common/tests/System/Net/Security/FakeNegotiateServer.cs diff --git a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNtlmServer.cs b/src/libraries/Common/tests/System/Net/Security/FakeNtlmServer.cs index e4cfba2e544..e4cfba2e544 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNtlmServer.cs +++ b/src/libraries/Common/tests/System/Net/Security/FakeNtlmServer.cs diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs new file mode 100644 index 00000000000..881555d67ff --- /dev/null +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using System.Net.Security; +using System.Net.Test.Common; +using System.Security.Principal; +using System.Threading.Tasks; + +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Http.Functional.Tests +{ + public partial class NtAuthTests : IClassFixture<NtAuthServers> + { + public static bool IsNtlmAvailable => + Capability.IsNtlmInstalled() || OperatingSystem.IsAndroid() || OperatingSystem.IsTvOS(); + + private static NetworkCredential s_testCredentialRight = new NetworkCredential("rightusername", "rightpassword"); + + internal static async Task HandleAuthenticationRequestWithFakeServer(LoopbackServer.Connection connection, bool useNtlm) + { + HttpRequestData request = await connection.ReadRequestDataAsync(); + FakeNtlmServer? fakeNtlmServer = null; + FakeNegotiateServer? fakeNegotiateServer = null; + string authHeader = null; + + foreach (HttpHeaderData header in request.Headers) + { + if (header.Name == "Authorization") + { + authHeader = header.Value; + break; + } + } + + if (string.IsNullOrEmpty(authHeader)) + { + // This is initial request, we reject with showing supported mechanisms. + if (useNtlm) + { + authHeader = "WWW-Authenticate: NTLM\r\n"; + } + else + { + authHeader = "WWW-Authenticate: Negotiate\r\n"; + } + + await connection.SendResponseAsync(HttpStatusCode.Unauthorized, authHeader).ConfigureAwait(false); + connection.CompleteRequestProcessing(); + + // Read next requests and fall-back to loop bellow to process it. + request = await connection.ReadRequestDataAsync(); + } + + bool isAuthenticated = false; + do + { + foreach (HttpHeaderData header in request.Headers) + { + if (header.Name == "Authorization") + { + authHeader = header.Value; + break; + } + } + + Assert.NotNull(authHeader); + var tokens = authHeader.Split(' ', 2, StringSplitOptions.TrimEntries); + // Should be type and base64 encoded blob + Assert.Equal(2, tokens.Length); + + if (fakeNtlmServer == null) + { + fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight) { ForceNegotiateVersion = true }; + if (!useNtlm) + { + fakeNegotiateServer = new FakeNegotiateServer(fakeNtlmServer); + } + } + + byte[]? outBlob; + + if (fakeNegotiateServer != null) + { + outBlob = fakeNegotiateServer.GetOutgoingBlob(Convert.FromBase64String(tokens[1])); + isAuthenticated = fakeNegotiateServer.IsAuthenticated; + } + else + { + outBlob = fakeNtlmServer.GetOutgoingBlob(Convert.FromBase64String(tokens[1])); + isAuthenticated = fakeNtlmServer.IsAuthenticated; + } + + if (outBlob != null) + { + authHeader = $"WWW-Authenticate: {tokens[0]} {Convert.ToBase64String(outBlob)}\r\n"; + await connection.SendResponseAsync(isAuthenticated ? HttpStatusCode.OK : HttpStatusCode.Unauthorized, authHeader); + connection.CompleteRequestProcessing(); + + if (!isAuthenticated) + { + request = await connection.ReadRequestDataAsync(); + } + } + } + while (!isAuthenticated); + + await connection.SendResponseAsync(HttpStatusCode.OK); + } + + [ConditionalTheory(nameof(IsNtlmAvailable))] + [InlineData(true)] + [InlineData(false)] + public async Task DefaultHandler_FakeServer_Success(bool useNtlm) + { + await LoopbackServer.CreateClientAndServerAsync( + async uri => + { + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, uri); + requestMessage.Version = new Version(1, 1); + + HttpMessageHandler handler = new HttpClientHandler() { Credentials = s_testCredentialRight }; + using (var client = new HttpClient(handler)) + { + HttpResponseMessage response = await client.SendAsync(requestMessage); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }, + async server => + { + await server.AcceptConnectionAsync(async connection => + { + await HandleAuthenticationRequestWithFakeServer(connection, useNtlm); + }).ConfigureAwait(false); + }); + } + } +} diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index 59a84cb1826..e5da76698df 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -352,6 +352,7 @@ Link="Common\System\Net\Http\HttpClientHandlerTest.Decompression.cs" /> <Compile Include="HttpClientMiniStressTest.cs" /> <Compile Include="NtAuthTests.cs" /> + <Compile Include="NtAuthTests.FakeServer.cs" /> <Compile Include="ReadOnlyMemoryContentTest.cs" /> <Compile Include="SocketsHttpHandlerTest.cs" /> <Compile Include="$(CommonTestPath)System\Net\Http\Http2Frames.cs" @@ -379,6 +380,17 @@ <Compile Include="LoopbackSocksServer.cs" /> <Compile Include="SocksProxyTest.cs" /> </ItemGroup> + <!-- NTLM/Negotiate authentication fakes --> + <ItemGroup> + <Compile Include="$(CommonPath)System\Net\Security\MD4.cs" + Link="Common\System\Net\Security\MD4.cs" /> + <Compile Include="$(CommonPath)System\Net\Security\RC4.cs" + Link="Common\System\Net\Security\RC4.cs" /> + <Compile Include="$(CommonTestPath)System\Net\Security\FakeNtlmServer.cs" + Link="Common\System\Net\Security\FakeNtlmServer.cs" /> + <Compile Include="$(CommonTestPath)System\Net\Security\FakeNegotiateServer.cs" + Link="Common\System\Net\Security\FakeNegotiateServer.cs" /> + </ItemGroup> <ItemGroup> <EmbeddedResource Include="SelectedSitesTest.txt"> <Link>SelectedSitesTest.txt</Link> diff --git a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj index 55df154298e..3f61524d58c 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj @@ -30,8 +30,10 @@ <!-- Fakes --> <Compile Include="Fakes\FakeSslStream.Implementation.cs" /> <Compile Include="Fakes\FakeAuthenticatedStream.cs" /> - <Compile Include="Fakes\FakeNtlmServer.cs" /> - <Compile Include="Fakes\FakeNegotiateServer.cs" /> + <Compile Include="$(CommonTestPath)System\Net\Security\FakeNtlmServer.cs" + Link="Common\System\Net\Security\FakeNtlmServer.cs" /> + <Compile Include="$(CommonTestPath)System\Net\Security\FakeNegotiateServer.cs" + Link="Common\System\Net\Security\FakeNegotiateServer.cs" /> <!-- Common test files --> <Compile Include="$(CommonPath)DisableRuntimeMarshalling.cs" Link="Common\DisableRuntimeMarshalling.cs" /> |