Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,21 @@ public class AuthorizationHeaderAuthenticatedHttpClientTests
[Fact]
public async Task TestRequestAddsAuthenticationHeader()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.Expect("https://www.example.com")
.WithHeaders("Authorization", "test-value")
.Respond(HttpStatusCode.OK);
var client = AuthorizationHeaderAuthenticatedHttpClient.GetClient(new AuthorizationHeaderAuthenticatedHttpClientOptions
using (var mockHttp = new MockHttpMessageHandler())
{
Value = "test-value"
}, mockHttp);
mockHttp
.Expect("https://www.example.com")
.WithHeaders("Authorization", "test-value")
.Respond(HttpStatusCode.OK);
var client = AuthorizationHeaderAuthenticatedHttpClient.GetClient(new AuthorizationHeaderAuthenticatedHttpClientOptions
{
Value = "test-value"
}, mockHttp);

await client.GetStringAsync("https://www.example.com");
await client.GetStringAsync(new Uri("https://www.example.com")).ConfigureAwait(false);

mockHttp.VerifyNoOutstandingExpectation();
mockHttp.VerifyNoOutstandingExpectation();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="2.6.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public AuthorizationHeaderAuthenticatedHttpMessageHandler(
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = _authorizationHeader;
return await base.SendAsync(request, cancellationToken);
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,80 +19,94 @@ public class AzureAdAuthenticatedHttpClientTests
[Fact]
public async Task TestRequestHasAuthorizationHeader()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.Expect("https://www.example.com")
.WithHeaders("Authorization", "Bearer test-access-token")
.Respond(HttpStatusCode.OK);
var mockMsgHandler = new Mock<AzureAdAuthenticatedHttpMessageHandler>(new AzureAdAuthenticatedHttpClientOptions
using (var mockHttp = new MockHttpMessageHandler())
{
Tenant = "test-tenant",
ClientId = "test-client-id",
AppKey = "test-client-app-key",
ResourceId = "test-resource-id"
}, mockHttp);
mockMsgHandler
.Setup(handler => handler.AcquireAccessTokenAsync())
.Returns(Task.FromResult("test-access-token"));
mockMsgHandler.CallBase = true;
var client = new HttpClient(mockMsgHandler.Object);
mockHttp
.Expect("https://www.example.com")
.WithHeaders("Authorization", "Bearer test-access-token")
.Respond(HttpStatusCode.OK);
var mockMsgHandler = new Mock<AzureAdAuthenticatedHttpMessageHandler>(new AzureAdAuthenticatedHttpClientOptions
{
Tenant = "test-tenant",
ClientId = "test-client-id",
AppKey = "test-client-app-key",
ResourceId = "test-resource-id"
}, mockHttp);
mockMsgHandler
.Setup(handler => handler.AcquireAccessTokenAsync())
.Returns(Task.FromResult("test-access-token"));
mockMsgHandler.CallBase = true;

await client.GetStringAsync("https://www.example.com");
using (var client = new HttpClient(mockMsgHandler.Object))
{
await client.GetStringAsync(new Uri("https://www.example.com")).ConfigureAwait(false);

mockHttp.VerifyNoOutstandingExpectation();
mockHttp.VerifyNoOutstandingExpectation();
}
}
}

[Fact]
public async Task TestRequestRetriesThreeTimesToAcquireAccessToken()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.Expect("https://www.example.com")
.WithHeaders("Authorization", "Bearer test-access-token")
.Respond(HttpStatusCode.OK);
var mockMsgHandler = new Mock<AzureAdAuthenticatedHttpMessageHandler>(new AzureAdAuthenticatedHttpClientOptions
using (var mockHttp = new MockHttpMessageHandler())
{
Tenant = "test-tenant",
ClientId = "test-client-id",
AppKey = "test-client-app-key",
ResourceId = "test-resource-id"
}, mockHttp);
mockMsgHandler
.SetupSequence(handler => handler.AcquireAccessTokenAsync())
.Throws(new AdalException("temporarily_unavailable"))
.Throws(new AdalException("temporarily_unavailable"))
.Returns(Task.FromResult("test-access-token"));
mockMsgHandler.CallBase = true;
var client = new HttpClient(mockMsgHandler.Object);
mockHttp
.Expect("https://www.example.com")
.WithHeaders("Authorization", "Bearer test-access-token")
.Respond(HttpStatusCode.OK);
var mockMsgHandler = new Mock<AzureAdAuthenticatedHttpMessageHandler>(new AzureAdAuthenticatedHttpClientOptions
{
Tenant = "test-tenant",
ClientId = "test-client-id",
AppKey = "test-client-app-key",
ResourceId = "test-resource-id"
}, mockHttp);
mockMsgHandler
.SetupSequence(handler => handler.AcquireAccessTokenAsync())
.Throws(new AdalException("temporarily_unavailable"))
.Throws(new AdalException("temporarily_unavailable"))
.Returns(Task.FromResult("test-access-token"));
mockMsgHandler.CallBase = true;

await client.GetStringAsync("https://www.example.com");
using (var client = new HttpClient(mockMsgHandler.Object))
{
await client.GetStringAsync(new Uri("https://www.example.com")).ConfigureAwait(false);

mockHttp.VerifyNoOutstandingExpectation();
}
mockHttp.VerifyNoOutstandingExpectation();
}
}
}

[Fact]
public async Task TestRequestFailsOnRepeatedFailuresToAcquireAccessTokenFailure()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp.Fallback.Respond(req => new HttpResponseMessage(HttpStatusCode.Unauthorized));
var mockMsgHandler = new Mock<AzureAdAuthenticatedHttpMessageHandler>(new AzureAdAuthenticatedHttpClientOptions
using (var mockHttp = new MockHttpMessageHandler())
{
Tenant = "test-tenant",
ClientId = "test-client-id",
AppKey = "test-client-app-key",
ResourceId = "test-resource-id"
}, mockHttp);
mockMsgHandler
.SetupSequence(handler => handler.AcquireAccessTokenAsync())
.Throws(new AdalException("temporarily_unavailable"))
.Throws(new AdalException("temporarily_unavailable"))
.Throws(new AdalException("temporarily_unavailable"));
mockMsgHandler.CallBase = true;
var client = new HttpClient(mockMsgHandler.Object);
mockHttp.Fallback.Respond(req => new HttpResponseMessage(HttpStatusCode.Unauthorized));
var mockMsgHandler = new Mock<AzureAdAuthenticatedHttpMessageHandler>(new AzureAdAuthenticatedHttpClientOptions
{
Tenant = "test-tenant",
ClientId = "test-client-id",
AppKey = "test-client-app-key",
ResourceId = "test-resource-id"
}, mockHttp);
mockMsgHandler
.SetupSequence(handler => handler.AcquireAccessTokenAsync())
.Throws(new AdalException("temporarily_unavailable"))
.Throws(new AdalException("temporarily_unavailable"))
.Throws(new AdalException("temporarily_unavailable"));
mockMsgHandler.CallBase = true;

var exc = await Record.ExceptionAsync(async () => await client.GetStringAsync("https://www.example.com"));
using (var client = new HttpClient(mockMsgHandler.Object))
{
var exc = await Record.ExceptionAsync(
async () => await client.GetStringAsync(new Uri("https://www.example.com")).ConfigureAwait(false)
).ConfigureAwait(false);

Assert.IsType<HttpRequestException>(exc);
}
Assert.IsType<HttpRequestException>(exc);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="2.8.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ namespace CoderPatros.AuthenticatedHttpClient
// https://github.com/Azure-Samples/active-directory-dotnet-daemon/blob/master/TodoListDaemon/Program.cs
public static class AzureAdAuthenticatedHttpClient
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
public static HttpClient GetClient(AzureAdAuthenticatedHttpClientOptions options)
{
var msgHandler = new AzureAdAuthenticatedHttpMessageHandler(options);
return new HttpClient(msgHandler);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
public static HttpClient GetClient(AzureAdAuthenticatedHttpClientOptions options, HttpMessageHandler innerHandler)
{
var msgHandler = new AzureAdAuthenticatedHttpMessageHandler(options, innerHandler);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.Contracts;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Globalization;
Expand All @@ -18,6 +19,8 @@ public class AzureAdAuthenticatedHttpMessageHandler : DelegatingHandler

public AzureAdAuthenticatedHttpMessageHandler(AzureAdAuthenticatedHttpClientOptions options)
{
Contract.Requires(options != null);

_resourceId = options.ResourceId;

var authority = String.Format(CultureInfo.InvariantCulture, options.AadInstance, options.Tenant);
Expand All @@ -35,7 +38,7 @@ public AzureAdAuthenticatedHttpMessageHandler(

internal virtual async Task<string> AcquireAccessTokenAsync()
{
var result = await _authContext.AcquireTokenAsync(_resourceId, _clientCredential);
var result = await _authContext.AcquireTokenAsync(_resourceId, _clientCredential).ConfigureAwait(false);
return result?.AccessToken;
}

Expand All @@ -55,7 +58,7 @@ private async Task<string> AcquireTokenWithRetriesAsync(CancellationToken cancel
try
{
// ADAL includes an in memory cache, so this call will only send a message to the server if the cached token is expired.
token = await AcquireAccessTokenAsync();
token = await AcquireAccessTokenAsync().ConfigureAwait(false);
}
catch (AdalException ex)
{
Expand All @@ -80,7 +83,9 @@ private async Task<string> AcquireTokenWithRetriesAsync(CancellationToken cancel

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var accessToken = await AcquireTokenWithRetriesAsync(cancellationToken);
Contract.Requires(request != null);

var accessToken = await AcquireTokenWithRetriesAsync(cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return null;
Expand All @@ -90,7 +95,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
"Bearer",
accessToken);

return await base.SendAsync(request, cancellationToken);
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.6" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,28 @@ public class AzureAppServiceManagedIdentityAuthenticatedHttpClientTests
[Fact]
public async Task TestRequestHasAuthorizationHeader()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.Expect("https://www.example.com")
.WithHeaders("Authorization", "Bearer test-access-token")
.Respond(HttpStatusCode.OK);
var mockMsgHandler = new Mock<AzureAppServiceManagedIdentityAuthenticatedHttpMessageHandler>(new AzureAppServiceManagedIdentityAuthenticatedHttpClientOptions
using (var mockHttp = new MockHttpMessageHandler())
{
ResourceId = "test-resource-id"
}, mockHttp);
mockMsgHandler
.Setup(handler => handler.GetAccessTokenAsync())
.Returns(Task.FromResult("test-access-token"));
mockMsgHandler.CallBase = true;
var client = new HttpClient(mockMsgHandler.Object);
mockHttp
.Expect("https://www.example.com")
.WithHeaders("Authorization", "Bearer test-access-token")
.Respond(HttpStatusCode.OK);
var mockMsgHandler = new Mock<AzureAppServiceManagedIdentityAuthenticatedHttpMessageHandler>(new AzureAppServiceManagedIdentityAuthenticatedHttpClientOptions
{
ResourceId = "test-resource-id"
}, mockHttp);
mockMsgHandler
.Setup(handler => handler.GetAccessTokenAsync())
.Returns(Task.FromResult("test-access-token"));
mockMsgHandler.CallBase = true;

await client.GetStringAsync("https://www.example.com");
using (var client = new HttpClient(mockMsgHandler.Object))
{
await client.GetStringAsync(new Uri("https://www.example.com")).ConfigureAwait(false);

mockHttp.VerifyNoOutstandingExpectation();
mockHttp.VerifyNoOutstandingExpectation();
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="2.8.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ namespace CoderPatros.AuthenticatedHttpClient
{
public static class AzureAppServiceManagedIdentityAuthenticatedHttpClient
{
public static HttpClient GetClient(AzureAppServiceManagedIdentityAuthenticatedHttpClientOptions options)
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
public static HttpClient GetClient(AzureAppServiceManagedIdentityAuthenticatedHttpClientOptions options)
{
var msgHandler = new AzureAppServiceManagedIdentityAuthenticatedHttpMessageHandler(options);
return new HttpClient(msgHandler);
}

public static HttpClient GetClient(AzureAppServiceManagedIdentityAuthenticatedHttpClientOptions options, HttpMessageHandler innerHandler)
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
public static HttpClient GetClient(AzureAppServiceManagedIdentityAuthenticatedHttpClientOptions options, HttpMessageHandler innerHandler)
{
var msgHandler = new AzureAppServiceManagedIdentityAuthenticatedHttpMessageHandler(options, innerHandler);
return new HttpClient(msgHandler);
Expand Down
Loading