Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add create() overloads to support adding Authorization handler to HTTP clients #924

Merged
merged 7 commits into from
Nov 8, 2024
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
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,29 @@ For an example of authenticating a UWP app using the V2 Authentication Endpoint,
You can create an instance of **HttpClient** that is pre-configured for making requests to Microsoft Graph APIs using `GraphClientFactory`.

```cs
HttpClient httpClient = GraphClientFactory.Create( version: "beta");
// The client credentials flow requires that you request the
// /.default scope, and pre-configure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
var scopes = new[] { "https://graph.microsoft.com/.default" };

// Values from app registration
var clientId = "YOUR_CLIENT_ID";
var tenantId = "YOUR_TENANT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";

// using Azure.Identity;
var options = new ClientSecretCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
};

// https://learn.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);

HttpClient httpClient = GraphClientFactory.create(tokenCredential: clientSecretCredential, version: "beta");

```

For more information on initializing a client instance, see the [library overview](https://docs.microsoft.com/en-us/graph/sdks/sdks-overview)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ namespace Microsoft.Graph
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Kiota.Http.HttpClientLibrary.Extensions;

/// <summary>
/// Contains extension methods for <see cref="HttpRequestMessage"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ namespace Microsoft.Graph
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Serialization.Json;

/// <summary>
/// Contains extension methods for <see cref="IDecryptableContentExtensions"/>
Expand Down Expand Up @@ -43,7 +41,7 @@ public static class IDecryptableContentExtensions

/// <summary>
/// Validates the signature and decrypted content attached with the notification.
/// https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data#decrypting-resource-data-from-change-notifications
/// https://docs.microsoft.com/en-us/graph/webhooks-with-resource-data#decrypting-resource-data-from-change-notifications
/// </summary>
/// <param name="encryptedContent">The encrypted content of type <see cref="IDecryptableContent"/></param>
/// <param name="certificateProvider">Certificate provider to decrypt the content.
Expand Down
6 changes: 3 additions & 3 deletions src/Microsoft.Graph.Core/Microsoft.Graph.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@
<PackageReference Include="Microsoft.Kiota.Abstractions" Version="1.14.0" />
<PackageReference Include="Microsoft.Kiota.Authentication.Azure" Version="1.14.0" />
<PackageReference Include="Microsoft.Kiota.Serialization.Json" Version="1.14.0" />
<PackageReference Include="Microsoft.Kiota.Serialization.Text" Version="1.12.3" />
<PackageReference Include="Microsoft.Kiota.Serialization.Form" Version="1.12.3" />
<PackageReference Include="Microsoft.Kiota.Serialization.Text" Version="1.14.0" />
<PackageReference Include="Microsoft.Kiota.Serialization.Form" Version="1.14.0" />
<PackageReference Include="Microsoft.Kiota.Http.HttpClientLibrary" Version="1.14.0" />
<PackageReference Include="Microsoft.Kiota.Serialization.Multipart" Version="1.12.3" />
<PackageReference Include="Microsoft.Kiota.Serialization.Multipart" Version="1.14.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.11.20">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
4 changes: 1 addition & 3 deletions src/Microsoft.Graph.Core/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
#if DEBUG
[assembly: InternalsVisibleTo("Microsoft.Graph.DotnetCore.Core.Test")]
#endif
1 change: 0 additions & 1 deletion src/Microsoft.Graph.Core/Requests/AsyncMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions;
Expand All @@ -19,8 +18,8 @@
/// <typeparam name="T">The object type to return.</typeparam>
public class AsyncMonitor<T> : IAsyncMonitor<T> where T : IParsable, new()
{
private AsyncOperationStatus asyncOperationStatus;

Check warning on line 21 in src/Microsoft.Graph.Core/Requests/AsyncMonitor.cs

View workflow job for this annotation

GitHub Actions / Build

Remove the field 'asyncOperationStatus' and declare it as a local variable in the relevant methods.
private IBaseClient client;

Check warning on line 22 in src/Microsoft.Graph.Core/Requests/AsyncMonitor.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'client' 'readonly'.

internal string monitorUrl;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Microsoft.Graph
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Serialization;
Expand Down
9 changes: 4 additions & 5 deletions src/Microsoft.Graph.Core/Requests/DeltaResponseHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Microsoft.Graph
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -21,7 +20,7 @@ namespace Microsoft.Graph
#endif

/// <summary>
/// PREVIEW
/// PREVIEW
/// A response handler that exposes the list of changes returned in a response.
/// This supports scenarios where the service expresses changes to 'null'. The
/// deserializer can't express changes to null so you can now discover if a property
Expand Down Expand Up @@ -56,7 +55,7 @@ public async Task<ModelType> HandleResponseAsync<NativeResponseType, ModelType>(
// set on the response body object.
var responseString = await GetResponseStringAsync(responseMessage).ConfigureAwait(false);

// Get the response body object with the change list
// Get the response body object with the change list
// set on each response item.
var responseWithChangeList = await GetResponseBodyWithChangelistAsync(responseString).ConfigureAwait(false);
using var responseWithChangeListStream = new MemoryStream(Encoding.UTF8.GetBytes(responseWithChangeList));
Expand Down Expand Up @@ -112,7 +111,7 @@ private async Task<string> GetResponseBodyWithChangelistAsync(string deltaRespon
// return a string instead.
using (var responseJsonDocument = JsonDocument.Parse(deltaResponseBody))
{
// An array of delta objects. We will need to process
// An array of delta objects. We will need to process
// each one independently of each other.
if (!responseJsonDocument.RootElement.TryGetProperty("value", out var pageOfDeltaObjects))
{
Expand Down Expand Up @@ -193,7 +192,7 @@ private async Task GetObjectPropertiesAsync(JsonElement changedObject, List<stri
var arrayEnumerator = property.Value.EnumerateArray();
if (!arrayEnumerator.Any())
{
// Handle the edge case when the change involves changing to an empty array
// Handle the edge case when the change involves changing to an empty array
// as we can't observe elements in an empty collection in the foreach loop below
changes.Add(parent);
break;
Expand Down
57 changes: 56 additions & 1 deletion src/Microsoft.Graph.Core/Requests/GraphClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ namespace Microsoft.Graph
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using Azure.Core;
using Microsoft.Graph.Authentication;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;

Expand Down Expand Up @@ -107,6 +110,58 @@ public static HttpClient Create(
return client;
}

/// <summary>
/// Creates a new <see cref="HttpClient"/> instance configured to authenticate requests using the provided <see cref="BaseBearerTokenAuthenticationProvider"/>.
/// </summary>
/// <param name="authenticationProvider">The authentication provider to initialise the Authorization handler</param>
/// <param name="handlers">Custom middleware pipeline to which the Authorization handler is appended. If null, default handlers are initialised</param>
/// <param name="version">The Graph version to use in the base URL</param>
/// <param name="nationalCloud">The national cloud endpoint to use</param>
/// <param name="proxy">The proxy to be used with the created client</param>
/// <param name="finalHandler">The last HttpMessageHandler to HTTP calls.</param>
/// <param name="disposeHandler">true if the inner handler should be disposed of by Dispose(), false if you intend to reuse the inner handler..</param>
/// <returns>An <see cref="HttpClient"/> instance with the configured handlers</returns>
public static HttpClient Create(
BaseBearerTokenAuthenticationProvider authenticationProvider,
IEnumerable<DelegatingHandler> handlers = null,
string version = "v1.0",
string nationalCloud = Global_Cloud,
IWebProxy proxy = null,
HttpMessageHandler finalHandler = null,
bool disposeHandler = true)
{
if (handlers == null)
{
handlers = CreateDefaultHandlers();
}
var handlerList = handlers.ToList();
handlerList.Add(new AuthorizationHandler(authenticationProvider));
return Create(handlerList, version, nationalCloud, proxy, finalHandler, disposeHandler);
}

/// <summary>
/// Creates a new <see cref="HttpClient"/> instance configured to authenticate requests using the provided <see cref="TokenCredential"/>.
/// </summary>
/// <param name="tokenCredential">Token credential object use to initialise an <see cref="AzureIdentityAuthenticationProvider"/></param>
/// <param name="handlers">Custom middleware pipeline to which the Authorization handler is appended. If null, default handlers are initialised</param>
/// <param name="version">The Graph version to use in the base URL</param>
/// <param name="nationalCloud">The national cloud endpoint to use</param>
/// <param name="proxy">The proxy to be used with the created client</param>
/// <param name="finalHandler">The last HttpMessageHandler to HTTP calls</param>
/// <param name="disposeHandler">true if the inner handler should be disposed of by Dispose(), false if you intend to reuse the inner handler.</param>
/// <returns>An <see cref="HttpClient"/> instance with the configured handlers</returns>
public static HttpClient Create(
TokenCredential tokenCredential,
IEnumerable<DelegatingHandler> handlers = null,
string version = "v1.0",
string nationalCloud = Global_Cloud,
IWebProxy proxy = null,
HttpMessageHandler finalHandler = null,
bool disposeHandler = true)
{
return Create(new AzureIdentityAuthenticationProvider(tokenCredential, null, null, true), handlers, version, nationalCloud, proxy, finalHandler, disposeHandler);
}

/// <summary>
/// Create a default set of middleware for calling Microsoft Graph
/// </summary>
Expand Down Expand Up @@ -211,7 +266,7 @@ internal static HttpMessageHandler GetNativePlatformHttpHandler(IWebProxy proxy
return new WinHttpHandler { Proxy = proxy, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate , WindowsProxyUsePolicy = proxyPolicy, SendTimeout = Timeout.InfiniteTimeSpan, ReceiveDataTimeout = Timeout.InfiniteTimeSpan, ReceiveHeadersTimeout = Timeout.InfiniteTimeSpan };
#elif NET6_0_OR_GREATER
//use resilient configs when we can https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#alternatives-to-ihttpclientfactory-1
return new SocketsHttpHandler { Proxy = proxy, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.All, PooledConnectionLifetime = TimeSpan.FromMinutes(1)};
return new SocketsHttpHandler { Proxy = proxy, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.All, PooledConnectionLifetime = TimeSpan.FromMinutes(1)};
#else
return new HttpClientHandler { Proxy = proxy, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate };
#endif
Expand Down
2 changes: 0 additions & 2 deletions src/Microsoft.Graph.Core/Requests/GraphRequestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

namespace Microsoft.Graph
{
using System.Collections.Generic;
using System.Threading;
using Microsoft.Kiota.Abstractions;

/// <summary>
/// The graph request context class
Expand Down
3 changes: 1 addition & 2 deletions src/Microsoft.Graph.Core/Requests/GraphResponse{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Microsoft.Graph
{
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
Expand All @@ -27,7 +26,7 @@ public GraphResponse(RequestInformation requestInformation, HttpResponseMessage
}

/// <summary>
/// Gets the deserialized object
/// Gets the deserialized object
/// </summary>
/// <param name="responseHandler">The response handler to use for the reponse</param>
/// <param name="errorMappings">The errorMappings to use in the event of a non sucess request</param>
Expand Down
2 changes: 0 additions & 2 deletions src/Microsoft.Graph.Core/Requests/ResponseHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ namespace Microsoft.Graph
{
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Serialization;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Kiota.Http.HttpClientLibrary.Extensions;
using Xunit;

namespace Microsoft.Graph.DotnetCore.Core.Test.Helpers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Microsoft.Graph.DotnetCore.Core.Test.Mocks
{
using System.Net.Http;
using Microsoft.Graph.Core.Requests;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions.Authentication;
using Moq;

namespace Microsoft.Graph.DotnetCore.Core.Test.Mocks
{
public class MockAccessTokenProvider : Mock<IAccessTokenProvider>
{
public MockAccessTokenProvider(string accessToken = null) : base(MockBehavior.Strict)
{
this.Setup(x => x.GetAuthorizationTokenAsync(
It.IsAny<Uri>(),
It.IsAny<Dictionary<string, object>>(),
It.IsAny<CancellationToken>()
)).Returns(Task.FromResult(accessToken));

this.Setup(x => x.AllowedHostsValidator).Returns(
new AllowedHostsValidator(new List<string> { "graph.microsoft.com" })
);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace Microsoft.Graph.DotnetCore.Core.Test.Requests
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text.Json;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ namespace Microsoft.Graph.DotnetCore.Core.Test.Requests
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options;
using Mocks;
using Moq;
using Xunit;

public class GraphClientFactoryTests : IDisposable
Expand Down Expand Up @@ -315,6 +317,40 @@ public void CreateClientWithFinalHandlerDisposesTheFinalHandler(bool shouldDispo
Assert.Equal(shouldDisposeHandler, finalHandler.Disposed);
}

[Fact]
public async Task CreateClientWithAuthenticationProviderAuthenticatesRequestAsync()
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/me");
var responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
this.testHttpMessageHandler.SetHttpResponse(responseMessage);

var authProvider = new Mock<BaseBearerTokenAuthenticationProvider>(new MockAccessTokenProvider("token").Object);

using (HttpClient client = GraphClientFactory.Create(authenticationProvider: authProvider.Object, finalHandler: this.testHttpMessageHandler))
{
var response = await client.SendAsync(httpRequestMessage, new CancellationToken());
Assert.Equal("Bearer token", response.RequestMessage.Headers.Authorization.ToString());
}
}

[Fact]
public async Task CreateClientWithTokenCredentialAuthenticatesRequestAsync()
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/me");
var responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
this.testHttpMessageHandler.SetHttpResponse(responseMessage);

var tokenCredential = new Mock<TokenCredential>();
tokenCredential.Setup(x => x.GetTokenAsync(It.IsAny<TokenRequestContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new AccessToken("mockToken", DateTimeOffset.UtcNow.AddMinutes(10)));

using (HttpClient client = GraphClientFactory.Create(tokenCredential: tokenCredential.Object, finalHandler: this.testHttpMessageHandler))
{
var response = await client.SendAsync(httpRequestMessage, new CancellationToken());
Assert.Equal("Bearer mockToken", response.RequestMessage.Headers.Authorization.ToString());
}
}

private class MockHttpHandler : HttpMessageHandler
{
public bool Disposed;
Expand Down
Loading
Loading