Skip to content
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

[Authentication] updates to support Arlington #3734

Merged
merged 14 commits into from
Apr 29, 2020
Merged

Conversation

willportnoy
Copy link
Member

No description provided.

@willportnoy willportnoy added the R9 Release 9 - May 15th, 2020 label Apr 14, 2020
@@ -217,6 +221,9 @@ private bool IsAdalServiceUnavailable(Exception ex)
return adalServiceException.ErrorCode == MsalTemporarilyUnavailable || adalServiceException.StatusCode == 429;
}

private bool IsAdalServiceInvalidRequest(Exception ex)
=> ex is AdalServiceException adal && adal.StatusCode == 400;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we only need to care about 400's or 4xx in general or perhaps not 2xx? is there any documentation around it that you refereed for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I verified the 400 for Arlington bot requesting against Fairfax resource app.

I have not yet verified the status code for Fairfax bot requesting against the Arlington resource app.

I didn't see documentation, unfortunately.


if (OAuthScope == GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope)
{
var optionOld = Make(GovernmentAuthenticationConstants.ToChannelFromBotLoginUrl, GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of calling them old and new, consider maintaining a list instead...who knows if there'll be a new "new" later

}
}

if (info != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slim down to info?.throw

var inner = this.inners[index];
try
{
return await inner.GetTokenAsync(forceRefresh).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest remembering which inner works and using that for further calls. it's just extra work to always loop through

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this needs to be thread safe, I was opting for simplicity.

var optionOld = Make(GovernmentAuthenticationConstants.ToChannelFromBotLoginUrl, GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope);
var optionNew = Make("https://login.microsoftonline.us/botframework.onmicrosoft.us", "https://api.botframework.onmicrosoft.us");

return new Authenticators(optionOld, optionNew);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep optionNew as your first argument. almost all new bots going forward will be in that new tenant and hence trying the new one first will make sure that the for loop doesnt execute more than one iteration for most new bots

/// <summary>
/// TO GOVERNMENT CHANNEL FROM BOT: Login URL V2.
/// </summary>
public const string ToChannelFromBotLoginUrlV2 = "https://login.microsoftonline.us/botframework.onmicrosoft.us";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@cleemullins cleemullins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no unit tests. here, despite quite a bit of code churn. How do we test this?

{
internal sealed class Authenticators : IAuthenticator
{
private readonly IAuthenticator[] inners;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming convention. "_inners"


public Authenticators(params IAuthenticator[] inners)
{
this.inners = inners ?? throw new ArgumentNullException(nameof(inners));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coding standard here is just "_inners = inners ?? [...]"

}

private bool IsAdalServiceInvalidRequest(Exception ex)
=> ex is AdalServiceException adal && adal.StatusCode == (int)HttpStatusCode.BadRequest;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not at all clear on what this line of code is doing. Can you add a nice multi-line comment.

If nothing else, BadRequest and InvalidRequest are two very different things.

Also, the complexity of this if statement is staggering, as I've got to know the operator presicdence for:

  1. is
  2. &&
  3. ==
  4. (int)

... and understand the more modern C# declare inline syntax.

Please turn this into a simpler method.

@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why adding Arlington as a new auth point requires a new class called "Authenticators". CAn you elaborte with a nice long

comment on the class?

{
ExceptionDispatchInfo info = null;

for (int index = 0; index < this.inners.Length; ++index)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't follow this. I'm lost across "info, index, this.inners, and inners".

Please consider easier variable names.

/// <summary>
/// TO GOVERNMENT CHANNEL FROM BOT: OAuth scope to request V2.
/// </summary>
public const string ToChannelFromBotOAuthScopeV2 = "https://api.botframework.onmicrosoft.us";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is really the only line I expected to see added.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the hardcoded tenant above (cab8a31a-1906-4287-a0d8-4eef66b95f6e - USME) that needed to be updated to a different tenant. And then

  1. the scope (api.botframework.onmicrosoft.us) is trivially name-spaced within the resource app identifier (api.botframework.onmicrosoft.us), and
  2. the app identifier (api.botframework.onmicrosoft.us) is name-spaced within the tenant domain (botframework.onmicrosoft.us, which we may modify to a custom domain botframework.azure.us to match our DNS name).

@@ -62,5 +65,34 @@ public override string OAuthEndpoint
{
get { return GovernmentAuthenticationConstants.ToChannelFromBotLoginUrl; }
}

protected override Lazy<IAuthenticator> BuildIAuthenticator()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot follow this method, and all of it's nested lambda functions.

Please simplify.

return Make(OAuthEndpoint, OAuthScope);
}
},
LazyThreadSafetyMode.ExecutionAndPublication);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this, and are we sure it's right? I have no idea what it does.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm following the existing style:

}
else
{
return Make(OAuthEndpoint, OAuthScope);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get how this branch of the "IF" statement returns an "Make()" function result, while the other returns a new set of authenticators.

... yet the parent function needs the lazy.

There's got to be a more clear way to express this to simple minds like mine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carlosscastro can you give some insight into the need for the two methods (with Lazy AdalAuthenticator and Lazy IAuthenticator)? I tried to copy the existing code pattern from MicrosoftAppCredentials.

@@ -225,7 +251,7 @@ private RetryParams ComputeAdalRetry(Exception ex)

// When the Service Token Server (STS) is too busy because of “too many requests”,
// it returns an HTTP error 429 with a hint about when you can try again (Retry-After response field) as a delay in seconds
if (adalServiceException.ErrorCode == MsalTemporarilyUnavailable || adalServiceException.StatusCode == 429)
if (adalServiceException.ErrorCode == MsalTemporarilyUnavailable || adalServiceException.StatusCode == HttpTooManyRequests)
Copy link
Contributor

@tomlm tomlm Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpTooManyRequests [](start = 119, length = 19)

(int)HttpStatusCode.TooManyRequests.

@@ -214,7 +220,27 @@ private bool IsAdalServiceUnavailable(Exception ex)

// When the Service Token Server (STS) is too busy because of “too many requests”,
// it returns an HTTP error 429
return adalServiceException.ErrorCode == MsalTemporarilyUnavailable || adalServiceException.StatusCode == 429;
return adalServiceException.ErrorCode == MsalTemporarilyUnavailable || adalServiceException.StatusCode == HttpTooManyRequests;
Copy link
Contributor

@tomlm tomlm Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpTooManyRequests [](start = 118, length = 19)

(int)HttpStatusCode.TooManyRequests

Copy link
Contributor

@tomlm tomlm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@tomlm tomlm added the Draft PR label Apr 15, 2020
@willportnoy willportnoy marked this pull request as ready for review April 29, 2020 00:33
@@ -16,7 +16,7 @@ public static class GovernmentAuthenticationConstants
/// <summary>
/// TO GOVERNMENT CHANNEL FROM BOT: Login URL.
/// </summary>
public const string ToChannelFromBotLoginUrl = "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e";
public const string ToChannelFromBotLoginUrl = "https://login.microsoftonline.us/MicrosoftServices.onmicrosoft.us";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does casing matter here? URI Segments are normally not case sensitive, but I'm worried the casing here might cause comparison issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
R9 Release 9 - May 15th, 2020
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants