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

Account deletion support for Organization members #5797

Merged
merged 14 commits into from
Apr 30, 2018

Conversation

scottbommarito
Copy link
Contributor

#5025

User view:
image

Admin view:
image

@scottbommarito scottbommarito changed the title Sb deleteorgmember Account deletion support for Organization members Apr 12, 2018
@@ -1,113 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed this file because it seemed redundant--it's only testing a field on ListPackageItemViewModel that is passed to DeleteAccountViewModel. The field already has its own tests in ListPackageItemViewModel.

{
public DeleteUserAccountViewModel()
{
// This constructor exists so that we can have a form that uses this model.
Copy link
Member

Choose a reason for hiding this comment

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

nit: move to doc comment?

We need this and the setters because of the _DeleteUserAccountForm.cshtml postback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup

@@ -1,5 +1,5 @@
@using NuGetGallery
@model NuGetGallery.Areas.Admin.ViewModels.DeleteUserAccountViewModel
@model DeleteAccountViewModel
Copy link
Member

Choose a reason for hiding this comment

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

Why change from DeleteUserAccountViewModel to DeleteAccountViewModel?

If this is going to be shared for deleting org accounts, should the view also change to DeleteAccount.cshtml?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed this because

DeleteUserAccountViewModel used to inherit from DeleteAccountViewModel.
DeleteUserAccountViewModel contained the information required by the form, while DeleteAccountViewModel had the information required by rendering the view.
I thought having DeleteUserAccountViewModel depend on DeleteAccountViewModel was confusing because the two objects served very different purposes and the improper inheritance would lead to DeleteAccountViewModels being created with empty fields.

@Html.ValidationMessageFor(m => m.Signature)
</div>

@if (Model.HasOrphanPackages)
Copy link
Member

Choose a reason for hiding this comment

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

Was the HasOrphanPackages already updated to account for packages for the member's organizations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

<div class="form-group">
@Html.EditorFor(m => m.ShouldUnlist)
<label class="checkbox-inline">
<b>Unlist the packages without any user.</b>
Copy link
Member

Choose a reason for hiding this comment

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

Maybe Unlist the packages without any owner is better?

migrationRequest.AdminUser.OrganizationMigrationRequests.Remove(migrationRequest);
}

await _entitiesContext.SaveChangesAsync();
Copy link
Member

Choose a reason for hiding this comment

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

There's logic in DeleteGalleryUserAccountAsync to commitAsTransaction. Given that,

  • Should this call SaveChangesAsync?
  • Are all of the RemoveX operations honoring the deferred commit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I understand

  • SaveChangesAsync "enlists in any ambient transactions" e.g. it attaches the changes to the ongoing transaction
  • other services that are called also call SaveChangesAsync

I was under the impression this was the right way to do things but if it's wrong then we'll need to fix other things as well. In my experience when I was testing this and it failed at this point, it rolled back any changes that it was attempting to make.

Copy link
Contributor

Choose a reason for hiding this comment

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

@scottbommarito is correct if called under a transaction it appends the changes and commit everything at the end. If not under a transaction each SaveChangesAsync will be committed individually

@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using NuGetGallery.Areas.Admin;
using NuGetGallery.Areas.Admin.Models;
Copy link
Member

Choose a reason for hiding this comment

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

nit: sort after System usings

HasPendingRequests = supportRequestService.GetIssues()
.Where(issue =>
(issue.UserKey.HasValue && issue.UserKey.Value == User.Key) &&
string.Equals(issue.IssueTitle, Strings.AccountDelete_SupportRequestTitle) &&
Copy link
Member

Choose a reason for hiding this comment

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

Ignore case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Existing code that I moved here. Ignore case is probably correct though.

.Select(p => new ListPackageItemViewModel(p, currentUser))
.ToList();

HasPendingRequests = supportRequestService.GetIssues()
Copy link
Member

Choose a reason for hiding this comment

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

Aside from checking for support requests, are we also deleting them? Could there be PII in the support requests that necessitate deleting them as part of Delete Account?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deletion is done inside DeleteAccountService I believe.

@@ -35,6 +35,7 @@
<li>Revoke your api key(s).</li>
<li>Remove you as the owner for any packages you own.</li>
<li>Remove your ownership from any ID prefix reservations and delete any ID prefix reservations that you were the only owner of.</li>
<li>Remove your membership from any organizations you are a member of.</li>
Copy link
Member

Choose a reason for hiding this comment

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

nit: would remove redundancy... Remove your membership from any organizations or Remove yourself from any organizations that you are a member of

<td class="align-middle ">
@if (organization.MemberCount == 1)
{
<i class="ms-Icon ms-Icon--StatusErrorFull red-check" title="After account delete the organization will not have any members."></i>
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should just automatically delete any orphaned organizations... to avoid support requests. We're already unlisting all the packages. This user can't easily request the orphaned org(s) be deleted after their account is deleted.

Copy link
Contributor Author

@scottbommarito scottbommarito Apr 13, 2018

Choose a reason for hiding this comment

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

I can't imagine that anyone would be too concerned about their orphaned organization if they were willing to orphan it.
I think leaving the organization might make it easier for a real user to take over ownership of the packages (the next PR after this is to allow site admins to change any organization's membership).

@anangaur
Copy link
Member

Should we show admins instead of members? Because ultimately you would need additional admins to be present for an organization.

In case the user deleting his/her account is the only Admin, we should just delete the orgs as well and make all the packages orphans. We do not want to end up in a situation where there is a dangling organization with only collaborators.

OR

We continue to show members and not just admins.

  1. Organizations where the user account is the sole member (Admin) --> Delete the organization and orphan all the packages as if they directly belonged to the user
  2. Organization where the user account is one of the n admins --> User is deleted. Organization exists with other Admins to manage it -- No brainer :)
  3. Organization where the user is the sole Admin but there are collaborators --> User is deleted and all the collaborators are promoted as Admins. We should show this message on the page.

@scottbommarito
Copy link
Contributor Author

@anangaur @chenriksson Currently this implementation does NOT delete orphaned organizations. This is because deleting these organizations requires actually implementing the delete flow for organizations. I don't mind doing that--I've already done most of the work required for it--but I don't see any major problems with having orphaned organizations when the main reason for implementing this feature is GDPR concerns.

@anangaur
Copy link
Member

We need to have delete Organization feature and IMO we should be more liberal in releasing the namespace. As discussed earlier, delete org feature should be available on the manage organization page itself and not just on the profile page.

In general we should have the capability to release deleted orgs/accounts namespace on request (with some validations).

@scottbommarito
Copy link
Contributor Author

scottbommarito commented Apr 27, 2018

@anangaur @chenriksson @cristinamanum The implementation has been updated to delete any orphaned organizations as @anangaur requested above, please review again.

Scott Bommarito added 2 commits April 27, 2018 09:23
</div>
<hr />
<p>
This action <strong>CANNOT</strong> be undone. This will permanently delete the account <b>@Model.AccountName</b>.
Copy link
Contributor

Choose a reason for hiding this comment

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

The "permanently delete" will imply that the account nae will be released and this is not the case all the time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to

You will not be able to regain access to the account <b>@Model.AccountName</b> or any of its packages.

}
else if (memberCount - 1 <= collaborators.Count())
{
// All other members of this organization are collaborators, so we should promote them to administrators.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why we do this? It could result in a large number of admins.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think there's any metric we can use to choose one collaborator or a subset of the collaborators to promote when the only admin has left. Therefore, we promote all collaborators because then we're guaranteed to promote at least one person who actually should be an administrator.

Copy link
Member

Choose a reason for hiding this comment

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

Agree w/ @cristinamanum - and what if multiple collaborators want to claim ownership? I'm wondering if we need a modified dispute resolution policy for obtaining admin permissions for an orphaned organization. What did @anangaur say?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@anangaur suggested this--promote all collaborators when the last admin deletes their account.

This isn't an orphaned organization, this is an organization whose last admin left that still has collaborators. I think if the owner of this organization doesn't care enough to give administrator rights to another member, I don't think it's worth detailing a fancy policy.

Copy link
Member

@chenriksson chenriksson left a comment

Choose a reason for hiding this comment

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

LGTM, except same concern as @cristinamanum on promoting collaborators

}
else if (memberCount - 1 <= collaborators.Count())
{
// All other members of this organization are collaborators, so we should promote them to administrators.
Copy link
Member

Choose a reason for hiding this comment

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

Agree w/ @cristinamanum - and what if multiple collaborators want to claim ownership? I'm wondering if we need a modified dispute resolution policy for obtaining admin permissions for an orphaned organization. What did @anangaur say?

{
user.OrganizationMigrationRequests.Remove(transformationRequest);
transformationRequest.NewOrganization.OrganizationMigrationRequest = null;
}
Copy link
Member

Choose a reason for hiding this comment

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

Can these be simplified? i.e., user.OrganizationRequests.Clear()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to make sure that there's no dependency from either end, e.g. this user has no OrganizationMigrationRequests and there are no users that have OrganizationMigrationRequests that depend on this user. not sure if the cascade was set up correctly.

{
var organization = membership.Organization;
var collaborators = organization.Members.Where(m => !m.IsAdmin).ToList();
var memberCount = organization.Members.Count();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that these two lines do two queries to db? Maybe we can do one query and do the filter and the count on the same collection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -249,12 +283,6 @@ private List<Package> GetPackagesOwnedByUser(User user)
await _userRepository.CommitChangesAsync();
}

private async Task RemoveUser(Organization organization)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this delete action replaced by other call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, _userRepository.DeleteOnCommit(organization) as called by RemoveUser(User user) works fine for this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed offline with @scottbommarito and this method is not needed anymore. The orgs will be deleted as user entities.

Copy link
Contributor

@cristinamanum cristinamanum left a comment

Choose a reason for hiding this comment

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

Added few comments.

@scottbommarito scottbommarito merged commit 932026b into dev Apr 30, 2018
@scottbommarito scottbommarito deleted the sb-deleteorgmember branch April 30, 2018 19:24
@scottbommarito scottbommarito restored the sb-deleteorgmember branch July 22, 2019 23:20
@scottbommarito scottbommarito deleted the sb-deleteorgmember branch July 22, 2019 23:21
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.

4 participants