-
Notifications
You must be signed in to change notification settings - Fork 249
Add ExtraHeaderParameters and ExtraQueryParameters support to DownstreamApi #3413
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
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
f102fca
Initial plan
Copilot 5546d4e
Initial analysis and plan for adding ExtraHeaderParameters and ExtraQ…
Copilot cc55f6a
Implement ExtraHeaderParameters and ExtraQueryParameters support in D…
Copilot 40cb69d
Update API documentation and improve ExtraHeaderParameters implementa…
Copilot 7166312
Remove reflection usage and use direct property access for ExtraHeade…
Copilot 6238b09
Remove unnecessary helper methods and simplify ExtraHeaderParameters/…
Copilot e08ae74
Revert global.json SDK version change as requested
Copilot c42c720
Address test feedback: use StringComparison.Ordinal and DownstreamApi…
Copilot 6af861e
Fix test expectation for spaces in query parameters
Copilot 647904f
Fix test expectation to match actual URI encoding behavior
Copilot c1fbf5f
Fixing the expected string in UpdateRequestAsync_WithSpecialCharacter…
jmprieur 3456578
Merge branch 'master' into copilot/fix-3387
jmprieur e7b0312
Merge branch 'master' into copilot/fix-3387
jmprieur 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
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
204 changes: 204 additions & 0 deletions
204
tests/Microsoft.Identity.Web.Test/DownstreamWebApiSupport/ExtraParametersTests.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,204 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Net.Http; | ||
| using System.Security.Claims; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Extensions.Options; | ||
| using Microsoft.Identity.Abstractions; | ||
| using Microsoft.Identity.Web.Test.Resource; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Identity.Web.Tests | ||
| { | ||
| public class ExtraParametersTests | ||
| { | ||
| private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider; | ||
| private readonly IHttpClientFactory _httpClientFactory; | ||
| private readonly IOptionsMonitor<DownstreamApiOptions> _namedDownstreamApiOptions; | ||
| private readonly ILogger<DownstreamApi> _logger; | ||
| private readonly DownstreamApi _downstreamApi; | ||
|
|
||
| public ExtraParametersTests() | ||
| { | ||
| _authorizationHeaderProvider = new MyAuthorizationHeaderProvider(); | ||
| _httpClientFactory = new HttpClientFactoryTest(); | ||
| _namedDownstreamApiOptions = new MyMonitor(); | ||
| _logger = new LoggerFactory().CreateLogger<DownstreamApi>(); | ||
|
|
||
| _downstreamApi = new DownstreamApi( | ||
| _authorizationHeaderProvider, | ||
| _namedDownstreamApiOptions, | ||
| _httpClientFactory, | ||
| _logger); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task UpdateRequestAsync_WithExtraHeaderParameters_AddsHeadersToRequest() | ||
| { | ||
| // Arrange | ||
| var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.com/api"); | ||
| var options = new DownstreamApiOptions() | ||
| { | ||
| ExtraHeaderParameters = new Dictionary<string, string> | ||
| { | ||
| { "OData-Version", "4.0" }, | ||
| { "Custom-Header", "test-value" } | ||
| } | ||
| }; | ||
|
|
||
| // Act | ||
| await _downstreamApi.UpdateRequestAsync(httpRequestMessage, null, options, false, null, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| Assert.True(httpRequestMessage.Headers.Contains("OData-Version")); | ||
| Assert.True(httpRequestMessage.Headers.Contains("Custom-Header")); | ||
| Assert.Equal("4.0", httpRequestMessage.Headers.GetValues("OData-Version").First()); | ||
| Assert.Equal("test-value", httpRequestMessage.Headers.GetValues("Custom-Header").First()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task UpdateRequestAsync_WithExtraQueryParameters_AddsQueryParametersToUrl() | ||
| { | ||
| // Arrange | ||
| var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.com/api"); | ||
| var options = new DownstreamApiOptions() | ||
| { | ||
| ExtraQueryParameters = new Dictionary<string, string> | ||
| { | ||
| { "param1", "value1" }, | ||
| { "param2", "value2" } | ||
| } | ||
| }; | ||
|
|
||
| // Act | ||
| await _downstreamApi.UpdateRequestAsync(httpRequestMessage, null, options, false, null, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| var requestUri = httpRequestMessage.RequestUri!.ToString(); | ||
| Assert.Contains("param1=value1", requestUri, StringComparison.Ordinal); | ||
| Assert.Contains("param2=value2", requestUri, StringComparison.Ordinal); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task UpdateRequestAsync_WithExtraQueryParameters_AppendsToExistingQuery() | ||
| { | ||
| // Arrange | ||
| var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.com/api?existing=true"); | ||
| var options = new DownstreamApiOptions() | ||
| { | ||
| ExtraQueryParameters = new Dictionary<string, string> | ||
| { | ||
| { "new", "param" } | ||
| } | ||
| }; | ||
|
|
||
| // Act | ||
| await _downstreamApi.UpdateRequestAsync(httpRequestMessage, null, options, false, null, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| var requestUri = httpRequestMessage.RequestUri!.ToString(); | ||
| Assert.Contains("existing=true", requestUri, StringComparison.Ordinal); | ||
| Assert.Contains("new=param", requestUri, StringComparison.Ordinal); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task UpdateRequestAsync_WithoutExtraParameters_DoesNotModifyRequest() | ||
| { | ||
| // Arrange | ||
| var originalUri = "https://example.com/api"; | ||
| var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, originalUri); | ||
| var options = new DownstreamApiOptions(); // No extra parameters | ||
|
|
||
| // Act | ||
| await _downstreamApi.UpdateRequestAsync(httpRequestMessage, null, options, false, null, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| Assert.Equal(originalUri, httpRequestMessage.RequestUri!.ToString()); | ||
| } | ||
|
|
||
|
|
||
| [Fact] | ||
| public async Task UpdateRequestAsync_WithEmptyExtraParameters_DoesNotModifyRequest() | ||
| { | ||
| // Arrange | ||
| var originalUri = "https://example.com/api"; | ||
| var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, originalUri); | ||
| var options = new DownstreamApiOptions() | ||
| { | ||
| ExtraHeaderParameters = new Dictionary<string, string>(), // Empty dictionary | ||
| ExtraQueryParameters = new Dictionary<string, string>() // Empty dictionary | ||
| }; | ||
|
|
||
| // Act | ||
| await _downstreamApi.UpdateRequestAsync(httpRequestMessage, null, options, false, null, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| Assert.Equal(originalUri, httpRequestMessage.RequestUri!.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task UpdateRequestAsync_WithSpecialCharacters_EscapesCorrectly() | ||
| { | ||
| // Arrange | ||
| var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.com/api"); | ||
| var options = new DownstreamApiOptions() | ||
| { | ||
| ExtraQueryParameters = new Dictionary<string, string> | ||
| { | ||
| { "special", "value with spaces & symbols" } | ||
| } | ||
| }; | ||
|
|
||
| // Act | ||
| await _downstreamApi.UpdateRequestAsync(httpRequestMessage, null, options, false, null, CancellationToken.None); | ||
|
|
||
| // Assert | ||
| var requestUri = httpRequestMessage.RequestUri!.ToString(); | ||
| Assert.Contains("special=value with spaces %26 symbols", requestUri, StringComparison.Ordinal); | ||
| } | ||
|
|
||
| private class MyAuthorizationHeaderProvider : IAuthorizationHeaderProvider | ||
| { | ||
| public Task<string> CreateAuthorizationHeaderForAppAsync(string scopes, AuthorizationHeaderProviderOptions? downstreamApiOptions = null, CancellationToken cancellationToken = default) | ||
| { | ||
| return Task.FromResult("Bearer ey"); | ||
| } | ||
|
|
||
| public Task<string> CreateAuthorizationHeaderForUserAsync(IEnumerable<string> scopes, AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null, ClaimsPrincipal? claimsPrincipal = null, CancellationToken cancellationToken = default) | ||
| { | ||
| return Task.FromResult("Bearer ey"); | ||
| } | ||
|
|
||
| public Task<string> CreateAuthorizationHeaderAsync(IEnumerable<string> scopes, AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null, ClaimsPrincipal? claimsPrincipal = null, CancellationToken cancellationToken = default) | ||
| { | ||
| return Task.FromResult("Bearer ey"); | ||
| } | ||
| } | ||
|
|
||
| private class MyMonitor : IOptionsMonitor<DownstreamApiOptions> | ||
| { | ||
| public DownstreamApiOptions CurrentValue => new DownstreamApiOptions(); | ||
|
|
||
| public DownstreamApiOptions Get(string? name) | ||
| { | ||
| return new DownstreamApiOptions(); | ||
| } | ||
|
|
||
| public DownstreamApiOptions Get(string name, string key) | ||
| { | ||
| return new DownstreamApiOptions(); | ||
| } | ||
|
|
||
| public IDisposable OnChange(Action<DownstreamApiOptions, string> listener) | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
| } | ||
| } | ||
| } |
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.