Skip to content

Commit

Permalink
Create derived UserAccountViewModel for User (non-Organization) accou…
Browse files Browse the repository at this point in the history
…nts (#5295)
  • Loading branch information
chenriksson committed Jan 25, 2018
1 parent 518c019 commit a36fae4
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 152 deletions.
82 changes: 50 additions & 32 deletions src/NuGetGallery/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public virtual ActionResult ConfirmationRequiredPost()
[Authorize]
public virtual ActionResult Account()
{
return AccountView(new AccountViewModel());
return AccountView<UserAccountViewModel>();
}

[HttpGet]
Expand Down Expand Up @@ -208,7 +208,7 @@ public virtual ActionResult DeleteRequest()
.Select(p => new ListPackageItemViewModel(p, user))
.ToList();

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

Expand All @@ -219,7 +219,7 @@ public virtual ActionResult DeleteRequest()
AccountName = user.Username,
HasPendingRequests = hasPendingRequest
};

return View("DeleteAccount", model);
}

Expand Down Expand Up @@ -248,7 +248,7 @@ public virtual async Task<ActionResult> RequestAccountDeletion()
return RedirectToAction("DeleteRequest");
}
_messageService.SendAccountDeleteNotice(user.ToMailAddress(), user.Username);

return RedirectToAction("DeleteRequest");
}

Expand All @@ -257,13 +257,13 @@ public virtual async Task<ActionResult> RequestAccountDeletion()
public virtual ActionResult Delete(string accountName)
{
var user = _userService.FindByUsername(accountName);
if(user == null || user.IsDeleted || (user is Organization))
if (user == null || user.IsDeleted || (user is Organization))
{
return HttpNotFound("User not found.");
}

var listPackageItems = _packageService
.FindPackagesByAnyMatchingOwner(user, includeUnlisted:true)
.FindPackagesByAnyMatchingOwner(user, includeUnlisted: true)
.Select(p => new ListPackageItemViewModel(p, user))
.ToList();
var model = new DeleteUserAccountViewModel
Expand Down Expand Up @@ -348,13 +348,13 @@ private ApiKeyOwnerViewModel CreateApiKeyOwnerViewModel(User user, bool canPushN
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> ChangeEmailSubscription(AccountViewModel model)
public virtual async Task<ActionResult> ChangeEmailSubscription(UserAccountViewModel model)
{
var user = GetCurrentUser();

await _userService.ChangeEmailSubscriptionAsync(
user,
model.ChangeNotifications.EmailAllowed,
user,
model.ChangeNotifications.EmailAllowed,
model.ChangeNotifications.NotifyPackagePushed);

TempData["Message"] = Strings.EmailPreferencesUpdated;
Expand Down Expand Up @@ -527,9 +527,7 @@ public virtual async Task<ActionResult> ResetPassword(string username, string to
_messageService.SendCredentialAddedNotice(credential.User, _authService.DescribeCredential(credential));
}

return RedirectToAction(
actionName: "PasswordChanged",
controllerName: "Users");
return RedirectToAction("PasswordChanged");
}

[Authorize]
Expand Down Expand Up @@ -607,7 +605,7 @@ public virtual ActionResult Profiles(string username, int page = 1)
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> ChangeEmail(AccountViewModel model)
public virtual async Task<ActionResult> ChangeEmail(UserAccountViewModel model)
{
if (!ModelState.IsValidField("ChangeEmail.NewEmail"))
{
Expand Down Expand Up @@ -667,11 +665,11 @@ public virtual async Task<ActionResult> ChangeEmail(AccountViewModel model)
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> CancelChangeEmail(AccountViewModel model)
public virtual async Task<ActionResult> CancelChangeEmail(UserAccountViewModel model)
{
var user = GetCurrentUser();

if(string.IsNullOrWhiteSpace(user.UnconfirmedEmailAddress))
if (string.IsNullOrWhiteSpace(user.UnconfirmedEmailAddress))
{
return RedirectToAction(actionName: "Account", controllerName: "Users");
}
Expand All @@ -687,7 +685,7 @@ public virtual async Task<ActionResult> CancelChangeEmail(AccountViewModel model
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> ChangePassword(AccountViewModel model)
public virtual async Task<ActionResult> ChangePassword(UserAccountViewModel model)
{
var user = GetCurrentUser();

Expand Down Expand Up @@ -786,7 +784,7 @@ public virtual async Task<JsonResult> RegenerateCredential(string credentialType
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(Strings.Unsupported);
}

var newCredentialViewModel = await GenerateApiKeyInternal(
cred.Description,
BuildScopes(cred.Scopes),
Expand Down Expand Up @@ -1010,34 +1008,54 @@ private async Task<ActionResult> RemoveCredentialInternal(User user, Credential
return RedirectToAction("Account");
}

private ActionResult AccountView(AccountViewModel model)
private ActionResult AccountView<TAccountViewModel>(TAccountViewModel model = null)
where TAccountViewModel : AccountViewModel
{
var user = GetCurrentUser();
model = model ?? Activator.CreateInstance<TAccountViewModel>();

// only users for now, but organizations are coming
var userModel = model as UserAccountViewModel;
if (userModel == null)
{
throw new ArgumentException("Invalid view model type.", nameof(model));
}

// update model for all accounts
var account = GetCurrentUser();

model.CuratedFeeds = _curatedFeedService
.GetFeedsForManager(user.Key)
.GetFeedsForManager(account.Key)
.Select(f => f.Name)
.ToList();
model.CredentialGroups = GetCredentialGroups(user);

model.HasPassword = account.Credentials.Any(c => c.Type.StartsWith(CredentialTypes.Password.Prefix));
model.CurrentEmailAddress = account.UnconfirmedEmailAddress ?? account.EmailAddress;
model.HasConfirmedEmailAddress = !string.IsNullOrEmpty(account.EmailAddress);
model.HasUnconfirmedEmailAddress = !string.IsNullOrEmpty(account.UnconfirmedEmailAddress);

model.ChangeEmail = new ChangeEmailViewModel();

model.ChangeNotifications = model.ChangeNotifications ?? new ChangeNotificationsViewModel();
model.ChangeNotifications.EmailAllowed = account.EmailAllowed;
model.ChangeNotifications.NotifyPackagePushed = account.NotifyPackagePushed;

// update model for user accounts
UpdateUserAccountModel(account, userModel);

return View("Account", model);
}

private void UpdateUserAccountModel(User account, UserAccountViewModel model)
{
model.CredentialGroups = GetCredentialGroups(account);
model.SignInCredentialCount = model
.CredentialGroups
.Where(p => p.Key == CredentialKind.Password || p.Key == CredentialKind.External)
.Sum(p => p.Value.Count);

model.ExpirationInDaysForApiKeyV1 = _config.ExpirationInDaysForApiKeyV1;
model.HasPassword = model.CredentialGroups.ContainsKey(CredentialKind.Password);
model.CurrentEmailAddress = user.UnconfirmedEmailAddress ?? user.EmailAddress;
model.HasConfirmedEmailAddress = !string.IsNullOrEmpty(user.EmailAddress);
model.HasUnconfirmedEmailAddress = !string.IsNullOrEmpty(user.UnconfirmedEmailAddress);

model.ChangePassword = model.ChangePassword ?? new ChangePasswordViewModel();
model.ChangePassword.EnablePasswordLogin = model.HasPassword;

model.ChangeNotifications = model.ChangeNotifications ?? new ChangeNotificationsViewModel();
model.ChangeNotifications.EmailAllowed = user.EmailAllowed;
model.ChangeNotifications.NotifyPackagePushed = user.NotifyPackagePushed;

return View("Account", model);
}

private Dictionary<CredentialKind, List<CredentialViewModel>> GetCredentialGroups(User user)
Expand Down
6 changes: 6 additions & 0 deletions src/NuGetGallery/NuGetGallery.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -1623,6 +1623,11 @@
<Compile Include="Areas\Admin\ViewModels\DeleteUserAccountViewModel.cs" />
<Compile Include="ViewModels\ListOrganizationsItemViewModel.cs" />
<Compile Include="ViewModels\ListOrganizationsViewModel.cs" />
<Compile Include="ViewModels\ChangeEmailViewModel.cs" />
<Compile Include="ViewModels\ChangeNotificationsViewModel.cs" />
<Compile Include="ViewModels\ChangePasswordViewModel.cs" />
<Compile Include="ViewModels\CredentialKind.cs" />
<Compile Include="ViewModels\CredentialViewModel.cs" />
<Compile Include="ViewModels\ListPackageOwnerViewModel.cs" />
<Compile Include="ViewModels\PackageDeleteDecision.cs" />
<Compile Include="ViewModels\DeleteAccountViewModel.cs" />
Expand All @@ -1646,6 +1651,7 @@
<Compile Include="ViewModels\ThirdPartyPackageManagerViewModel.cs" />
<Compile Include="ViewModels\TransformAccountFailedViewModel.cs" />
<Compile Include="ViewModels\TransformAccountViewModel.cs" />
<Compile Include="ViewModels\UserAccountViewModel.cs" />
<Compile Include="WebApi\PlainTextResult.cs" />
<Compile Include="WebApi\QueryResult.cs" />
<Compile Include="WebApi\QueryResultDefaults.cs" />
Expand Down
104 changes: 7 additions & 97 deletions src/NuGetGallery/ViewModels/AccountViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,114 +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;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using NuGetGallery.Authentication.Providers;
using NuGetGallery.Infrastructure;

namespace NuGetGallery
{
public class AccountViewModel
public abstract class AccountViewModel
{
public AccountViewModel()
{
CuratedFeeds = new List<string>();
ChangePassword = new ChangePasswordViewModel();
ChangeEmail = new ChangeEmailViewModel();
CredentialGroups = new Dictionary<CredentialKind, List<CredentialViewModel>>();
ChangeNotifications = new ChangeNotificationsViewModel();
}

public IList<string> CuratedFeeds { get; set; }
public ChangePasswordViewModel ChangePassword { get; set; }
public ChangeEmailViewModel ChangeEmail { get; set; }
public ChangeNotificationsViewModel ChangeNotifications { get; set; }
public int ExpirationInDaysForApiKeyV1 { get; set; }
public bool HasPassword { get; set; }
public string CurrentEmailAddress { get; set; }
public bool HasUnconfirmedEmailAddress { get; set; }
public bool HasConfirmedEmailAddress { get; set; }
public IDictionary<CredentialKind, List<CredentialViewModel>> CredentialGroups { get; set; }
public int SignInCredentialCount { get; set; }
}

public class ChangeNotificationsViewModel
{
public bool EmailAllowed { get; set; }
public bool NotifyPackagePushed { get; set; }
}

public class ChangeEmailViewModel
{
[Required]
[StringLength(255)]
[Display(Name = "New Email Address")]
[RegularExpression(RegisterViewModel.EmailValidationRegex, ErrorMessage = RegisterViewModel.EmailValidationErrorMessage)]
public string NewEmail { get; set; }

[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
[StringLength(64)]
[AllowHtml]
public string Password { get; set; }
}

public class ChangePasswordViewModel
{
[Required]
[Display(Name = "Enable Password Login")]
public bool EnablePasswordLogin { get; set; }
public ChangeEmailViewModel ChangeEmail { get; set; }

[Required]
[Display(Name = "Current Password")]
[AllowHtml]
public string OldPassword { get; set; }
public ChangeNotificationsViewModel ChangeNotifications { get; set; }

[Required]
[Display(Name = "New Password")]
[PasswordValidation]
[AllowHtml]
public string NewPassword { get; set; }
public bool HasPassword { get; set; }

[Required]
[Display(Name = "Verify Password")]
[PasswordValidation]
[AllowHtml]
public string VerifyPassword { get; set; }
}

public class CredentialViewModel
{
public int Key { get; set; }
public string Type { get; set; }
public string TypeCaption { get; set; }
public string Identity { get; set; }
public DateTime Created { get; set; }
public DateTime? Expires { get; set; }
public CredentialKind Kind { get; set; }
public AuthenticatorUI AuthUI { get; set; }
public string Description { get; set; }
public List<ScopeViewModel> Scopes { get; set; }
public bool HasExpired { get; set; }
public string Value { get; set; }
public TimeSpan? ExpirationDuration { get; set; }
public string CurrentEmailAddress { get; set; }

public bool IsNonScopedApiKey
{
get
{
return CredentialTypes.IsApiKey(Type) && !Scopes.AnySafe();
}
}
}
public bool HasUnconfirmedEmailAddress { get; set; }

public enum CredentialKind
{
Password,
Token,
External
public bool HasConfirmedEmailAddress { get; set; }
}
}
24 changes: 24 additions & 0 deletions src/NuGetGallery/ViewModels/ChangeEmailViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +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.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace NuGetGallery
{
public class ChangeEmailViewModel
{
[Required]
[StringLength(255)]
[Display(Name = "New Email Address")]
[RegularExpression(RegisterViewModel.EmailValidationRegex, ErrorMessage = RegisterViewModel.EmailValidationErrorMessage)]
public string NewEmail { get; set; }

[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
[StringLength(64)]
[AllowHtml]
public string Password { get; set; }
}
}
12 changes: 12 additions & 0 deletions src/NuGetGallery/ViewModels/ChangeNotificationsViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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.

namespace NuGetGallery
{
public class ChangeNotificationsViewModel
{
public bool EmailAllowed { get; set; }

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

0 comments on commit a36fae4

Please sign in to comment.