-
Notifications
You must be signed in to change notification settings - Fork 644
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
Update External Account Linking Flow #5338
Changes from 8 commits
861b9ae
5020916
83decaa
ae86e64
f1f1e62
2c0fcf5
eef55c8
268601d
02421e3
c59bfed
a671794
d077f72
b39f83a
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 |
---|---|---|
|
@@ -83,7 +83,16 @@ public override ActionResult Challenge(string redirectUrl) | |
|
||
public override IdentityInformation GetIdentityInformation(ClaimsIdentity claimsIdentity) | ||
{ | ||
return ClaimsExtentions.GetIdentityInformation(claimsIdentity, DefaultAuthenticationType); | ||
var identityInfo = ClaimsExtentions.GetIdentityInformation(claimsIdentity, DefaultAuthenticationType); | ||
|
||
// The claims returned by AzureActiveDirectory have the email as the name claim are missing the email claim. | ||
// Copy the object returned by the method but set the email as the name. | ||
return new IdentityInformation( | ||
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. I think the correct fix here would be to extract the name claim correctly from the 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. Fixed it. All of the AAD on dev (and probably also int and prod as well) have incorrect identities as well. |
||
identityInfo.Identifier, | ||
identityInfo.Name, | ||
identityInfo.Name, | ||
identityInfo.AuthenticationType, | ||
identityInfo.TenantId); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,18 +144,22 @@ public virtual async Task<ActionResult> SignIn(LogOnViewModel model, string retu | |
modelErrorMessage = string.Format(CultureInfo.CurrentCulture, Strings.UserAccountLocked, timeRemaining); | ||
} | ||
|
||
ModelState.AddModelError("SignIn", modelErrorMessage); | ||
|
||
return SignInOrExternalLinkView(model, linkingAccount); | ||
return SignInFailure(model, linkingAccount, modelErrorMessage); | ||
} | ||
|
||
var user = authenticationResult.AuthenticatedUser; | ||
var authenticatedUser = authenticationResult.AuthenticatedUser; | ||
|
||
if (linkingAccount) | ||
{ | ||
// Verify account has no other external accounts | ||
if (authenticatedUser.User.Credentials.Any(c => c.IsExternal()) && !authenticatedUser.User.IsAdministrator()) | ||
{ | ||
return SignInFailure(model, linkingAccount, Strings.LinkingMultipleExternalAccountsUnsupported); | ||
} | ||
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. Do you need this check here? Isn't this failure covered in Why 2 different error messages for these checks? 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. This is for the form POST, not the initial callback. There needs to be verification here in case somebody hacks the form to put the wrong value in. The messages are basically the same, but adapted for the context in which they appear. |
||
|
||
// Link with an external account | ||
user = await AssociateCredential(user); | ||
if (user == null) | ||
authenticatedUser = await AssociateCredential(authenticatedUser); | ||
if (authenticatedUser == null) | ||
{ | ||
return ExternalLinkExpired(); | ||
} | ||
|
@@ -165,16 +169,23 @@ public virtual async Task<ActionResult> SignIn(LogOnViewModel model, string retu | |
// to require a specific authentication provider, challenge that provider if needed. | ||
ActionResult challenge; | ||
if (ShouldChallengeEnforcedProvider( | ||
NuGetContext.Config.Current.EnforcedAuthProviderForAdmin, user, returnUrl, out challenge)) | ||
NuGetContext.Config.Current.EnforcedAuthProviderForAdmin, authenticatedUser, returnUrl, out challenge)) | ||
{ | ||
return challenge; | ||
} | ||
|
||
// Create session | ||
await _authService.CreateSessionAsync(OwinContext, user); | ||
await _authService.CreateSessionAsync(OwinContext, authenticatedUser); | ||
return SafeRedirect(returnUrl); | ||
} | ||
|
||
private ActionResult SignInFailure(LogOnViewModel model, bool linkingAccount, string modelErrorMessage) | ||
{ | ||
ModelState.AddModelError("SignIn", modelErrorMessage); | ||
|
||
return SignInOrExternalLinkView(model, linkingAccount); | ||
} | ||
|
||
internal bool ShouldChallengeEnforcedProvider(string enforcedProviders, AuthenticatedUser authenticatedUser, string returnUrl, out ActionResult challenge) | ||
{ | ||
if (!string.IsNullOrEmpty(enforcedProviders) | ||
|
@@ -362,9 +373,7 @@ public virtual async Task<ActionResult> LinkExternalAccount(string returnUrl) | |
{ | ||
// Consume the exception for now, for backwards compatibility to previous MSA provider. | ||
email = result.ExternalIdentity.GetClaimOrDefault(ClaimTypes.Email); | ||
name = result | ||
.ExternalIdentity | ||
.GetClaimOrDefault(ClaimTypes.Name); | ||
name = result.ExternalIdentity.GetClaimOrDefault(ClaimTypes.Name); | ||
} | ||
|
||
// Check for a user with this email address | ||
|
@@ -374,11 +383,33 @@ public virtual async Task<ActionResult> LinkExternalAccount(string returnUrl) | |
existingUser = _userService.FindByEmailAddress(email); | ||
} | ||
|
||
var foundExistingUser = existingUser != null; | ||
string existingUserLinkingError = null; | ||
|
||
if (foundExistingUser) | ||
{ | ||
if (existingUser is Organization) | ||
{ | ||
existingUserLinkingError = string.Format( | ||
CultureInfo.CurrentCulture, | ||
Strings.LinkingOrganizationUnsupported, | ||
email); | ||
} | ||
else if (existingUser.Credentials.Any(c => c.IsExternal()) && !existingUser.IsAdministrator()) | ||
{ | ||
existingUserLinkingError = string.Format( | ||
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. Would put the 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. done |
||
CultureInfo.CurrentCulture, | ||
Strings.AccountIsLinkedToAnotherExternalAccount, | ||
email); | ||
} | ||
} | ||
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. Would remove this block and just do string.Format when setting 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. When we implement more error messages with different format strings we can do that, but right now I don't see a reason to do so. |
||
|
||
var external = new AssociateExternalAccountViewModel() | ||
{ | ||
ProviderAccountNoun = authUI.AccountNoun, | ||
AccountName = name, | ||
FoundExistingUser = existingUser != null | ||
FoundExistingUser = foundExistingUser, | ||
ExistingUserLinkingError = existingUserLinkingError | ||
}; | ||
|
||
var model = new LogOnViewModel | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -736,4 +736,13 @@ For more information, please contact '{2}'.</value> | |
<data name="TransformAccount_RequestExists" xml:space="preserve"> | ||
<value>Another tranform request was created on {0} with organization admin '{1}'.</value> | ||
</data> | ||
<data name="AccountIsLinkedToAnotherExternalAccount" xml:space="preserve"> | ||
<value>We found an account with the email address associated with this external account ({0}), but linked to a different external account.</value> | ||
</data> | ||
<data name="LinkingMultipleExternalAccountsUnsupported" xml:space="preserve"> | ||
<value>You cannot link your NuGet.org account to multiple external accounts.</value> | ||
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. Should this direct user to Account page to unlink other external accounts? 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. @anangaur was working on the messaging--not sure the status of it. I suggested doing something similar to him or at least a message explaining how to do so. 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. Please run through the flow for strings with @anangaur once before merging. |
||
</data> | ||
<data name="LinkingOrganizationUnsupported" xml:space="preserve"> | ||
<value>We found an organization with the email address associated with this external account ({0}), but organizations cannot be linked to external accounts.</value> | ||
</data> | ||
</root> |
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.
@scottbommarito , I understand you wanted to make the methods use shared code, but in this case, since it's only a single line (credential.Type.StartsWith()) the result is overly complex. For example: IsPassword(Credential), instead of having a single line, now calls into IsPassword(type) => IsSubType() => string.StartsWith()
It's hard to read and not necessary.
Please undo this change.
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 initially agreed with you and was going to make this change, but then I realized that despite the fact that it does create deeper call stacks, it also substantially reduces the code because there is no longer the need for every method to have an
if (type == null) throw new ArgumentNullException
in addition to the fact that it unifies all of theseStartsWith
andEquals
calls.@shishirx34's PR, that came after this one, also touches this code and creates about 20 lines for something that could be closer to 10 with this restructuring. (https://github.com/NuGet/NuGetGallery/pull/5355/files#diff-624219da9e2d31ff3446f6c5be30fe20R43)
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 think the point shouldn't always be to reduce the number of lines if it makes the code unreadable. If few lines to a non-often changing code are repetitive its fine if they are duplicated. I agree with @skofman1 and feel like this is an unnecessary refactoring leading to hard to read code, please undo these changes and keep the code easy to read.
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.
changed to be one-liners that return false if credential or type is null