mirror of
https://github.com/bitwarden/server.git
synced 2024-12-22 16:57:36 +01:00
[PM-12074] - Refactored Index
to use UserViewModel
(#4797)
* Refactored View and Edit models to have all needed fields.
This commit is contained in:
parent
72b7f6c065
commit
81b151b1c0
@ -1,11 +1,11 @@
|
||||
using Bit.Admin.Enums;
|
||||
#nullable enable
|
||||
|
||||
using Bit.Admin.Enums;
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Admin.Services;
|
||||
using Bit.Admin.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -24,9 +24,9 @@ public class UsersController : Controller
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IAccessControlService _accessControlService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public UsersController(
|
||||
IUserRepository userRepository,
|
||||
@ -34,18 +34,18 @@ public class UsersController : Controller
|
||||
IPaymentService paymentService,
|
||||
GlobalSettings globalSettings,
|
||||
IAccessControlService accessControlService,
|
||||
ICurrentContext currentContext,
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||
IFeatureService featureService,
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery)
|
||||
IUserService userService)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_cipherRepository = cipherRepository;
|
||||
_paymentService = paymentService;
|
||||
_globalSettings = globalSettings;
|
||||
_accessControlService = accessControlService;
|
||||
_currentContext = currentContext;
|
||||
_featureService = featureService;
|
||||
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||
_featureService = featureService;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.User_List_View)]
|
||||
@ -64,19 +64,26 @@ public class UsersController : Controller
|
||||
var skip = (page - 1) * count;
|
||||
var users = await _userRepository.SearchAsync(email, skip, count);
|
||||
|
||||
var userModels = new List<UserViewModel>();
|
||||
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization))
|
||||
{
|
||||
var user2Fa = (await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(users.Select(u => u.Id))).ToList();
|
||||
// TempDataSerializer is having an issue serializing an empty IEnumerable<Tuple<T1,T2>>, do not set if empty.
|
||||
if (user2Fa.Count != 0)
|
||||
var twoFactorAuthLookup = (await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(users.Select(u => u.Id))).ToList();
|
||||
|
||||
userModels = UserViewModel.MapViewModels(users, twoFactorAuthLookup).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var user in users)
|
||||
{
|
||||
TempData["UsersTwoFactorIsEnabled"] = user2Fa;
|
||||
var isTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
||||
userModels.Add(UserViewModel.MapViewModel(user, isTwoFactorEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
return View(new UsersModel
|
||||
{
|
||||
Items = users as List<User>,
|
||||
Items = userModels,
|
||||
Email = string.IsNullOrWhiteSpace(email) ? null : email,
|
||||
Page = page,
|
||||
Count = count,
|
||||
@ -87,13 +94,17 @@ public class UsersController : Controller
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||
return View(new UserViewModel(user, ciphers));
|
||||
|
||||
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||
|
||||
return View(UserViewModel.MapViewModel(user, isTwoFactorEnabled, ciphers));
|
||||
}
|
||||
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
@ -108,7 +119,8 @@ public class UsersController : Controller
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||
var billingInfo = await _paymentService.GetBillingAsync(user);
|
||||
var billingHistoryInfo = await _paymentService.GetBillingHistoryAsync(user);
|
||||
return View(new UserEditModel(user, ciphers, billingInfo, billingHistoryInfo, _globalSettings));
|
||||
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||
return View(new UserEditModel(user, isTwoFactorEnabled, ciphers, billingInfo, billingHistoryInfo, _globalSettings));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
@ -7,18 +7,23 @@ using Bit.Core.Vault.Entities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class UserEditModel : UserViewModel
|
||||
public class UserEditModel
|
||||
{
|
||||
public UserEditModel() { }
|
||||
public UserEditModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public UserEditModel(
|
||||
User user,
|
||||
bool isTwoFactorEnabled,
|
||||
IEnumerable<Cipher> ciphers,
|
||||
BillingInfo billingInfo,
|
||||
BillingHistoryInfo billingHistoryInfo,
|
||||
GlobalSettings globalSettings)
|
||||
: base(user, ciphers)
|
||||
{
|
||||
User = UserViewModel.MapViewModel(user, isTwoFactorEnabled, ciphers);
|
||||
|
||||
BillingInfo = billingInfo;
|
||||
BillingHistoryInfo = billingHistoryInfo;
|
||||
BraintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
@ -35,32 +40,32 @@ public class UserEditModel : UserViewModel
|
||||
PremiumExpirationDate = user.PremiumExpirationDate;
|
||||
}
|
||||
|
||||
public BillingInfo BillingInfo { get; set; }
|
||||
public BillingHistoryInfo BillingHistoryInfo { get; set; }
|
||||
public UserViewModel User { get; init; }
|
||||
public BillingInfo BillingInfo { get; init; }
|
||||
public BillingHistoryInfo BillingHistoryInfo { get; init; }
|
||||
public string RandomLicenseKey => CoreHelpers.SecureRandomString(20);
|
||||
public string OneYearExpirationDate => DateTime.Now.AddYears(1).ToString("yyyy-MM-ddTHH:mm");
|
||||
public string BraintreeMerchantId { get; set; }
|
||||
public string BraintreeMerchantId { get; init; }
|
||||
|
||||
[Display(Name = "Name")]
|
||||
public string Name { get; set; }
|
||||
public string Name { get; init; }
|
||||
[Required]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
public string Email { get; init; }
|
||||
[Display(Name = "Email Verified")]
|
||||
public bool EmailVerified { get; set; }
|
||||
public bool EmailVerified { get; init; }
|
||||
[Display(Name = "Premium")]
|
||||
public bool Premium { get; set; }
|
||||
public bool Premium { get; init; }
|
||||
[Display(Name = "Max. Storage GB")]
|
||||
public short? MaxStorageGb { get; set; }
|
||||
public short? MaxStorageGb { get; init; }
|
||||
[Display(Name = "Gateway")]
|
||||
public Core.Enums.GatewayType? Gateway { get; set; }
|
||||
public Core.Enums.GatewayType? Gateway { get; init; }
|
||||
[Display(Name = "Gateway Customer Id")]
|
||||
public string GatewayCustomerId { get; set; }
|
||||
public string GatewayCustomerId { get; init; }
|
||||
[Display(Name = "Gateway Subscription Id")]
|
||||
public string GatewaySubscriptionId { get; set; }
|
||||
public string GatewaySubscriptionId { get; init; }
|
||||
[Display(Name = "License Key")]
|
||||
public string LicenseKey { get; set; }
|
||||
public string LicenseKey { get; init; }
|
||||
[Display(Name = "Premium Expiration Date")]
|
||||
public DateTime? PremiumExpirationDate { get; set; }
|
||||
|
||||
public DateTime? PremiumExpirationDate { get; init; }
|
||||
}
|
||||
|
@ -1,18 +1,131 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Vault.Entities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class UserViewModel
|
||||
{
|
||||
public UserViewModel() { }
|
||||
public Guid Id { get; }
|
||||
public string Name { get; }
|
||||
public string Email { get; }
|
||||
public DateTime CreationDate { get; }
|
||||
public DateTime? PremiumExpirationDate { get; }
|
||||
public bool Premium { get; }
|
||||
public short? MaxStorageGb { get; }
|
||||
public bool EmailVerified { get; }
|
||||
public bool TwoFactorEnabled { get; }
|
||||
public DateTime AccountRevisionDate { get; }
|
||||
public DateTime RevisionDate { get; }
|
||||
public DateTime? LastEmailChangeDate { get; }
|
||||
public DateTime? LastKdfChangeDate { get; }
|
||||
public DateTime? LastKeyRotationDate { get; }
|
||||
public DateTime? LastPasswordChangeDate { get; }
|
||||
public GatewayType? Gateway { get; }
|
||||
public string GatewayCustomerId { get; }
|
||||
public string GatewaySubscriptionId { get; }
|
||||
public string LicenseKey { get; }
|
||||
public int CipherCount { get; set; }
|
||||
|
||||
public UserViewModel(User user, IEnumerable<Cipher> ciphers)
|
||||
public UserViewModel(Guid id,
|
||||
string name,
|
||||
string email,
|
||||
DateTime creationDate,
|
||||
DateTime? premiumExpirationDate,
|
||||
bool premium,
|
||||
short? maxStorageGb,
|
||||
bool emailVerified,
|
||||
bool twoFactorEnabled,
|
||||
DateTime accountRevisionDate,
|
||||
DateTime revisionDate,
|
||||
DateTime? lastEmailChangeDate,
|
||||
DateTime? lastKdfChangeDate,
|
||||
DateTime? lastKeyRotationDate,
|
||||
DateTime? lastPasswordChangeDate,
|
||||
GatewayType? gateway,
|
||||
string gatewayCustomerId,
|
||||
string gatewaySubscriptionId,
|
||||
string licenseKey,
|
||||
IEnumerable<Cipher> ciphers)
|
||||
{
|
||||
User = user;
|
||||
Id = id;
|
||||
Name = name;
|
||||
Email = email;
|
||||
CreationDate = creationDate;
|
||||
PremiumExpirationDate = premiumExpirationDate;
|
||||
Premium = premium;
|
||||
MaxStorageGb = maxStorageGb;
|
||||
EmailVerified = emailVerified;
|
||||
TwoFactorEnabled = twoFactorEnabled;
|
||||
AccountRevisionDate = accountRevisionDate;
|
||||
RevisionDate = revisionDate;
|
||||
LastEmailChangeDate = lastEmailChangeDate;
|
||||
LastKdfChangeDate = lastKdfChangeDate;
|
||||
LastKeyRotationDate = lastKeyRotationDate;
|
||||
LastPasswordChangeDate = lastPasswordChangeDate;
|
||||
Gateway = gateway;
|
||||
GatewayCustomerId = gatewayCustomerId;
|
||||
GatewaySubscriptionId = gatewaySubscriptionId;
|
||||
LicenseKey = licenseKey;
|
||||
CipherCount = ciphers.Count();
|
||||
}
|
||||
|
||||
public User User { get; set; }
|
||||
public int CipherCount { get; set; }
|
||||
public static IEnumerable<UserViewModel> MapViewModels(
|
||||
IEnumerable<User> users,
|
||||
IEnumerable<(Guid userId, bool twoFactorIsEnabled)> lookup) =>
|
||||
users.Select(user => MapViewModel(user, lookup));
|
||||
|
||||
public static UserViewModel MapViewModel(User user,
|
||||
IEnumerable<(Guid userId, bool twoFactorIsEnabled)> lookup) =>
|
||||
new(
|
||||
user.Id,
|
||||
user.Name,
|
||||
user.Email,
|
||||
user.CreationDate,
|
||||
user.PremiumExpirationDate,
|
||||
user.Premium,
|
||||
user.MaxStorageGb,
|
||||
user.EmailVerified,
|
||||
IsTwoFactorEnabled(user, lookup),
|
||||
user.AccountRevisionDate,
|
||||
user.RevisionDate,
|
||||
user.LastEmailChangeDate,
|
||||
user.LastKdfChangeDate,
|
||||
user.LastKeyRotationDate,
|
||||
user.LastPasswordChangeDate,
|
||||
user.Gateway,
|
||||
user.GatewayCustomerId ?? string.Empty,
|
||||
user.GatewaySubscriptionId ?? string.Empty,
|
||||
user.LicenseKey ?? string.Empty,
|
||||
Array.Empty<Cipher>());
|
||||
|
||||
public static UserViewModel MapViewModel(User user, bool isTwoFactorEnabled) =>
|
||||
MapViewModel(user, isTwoFactorEnabled, Array.Empty<Cipher>());
|
||||
|
||||
public static UserViewModel MapViewModel(User user, bool isTwoFactorEnabled, IEnumerable<Cipher> ciphers) =>
|
||||
new(
|
||||
user.Id,
|
||||
user.Name,
|
||||
user.Email,
|
||||
user.CreationDate,
|
||||
user.PremiumExpirationDate,
|
||||
user.Premium,
|
||||
user.MaxStorageGb,
|
||||
user.EmailVerified,
|
||||
isTwoFactorEnabled,
|
||||
user.AccountRevisionDate,
|
||||
user.RevisionDate,
|
||||
user.LastEmailChangeDate,
|
||||
user.LastKdfChangeDate,
|
||||
user.LastKeyRotationDate,
|
||||
user.LastPasswordChangeDate,
|
||||
user.Gateway,
|
||||
user.GatewayCustomerId ?? string.Empty,
|
||||
user.GatewaySubscriptionId ?? string.Empty,
|
||||
user.LicenseKey ?? string.Empty,
|
||||
ciphers);
|
||||
|
||||
public static bool IsTwoFactorEnabled(User user,
|
||||
IEnumerable<(Guid userId, bool twoFactorIsEnabled)> twoFactorIsEnabledLookup) =>
|
||||
twoFactorIsEnabledLookup.FirstOrDefault(x => x.userId == user.Id).twoFactorIsEnabled;
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
using Bit.Core.Entities;
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class UsersModel : PagedModel<User>
|
||||
public class UsersModel : PagedModel<UserViewModel>
|
||||
{
|
||||
public string Email { get; set; }
|
||||
public string Action { get; set; }
|
||||
|
@ -86,7 +86,7 @@
|
||||
@if (canViewUserInformation)
|
||||
{
|
||||
<h2>User Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model)
|
||||
@await Html.PartialAsync("_ViewInformation", Model.User)
|
||||
}
|
||||
@if (canViewBillingInformation)
|
||||
{
|
||||
|
@ -1,6 +1,4 @@
|
||||
@model UsersModel
|
||||
@inject Bit.Core.Services.IUserService userService
|
||||
@inject Bit.Core.Services.IFeatureService featureService
|
||||
@{
|
||||
ViewData["Title"] = "Users";
|
||||
}
|
||||
@ -16,100 +14,88 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th style="width: 150px;">Created</th>
|
||||
<th style="width: 170px; min-width: 170px;">Details</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th style="width: 150px;">Created</th>
|
||||
<th style="width: 170px; min-width: 170px;">Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if(!Model.Items.Any())
|
||||
@if (!Model.Items.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="4">No results to list.</td>
|
||||
</tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var user in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td colspan="4">No results to list.</td>
|
||||
<td>
|
||||
<a asp-action="@Model.Action" asp-route-id="@user.Id">@user.Email</a>
|
||||
</td>
|
||||
<td>
|
||||
<span title="@user.CreationDate.ToString()">
|
||||
@user.CreationDate.ToShortDateString()
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (user.Premium)
|
||||
{
|
||||
<i class="fa fa-star fa-lg fa-fw"
|
||||
title="Premium, expires @(user.PremiumExpirationDate?.ToShortDateString() ?? "-")">
|
||||
</i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-star-o fa-lg fa-fw text-muted" title="Not Premium"></i>
|
||||
}
|
||||
@if (user.MaxStorageGb.HasValue && user.MaxStorageGb > 1)
|
||||
{
|
||||
<i class="fa fa-plus-square fa-lg fa-fw"
|
||||
title="Additional Storage, @(user.MaxStorageGb - 1) GB">
|
||||
</i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-plus-square-o fa-lg fa-fw text-muted"
|
||||
title="No Additional Storage">
|
||||
</i>
|
||||
}
|
||||
@if (user.EmailVerified)
|
||||
{
|
||||
<i class="fa fa-check-circle fa-lg fa-fw" title="Email Verified"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-times-circle-o fa-lg fa-fw text-muted" title="Email Not Verified"></i>
|
||||
}
|
||||
@if (user.TwoFactorEnabled)
|
||||
{
|
||||
<i class="fa fa-lock fa-lg fa-fw" title="2FA Enabled"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-unlock fa-lg fa-fw text-muted" title="2FA Not Enabled"></i>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach(var user in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<a asp-action="@Model.Action" asp-route-id="@user.Id">@user.Email</a>
|
||||
</td>
|
||||
<td>
|
||||
<span title="@user.CreationDate.ToString()">
|
||||
@user.CreationDate.ToShortDateString()
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@if(user.Premium)
|
||||
{
|
||||
<i class="fa fa-star fa-lg fa-fw"
|
||||
title="Premium, expires @(user.PremiumExpirationDate?.ToShortDateString() ?? "-")"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-star-o fa-lg fa-fw text-muted" title="Not Premium"></i>
|
||||
}
|
||||
@if(user.MaxStorageGb.HasValue && user.MaxStorageGb > 1)
|
||||
{
|
||||
<i class="fa fa-plus-square fa-lg fa-fw"
|
||||
title="Additional Storage, @(user.MaxStorageGb - 1) GB"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-plus-square-o fa-lg fa-fw text-muted"
|
||||
title="No Additional Storage"></i>
|
||||
}
|
||||
@if(user.EmailVerified)
|
||||
{
|
||||
<i class="fa fa-check-circle fa-lg fa-fw" title="Email Verified"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-times-circle-o fa-lg fa-fw text-muted" title="Email Not Verified"></i>
|
||||
}
|
||||
@if (featureService.IsEnabled(Bit.Core.FeatureFlagKeys.MembersTwoFAQueryOptimization))
|
||||
{
|
||||
var usersTwoFactorIsEnabled = TempData["UsersTwoFactorIsEnabled"] as IEnumerable<(Guid userId, bool twoFactorIsEnabled)>;
|
||||
var matchingUser2Fa = usersTwoFactorIsEnabled?.FirstOrDefault(tuple => tuple.userId == user.Id);
|
||||
|
||||
@if(matchingUser2Fa is { twoFactorIsEnabled: true })
|
||||
{
|
||||
<i class="fa fa-lock fa-lg fa-fw" title="2FA Enabled"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-unlock fa-lg fa-fw text-muted" title="2FA Not Enabled"></i>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if(await userService.TwoFactorIsEnabledAsync(user))
|
||||
{
|
||||
<i class="fa fa-lock fa-lg fa-fw" title="2FA Enabled"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-unlock fa-lg fa-fw text-muted" title="2FA Not Enabled"></i>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
@if(Model.PreviousPage.HasValue)
|
||||
@if (Model.PreviousPage.HasValue)
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" asp-action="Index" asp-route-page="@Model.PreviousPage.Value"
|
||||
asp-route-count="@Model.Count" asp-route-email="@Model.Email">Previous</a>
|
||||
asp-route-count="@Model.Count" asp-route-email="@Model.Email">
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
@ -118,11 +104,13 @@
|
||||
<a class="page-link" href="#" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
}
|
||||
@if(Model.NextPage.HasValue)
|
||||
@if (Model.NextPage.HasValue)
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" asp-action="Index" asp-route-page="@Model.NextPage.Value"
|
||||
asp-route-count="@Model.Count" asp-route-email="@Model.Email">Next</a>
|
||||
asp-route-count="@Model.Count" asp-route-email="@Model.Email">
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
|
@ -1,13 +1,13 @@
|
||||
@model UserViewModel
|
||||
@{
|
||||
ViewData["Title"] = "User: " + Model.User.Email;
|
||||
ViewData["Title"] = "User: " + Model.Email;
|
||||
}
|
||||
|
||||
<h1>User <small>@Model.User.Email</small></h1>
|
||||
<h1>User <small>@Model.Email</small></h1>
|
||||
|
||||
<h2>Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model)
|
||||
<form asp-action="Delete" asp-route-id="@Model.User.Id"
|
||||
<form asp-action="Delete" asp-route-id="@Model.Id"
|
||||
onsubmit="return confirm('Are you sure you want to delete this user?')">
|
||||
<button class="btn btn-danger" type="submit">Delete</button>
|
||||
</form>
|
||||
|
@ -1,43 +1,42 @@
|
||||
@model UserViewModel
|
||||
@inject Bit.Core.Services.IUserService userService
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4 col-lg-3">Id</dt>
|
||||
<dd class="col-sm-8 col-lg-9"><code>@Model.User.Id</code></dd>
|
||||
<dd class="col-sm-8 col-lg-9"><code>@Model.Id</code></dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Premium</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.User.Premium ? "Yes" : "No")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.Premium ? "Yes" : "No")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Premium Expires</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.User.PremiumExpirationDate?.ToString() ?? "-")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.PremiumExpirationDate?.ToString() ?? "-")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Email Verified</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.User.EmailVerified ? "Yes" : "No")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.EmailVerified ? "Yes" : "No")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Using 2FA</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(await userService.TwoFactorIsEnabledAsync(Model.User) ? "Yes" : "No")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.TwoFactorEnabled ? "Yes" : "No")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Items</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.CipherCount</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Vault Modified</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.User.AccountRevisionDate.ToString()</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.AccountRevisionDate.ToString()</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Created</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.User.CreationDate.ToString()</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.CreationDate.ToString()</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Modified</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.User.RevisionDate.ToString()</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.RevisionDate.ToString()</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Last Email Address Change</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.User.LastEmailChangeDate?.ToString() ?? "-")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.LastEmailChangeDate?.ToString() ?? "-")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Last KDF Change</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.User.LastKdfChangeDate?.ToString() ?? "-")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.LastKdfChangeDate?.ToString() ?? "-")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Last Key Rotation</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.User.LastKeyRotationDate?.ToString() ?? "-")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.LastKeyRotationDate?.ToString() ?? "-")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Last Password Change</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.User.LastPasswordChangeDate?.ToString() ?? "-")</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.LastPasswordChangeDate?.ToString() ?? "-")</dd>
|
||||
|
||||
</dl>
|
||||
|
@ -18,5 +18,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Admin\Admin.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
108
test/Admin.Test/Models/UserViewModelTests.cs
Normal file
108
test/Admin.Test/Models/UserViewModelTests.cs
Normal file
@ -0,0 +1,108 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Admin.Test.Models;
|
||||
|
||||
public class UserViewModelTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void IsTwoFactorEnabled_GivenUserAndIsInLookup_WhenUserHasTwoFactorEnabled_ThenReturnsTrue(User user)
|
||||
{
|
||||
var lookup = new List<(Guid, bool)>
|
||||
{
|
||||
(user.Id, true)
|
||||
};
|
||||
|
||||
var actual = UserViewModel.IsTwoFactorEnabled(user, lookup);
|
||||
|
||||
Assert.True(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void IsTwoFactorEnabled_GivenUserAndIsInLookup_WhenUserDoesNotHaveTwoFactorEnabled_ThenReturnsFalse(User user)
|
||||
{
|
||||
var lookup = new List<(Guid, bool)>
|
||||
{
|
||||
(Guid.NewGuid(), true)
|
||||
};
|
||||
|
||||
var actual = UserViewModel.IsTwoFactorEnabled(user, lookup);
|
||||
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void IsTwoFactorEnabled_GivenUserAndIsNotInLookup_WhenUserDoesNotHaveTwoFactorEnabled_ThenReturnsFalse(User user)
|
||||
{
|
||||
var lookup = new List<(Guid, bool)>();
|
||||
|
||||
var actual = UserViewModel.IsTwoFactorEnabled(user, lookup);
|
||||
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void MapUserViewModel_GivenUser_WhenPopulated_ThenMapsToUserViewModel(User user)
|
||||
{
|
||||
var actual = UserViewModel.MapViewModel(user, true);
|
||||
|
||||
Assert.Equal(actual.Id, user.Id);
|
||||
Assert.Equal(actual.Email, user.Email);
|
||||
Assert.Equal(actual.CreationDate, user.CreationDate);
|
||||
Assert.Equal(actual.PremiumExpirationDate, user.PremiumExpirationDate);
|
||||
Assert.Equal(actual.Premium, user.Premium);
|
||||
Assert.Equal(actual.MaxStorageGb, user.MaxStorageGb);
|
||||
Assert.Equal(actual.EmailVerified, user.EmailVerified);
|
||||
Assert.True(actual.TwoFactorEnabled);
|
||||
Assert.Equal(actual.AccountRevisionDate, user.AccountRevisionDate);
|
||||
Assert.Equal(actual.RevisionDate, user.RevisionDate);
|
||||
Assert.Equal(actual.LastEmailChangeDate, user.LastEmailChangeDate);
|
||||
Assert.Equal(actual.LastKdfChangeDate, user.LastKdfChangeDate);
|
||||
Assert.Equal(actual.LastKeyRotationDate, user.LastKeyRotationDate);
|
||||
Assert.Equal(actual.LastPasswordChangeDate, user.LastPasswordChangeDate);
|
||||
Assert.Equal(actual.Gateway, user.Gateway);
|
||||
Assert.Equal(actual.GatewayCustomerId, user.GatewayCustomerId);
|
||||
Assert.Equal(actual.GatewaySubscriptionId, user.GatewaySubscriptionId);
|
||||
Assert.Equal(actual.LicenseKey, user.LicenseKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void MapUserViewModel_GivenUserWithTwoFactorEnabled_WhenPopulated_ThenMapsToUserViewModel(User user)
|
||||
{
|
||||
var lookup = new List<(Guid, bool)> { (user.Id, true) };
|
||||
|
||||
var actual = UserViewModel.MapViewModel(user, lookup);
|
||||
|
||||
Assert.True(actual.TwoFactorEnabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void MapUserViewModel_GivenUserWithoutTwoFactorEnabled_WhenPopulated_ThenTwoFactorIsEnabled(User user)
|
||||
{
|
||||
var lookup = new List<(Guid, bool)> { (user.Id, false) };
|
||||
|
||||
var actual = UserViewModel.MapViewModel(user, lookup);
|
||||
|
||||
Assert.False(actual.TwoFactorEnabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void MapUserViewModel_GivenUser_WhenNotInLookUpList_ThenTwoFactorIsDisabled(User user)
|
||||
{
|
||||
var lookup = new List<(Guid, bool)> { (Guid.NewGuid(), true) };
|
||||
|
||||
var actual = UserViewModel.MapViewModel(user, lookup);
|
||||
|
||||
Assert.False(actual.TwoFactorEnabled);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
namespace Admin.Test;
|
||||
|
||||
// Delete this file once you have real tests
|
||||
public class PlaceholderUnitTest
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ public class OrganizationUsersControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PutResetPasswordEnrollment_InivitedUser_AcceptsInvite(Guid orgId, Guid userId, OrganizationUserResetPasswordEnrollmentRequestModel model,
|
||||
public async Task PutResetPasswordEnrollment_InvitedUser_AcceptsInvite(Guid orgId, Guid userId, OrganizationUserResetPasswordEnrollmentRequestModel model,
|
||||
User user, OrganizationUser orgUser, SutProvider<OrganizationUsersController> sutProvider)
|
||||
{
|
||||
orgUser.Status = Core.Enums.OrganizationUserStatusType.Invited;
|
||||
|
Loading…
Reference in New Issue
Block a user