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

Add Manage/Privacy-Delete/Download functionality #1559

wants to merge 8 commits into from

Conversation

HaoK
Copy link
Member

@HaoK HaoK commented Jan 9, 2018

Initial changes to identity default UI for Manage/Privacy/DownloadPersonalData and DeletePersonalData

Still need final tweaks to UI/text which we'll plan to get from @DamianEdwards review in person.

@blowdart @danroth27 @javiercn @ajcvickers @mkArtakMSFT

@blowdart
Copy link
Member

blowdart commented Jan 9, 2018

There won't be time for in person with @DamianEdwards is off having fun in London, so let's use @danroth27 and I as his proxy.

@HaoK
Copy link
Member Author

HaoK commented Jan 10, 2018

@danroth27 who do you want to review this PR from the perspective of proper RazorPages usage/patterns etc? I mostly just copied what the existing pages did, I only intentionally deviated slightly using the NavPages constant instead of hardcoding the value in the ActivePages setting.

@DamianEdwards
Copy link
Member

Get Ryan and Pranav to look at it pls

@HaoK HaoK requested review from pranavkm and rynowak January 10, 2018 20:18
</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
Copy link
Contributor

Choose a reason for hiding this comment

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

<partial name="_ValidationScriptsPartial" />

Copy link
Member

Choose a reason for hiding this comment

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

@pranavkm What's the advantage of <partial over Html.PartialAsync?

Copy link
Contributor

Choose a reason for hiding this comment

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

We generally prefer tag helpers to HtmlHelpers. It's cleaner, more idiomatic code.

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

// REVIEW: should this redirect to somewhere that says user was deleted?
return RedirectToPage("./");
Copy link
Contributor

Choose a reason for hiding this comment

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

This should redirect to the website root return Redirect("~/"); or a successfully deleted page. Redirecting to the page ./ is kinda weird.

Copy link
Member

Choose a reason for hiding this comment

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

No, the right thing to do here is to pass in a return url and do a local redirect, look at the logout action for how to do it.

Copy link
Member Author

Choose a reason for hiding this comment

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

So most things in manage are doing some form of RedirectToPage... is one better than the other?

Copy link
Contributor

Choose a reason for hiding this comment

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

RedirectToPage expects to resolve a page. Since there isn't a page named ./, it ends up resolving the empty string which ends up in the app root. But that's just a weird way of going about it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah so after they get deleted, manage will no longer be valid, so I really was just trying to redirect to home or whatever. Unless we think its worth adding a dedicated page for a Deleted confirmation

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

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
Copy link
Contributor

Choose a reason for hiding this comment

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

<partial name="_ValidationScriptsPartial" />


public DownloadPersonalDataModel(
UserManager<IdentityUser> userManager,
ILogger<PrivacyModel> logger)
Copy link
Contributor

Choose a reason for hiding this comment

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

ILogger<DownloadPersonalDataModel>?

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

// REVIEW: do we want to set a force download header?
return new FileContentResult(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(user)), "text/json");
Copy link
Contributor

Choose a reason for hiding this comment

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

You likely need Encoding.UTF8. That said, this kinda feels icky, but there really doesn't seem like a more definitive way to do this.

Copy link
Member

Choose a reason for hiding this comment

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

We probably want to force the download header, but I would just do OK(user) and set the header before. We can add a file content result in MVC that takes an object. I've filed aspnet/Mvc#7232 to track this

public DeletePersonalDataModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<PrivacyModel> logger)
Copy link
Contributor

Choose a reason for hiding this comment

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

ILogger<DeletePersonalDataModel>

}

var result = await _userManager.DeleteAsync(user);
if (!result.Succeeded)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it still correct for a user to be signed in if this fails?

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 default implementation basically never will fail, it would only blow up with an exception, but this is a valid point, I guess we should only sign out if it succeeded

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

Choose a reason for hiding this comment

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

ApplicationException -> InvalidOperationException

@model PrivacyModel
@{
ViewData["Title"] = "Privacy";
ViewData["ActivePage"] = "Privacy";
Copy link
Contributor

Choose a reason for hiding this comment

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

ManageNavPages.Privacy

_logger = logger;
}

public async Task<IActionResult> OnGet()
Copy link
Contributor

Choose a reason for hiding this comment

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

When do we get to this handler? Privacy.cshtml posts to this action and there isn't a link to this page.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah we don't, I'll remove

Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

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

Looks promising.

<strong>This action will delete your user.</strong>
</p>
<p>
This is unrecoverable.
Copy link
Member

Choose a reason for hiding this comment

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

Not sure what this sentence means

Copy link
Member Author

@HaoK HaoK Jan 11, 2018

Choose a reason for hiding this comment

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

"This action will delete your user."
"This is unrecoverable."

means you can't recover your user after you delete it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should this elaborate and say, once deleted you cannot use your login credentials and would need to re-register?

Kinda unrelated, but is there a hook that let's the app developer also clean up other stored details about the user when this is deleted? For instance, if the user id is used to store my order history or posted comments?

Copy link
Member Author

Choose a reason for hiding this comment

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

Don't worry about the exact text yet, I'll update that shortly.

re hook, not really, ideally we'd have an easy way for them to just drop in a new DeletePersonalData.cshtml.cs to take over this work entirely to customize. Or I guess scaffold it all and change the code if that feature doesn't make it...

</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
Copy link
Member

Choose a reason for hiding this comment

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

@pranavkm What's the advantage of <partial over Html.PartialAsync?

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

// REVIEW: should this redirect to somewhere that says user was deleted?
return RedirectToPage("./");
Copy link
Member

Choose a reason for hiding this comment

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

No, the right thing to do here is to pass in a return url and do a local redirect, look at the logout action for how to do it.

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

// REVIEW: do we want to set a force download header?
return new FileContentResult(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(user)), "text/json");
Copy link
Member

Choose a reason for hiding this comment

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

We probably want to force the download header, but I would just do OK(user) and set the header before. We can add a file content result in MVC that takes an object. I've filed aspnet/Mvc#7232 to track this


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!

</div>

@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")

Choose a reason for hiding this comment

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

Similar to the rest of the comments - prefer <partial ... /> tag syntax instead

@HaoK
Copy link
Member Author

HaoK commented Jan 11, 2018

Ok updated switching all default UI over to use the new partial and consistently use ManageNavClass constants in for the active page stuff

@HaoK
Copy link
Member Author

HaoK commented Jan 12, 2018

Text has been updated, Privacy tab => Personal Data, will add one more change which will require users to reenter their password if they have a local password before being allowed to delete their account

@HaoK
Copy link
Member Author

HaoK commented Jan 12, 2018

Updated flow with password confirmation on Delete page if the user has a password. Also added white list to only include Id/UserName/Email/Phone/and Confirmed properties)

@HaoK
Copy link
Member Author

HaoK commented Jan 12, 2018

I think all feedback has been addressed now, added Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json"); to force download.

@pranavkm can you do a final review/sign off?

{
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

@HaoK
Copy link
Member Author

HaoK commented Jan 17, 2018

f1ed482

@HaoK HaoK closed this Jan 17, 2018
@grahamehorner
Copy link

If a user has two factor authentication enable would the second factor used for verification before deletion?

@HaoK
Copy link
Member Author

HaoK commented Jan 18, 2018

No, we just check password right now, checking a second factor is significantly more involved, but its certainly doable if an app wants that, you basically just need to duplicate the two factor login flow

prafullbhosale added a commit to dotnet/Scaffolding that referenced this pull request Jan 24, 2018
prafullbhosale added a commit to dotnet/Scaffolding that referenced this pull request Jan 24, 2018
prafullbhosale added a commit to dotnet/Scaffolding that referenced this pull request Jan 26, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants