Skip to content

Commit b79b38a

Browse files
authored
Merge branch 'master' into lozensky/AuthorityHttpsCheck
2 parents ee04b58 + 2412200 commit b79b38a

File tree

3 files changed

+82
-20
lines changed

3 files changed

+82
-20
lines changed

.github/workflows/dotnetcore.yml

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,25 @@ jobs:
5656
dotnet tool install -g dotnet-reportgenerator-globaltool --version 5.4.1
5757
reportgenerator -reports:./**/coverage.cobertura.xml -targetdir:CodeCoverage -reporttypes:'MarkdownSummaryGithub;Cobertura' -filefilters:'+src/**/*.cs'
5858
59-
- name: Write coverage to job summary
60-
shell: bash
61-
run: |
62-
cat CodeCoverage/SummaryGithub.md >> $GITHUB_STEP_SUMMARY
63-
echo "COMMENT_CONTENT_ENV_VAR<<EOF" >> $GITHUB_ENV
64-
echo $(cat CodeCoverage/SummaryGithub.md) >> $GITHUB_ENV
65-
echo "EOF" >> $GITHUB_ENV
66-
67-
- name: Comment coverage in PR
68-
uses: actions/github-script@v7
69-
id: comment
70-
with:
71-
script: |
72-
github.rest.issues.createComment({
73-
issue_number: context.issue.number,
74-
owner: context.repo.owner,
75-
repo: context.repo.repo,
76-
body: process.env.COMMENT_CONTENT_ENV_VAR
77-
})
59+
# - name: Write coverage to job summary
60+
# shell: bash
61+
# run: |
62+
# cat CodeCoverage/SummaryGithub.md >> $GITHUB_STEP_SUMMARY
63+
# echo "COMMENT_CONTENT_ENV_VAR<<EOF" >> $GITHUB_ENV
64+
# echo $(cat CodeCoverage/SummaryGithub.md) >> $GITHUB_ENV
65+
# echo "EOF" >> $GITHUB_ENV
66+
67+
# - name: Comment coverage in PR
68+
# uses: actions/github-script@v7
69+
# id: comment
70+
# with:
71+
# script: |
72+
# github.rest.issues.createComment({
73+
# issue_number: context.issue.number,
74+
# owner: context.repo.owner,
75+
# repo: context.repo.repo,
76+
# body: process.env.COMMENT_CONTENT_ENV_VAR
77+
# })
7878

7979
- name: Test with .NET 462
8080
run: dotnet test --no-restore --no-build Microsoft.Identity.Web.sln -f net462 -v normal -p:FROM_GITHUB_ACTION=true --configuration Release --filter "(FullyQualifiedName!~Microsoft.Identity.Web.Test.Integration)&(FullyQualifiedName!~WebAppUiTests)&(FullyQualifiedName!~IntegrationTests)"

src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal partial class DownstreamApi : IDownstreamApi
2828
private readonly IOptionsMonitor<DownstreamApiOptions> _namedDownstreamApiOptions;
2929
private const string Authorization = "Authorization";
3030
protected readonly ILogger<DownstreamApi> _logger;
31+
private const string AuthSchemeDstsSamlBearer = "http://schemas.microsoft.com/dsts/saml2-bearer";
3132

3233
/// <summary>
3334
/// Constructor.
@@ -522,7 +523,15 @@ public Task<HttpResponseMessage> CallApiForAppAsync(
522523
user,
523524
cancellationToken).ConfigureAwait(false);
524525

525-
httpRequestMessage.Headers.Add(Authorization, authorizationHeader);
526+
if (authorizationHeader.StartsWith(AuthSchemeDstsSamlBearer, StringComparison.OrdinalIgnoreCase))
527+
{
528+
// TryAddWithoutValidation method bypasses strict validation, allowing non-standard headers to be added for custom Header schemes that cannot be parsed.
529+
httpRequestMessage.Headers.TryAddWithoutValidation(Authorization, authorizationHeader);
530+
}
531+
else
532+
{
533+
httpRequestMessage.Headers.Add(Authorization, authorizationHeader);
534+
}
526535
}
527536
else
528537
{

tests/Microsoft.Identity.Web.Test/DownstreamWebApiSupport/DownstreamApiTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@ namespace Microsoft.Identity.Web.Tests
2424
public class DownstreamApiTests
2525
{
2626
private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider;
27+
private readonly IAuthorizationHeaderProvider _authorizationHeaderProviderSaml;
2728
private readonly IHttpClientFactory _httpClientFactory;
2829
private readonly IOptionsMonitor<DownstreamApiOptions> _namedDownstreamApiOptions;
2930
private readonly ILogger<DownstreamApi> _logger;
3031
private readonly DownstreamApi _input;
32+
private readonly DownstreamApi _inputSaml;
3133

3234
public DownstreamApiTests()
3335
{
3436
_authorizationHeaderProvider = new MyAuthorizationHeaderProvider();
37+
_authorizationHeaderProviderSaml = new MySamlAuthorizationHeaderProvider();
3538
_httpClientFactory = new HttpClientFactoryTest();
3639
_namedDownstreamApiOptions = new MyMonitor();
3740
_logger = new LoggerFactory().CreateLogger<DownstreamApi>();
@@ -41,6 +44,12 @@ public DownstreamApiTests()
4144
_namedDownstreamApiOptions,
4245
_httpClientFactory,
4346
_logger);
47+
48+
_inputSaml = new DownstreamApi(
49+
_authorizationHeaderProviderSaml,
50+
_namedDownstreamApiOptions,
51+
_httpClientFactory,
52+
_logger);
4453
}
4554

4655
[Fact]
@@ -123,6 +132,32 @@ public async Task UpdateRequestAsync_WithScopes_AddsAuthorizationHeaderToRequest
123132
Assert.Equal(options.AcquireTokenOptions.ExtraQueryParameters, DownstreamApi.CallerSDKDetails);
124133
}
125134

135+
[Theory]
136+
[InlineData(true)]
137+
[InlineData(false)]
138+
139+
public async Task UpdateRequestAsync_WithScopes_AddsSamlAuthorizationHeaderToRequestAsync(bool appToken)
140+
{
141+
// Arrange
142+
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.com");
143+
var content = new StringContent("test content");
144+
var options = new DownstreamApiOptions
145+
{
146+
Scopes = ["scope1", "scope2"],
147+
BaseUrl = "https://localhost:44321/WeatherForecast"
148+
};
149+
var user = new ClaimsPrincipal();
150+
151+
// Act
152+
await _inputSaml.UpdateRequestAsync(httpRequestMessage, content, options, appToken, user, CancellationToken.None);
153+
154+
// Assert
155+
Assert.True(httpRequestMessage.Headers.Contains("Authorization"));
156+
Assert.Equal("http://schemas.microsoft.com/dsts/saml2-bearer ey", httpRequestMessage.Headers.GetValues("Authorization").FirstOrDefault());
157+
Assert.Equal("application/json", httpRequestMessage.Headers.Accept.Single().MediaType);
158+
Assert.Equal(options.AcquireTokenOptions.ExtraQueryParameters, DownstreamApi.CallerSDKDetails);
159+
}
160+
126161
[Fact]
127162
public void SerializeInput_ReturnsCorrectHttpContent()
128163
{
@@ -420,5 +455,23 @@ public Task<string> CreateAuthorizationHeaderAsync(IEnumerable<string> scopes, A
420455
return Task.FromResult("Bearer ey");
421456
}
422457
}
458+
459+
public class MySamlAuthorizationHeaderProvider : IAuthorizationHeaderProvider
460+
{
461+
public Task<string> CreateAuthorizationHeaderForAppAsync(string scopes, AuthorizationHeaderProviderOptions? downstreamApiOptions = null, CancellationToken cancellationToken = default)
462+
{
463+
return Task.FromResult("http://schemas.microsoft.com/dsts/saml2-bearer ey");
464+
}
465+
466+
public Task<string> CreateAuthorizationHeaderForUserAsync(IEnumerable<string> scopes, AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null, ClaimsPrincipal? claimsPrincipal = null, CancellationToken cancellationToken = default)
467+
{
468+
return Task.FromResult("http://schemas.microsoft.com/dsts/saml2-bearer ey");
469+
}
470+
471+
public Task<string> CreateAuthorizationHeaderAsync(IEnumerable<string> scopes, AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null, ClaimsPrincipal? claimsPrincipal = null, CancellationToken cancellationToken = default)
472+
{
473+
return Task.FromResult("http://schemas.microsoft.com/dsts/saml2-bearer ey");
474+
}
475+
}
423476
}
424477

0 commit comments

Comments
 (0)