Skip to content
This repository was archived by the owner on Dec 20, 2018. It is now read-only.

Add Manage/Privacy-Delete/Download functionality #1559

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/UI/Areas/Identity/Pages/Account/ExternalLogin.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@
</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<partial name="_ValidationScriptsPartial" />
}
2 changes: 1 addition & 1 deletion src/UI/Areas/Identity/Pages/Account/ForgotPassword.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@
</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<partial name="_ValidationScriptsPartial" />
}
2 changes: 1 addition & 1 deletion src/UI/Areas/Identity/Pages/Account/Login.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,5 @@
</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<partial name="_ValidationScriptsPartial" />
}
4 changes: 2 additions & 2 deletions src/UI/Areas/Identity/Pages/Account/LoginWith2fa.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
</div>
</div>
<p>
Don't have access to your authenticator device? You can
Don't have access to your authenticator device? You can
<a asp-page="./LoginWithRecoveryCode" asp-route-returnUrl="@Model.ReturnUrl">log in with a recovery code</a>.
</p>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<partial name="_ValidationScriptsPartial" />
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")

@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@
</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<partial name="_ValidationScriptsPartial" />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@page
@model DeletePersonalDataModel
@{
ViewData["Title"] = "Delete Personal Data";
ViewData["ActivePage"] = ManageNavPages.DeletePersonalData;
}

<h4>@ViewData["Title"]</h4>

<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
</div>

<div>
<form method="post" class="form-group">
<div asp-validation-summary="All" class="text-danger"></div>
@if (Model.RequirePassword)
{
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
}
<button class="btn btn-danger" type="submit">Delete data and close my account</button>
</form>
</div>

@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage
{
public class DeletePersonalDataModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<DeletePersonalDataModel> _logger;

public DeletePersonalDataModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<DeletePersonalDataModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}

[BindProperty]
public InputModel Input { get; set; }

public class InputModel
{
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}

public bool RequirePassword { get; set; }

public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}

RequirePassword = await _userManager.HasPasswordAsync(user);
return Page();
}

public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}

RequirePassword = await _userManager.HasPasswordAsync(user);
if (RequirePassword)
{
if (!await _userManager.CheckPasswordAsync(user, Input.Password))
{
ModelState.AddModelError(string.Empty, "Password not correct.");
Copy link
Contributor

Choose a reason for hiding this comment

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

AddModelError("Input.Password", ) if we'd like to show error along the password field.

Copy link
Member Author

Choose a reason for hiding this comment

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

The error shows up at the top in the summary and at the bottom (below the password field) with this change so I think i'll leave it as is, so its only shown at the top

return Page();
}
}

var result = await _userManager.DeleteAsync(user);
if (!result.Succeeded)
{
throw new InvalidOperationException($"Unexpected error occurred deleteing user with ID '{user.Id}'.");
}

await _signInManager.SignOutAsync();

_logger.LogInformation("User with ID '{UserId}' deleted themselves.", _userManager.GetUserId(User));

Choose a reason for hiding this comment

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

Is it ok to log the userId as is (as this can be the email address, which is PII)

Copy link
Member Author

Choose a reason for hiding this comment

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

The UserName is the email, the user id is a guid by default, but this is the pattern the existing templates use for logging I believe.

Choose a reason for hiding this comment

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

Thanks!


return Redirect("~/");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@model Disable2faModel
@{
ViewData["Title"] = "Disable two-factor authentication (2FA)";
ViewData["ActivePage"] = "TwoFactorAuthentication";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}

<h2>@ViewData["Title"]</h2>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@page
@model DownloadPersonalDataModel
@{
ViewData["Title"] = "Download Your Data";
ViewData["ActivePage"] = ManageNavPages.DownloadPersonalData;
}

<h4>@ViewData["Title"]</h4>

@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage
{
public class DownloadPersonalDataModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<DownloadPersonalDataModel> _logger;

public DownloadPersonalDataModel(
UserManager<IdentityUser> userManager,
ILogger<DownloadPersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}

public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}

_logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));

// Only include personal data for download
var personalData = new Dictionary<string, string>();
personalData.Add("UserId", await _userManager.GetUserIdAsync(user));
personalData.Add("UserName", await _userManager.GetUserNameAsync(user));
personalData.Add("Email", await _userManager.GetEmailAsync(user));
personalData.Add("EmailConfirmed", (await _userManager.IsEmailConfirmedAsync(user)).ToString());
personalData.Add("PhoneNumber", await _userManager.GetPhoneNumberAsync(user));
personalData.Add("PhoneNumberConfirmed", (await _userManager.IsEmailConfirmedAsync(user)).ToString());

Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
return new FileContentResult(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(personalData)), "text/json");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@model EnableAuthenticatorModel
@{
ViewData["Title"] = "Configure authenticator app";
ViewData["ActivePage"] = "TwoFactorAuthentication";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}

<h4>@ViewData["Title"]</h4>
Expand Down Expand Up @@ -49,5 +49,5 @@
</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<partial name="_ValidationScriptsPartial" />
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@model GenerateRecoveryCodesModel
@{
ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
ViewData["ActivePage"] = "TwoFactorAuthentication";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}

<h4>@ViewData["Title"]</h4>
Expand Down
2 changes: 1 addition & 1 deletion src/UI/Areas/Identity/Pages/Account/Manage/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@
</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<partial name="_ValidationScriptsPartial" />
}
12 changes: 12 additions & 0 deletions src/UI/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,28 @@ public static class ManageNavPages

public static string ChangePassword => "ChangePassword";

public static string DownloadPersonalData => "DownloadPersonalData";

public static string DeletePersonalData => "DeletePersonalData";

public static string ExternalLogins => "ExternalLogins";

public static string PersonalData => "PersonalData";

public static string TwoFactorAuthentication => "TwoFactorAuthentication";

public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);

public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);

public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);

public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);

public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);

public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);

public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);

public static string PageNavClass(ViewContext viewContext, string page)
Expand Down
27 changes: 27 additions & 0 deletions src/UI/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@page
@model PersonalDataModel
@{
ViewData["Title"] = "Personal Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}

<h4>@ViewData["Title"]</h4>

<div class="row">
<div class="col-md-6">
<p>Your account contains personal data that you have given us. This page allows you to download or delete that data.</p>
<p>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
<form asp-page="DownloadPersonalData" method="post" class="form-group">
<button class="btn btn-default" type="submit">Download</button>
</form>
<p>
<a asp-page="DeletePersonalData" class="btn btn-default">Delete</a>
</p>
</div>
</div>

@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
35 changes: 35 additions & 0 deletions src/UI/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage
{
public class PersonalDataModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<PersonalDataModel> _logger;

public PersonalDataModel(
UserManager<IdentityUser> userManager,
ILogger<PersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}

public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}

return Page();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@model ResetAuthenticatorModel
@{
ViewData["Title"] = "Reset authenticator key";
ViewData["ActivePage"] = "TwoFactorAuthentication";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}

<h4>@ViewData["Title"]</h4>
Expand Down
4 changes: 2 additions & 2 deletions src/UI/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@model SetPasswordModel
@{
ViewData["Title"] = "Set password";
ViewData["ActivePage"] = "ChangePassword";
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
}

<h4>Set your password</h4>
Expand Down Expand Up @@ -31,5 +31,5 @@
</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<partial name="_ValidationScriptsPartial" />
}
Loading