Skip to content

Commit 0db809f

Browse files
Avery-DunniNinjaCopilot
authored
Cherry-pick: DownstreamApi reserved/duplicate header handling (#3793) (#3806)
Cherry-pick of PR #3793 from master to rel/3v with conflict resolution: - Resolved merge conflicts in DownstreamApi.cs and DownstreamApi.Logger.cs - Updated InternalAPI.Unshipped.txt for all target frameworks - Adapted test method calls from UpdateRequestWithCertificateAsync to UpdateRequestAsync (3.x API) Co-authored-by: Ignacio Inglese <iinglese@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7f8ed3a commit 0db809f

12 files changed

Lines changed: 324 additions & 42 deletions

File tree

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ internal static class Logger
2828
DownstreamApiLoggingEventId.UnauthenticatedApiCall,
2929
"[MsIdWeb] An unauthenticated call was made to the Api with null Scopes");
3030

31+
private static readonly Action<ILogger, string, Exception?> s_reservedHeaderIgnored =
32+
LoggerMessage.Define<string>(
33+
LogLevel.Warning,
34+
DownstreamApiLoggingEventId.ReservedHeaderIgnored,
35+
"[MsIdWeb] Header '{HeaderName}' supplied through ExtraHeaderParameters was ignored because the name is reserved for the library.");
36+
37+
private static readonly Action<ILogger, string, Exception?> s_duplicateHeaderIgnored =
38+
LoggerMessage.Define<string>(
39+
LogLevel.Warning,
40+
DownstreamApiLoggingEventId.DuplicateHeaderIgnored,
41+
"[MsIdWeb] Header '{HeaderName}' supplied through ExtraHeaderParameters was ignored because the request already carries a value for it.");
3142

3243
/// <summary>
3344
/// Logger for handling options exceptions in DownstreamApi.
@@ -52,6 +63,25 @@ public static void HttpRequestError(
5263
public static void UnauthenticatedApiCall(
5364
ILogger logger,
5465
Exception? ex) => s_unauthenticatedApiCall(logger, ex);
66+
67+
/// <summary>
68+
/// Logs that an ExtraHeaderParameters entry was skipped because its name is reserved.
69+
/// </summary>
70+
/// <param name="logger">Logger.</param>
71+
/// <param name="headerName">Header name that was ignored.</param>
72+
public static void ReservedHeaderIgnored(
73+
ILogger logger,
74+
string headerName) => s_reservedHeaderIgnored(logger, headerName, null);
75+
76+
/// <summary>
77+
/// Logs that an ExtraHeaderParameters entry was skipped because the request already
78+
/// carries a value for the same header name.
79+
/// </summary>
80+
/// <param name="logger">Logger.</param>
81+
/// <param name="headerName">Header name that was ignored.</param>
82+
public static void DuplicateHeaderIgnored(
83+
ILogger logger,
84+
string headerName) => s_duplicateHeaderIgnored(logger, headerName, null);
5585
}
5686
}
5787
}

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

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -582,48 +582,63 @@ public Task<HttpResponseMessage> CallApiForAppAsync(
582582
{
583583
Logger.UnauthenticatedApiCall(_logger, null);
584584
}
585-
if (!string.IsNullOrEmpty(effectiveOptions.AcceptHeader))
586-
{
587-
httpRequestMessage.Headers.Accept.ParseAdd(effectiveOptions.AcceptHeader);
588-
}
589-
590-
// Add extra headers if specified directly on DownstreamApiOptions
591-
if (effectiveOptions.ExtraHeaderParameters != null)
592-
{
593-
foreach (var header in effectiveOptions.ExtraHeaderParameters)
594-
{
595-
httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
596-
}
597-
}
598-
599-
// Add extra query parameters if specified directly on DownstreamApiOptions
600-
if (effectiveOptions.ExtraQueryParameters != null && effectiveOptions.ExtraQueryParameters.Count > 0)
601-
{
602-
var uriBuilder = new UriBuilder(httpRequestMessage.RequestUri!);
603-
var existingQuery = uriBuilder.Query;
604-
var queryString = new StringBuilder(existingQuery);
605-
606-
foreach (var queryParam in effectiveOptions.ExtraQueryParameters)
607-
{
608-
if (queryString.Length > 1) // if there are existing query parameters
609-
{
610-
queryString.Append('&');
611-
}
612-
else if (queryString.Length == 0)
613-
{
614-
queryString.Append('?');
615-
}
616-
617-
queryString.Append(Uri.EscapeDataString(queryParam.Key));
618-
queryString.Append('=');
619-
queryString.Append(Uri.EscapeDataString(queryParam.Value));
620-
}
621-
622-
uriBuilder.Query = queryString.ToString().TrimStart('?');
623-
httpRequestMessage.RequestUri = uriBuilder.Uri;
624-
}
625-
626-
// Opportunity to change the request message
585+
586+
if (!string.IsNullOrEmpty(effectiveOptions.AcceptHeader))
587+
{
588+
httpRequestMessage.Headers.Accept.ParseAdd(effectiveOptions.AcceptHeader);
589+
}
590+
591+
// Add extra headers if specified directly on DownstreamApiOptions.
592+
// Skip names that are reserved for the library or already present on
593+
// the outgoing request to keep the library-set values authoritative.
594+
if (effectiveOptions.ExtraHeaderParameters != null)
595+
{
596+
foreach (var header in effectiveOptions.ExtraHeaderParameters)
597+
{
598+
if (ReservedHeaderNames.IsReserved(header.Key))
599+
{
600+
Logger.ReservedHeaderIgnored(_logger, header.Key);
601+
continue;
602+
}
603+
604+
if (httpRequestMessage.Headers.Contains(header.Key))
605+
{
606+
Logger.DuplicateHeaderIgnored(_logger, header.Key);
607+
continue;
608+
}
609+
610+
httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
611+
}
612+
}
613+
614+
// Add extra query parameters if specified directly on DownstreamApiOptions
615+
if (effectiveOptions.ExtraQueryParameters != null && effectiveOptions.ExtraQueryParameters.Count > 0)
616+
{
617+
var uriBuilder = new UriBuilder(httpRequestMessage.RequestUri!);
618+
var existingQuery = uriBuilder.Query;
619+
var queryString = new StringBuilder(existingQuery);
620+
621+
foreach (var queryParam in effectiveOptions.ExtraQueryParameters)
622+
{
623+
if (queryString.Length > 1) // if there are existing query parameters
624+
{
625+
queryString.Append('&');
626+
}
627+
else if (queryString.Length == 0)
628+
{
629+
queryString.Append('?');
630+
}
631+
632+
queryString.Append(Uri.EscapeDataString(queryParam.Key));
633+
queryString.Append('=');
634+
queryString.Append(Uri.EscapeDataString(queryParam.Value));
635+
}
636+
637+
uriBuilder.Query = queryString.ToString().TrimStart('?');
638+
httpRequestMessage.RequestUri = uriBuilder.Uri;
639+
}
640+
641+
// Opportunity to change the request message
627642
effectiveOptions.CustomizeHttpRequestMessage?.Invoke(httpRequestMessage);
628643
}
629644

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ internal static class DownstreamApiLoggingEventId
1111
// DownstreamApi EventIds 100+
1212
public static readonly EventId HttpRequestError = new(100, "HttpRequestError");
1313
public static readonly EventId UnauthenticatedApiCall = new(101, "UnauthenticatedApiCall");
14+
public static readonly EventId ReservedHeaderIgnored = new(102, "ReservedHeaderIgnored");
15+
public static readonly EventId DuplicateHeaderIgnored = new(103, "DuplicateHeaderIgnored");
1416
#pragma warning restore IDE1006 // Naming styles
1517
}
1618
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
#nullable enable
2+
Microsoft.Identity.Web.ReservedHeaderNames
3+
static Microsoft.Identity.Web.DownstreamApi.Logger.DuplicateHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
4+
static Microsoft.Identity.Web.DownstreamApi.Logger.ReservedHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
5+
static Microsoft.Identity.Web.ReservedHeaderNames.IsReserved(string! headerName) -> bool
6+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.DuplicateHeaderIgnored -> Microsoft.Extensions.Logging.EventId
7+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.ReservedHeaderIgnored -> Microsoft.Extensions.Logging.EventId
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
#nullable enable
2+
Microsoft.Identity.Web.ReservedHeaderNames
3+
static Microsoft.Identity.Web.DownstreamApi.Logger.DuplicateHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
4+
static Microsoft.Identity.Web.DownstreamApi.Logger.ReservedHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
5+
static Microsoft.Identity.Web.ReservedHeaderNames.IsReserved(string! headerName) -> bool
6+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.DuplicateHeaderIgnored -> Microsoft.Extensions.Logging.EventId
7+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.ReservedHeaderIgnored -> Microsoft.Extensions.Logging.EventId
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
#nullable enable
2+
Microsoft.Identity.Web.ReservedHeaderNames
3+
static Microsoft.Identity.Web.DownstreamApi.Logger.DuplicateHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
4+
static Microsoft.Identity.Web.DownstreamApi.Logger.ReservedHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
5+
static Microsoft.Identity.Web.ReservedHeaderNames.IsReserved(string! headerName) -> bool
6+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.DuplicateHeaderIgnored -> Microsoft.Extensions.Logging.EventId
7+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.ReservedHeaderIgnored -> Microsoft.Extensions.Logging.EventId
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
#nullable enable
2+
Microsoft.Identity.Web.ReservedHeaderNames
3+
static Microsoft.Identity.Web.DownstreamApi.Logger.DuplicateHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
4+
static Microsoft.Identity.Web.DownstreamApi.Logger.ReservedHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
5+
static Microsoft.Identity.Web.ReservedHeaderNames.IsReserved(string! headerName) -> bool
6+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.DuplicateHeaderIgnored -> Microsoft.Extensions.Logging.EventId
7+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.ReservedHeaderIgnored -> Microsoft.Extensions.Logging.EventId
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
#nullable enable
2+
Microsoft.Identity.Web.ReservedHeaderNames
3+
static Microsoft.Identity.Web.DownstreamApi.Logger.DuplicateHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
4+
static Microsoft.Identity.Web.DownstreamApi.Logger.ReservedHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
5+
static Microsoft.Identity.Web.ReservedHeaderNames.IsReserved(string! headerName) -> bool
6+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.DuplicateHeaderIgnored -> Microsoft.Extensions.Logging.EventId
7+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.ReservedHeaderIgnored -> Microsoft.Extensions.Logging.EventId
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
#nullable enable
2+
Microsoft.Identity.Web.ReservedHeaderNames
3+
static Microsoft.Identity.Web.DownstreamApi.Logger.DuplicateHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
4+
static Microsoft.Identity.Web.DownstreamApi.Logger.ReservedHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
5+
static Microsoft.Identity.Web.ReservedHeaderNames.IsReserved(string! headerName) -> bool
6+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.DuplicateHeaderIgnored -> Microsoft.Extensions.Logging.EventId
7+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.ReservedHeaderIgnored -> Microsoft.Extensions.Logging.EventId
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
#nullable enable
2+
Microsoft.Identity.Web.ReservedHeaderNames
3+
static Microsoft.Identity.Web.DownstreamApi.Logger.DuplicateHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
4+
static Microsoft.Identity.Web.DownstreamApi.Logger.ReservedHeaderIgnored(Microsoft.Extensions.Logging.ILogger! logger, string! headerName) -> void
5+
static Microsoft.Identity.Web.ReservedHeaderNames.IsReserved(string! headerName) -> bool
6+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.DuplicateHeaderIgnored -> Microsoft.Extensions.Logging.EventId
7+
static readonly Microsoft.Identity.Web.DownstreamApiLoggingEventId.ReservedHeaderIgnored -> Microsoft.Extensions.Logging.EventId

0 commit comments

Comments
 (0)