if (string.IsNullOrEmpty(username))
return false;
}
- if (trimmedValue.Contains(nameof(userhash)) && trimmedValue.Contains("true"))
+ else if (trimmedValue.Contains(nameof(userhash)) && trimmedValue.Contains("true"))
{
userhash = true;
}
startIndex += 1;
algorithm = trimmedValue.Substring(startIndex, trimmedValue.Length - startIndex).Trim();
}
-
- if (string.IsNullOrEmpty(algorithm))
- algorithm = "sha-256";
}
else if (trimmedValue.Contains(nameof(opaque)))
{
}
else if (trimmedValue.Contains(nameof(qop)))
{
- int startIndex = trimmedValue.IndexOf('=');
+ int startIndex = trimmedValue.IndexOf('"');
if (startIndex != -1)
{
startIndex += 1;
+ qop = trimmedValue.Substring(startIndex, trimmedValue.Length - startIndex - 1);
+ }
+ else if ((startIndex = trimmedValue.IndexOf('=')) != -1)
+ {
+ startIndex += 1;
qop = trimmedValue.Substring(startIndex, trimmedValue.Length - startIndex).Trim();
}
}
return false;
}
+ if (string.IsNullOrEmpty(algorithm))
+ algorithm = "sha-256";
+
// Calculate response and compare with the client response hash.
string a1 = options.Username + ":" + realm + ":" + options.Password;
if (algorithm.Contains("sess"))
{
- a1 = ComputeHash(a1, algorithm) + ":" + nonce + ":" + cnonce ?? string.Empty;
+ a1 = ComputeHash(a1, algorithm) + ":" + nonce;
+
+ if (cnonce != null)
+ a1 += ":" + cnonce;
}
string a2 = requestMethod + ":" + uri;
- if (qop.Equals("auth-int"))
+ if (!string.IsNullOrEmpty(qop) && qop.Equals("auth-int"))
{
string content = requestContent ?? string.Empty;
a2 = a2 + ":" + ComputeHash(content, algorithm);
}
- string serverResponseHash = ComputeHash(ComputeHash(a1, algorithm) + ":" +
- nonce + ":" +
- nc + ":" +
- cnonce + ":" +
- qop + ":" +
- ComputeHash(a2, algorithm), algorithm);
+ string serverResponseHash = ComputeHash(a1, algorithm) + ":" + nonce + ":";
+
+ if (nc != null)
+ serverResponseHash += nc + ":";
+
+ if (cnonce != null)
+ serverResponseHash += cnonce + ":";
+
+ if (qop != null)
+ serverResponseHash += qop + ":";
+
+ serverResponseHash += ComputeHash(a2, algorithm);
+ serverResponseHash = ComputeHash(serverResponseHash, algorithm);
return response == serverResponseHash;
}
Stream stream = new NetworkStream(s, ownsSocket: false);
if (_options.UseSsl)
{
- var sslStream = new SslStream(stream, false, delegate { return true; });
+ var sslStream = new SslStream(stream, false, delegate
+ { return true; });
using (var cert = Configuration.Certificates.GetServerCertificate())
{
await sslStream.AuthenticateAsServerAsync(
{
var lines = new List<string>();
string line;
- while (!string.IsNullOrEmpty(line = await _reader.ReadLineAsync().ConfigureAwait(false)))
+ while (!string.IsNullOrEmpty(line = reader.ReadLine()));
+ ;
{
lines.Add(line);
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Net.Test.Common;
+using System.Text;
+using System.Threading.Tasks;
+
+using Xunit;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public class HttpClientHandler_Authentication_Test : HttpClientTestBase
+ {
+ private const string Username = "testusername";
+ private const string Password = "testpassword";
+ private const string Domain = "testdomain";
+
+ private NetworkCredential _credentials = new NetworkCredential(Username, Password, Domain);
+
+ private Func<HttpClientHandler, Uri, HttpStatusCode, NetworkCredential, Task> _createAndValidateRequest = async (handler, url, expectedStatusCode, credentials) =>
+ {
+ handler.Credentials = credentials;
+
+ using (HttpClient client = new HttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(url))
+ {
+ Assert.Equal(expectedStatusCode, response.StatusCode);
+ }
+ };
+
+ [Theory]
+ [MemberData(nameof(Authentication_TestData))]
+ public async Task HttpClientHandler_Authentication_Succeeds(string authenticateHeader, bool result)
+ {
+ var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ string serverResponse = $"HTTP/1.1 401 UnAuthorized\r\nDate: {DateTimeOffset.UtcNow:R}\r\nWWW-Authenticate: {authenticateHeader}\r\nContent-Length: 0\r\n\r\n";
+ HttpClientHandler handler = CreateHttpClientHandler();
+ Task serverTask = result ?
+ LoopbackServer.ReadRequestAndAuthenticateAsync(server, serverResponse, options) :
+ LoopbackServer.ReadRequestAndSendResponseAsync(server, serverResponse, options);
+ await TestHelper.WhenAllCompletedOrAnyFailed(_createAndValidateRequest(handler, url, result ? HttpStatusCode.OK : HttpStatusCode.Unauthorized, _credentials), serverTask);
+ }, options);
+ }
+
+ [Theory]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\"")]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello1\"\r\nWWW-Authenticate: Basic realm=\"hello2\"")]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Basic realm=\"hello\"")]
+ [InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\"\r\nWWW-Authenticate: Basic realm=\"hello\"")]
+ public async void HttpClientHandler_MultipleAuthenticateHeaders_Succeeds(string authenticateHeader)
+ {
+ var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ string serverResponse = $"HTTP/1.1 401 UnAuthorized\r\nDate: {DateTimeOffset.UtcNow:R}\r\n{authenticateHeader}\r\nContent-Length: 0\r\n\r\n";
+ HttpClientHandler handler = CreateHttpClientHandler();
+ Task serverTask = LoopbackServer.ReadRequestAndAuthenticateAsync(server, serverResponse, options);
+ await TestHelper.WhenAllCompletedOrAnyFailed(_createAndValidateRequest(handler, url, HttpStatusCode.OK, _credentials), serverTask);
+ }, options);
+ }
+
+ [Theory]
+ [InlineData("HTTP/1.1 401 UnAuthorized\r\nWWW-Authenticate: Basic realm=\"hello\"\r\nContent-Length: 0\r\n\r\n")]
+ [InlineData("HTTP/1.1 401 UnAuthorized\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"testnonce\"\r\nContent-Length: 0\r\n\r\n")]
+ public async void HttpClientHandler_IncorrectCredentials_Fails(string serverResponse)
+ {
+ var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ Task serverTask = LoopbackServer.ReadRequestAndAuthenticateAsync(server, serverResponse, options);
+ await TestHelper.WhenAllCompletedOrAnyFailed(_createAndValidateRequest(handler, url, HttpStatusCode.Unauthorized, new NetworkCredential("wronguser", "wrongpassword")), serverTask);
+ }, options);
+ }
+
+ public static IEnumerable<object[]> Authentication_TestData()
+ {
+ yield return new object[] { "Basic realm=\"testrealm\"", true };
+ yield return new object[] { "Basic ", true };
+ yield return new object[] { "Basic realm=withoutquotes", true };
+ yield return new object[] { "Basic something, Digest something", false };
+
+ // Add digest tests fail on CurlHandler.
+ if (PlatformDetection.IsWindows)
+ {
+ yield return new object[] { "Digest realm=\"testrealm\" nonce=\"testnonce\"", false };
+ yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}\", qop=auth-int"))}\"", true };
+ yield return new object[] { "Digest realm=\"api@example.org\", qop=\"auth\", algorithm=MD5-sess, nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", " +
+ "opaque=\"HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS\", charset=UTF-8, userhash=true", true };
+ yield return new object[] { "Digest realm=\"testrealm1\", nonce=\"testnonce1\" Digest realm=\"testrealm2\", nonce=\"testnonce2\"", false };
+ yield return new object[] { $"Basic realm=\"testrealm\", " +
+ $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}"))}\", algorithm=MD5", true };
+ yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"testnonce\", algorithm=MD5 " +
+ $"Basic realm=\"testrealm\"", false };
+ }
+ }
+ }
+}
protected override bool UseSocketsHttpHandler => true;
}
- public sealed class SocketsHttpHandler_Authentication_Test : HttpClientTestBase
+ public sealed class SocketsHttpHandler_HttpClientHandler_Authentication_Test : HttpClientHandler_Authentication_Test
{
protected override bool UseSocketsHttpHandler => true;
- private const string Username = "testusername";
- private const string Password = "testpassword";
- private const string Domain = "testdomain";
-
- private NetworkCredential _credentials = new NetworkCredential(Username, Password, Domain);
-
- private Func<HttpClientHandler, Uri, HttpStatusCode, NetworkCredential, Task> _createAndValidateRequest = async (handler, url, expectedStatusCode, credentials) =>
- {
- handler.Credentials = credentials;
-
- using (HttpClient client = new HttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(url))
- {
- Assert.Equal(expectedStatusCode, response.StatusCode);
- }
- };
-
- [Theory]
- [MemberData(nameof(Authentication_TestData))]
- public async void HttpClientHandler_Authentication_Succeeds(string authenticateHeader, bool result)
- {
- var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- string serverResponse = $"HTTP/1.1 401 UnAuthorized\r\nDate: {DateTimeOffset.UtcNow:R}\r\nWWW-Authenticate: {authenticateHeader}\r\nContent-Length: 0\r\n\r\n";
- HttpClientHandler handler = CreateHttpClientHandler();
- Task serverTask = result ?
- LoopbackServer.ReadRequestAndAuthenticateAsync(server, serverResponse, options) :
- LoopbackServer.ReadRequestAndSendResponseAsync(server, serverResponse, options);
- await TestHelper.WhenAllCompletedOrAnyFailed(_createAndValidateRequest(handler, url, result ? HttpStatusCode.OK : HttpStatusCode.Unauthorized, _credentials), serverTask);
- }, options);
- }
-
[Theory]
- [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\"")]
- [InlineData("WWW-Authenticate: Basic realm=\"hello1\"\r\nWWW-Authenticate: Basic realm=\"hello2\"")]
- [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Basic realm=\"hello\"")]
- [InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\"\r\nWWW-Authenticate: Basic realm=\"hello\"")]
- public async void HttpClientHandler_MultipleAuthenticateHeaders_Succeeds(string authenticateHeader)
+ [MemberData(nameof(Authentication_SocketsHttpHandler_TestData))]
+ public async void SocketsHttpHandler_Authentication_Succeeds(string authenticateHeader, bool result)
{
- var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- string serverResponse = $"HTTP/1.1 401 UnAuthorized\r\nDate: {DateTimeOffset.UtcNow:R}\r\n{authenticateHeader}\r\nContent-Length: 0\r\n\r\n";
- HttpClientHandler handler = CreateHttpClientHandler();
- Task serverTask = LoopbackServer.ReadRequestAndAuthenticateAsync(server, serverResponse, options);
- await TestHelper.WhenAllCompletedOrAnyFailed(_createAndValidateRequest(handler, url, HttpStatusCode.OK, _credentials), serverTask);
- }, options);
+ await HttpClientHandler_Authentication_Succeeds(authenticateHeader, result);
}
- [Theory]
- [InlineData("HTTP/1.1 401 UnAuthorized\r\nWWW-Authenticate: Basic realm=\"hello\"\r\nContent-Length: 0\r\n\r\n")]
- [InlineData("HTTP/1.1 401 UnAuthorized\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"testnonce\"\r\nContent-Length: 0\r\n\r\n")]
- public async void HttpClientHandler_IncorrectCredentials_Fails(string serverResponse)
+ public static IEnumerable<object[]> Authentication_SocketsHttpHandler_TestData()
{
- var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- Task serverTask = LoopbackServer.ReadRequestAndAuthenticateAsync(server, serverResponse, options);
- await TestHelper.WhenAllCompletedOrAnyFailed(_createAndValidateRequest(handler, url, HttpStatusCode.Unauthorized, new NetworkCredential("wronguser", "wrongpassword")), serverTask);
- }, options);
- }
-
- public static IEnumerable<object[]> Authentication_TestData()
- {
- yield return new object[] { "Basic realm=\"testrealm\"", true };
- yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}\""))}\"", true };
- yield return new object[] { "Digest realm=\"api@example.org\", qop=\"auth\", algorithm=SHA-256-sess, nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", " +
- "opaque=\"HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS\", charset=UTF-8, userhash=true", true };
- yield return new object[] { $"Basic realm=\"testrealm\", " +
- $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}"))}\"", true };
- yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}"))}\", " +
- $"Basic realm=\"testrealm\"", true };
- yield return new object[] { "Basic ", true };
- yield return new object[] { "Basic realm=withoutquotes", true };
+ // These test cases pass on SocketsHttpHandler, fail everywhere else.
yield return new object[] { "Basic realm=\"testrealm1\" basic realm=\"testrealm1\"", true };
yield return new object[] { "Basic something digest something", true };
-
yield return new object[] { "Digest ", false };
yield return new object[] { "Digest realm=withoutquotes, nonce=withoutquotes", false };
yield return new object[] { "Digest realm=\"testrealm\", nonce=\"testnonce\", algorithm=\"myown\"", false };
- yield return new object[] { "Digest realm=\"testrealm\" nonce=\"testnonce\"", false };
- yield return new object[] { "Basic something, Digest something", false };
- yield return new object[] { "Digest realm=\"testrealm1\", nonce=\"testnonce1\" Digest realm=\"testrealm2\", nonce=\"testnonce2\"", false };
}
}
<Compile Include="DelegatingHandlerTest.cs" />
<Compile Include="FakeDiagnosticSourceListenerObserver.cs" />
<Compile Include="FormUrlEncodedContentTest.cs" />
+ <Compile Include="HttpClientHandlerTest.Authentication.cs" />
<Compile Include="HttpClientHandlerTest.cs" />
<Compile Include="HttpClientHandlerTest.Cancellation.cs" />
<Compile Include="HttpClientHandlerTest.ClientCertificates.cs" />
<ItemGroup Condition="'$(TargetsOSX)'=='true'">
<TestCommandLines Include="ulimit -Sn 4096" />
</ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>