Skip to content

Commit

Permalink
Account delete user flow (#4977)
Browse files Browse the repository at this point in the history
* Account delete user flow

* PR feedback

* PR feedback

* PR feedback
  • Loading branch information
cristinamanum authored and Scott Bommarito committed Dec 1, 2017
1 parent b4f1065 commit 14f6c29
Show file tree
Hide file tree
Showing 20 changed files with 432 additions and 58 deletions.
7 changes: 6 additions & 1 deletion src/NuGetGallery/App_Start/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,14 @@ public static void RegisterUIRoutes(RouteCollection routes)

routes.MapRoute(
RouteName.AdminDeleteAccount,
"account/delete/{accountName}",
"account/delete/{accountName}",
new { controller = "Users", action = "Delete" });

routes.MapRoute(
RouteName.UserDeleteAccount,
"account/delete",
new { controller = "Users", action = "DeleteRequest" });

routes.MapRoute(
RouteName.Account,
"account/{action}",
Expand Down
10 changes: 5 additions & 5 deletions src/NuGetGallery/Areas/Admin/Models/IssueStatusKeys.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace NuGetGallery.Areas.Admin.Models
{
internal class IssueStatusKeys
public class IssueStatusKeys
{
internal const int New = 0;
internal const int Working = 1;
internal const int WaitingForCustomer = 2;
internal const int Resolved = 3;
public const int New = 0;
public const int Working = 1;
public const int WaitingForCustomer = 2;
public const int Resolved = 3;

/// <summary>
/// Does not exist in database.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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 System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NuGetGallery.Areas.Admin.Models;
Expand Down Expand Up @@ -33,7 +34,7 @@ public interface ISupportRequestService
/// <returns>Returns a <see cref="IReadOnlyCollection{Issue}"/> that matches the provided filter parameters.</returns>
IReadOnlyCollection<Issue> GetIssues(int? assignedTo = null, string reason = null, int? issueStatusId = null, string galleryUsername = null);

Task AddNewSupportRequestAsync(string subject, string message, string requestorEmailAddress, string reason,
Task<bool> AddNewSupportRequestAsync(string subject, string message, string requestorEmailAddress, string reason,
User user, Package package = null);
Task UpdateIssueAsync(int issueId, int? assignedToId, int issueStatusId, string comment, string editedBy);
int GetIssueCount(int? assignedToId, string reason, int? issueStatusId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,11 @@ public async Task UpdateIssueAsync(int issueId, int? assignedToId, int issueStat
}
}

public async Task AddNewSupportRequestAsync(string subject, string message, string requestorEmailAddress, string reason,
public async Task<bool> AddNewSupportRequestAsync(string subject, string message, string requestorEmailAddress, string reason,
User user, Package package = null)
{
var loggedInUser = user?.Username ?? "Anonymous";
bool result = true;

try
{
Expand Down Expand Up @@ -236,6 +237,7 @@ public async Task AddNewSupportRequestAsync(string subject, string message, stri
catch (SqlException sqlException)
{
QuietLog.LogHandledException(sqlException);
result = false;

var packageInfo = "N/A";
if (package != null)
Expand All @@ -249,8 +251,10 @@ public async Task AddNewSupportRequestAsync(string subject, string message, stri
}
catch (Exception e) //In case getting data from PagerDuty has failed
{
result = false;
QuietLog.LogHandledException(e);
}
return result;
}

private async Task AddIssueAsync(Issue issue)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
// 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 System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace NuGetGallery.Areas.Admin.ViewModels
{
public class DeleteUserAccountViewModel
public class DeleteUserAccountViewModel : DeleteAccountViewModel
{
public DeleteUserAccountViewModel()
{
ShouldUnlist = true;
}

public List<ListPackageItemViewModel> Packages { get; set; }

public User User { get; set; }

public string AccountName { get; set; }

[Required(ErrorMessage = "Please sign using your name.")]
[StringLength(1000)]
[Display(Name = "Signature")]
public string Signature { get; set; }

[Display(Name = "Unlist the packages with no other owners.")]
public bool ShouldUnlist { get; set; }

public bool HasOrphanPackages { get; set; }
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</div>
</div>
<div class="form-group danger-zone">
@using (Html.BeginForm("Delete", "Users", new { id = "delete-form" }))
@using (Html.BeginForm("Delete", "Users", FormMethod.Post, new { id = "delete-form" }))
{
@Html.HttpMethodOverride(HttpVerbs.Delete)
@Html.AntiForgeryToken()
Expand Down Expand Up @@ -71,7 +71,7 @@
@section BottomScripts {
<script type="text/javascript">
$(function () {
$('#delete-form').submit(function(e) {
$('#delete-form').submit(function (e) {
if (!confirm('Are you sure you want to continue to delete this account?')) {
e.preventDefault();
}
Expand Down
2 changes: 2 additions & 0 deletions src/NuGetGallery/Controllers/PackagesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -763,12 +763,14 @@ public virtual ActionResult ContactOwners(string id)
return HttpNotFound();
}

bool hasOwners = package.PackageRegistration.Owners.Any();
var model = new ContactOwnersViewModel
{
PackageId = package.PackageRegistration.Id,
ProjectUrl = package.ProjectUrl,
Owners = package.PackageRegistration.Owners.Where(u => u.EmailAllowed),
CopySender = true,
HasOwners = hasOwners
};

return View(model);
Expand Down
66 changes: 65 additions & 1 deletion src/NuGetGallery/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using System.Net.Mail;
using System.Threading.Tasks;
using System.Web.Mvc;
using NuGetGallery.Areas.Admin;
using NuGetGallery.Areas.Admin.Models;
using NuGetGallery.Areas.Admin.ViewModels;
using NuGetGallery.Authentication;
using NuGetGallery.Configuration;
Expand All @@ -28,6 +30,7 @@ public partial class UsersController
private readonly AuthenticationService _authService;
private readonly ICredentialBuilder _credentialBuilder;
private readonly IDeleteAccountService _deleteAccountService;
private readonly ISupportRequestService _supportRequestService;

public UsersController(
ICuratedFeedService feedsQuery,
Expand All @@ -38,7 +41,8 @@ public UsersController(
IAppConfiguration config,
AuthenticationService authService,
ICredentialBuilder credentialBuilder,
IDeleteAccountService deleteAccountService)
IDeleteAccountService deleteAccountService,
ISupportRequestService supportRequestService)
{
_curatedFeedService = feedsQuery ?? throw new ArgumentNullException(nameof(feedsQuery));
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
Expand All @@ -49,6 +53,7 @@ public UsersController(
_authService = authService ?? throw new ArgumentNullException(nameof(authService));
_credentialBuilder = credentialBuilder ?? throw new ArgumentNullException(nameof(credentialBuilder));
_deleteAccountService = deleteAccountService ?? throw new ArgumentNullException(nameof(deleteAccountService));
_supportRequestService = supportRequestService ?? throw new ArgumentNullException(nameof(supportRequestService));
}

[HttpGet]
Expand Down Expand Up @@ -94,6 +99,65 @@ public virtual ActionResult Account()
return AccountView(new AccountViewModel());
}

[HttpGet]
[Authorize]
public virtual ActionResult DeleteRequest()
{
var user = GetCurrentUser();

if (user == null || user.IsDeleted)
{
return HttpNotFound("User not found.");
}

var listPackageItems = _packageService
.FindPackagesByOwner(user, includeUnlisted: true)
.Select(p => new ListPackageItemViewModel(p))
.ToList();

bool hasPendingRequest = _supportRequestService.GetIssues().Where((issue)=> string.Equals(issue.CreatedBy, user.Username) &&
string.Equals(issue.IssueTitle, Strings.AccountDelete_SupportRequestTitle) &&
issue.Key != IssueStatusKeys.Resolved).Any();

var model = new DeleteAccountViewModel()
{
Packages = listPackageItems,
User = user,
AccountName = user.Username,
HasOrphanPackages = listPackageItems.Any(p => p.Owners.Count <= 1),
HasPendingRequests = hasPendingRequest
};

return View("DeleteAccount", model);
}

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> RequestAccountDeletion()
{
var user = GetCurrentUser();

if (user == null || user.IsDeleted)
{
return HttpNotFound("User not found.");
}

bool isSupportRequestCreated = await _supportRequestService.AddNewSupportRequestAsync(Strings.AccountDelete_SupportRequestTitle,
Strings.AccountDelete_SupportRequestTitle,
user.EmailAddress,
"The user requested to have the account deleted.",
user);
if (!isSupportRequestCreated)
{
TempData["RequestFailedMessage"] = Strings.AccountDelete_CreateSupportRequestFails;
return RedirectToAction("DeleteRequest");
}
_messageService.SendAccountDeleteNotice(user.ToMailAddress(), user.Username);

return RedirectToAction("DeleteRequest");
}

[HttpGet]
[Authorize(Roles = "Admins")]
public virtual ActionResult Delete(string accountName)
Expand Down
2 changes: 2 additions & 0 deletions src/NuGetGallery/NuGetGallery.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,7 @@
<Compile Include="Services\ReflowPackageService.cs" />
<Compile Include="Services\TelemetryService.cs" />
<Compile Include="Areas\Admin\ViewModels\DeleteUserAccountViewModel.cs" />
<Compile Include="ViewModels\DeleteAccountViewModel.cs" />
<Compile Include="ViewModels\PackageOwnersResultViewModel.cs" />
<Compile Include="ViewModels\ManagePackagesListViewModel.cs" />
<Compile Include="ViewModels\ApiKeyListViewModel.cs" />
Expand Down Expand Up @@ -1956,6 +1957,7 @@
<Content Include="Areas\Admin\Views\DeleteAccount\DeleteUserAccount.cshtml" />
<Content Include="Views\Users\_UserPackagesListForDeletedAccount.cshtml" />
<Content Include="Views\Users\_ReservedNamespacesList.cshtml" />
<Content Include="Views\Users\DeleteAccount.cshtml" />
<Content Include="Views\Authentication\SignInNuGetAccount.cshtml" />
</ItemGroup>
<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/NuGetGallery/RouteNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,6 @@ public static class RouteName
public const string JsonApi = "JsonApi";
public const string Downloads = "Downloads";
public const string AdminDeleteAccount = "AdminDeleteAccount";
public const string UserDeleteAccount = "DeleteAccount";
}
}
1 change: 1 addition & 0 deletions src/NuGetGallery/Services/IMessageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ public interface IMessageService
void SendCredentialAddedNotice(User user, Credential added);
void SendContactSupportEmail(ContactSupportRequest request);
void SendPackageAddedNotice(Package package, string packageUrl, string packageSupportUrl, string emailSettingsUrl);
void SendAccountDeleteNotice(MailAddress mailAddress, string userName);
}
}
30 changes: 30 additions & 0 deletions src/NuGetGallery/Services/MessageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,36 @@ [change your email notification settings]({5}).
}
}

public void SendAccountDeleteNotice(MailAddress mailAddress, string account)
{
string body = @"We received a request to delete your account {0}. If you did not initiate this request please contact the {1} team immediately.
{2}When your account will be deleted, we will:{2}
- revoke your API key(s)
- remove you as the owner for any package you own
- remove your ownership from any ID prefix reservations and delete any ID prefix reservations that you were the only owner of
{2}We will not delete the NuGet packages associated with the account.
Thanks,
{2}The {1} Team";

body = String.Format(
CultureInfo.CurrentCulture,
body,
account,
Config.GalleryOwner.DisplayName,
Environment.NewLine);

using (var mailMessage = new MailMessage())
{
mailMessage.Subject = Strings.AccountDelete_SupportRequestTitle;
mailMessage.Body = body;
mailMessage.From = Config.GalleryNoReplyAddress;

mailMessage.To.Add(mailAddress.Address);
SendMessage(mailMessage);
}
}
private static void AddOwnersToMailMessage(PackageRegistration packageRegistration, MailMessage mailMessage)
{
foreach (var owner in packageRegistration.Owners.Where(o => o.EmailAllowed))
Expand Down
18 changes: 18 additions & 0 deletions src/NuGetGallery/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/NuGetGallery/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -633,4 +633,10 @@ For more information, please contact '{2}'.</value>
<data name="VerifyPackage_UserNonExistent" xml:space="preserve">
<value>The user '{0}' doesn't exist. You cannot upload a package as a user that doesn't exist.</value>
</data>
<data name="AccountDelete_CreateSupportRequestFails" xml:space="preserve">
<value>The request failed to be submitted. Please try again or contact support.</value>
</data>
<data name="AccountDelete_SupportRequestTitle" xml:space="preserve">
<value>DeleteAccountRequest</value>
</data>
</root>
2 changes: 2 additions & 0 deletions src/NuGetGallery/ViewModels/ContactOwnersViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ public class ContactOwnersViewModel
[Required(ErrorMessage = "Please enter a message.")]
[StringLength(4000)]
public string Message { get; set; }

public bool HasOwners { get; set; }
}
}
20 changes: 20 additions & 0 deletions src/NuGetGallery/ViewModels/DeleteAccountViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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 System.Collections.Generic;

namespace NuGetGallery
{
public class DeleteAccountViewModel
{
public List<ListPackageItemViewModel> Packages { get; set; }

public User User { get; set; }

public string AccountName { get; set; }

public bool HasOrphanPackages { get; set; }

public bool HasPendingRequests { get; set; }
}
}
Loading

0 comments on commit 14f6c29

Please sign in to comment.