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

Organizations: transform account on confirmation #5228

Merged
merged 8 commits into from
Jan 9, 2018

Conversation

chenriksson
Copy link
Member

@chenriksson chenriksson commented Jan 3, 2018

  • Added embedded resource SQL script for account migration
  • Added UsersController confirmation action
  • Added UserService migration API
  • Added IDatabase for test mocks

Screenshot of error, if transform fails. Will confirm messages with @anangaur before merging.
image

Copy link
Contributor

@scottbommarito scottbommarito left a comment

Choose a reason for hiding this comment

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

Looks good in general, I'm just curious why a SQL script was necessary instead of using entity framework directly. Whatever the reason, it should be in a comment in the code.


[HttpGet]
[Authorize]
public virtual async Task<ActionResult> ConfirmTransform(string accountName, string token)
Copy link
Contributor

Choose a reason for hiding this comment

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

accountName should probably be accountToTransform for clarity

@@ -0,0 +1,47 @@
-- Copyright (c) .NET Foundation. 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.

Why is this a script and not done through EntityFramework?

I can't see anything here that would make it necessary to use a script. We've done transactions through EF before.

Copy link
Member Author

Choose a reason for hiding this comment

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

EF has a limitation with inheritance in that it doesn't support changing an object's type. It must be done with SQL. I can add a comment in UserService.TransformToOrganizationAccount.

Copy link
Member Author

Choose a reason for hiding this comment

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

Regarding transactions, note that by using a sproc here I also don't need a custom transaction and suspended execution strategy in the Gallery code.

The disadvantage of suspending execution strategy in Gallery code is that I believe it disables SqlAzure strategy defaults such as retry logic.


BEGIN TRANSACTION
BEGIN TRY
-- Ensure Organizations do not have credentials or memberships
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't "ensuring", this is wiping any of these existing entities if they exist.

IMO we should fail the migration if the user has any memberships--that would suggest that the user was already an organization, which is not something we should just ignore.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds reasonable... I'll add a SELECT outside the transaction and fail fast if the account has any memberships.

Gallery-side, we should also check for memberships before allowing migration. Account would need to be removed from organization(s) to be eligible for migration.


private bool CanTransformIntoOrganization(User user)
{
if (!user.Confirmed || user.IsAdministrator())
Copy link
Contributor

Choose a reason for hiding this comment

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

I would assume if that user was already an organization, we would also want to reject the transformation. Any reason this condition isn't also filtered here?

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 could add additional validation here to guard against it.

@@ -688,4 +688,25 @@ For more information, please contact '{2}'.</value>
<data name="PackageIsLocked" xml:space="preserve">
<value>Package '{0}' has been locked. This means you cannot publish a new version or change the listing status of a published package version. Please contact support@nuget.org.</value>
</data>
<data name="TransformAccount_AdminDoesNotHaveTenantId" xml:space="preserve">
<value>Failed to transform the account because you do not have an Azure AD sign-in with tenant ID.</value>
Copy link
Contributor

Choose a reason for hiding this comment

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

How about: You account must have an Azure AD sign-in with a tenant ID.

Copy link
Member Author

Choose a reason for hiding this comment

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

This would be shown on confirmation (admin sign-in), so it's not 'Your account'.

/cc @anangaur

Anand, let me know if you want to provide the exact error messages for these migration failure scenarios. Spec didn't go into details on this.

<value>Your account must be confirmed before you can create an organization.</value>
</data>
<data name="TransformAccount_DatabaseError" xml:space="preserve">
<value>Failed to transform the account due to a database error.</value>
Copy link
Contributor

Choose a reason for hiding this comment

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

"Database error" is a little bit cryptic to the average user.

How about: There was an unexpected error in transforming the account.

You can also throw in Contact support or something like that if this is something they should contact us about.

Copy link
Member Author

Choose a reason for hiding this comment

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

This would happen if the migration request didn't exist, a DB constraint prevented the update, etc. Probably not very likely. I'm ok with 'unexpected error'.

<h1>
Transform Account: Failed
</h1>
@if (TempData.ContainsKey("TransformError"))
Copy link
Contributor

Choose a reason for hiding this comment

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

If there isn't a TransformError, we should probably should a default message of some sort.

Copy link
Member Author

Choose a reason for hiding this comment

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

On migration success, this should redirect to the (future) 'Manage Organization' page. For now, I redirect to the admin account page.

I only added this view because I needed a way of displaying an error to the user.

{
// Arrange
var controller = GetController<UsersController>();
var currentUser = new User() { UnconfirmedEmailAddress = "unconfirmed@example.com" };
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: might be easier to use users from Fakes for this

E.g.

var fakes = Get<Fakes>();
var currentUser = fakes.User;

The advantages are clearer when you need an Admin user below:

var fakes = Get<Fakes>();
var adminUser = fakes.AdminUser;

@@ -41,6 +41,7 @@

<add key="Gallery.AsynchronousPackageValidationEnabled" value="false" />
<add key="Gallery.BlockingAsynchronousPackageValidationEnabled" value="false" />
<add key="Gallery.OrganizationsEnabledForDomains" value="microsoft.com" />
Copy link
Contributor

Choose a reason for hiding this comment

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

microsoft.com should be set in our private configs, not the default ones

Copy link
Member Author

Choose a reason for hiding this comment

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

Forgot to remove it - will do

}
catch (TransformAccountException e)
{
TempData["TransformError"] = e.AsUserSafeException().GetUserSafeMessage();
Copy link
Contributor

Choose a reason for hiding this comment

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

TransformAccountException doesn't inherit from UserSafeException, so we will always show a generic message here. Is this the intention?


if (string.IsNullOrWhiteSpace(token))
{
throw new ArgumentNullException(nameof(token));
Copy link
Contributor

Choose a reason for hiding this comment

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

ArgumentException since the value isn't necessarily null

@@ -688,4 +688,22 @@ For more information, please contact '{2}'.</value>
<data name="PackageIsLocked" xml:space="preserve">
<value>Package '{0}' has been locked. This means you cannot publish a new version or change the listing status of a published package version. Please contact support@nuget.org.</value>
</data>
<data name="TransformAccount_AdminDoesNotHaveTenantId" xml:space="preserve">
<value>Failed to transform the account because you do not have an Azure AD sign-in with tenant ID. Contact support@nuget.org for more details.</value>
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 think the average user knows what a tenant id is. Please go over the messages with Anand :)

@chenriksson
Copy link
Member Author

Per discussion in stand-up, I'm going to see if I can create the migration SQL script as an embedded resource and just invoke it inline from the Gallery. Having DB tests sounds nice, but this sounds like less work.

/cc @skofman1 @joelverhagen @scottbommarito

catch (Exception ex) when (ex is SqlException || ex is DataException)
{
// EF exception when saving account transformation to the database.
throw new TransformAccountException(Strings.TransformAccount_DatabaseError);
Copy link
Contributor

Choose a reason for hiding this comment

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

The exceptions you throw here are swallowed in UsersController, so we have no log of them. Please add a trace (somewhere)

@skofman1
Copy link
Contributor

skofman1 commented Jan 4, 2018

@chenriksson , we will need DB tests anyhow, since if the script breaks, there's no way to know about it.

Copy link
Contributor

@skofman1 skofman1 left a comment

Choose a reason for hiding this comment

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

🕐

@chenriksson
Copy link
Member Author

@skofman1 You're right - we still need the DB tests.

I've reverted the migration with sproc, and instead execute SQL via embedded resource.

sqlCommand = await reader.ReadToEndAsync();
}

if (!string.IsNullOrEmpty(sqlCommand))
Copy link
Member

Choose a reason for hiding this comment

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

Couldn't execute an empty command be indicative of a bug? I think we should throw here -- or let ExecuteSqlCommandAsync do its thing.


public async Task<bool> TransformUserToOrganization(User accountToTransform, User adminUser, string token)
{
// todo: check for tenantId and add organization policy to enforce this (future work, with manage organization)
Copy link
Member

Choose a reason for hiding this comment

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

Track TODO unless it's gonna happen very soon

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 my next work item. I've been tracking all migration with #5112, but can split this into smaller tasks.

@anangaur
Copy link
Member

anangaur commented Jan 8, 2018

Proposal:

Failed to transform to an organization

'Microsoft' is already an organization on NuGet.org.

@chenriksson
Copy link
Member Author

@anangaur Thanks! Here's updated screenshot:

image

Can you confirm these other messages? Note that most of these would be detected before the user can create the migration request (PR #5241), but I also safe guard against it here.
image

}
catch (Exception ex) when (ex is SqlException || ex is DataException)
{
// todo: log exception
Copy link
Contributor

Choose a reason for hiding this comment

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

log?

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed error handling, which seems more consistent with how we handle SQL exceptions in other places.

@skofman1
Copy link
Contributor

skofman1 commented Jan 9, 2018

@chenriksson , looks good! Please add a log and then :shipit:

@chenriksson chenriksson merged commit 3addad3 into dev Jan 9, 2018
@chenriksson chenriksson deleted the chenriks-org-transform branch January 25, 2018 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants