-
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
Account deletion support for Organization members #5797
Conversation
@@ -1,113 +0,0 @@ | |||
// Copyright (c) .NET Foundation. All rights reserved. |
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 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. |
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: move to doc comment?
We need this and the setters because of the _DeleteUserAccountForm.cshtml
postback?
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.
yup
@@ -1,5 +1,5 @@ | |||
@using NuGetGallery | |||
@model NuGetGallery.Areas.Admin.ViewModels.DeleteUserAccountViewModel | |||
@model DeleteAccountViewModel |
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.
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
?
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 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 DeleteAccountViewModel
s being created with empty fields.
@Html.ValidationMessageFor(m => m.Signature) | ||
</div> | ||
|
||
@if (Model.HasOrphanPackages) |
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.
Was the HasOrphanPackages
already updated to account for packages for the member's organizations?
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.
Yes
<div class="form-group"> | ||
@Html.EditorFor(m => m.ShouldUnlist) | ||
<label class="checkbox-inline"> | ||
<b>Unlist the packages without any user.</b> |
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.
Maybe Unlist the packages without any owner
is better?
migrationRequest.AdminUser.OrganizationMigrationRequests.Remove(migrationRequest); | ||
} | ||
|
||
await _entitiesContext.SaveChangesAsync(); |
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.
There's logic in DeleteGalleryUserAccountAsync
to commitAsTransaction
. Given that,
- Should this call
SaveChangesAsync
? - Are all of the
RemoveX
operations honoring the deferred commit?
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.
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.
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 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; |
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: sort after System
usings
HasPendingRequests = supportRequestService.GetIssues() | ||
.Where(issue => | ||
(issue.UserKey.HasValue && issue.UserKey.Value == User.Key) && | ||
string.Equals(issue.IssueTitle, Strings.AccountDelete_SupportRequestTitle) && |
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.
Ignore case?
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.
Existing code that I moved here. Ignore case is probably correct though.
.Select(p => new ListPackageItemViewModel(p, currentUser)) | ||
.ToList(); | ||
|
||
HasPendingRequests = supportRequestService.GetIssues() |
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.
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?
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.
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> |
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: 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> |
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 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.
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 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).
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.
|
@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. |
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). |
@anangaur @chenriksson @cristinamanum The implementation has been updated to delete any orphaned organizations as @anangaur requested above, please review again. |
</div> | ||
<hr /> | ||
<p> | ||
This action <strong>CANNOT</strong> be undone. This will permanently delete the account <b>@Model.AccountName</b>. |
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.
The "permanently delete" will imply that the account nae will be released and this is not the case all the time.
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
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. |
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.
Why we do this? It could result in a large number of admins.
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 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.
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.
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?
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.
@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.
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.
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. |
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.
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; | ||
} |
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.
Can these be simplified? i.e., user.OrganizationRequests.Clear()
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 wanted to make sure that there's no dependency from either end, e.g. this user has no OrganizationMigrationRequest
s and there are no users that have OrganizationMigrationRequest
s 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(); |
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 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?
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.
done
@@ -249,12 +283,6 @@ private List<Package> GetPackagesOwnedByUser(User user) | |||
await _userRepository.CommitChangesAsync(); | |||
} | |||
|
|||
private async Task RemoveUser(Organization organization) |
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.
Is this delete action replaced by other call?
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.
Yeah, _userRepository.DeleteOnCommit(organization)
as called by RemoveUser(User user)
works fine for 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.
Discussed offline with @scottbommarito and this method is not needed anymore. The orgs will be deleted as user entities.
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.
Added few comments.
#5025
User view:
Admin view: