-
Notifications
You must be signed in to change notification settings - Fork 334
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
Refactored WwwAuthenticateParameters #2907
Changes from 17 commits
45632d8
7081af9
520b92b
be08f01
d202fa8
db14929
52067f0
1b81ee1
dccfd49
79550f8
2ca6bf2
639403b
b27c449
e72fe5e
e36ca40
1782f3d
5393d35
a3d7f15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
|
@@ -7,7 +7,9 @@ | |
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Identity.Client.PlatformsCommon.Factories; | ||
|
||
namespace Microsoft.Identity.Client | ||
{ | ||
|
@@ -32,7 +34,7 @@ public IEnumerable<string> Scopes | |
{ | ||
get | ||
{ | ||
if (_scopes != null) | ||
if (_scopes is object) | ||
{ | ||
return _scopes; | ||
} | ||
|
@@ -134,13 +136,52 @@ public static WwwAuthenticateParameters CreateFromWwwAuthenticateHeaderValue(str | |
/// Create the authenticate parameters by attempting to call the resource unauthenticated, and analyzing the response. | ||
/// </summary> | ||
/// <param name="resourceUri">URI of the resource.</param> | ||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param> | ||
/// <returns>WWW-Authenticate Parameters extracted from response to the un-authenticated call.</returns> | ||
public static async Task<WwwAuthenticateParameters> CreateFromResourceResponseAsync(string resourceUri) | ||
public static Task<WwwAuthenticateParameters> CreateFromResourceResponseAsync(string resourceUri, CancellationToken cancellationToken = default) | ||
{ | ||
var httpClientFactory = PlatformProxyFactory.CreatePlatformProxy(null).CreateDefaultHttpClientFactory(); | ||
abatishchev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return CreateFromResourceResponseAsync(httpClientFactory, resourceUri, cancellationToken); | ||
} | ||
|
||
/// <summary> | ||
/// Create the authenticate parameters by attempting to call the resource unauthenticated, and analyzing the response. | ||
/// </summary> | ||
/// <param name="httpClientFactory">Factory to produce an instance of <see cref="HttpClient"/> to make the request with.</param> | ||
/// <param name="resourceUri">URI of the resource.</param> | ||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param> | ||
/// <returns>WWW-Authenticate Parameters extracted from response to the un-authenticated call.</returns> | ||
public static Task<WwwAuthenticateParameters> CreateFromResourceResponseAsync(IMsalHttpClientFactory httpClientFactory, string resourceUri, CancellationToken cancellationToken = default) | ||
{ | ||
if (httpClientFactory is null) | ||
{ | ||
throw new ArgumentException($"'{nameof(httpClientFactory)}' cannot be null.", nameof(httpClientFactory)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: We typically use ArgumentNullException for these types of checks on the api surface. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I noticed that too. Let me explain. The only already existing check is for resourceId for not null or empty or white space. And it throws just In this PR I'm adding two more checks, both are for reference types so I'm just checking for not null. So what I would do is to throw There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we do that as well in most places, i.e. throw Strings should really not be nullable :), but unfortunately we are not a library that uses the nullable references feature, so we have to live with these small inconsistencies. |
||
} | ||
|
||
var httpClient = httpClientFactory.GetHttpClient(); | ||
return CreateFromResourceResponseAsync(httpClient, resourceUri, cancellationToken); | ||
} | ||
|
||
/// <summary> | ||
/// Create the authenticate parameters by attempting to call the resource unauthenticated, and analyzing the response. | ||
/// </summary> | ||
/// <param name="httpClient">Instance of <see cref="HttpClient"/> to make the request with.</param> | ||
/// <param name="resourceUri">URI of the resource.</param> | ||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param> | ||
/// <returns>WWW-Authenticate Parameters extracted from response to the un-authenticated call.</returns> | ||
public static async Task<WwwAuthenticateParameters> CreateFromResourceResponseAsync(HttpClient httpClient, string resourceUri, CancellationToken cancellationToken = default) | ||
{ | ||
if (httpClient is null) | ||
{ | ||
throw new ArgumentException($"'{nameof(httpClient)}' cannot be null.", nameof(httpClient)); | ||
} | ||
if (string.IsNullOrWhiteSpace(resourceUri)) | ||
{ | ||
throw new ArgumentException($"'{nameof(resourceUri)}' cannot be null or whitespace.", nameof(resourceUri)); | ||
} | ||
|
||
// call this endpoint and see what the header says and return that | ||
HttpClient httpClient = new HttpClient(); | ||
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, resourceUri); | ||
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); | ||
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(resourceUri, cancellationToken).ConfigureAwait(false); | ||
var wwwAuthParam = CreateFromResponseHeaders(httpResponseMessage.Headers); | ||
return wwwAuthParam; | ||
} | ||
|
@@ -160,31 +201,24 @@ public static async Task<WwwAuthenticateParameters> CreateFromResourceResponseAs | |
httpResponseHeaders, | ||
scheme); | ||
|
||
try | ||
// read the header and checks if it contains an error with insufficient_claims value. | ||
if (string.Equals(parameters.Error, "insufficient_claims", StringComparison.OrdinalIgnoreCase) && | ||
parameters.Claims is object) | ||
{ | ||
// read the header and checks if it contains an error with insufficient_claims value. | ||
if (null != parameters.Error && "insufficient_claims" == parameters.Error) | ||
{ | ||
if (null != parameters.Claims) | ||
{ | ||
return parameters.Claims; | ||
} | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
throw ex; | ||
return parameters.Claims; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
internal static WwwAuthenticateParameters CreateWwwAuthenticateParameters(IDictionary<string, string> values) | ||
{ | ||
WwwAuthenticateParameters wwwAuthenticateParameters = new WwwAuthenticateParameters(); | ||
wwwAuthenticateParameters.RawParameters = values; | ||
string value; | ||
WwwAuthenticateParameters wwwAuthenticateParameters = new WwwAuthenticateParameters | ||
{ | ||
RawParameters = values | ||
}; | ||
|
||
string value; | ||
if (values.TryGetValue("authorization_uri", out value)) | ||
{ | ||
wwwAuthenticateParameters.Authority = value.Replace("/oauth2/authorize", string.Empty); | ||
|
@@ -325,7 +359,7 @@ private static string GetJsonFragment(string inputString) | |
var decoded = Encoding.UTF8.GetString(decodedBase64Bytes); | ||
return decoded; | ||
} | ||
catch (Exception) | ||
catch | ||
{ | ||
return inputString; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not seen this type of check before. I am curious - how does it differ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a new feature in C# 7.0, part of added pattern matching. It bypasses any potential operator overloads (for either
==
and/or!=
) and directly emits the intended IL for null/not null comparison.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: While this is definitely interesting, these checks obfuscate the fact that we are checking for null here. The word "is" usually implies that you are checking if an object is of a certain type, not whether or not it is null so this may be confusing to someone who is not aware of this feature and they may end up adding the null check in addition to this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it's a (relatively) new feature and might be confusing. As any other new feature :)
There are some potential benefits in using it, plus many most popular OSS projects have switched to using it too.
So here's our options:
!= null
and== null
is object
andis null
(my preference)is not null
andis null
(I personally find the former the least readable)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like option 1 or 3.
Thoughts @bgavrilMS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very interesting problem. It looks like "obj is null" is something we can do today. But "object is not null" is a C# 9.0 feature, which we don't currently use (but we probably should).
I agree that "a is object" is a bit counter-intuitive.
According to https://stackoverflow.com/questions/40676426/what-is-the-difference-between-x-is-null-and-x-null it looks like the compiler (Roslyn) was actually modified so that it emits the same kind of logic as long as
==
and!=
are not overloaded (I believe we haven't overloaded them for any object in MSAL). 'So maybe let's keep on using option 1 for consistency with MSAL, and separately we can look at moving to C# 9 and adding some analyzers for this kind of problem. Any changes should be consistent across the lib.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue tracking the analyzer work #2921