-
Notifications
You must be signed in to change notification settings - Fork 354
Introduce PowerPlatformConnectorClient2 #2909
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
Open
shgogna
wants to merge
4
commits into
main
Choose a base branch
from
shgogna/powerplatformconnectorsclientv2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
bd88991
Introduce PowerPlatformConnectorClient2
shgogna 4fc8508
Remove method that was originally part of the interface, but was removed
shgogna cf0dd38
Add SendAsync method overload for HttpRequestMessage instead of messa…
shgogna 20fce4a
Add Extension
shgogna File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
src/libraries/Microsoft.PowerFx.Connectors/IPowerPlatformConnectorClient2.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Net.Http; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.PowerFx.Connectors | ||
| { | ||
| /// <summary> | ||
| /// Delegate for providing a bearer token for authentication. | ||
| /// </summary> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>Token without "Bearer" scheme as the prefix.</returns> | ||
| public delegate Task<string> PowerPlatformConnectorClient2BearerTokenProvider( | ||
| CancellationToken cancellationToken); | ||
|
|
||
| public interface IPowerPlatformConnectorClient2 | ||
| { | ||
| /// <summary> | ||
| /// Sends an HTTP request to the Power Platform connector. | ||
| /// </summary> | ||
| /// <param name="method">HTTP method.</param> | ||
| /// <param name="operationPathAndQuery">Operation path and query string.</param> | ||
| /// <param name="headers">Headers.</param> | ||
| /// <param name="content">Content.</param> | ||
| /// <param name="diagnosticOptions">Diagnostic options.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns><see cref="HttpResponseMessage"/>.</returns> | ||
| Task<HttpResponseMessage> SendAsync( | ||
| HttpMethod method, | ||
| string operationPathAndQuery, | ||
| IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers, | ||
| HttpContent content, | ||
| PowerPlatformConnectorClient2DiagnosticOptions diagnosticOptions, | ||
| CancellationToken cancellationToken); | ||
| } | ||
| } |
46 changes: 46 additions & 0 deletions
46
src/libraries/Microsoft.PowerFx.Connectors/IPowerPlatformConnectorClient2Extensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Net.Http; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.PowerFx.Connectors | ||
| { | ||
| public static class IPowerPlatformConnectorClient2Extensions | ||
| { | ||
| /// <summary> | ||
| /// Sends an HTTP request to the Power Platform connector. | ||
| /// </summary> | ||
| /// <param name="client">Client.</param> | ||
| /// <param name="requestMessage">HTTP request message. URI path will be used as a relative path for the connector request.</param> | ||
| /// <param name="diagnosticOptions">Diagnostic options.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns><see cref="HttpResponseMessage"/>.</returns> | ||
| public static Task<HttpResponseMessage> SendAsync( | ||
| this IPowerPlatformConnectorClient2 client, | ||
| HttpRequestMessage requestMessage, | ||
| PowerPlatformConnectorClient2DiagnosticOptions diagnosticOptions, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| if (client is null) | ||
| { | ||
| throw new ArgumentNullException(nameof(client)); | ||
| } | ||
|
|
||
| if (requestMessage is null) | ||
| { | ||
| throw new ArgumentNullException(nameof(requestMessage)); | ||
| } | ||
|
|
||
| return client.SendAsync( | ||
| requestMessage.Method, | ||
| requestMessage.RequestUri.PathAndQuery, | ||
| requestMessage.Headers, | ||
| requestMessage.Content, | ||
| diagnosticOptions, | ||
| cancellationToken); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
197 changes: 197 additions & 0 deletions
197
src/libraries/Microsoft.PowerFx.Connectors/PowerPlatformConnectorClient2.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Net; | ||
| using System.Net.Http; | ||
| using System.Net.Http.Headers; | ||
| using System.Reflection; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.OpenApi.Models; | ||
|
|
||
| namespace Microsoft.PowerFx.Connectors | ||
| { | ||
| /// <summary> | ||
| /// Client for invoking operations of Power Platform connectors. | ||
| /// </summary> | ||
| public class PowerPlatformConnectorClient2 : IPowerPlatformConnectorClient2 | ||
| { | ||
| private readonly string _baseUrlStr; | ||
| private readonly HttpMessageInvoker _httpMessageInvoker; | ||
| private readonly PowerPlatformConnectorClient2BearerTokenProvider _tokenProvider; | ||
| private readonly string _environmentId; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="PowerPlatformConnectorClient2"/> class. | ||
| /// </summary> | ||
| /// <param name="document">Document used for extracting the base URL.</param> | ||
| /// <param name="httpMessageInvoker">HTTP message invoker.</param> | ||
| /// <param name="tokenProvider">Bearer token provider.</param> | ||
| /// <param name="environmentId">Environment ID.</param> | ||
| public PowerPlatformConnectorClient2( | ||
| OpenApiDocument document, | ||
| HttpMessageInvoker httpMessageInvoker, | ||
| PowerPlatformConnectorClient2BearerTokenProvider tokenProvider, | ||
| string environmentId) | ||
| : this(GetBaseUrlFromOpenApiDocument(document), httpMessageInvoker, tokenProvider, environmentId) | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="PowerPlatformConnectorClient2"/> class. | ||
| /// </summary> | ||
| /// <param name="baseUrl">Base URL for requests.</param> | ||
| /// <param name="httpMessageInvoker">HTTP message invoker.</param> | ||
| /// <param name="tokenProvider">Bearer token provider.</param> | ||
| /// <param name="environmentId">Environment ID.</param> | ||
| public PowerPlatformConnectorClient2( | ||
| Uri baseUrl, | ||
| HttpMessageInvoker httpMessageInvoker, | ||
| PowerPlatformConnectorClient2BearerTokenProvider tokenProvider, | ||
| string environmentId) | ||
| { | ||
| this._baseUrlStr = GetBaseUrlStr(baseUrl ?? throw new ArgumentNullException(nameof(baseUrl))); | ||
| this._httpMessageInvoker = httpMessageInvoker ?? throw new ArgumentNullException(nameof(httpMessageInvoker)); | ||
| this._tokenProvider = tokenProvider ?? throw new ArgumentNullException(nameof(tokenProvider)); | ||
| this._environmentId = environmentId ?? throw new ArgumentNullException(nameof(environmentId)); | ||
|
|
||
| static string GetBaseUrlStr(Uri uri) | ||
| { | ||
| var str = uri.GetLeftPart(UriPartial.Path); | ||
|
|
||
| // Note (shgogna): Ensure the base URL does NOT end with "/". | ||
| // This will allow us to concatenate the operation path to the base URL | ||
| // without worrying about the "/". | ||
| str = str.TrimEnd('/'); | ||
| return str; | ||
| } | ||
| } | ||
|
|
||
| // <inheritdoc /> | ||
| public Task<HttpResponseMessage> SendAsync( | ||
| HttpMethod method, | ||
| string operationPathAndQuery, | ||
| IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers, | ||
| HttpContent content, | ||
| PowerPlatformConnectorClient2DiagnosticOptions diagnosticOptions, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| if (operationPathAndQuery is null) | ||
| { | ||
| throw new ArgumentNullException(nameof(operationPathAndQuery)); | ||
| } | ||
|
|
||
| var uri = this.CombineBaseUrlWithOperationPathAndQuery(operationPathAndQuery); | ||
|
|
||
| if (!uri.AbsoluteUri.StartsWith(this._baseUrlStr, StringComparison.Ordinal)) | ||
| { | ||
| throw new ArgumentException("Path traversal detected during combination of base URL path and operation path.", nameof(operationPathAndQuery)); | ||
| } | ||
|
|
||
| return this.InternalSendAsync( | ||
| method, | ||
| uri, | ||
| headers, | ||
| content, | ||
| diagnosticOptions, | ||
| cancellationToken); | ||
| } | ||
|
|
||
| private async Task<HttpResponseMessage> InternalSendAsync( | ||
| HttpMethod method, | ||
| Uri uri, | ||
| IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers, | ||
| HttpContent content, | ||
| PowerPlatformConnectorClient2DiagnosticOptions diagnosticOptions, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| var authToken = await this._tokenProvider(cancellationToken).ConfigureAwait(false); | ||
|
|
||
| using (var req = new HttpRequestMessage(method, uri)) | ||
| { | ||
| req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); | ||
|
|
||
| this.AddDiagnosticHeaders(diagnosticOptions, req); | ||
|
|
||
| foreach (var header in headers) | ||
| { | ||
| req.Headers.Add(header.Key, header.Value); | ||
| } | ||
|
|
||
| req.Content = content; | ||
| return await this._httpMessageInvoker.SendAsync(req, cancellationToken).ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| private Uri CombineBaseUrlWithOperationPathAndQuery(string operationPathAndQuery) | ||
| { | ||
| if (operationPathAndQuery.StartsWith("/", StringComparison.Ordinal)) | ||
| { | ||
| return new Uri(this._baseUrlStr + operationPathAndQuery); | ||
| } | ||
| else | ||
| { | ||
| return new Uri(this._baseUrlStr + "/" + operationPathAndQuery); | ||
| } | ||
| } | ||
|
|
||
| private void AddDiagnosticHeaders( | ||
| PowerPlatformConnectorClient2DiagnosticOptions diagnosticOptions, | ||
| HttpRequestMessage req) | ||
| { | ||
| var userAgent = string.IsNullOrWhiteSpace(diagnosticOptions?.UserAgent) | ||
| ? $"PowerFx/{PowerPlatformConnectorClient.Version}" | ||
| : $"{diagnosticOptions.UserAgent} PowerFx/{PowerPlatformConnectorClient.Version}"; | ||
|
|
||
| var clientRequestId = string.IsNullOrWhiteSpace(diagnosticOptions?.ClientRequestId) | ||
| ? Guid.NewGuid().ToString() | ||
| : diagnosticOptions.ClientRequestId; | ||
|
|
||
| // CorrelationID can be the same as ClientRequestID | ||
| var correlationId = string.IsNullOrWhiteSpace(diagnosticOptions?.CorrelationId) | ||
| ? clientRequestId | ||
| : diagnosticOptions.CorrelationId; | ||
|
|
||
| req.Headers.Add("User-Agent", userAgent); | ||
| req.Headers.Add("x-ms-user-agent", userAgent); | ||
| req.Headers.Add("x-ms-client-environment-id", $"/providers/Microsoft.PowerApps/environments/{this._environmentId}"); | ||
| req.Headers.Add("x-ms-client-request-id", clientRequestId); | ||
| req.Headers.Add("x-ms-correlation-id", correlationId); | ||
|
|
||
| if (!string.IsNullOrWhiteSpace(diagnosticOptions?.ClientSessionId)) | ||
| { | ||
| req.Headers.Add("x-ms-client-session-id", diagnosticOptions.ClientSessionId); | ||
| } | ||
|
|
||
| if (!string.IsNullOrWhiteSpace(diagnosticOptions?.ClientTenantId)) | ||
| { | ||
| req.Headers.Add("x-ms-client-tenant-id", diagnosticOptions.ClientTenantId); | ||
| } | ||
|
|
||
| if (!string.IsNullOrWhiteSpace(diagnosticOptions?.ClientObjectId)) | ||
| { | ||
| req.Headers.Add("x-ms-client-object-id", diagnosticOptions.ClientObjectId); | ||
| } | ||
| } | ||
|
|
||
| private static Uri GetBaseUrlFromOpenApiDocument(OpenApiDocument document) | ||
| { | ||
| ConnectorErrors errors = new ConnectorErrors(); | ||
| Uri uri = document.GetFirstServerUri(errors); | ||
|
|
||
| if (uri is null) | ||
| { | ||
| errors.AddError("Swagger document doesn't contain an endpoint"); | ||
| } | ||
|
|
||
| if (errors.HasErrors) | ||
| { | ||
| throw new PowerFxConnectorException(string.Join(", ", errors.Errors)); | ||
| } | ||
|
|
||
| return uri; | ||
| } | ||
| } | ||
| } | ||
20 changes: 20 additions & 0 deletions
20
src/libraries/Microsoft.PowerFx.Connectors/PowerPlatformConnectorClient2DiagnosticOptions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| namespace Microsoft.PowerFx.Connectors | ||
| { | ||
| public class PowerPlatformConnectorClient2DiagnosticOptions | ||
| { | ||
| public string ClientSessionId { get; set; } | ||
|
|
||
| public string ClientRequestId { get; set; } | ||
|
|
||
| public string ClientTenantId { get; set; } | ||
|
|
||
| public string ClientObjectId { get; set; } | ||
|
|
||
| public string CorrelationId { get; set; } | ||
|
|
||
| public string UserAgent { get; set; } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.