Merge pull request #2048 from sbwalker/dev

Added password policy validation in install wizard
This commit is contained in:
Shaun Walker 2022-03-08 08:20:15 -05:00 committed by GitHub
commit a47ecbdea9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 88 deletions

View File

@ -171,7 +171,9 @@
connectionString = databaseConfigControl.GetConnectionString(); connectionString = databaseConfigControl.GetConnectionString();
} }
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@")) if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
{
if (await UserService.ValidatePasswordAsync(_hostPassword))
{ {
_loadingDisplay = ""; _loadingDisplay = "";
StateHasChanged(); StateHasChanged();
@ -207,6 +209,11 @@
} }
} }
else else
{
_message = Localizer["Message.Password.Invalid"];
}
}
else
{ {
_message = Localizer["Message.Require.DbInfo"]; _message = Localizer["Message.Require.DbInfo"];
} }

View File

@ -130,10 +130,13 @@
<value>Install Now</value> <value>Install Now</value>
</data> </data>
<data name="Error.DbConfig.Load" xml:space="preserve"> <data name="Error.DbConfig.Load" xml:space="preserve">
<value>Error loading Database Configuration Control</value> <value>Error Loading Database Configuration Control</value>
</data> </data>
<data name="Message.Require.DbInfo" xml:space="preserve"> <data name="Message.Require.DbInfo" xml:space="preserve">
<value>Please Enter All Required Fields. Ensure Passwords Match And Are Greater Than 5 Characters In Length. Ensure Email Address Provided Is Valid.</value> <value>Please Enter All Required Fields. Ensure Passwords Match And Email Address Provided Is Valid.</value>
</data>
<data name="Message.Password.Invalid" xml:space="preserve">
<value>The Password Provided Does Not Meet The Password Policy. Please Verify The Minimum Password Length And Complexity Requirements.</value>
</data> </data>
<data name="Register" xml:space="preserve"> <data name="Register" xml:space="preserve">
<value>Please Register Me For Major Product Updates And Security Bulletins</value> <value>Please Register Me For Major Product Updates And Security Bulletins</value>

View File

@ -96,5 +96,13 @@ namespace Oqtane.Services
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<User> VerifyTwoFactorAsync(User user, string token); Task<User> VerifyTwoFactorAsync(User user, string token);
/// <summary>
/// Validate a users password against the password policy
/// </summary>
/// <param name="password"></param>
/// <returns></returns>
Task<bool> ValidatePasswordAsync(string password);
} }
} }

View File

@ -3,6 +3,7 @@ using Oqtane.Models;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Oqtane.Documentation; using Oqtane.Documentation;
using System.Net;
namespace Oqtane.Services namespace Oqtane.Services
{ {
@ -73,5 +74,10 @@ namespace Oqtane.Services
{ {
return await PostJsonAsync<User>($"{Apiurl}/twofactor?token={token}", user); return await PostJsonAsync<User>($"{Apiurl}/twofactor?token={token}", user);
} }
public async Task<bool> ValidatePasswordAsync(string password)
{
return await GetJsonAsync<bool>($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}");
}
} }
} }

View File

@ -26,12 +26,12 @@ namespace Oqtane.Controllers
private readonly IUserRoleRepository _userRoles; private readonly IUserRoleRepository _userRoles;
private readonly UserManager<IdentityUser> _identityUserManager; private readonly UserManager<IdentityUser> _identityUserManager;
private readonly SignInManager<IdentityUser> _identitySignInManager; private readonly SignInManager<IdentityUser> _identitySignInManager;
private readonly ITenantManager _tenantManager;
private readonly INotificationRepository _notifications; private readonly INotificationRepository _notifications;
private readonly IFolderRepository _folders; private readonly IFolderRepository _folders;
private readonly ISyncManager _syncManager; private readonly ISyncManager _syncManager;
private readonly ISiteRepository _sites; private readonly ISiteRepository _sites;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias;
public UserController(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, ILogManager logger) public UserController(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, ILogManager logger)
{ {
@ -40,12 +40,12 @@ namespace Oqtane.Controllers
_userRoles = userRoles; _userRoles = userRoles;
_identityUserManager = identityUserManager; _identityUserManager = identityUserManager;
_identitySignInManager = identitySignInManager; _identitySignInManager = identitySignInManager;
_tenantManager = tenantManager;
_folders = folders; _folders = folders;
_notifications = notifications; _notifications = notifications;
_syncManager = syncManager; _syncManager = syncManager;
_sites = sites; _sites = sites;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias();
} }
// GET api/<controller>/5?siteid=x // GET api/<controller>/5?siteid=x
@ -54,7 +54,7 @@ namespace Oqtane.Controllers
public User Get(int id, string siteid) public User Get(int id, string siteid)
{ {
int SiteId; int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) if (int.TryParse(siteid, out SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
{ {
User user = _users.GetUser(id); User user = _users.GetUser(id);
if (user != null) if (user != null)
@ -77,7 +77,7 @@ namespace Oqtane.Controllers
public User Get(string name, string siteid) public User Get(string name, string siteid)
{ {
int SiteId; int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) if (int.TryParse(siteid, out SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
{ {
User user = _users.GetUser(name); User user = _users.GetUser(name);
if (user != null) if (user != null)
@ -129,7 +129,7 @@ namespace Oqtane.Controllers
[HttpPost] [HttpPost]
public async Task<User> Post([FromBody] User user) public async Task<User> Post([FromBody] User user)
{ {
if (ModelState.IsValid && user.SiteId == _alias.SiteId) if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId)
{ {
var User = await CreateUser(user); var User = await CreateUser(user);
return User; return User;
@ -178,7 +178,7 @@ namespace Oqtane.Controllers
if (!verified) if (!verified)
{ {
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string url = HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, newUser, "User Account Verification", body); var notification = new Notification(user.SiteId, newUser, "User Account Verification", body);
_notifications.AddNotification(notification); _notifications.AddNotification(notification);
@ -252,7 +252,7 @@ namespace Oqtane.Controllers
[Authorize] [Authorize]
public async Task<User> Put(int id, [FromBody] User user) public async Task<User> Put(int id, [FromBody] User user)
{ {
if (ModelState.IsValid && user.SiteId == _alias.SiteId && _users.GetUser(user.UserId, false) != null && (User.IsInRole(RoleNames.Admin) || User.Identity.Name == user.Username)) if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null && (User.IsInRole(RoleNames.Admin) || User.Identity.Name == user.Username))
{ {
if (user.Password != "") if (user.Password != "")
{ {
@ -264,7 +264,7 @@ namespace Oqtane.Controllers
} }
} }
user = _users.UpdateUser(user); user = _users.UpdateUser(user);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, user.UserId); _syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId);
user.Password = ""; // remove sensitive information user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user); _logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
} }
@ -285,7 +285,7 @@ namespace Oqtane.Controllers
{ {
int SiteId; int SiteId;
User user = _users.GetUser(id); User user = _users.GetUser(id);
if (user != null && int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) if (user != null && int.TryParse(siteid, out SiteId) && SiteId == _tenantManager.GetAlias().SiteId)
{ {
// remove user roles for site // remove user roles for site
foreach (UserRole userrole in _userRoles.GetUserRoles(user.UserId, SiteId).ToList()) foreach (UserRole userrole in _userRoles.GetUserRoles(user.UserId, SiteId).ToList())
@ -396,7 +396,7 @@ namespace Oqtane.Controllers
{ {
user = _users.GetUser(user.Username); user = _users.GetUser(user.Username);
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string url = HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nYou attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:\n\n" + url + string body = "Dear " + user.DisplayName + ",\n\nYou attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:\n\n" + url +
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." + "\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
"\n\nThank You!"; "\n\nThank You!";
@ -464,7 +464,7 @@ namespace Oqtane.Controllers
{ {
user = _users.GetUser(user.Username); user = _users.GetUser(user.Username);
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string url = HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url + string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url +
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." + "\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
"\n\nIf you did not request to reset your password you can safely ignore this message." + "\n\nIf you did not request to reset your password you can safely ignore this message." +
@ -532,6 +532,15 @@ namespace Oqtane.Controllers
return loginUser; return loginUser;
} }
// GET api/<controller>/validate/x
[HttpGet("validate/{password}")]
public async Task<bool> Validate(string password)
{
var validator = new PasswordValidator<IdentityUser>();
var result = await validator.ValidateAsync(_identityUserManager, null, password);
return result.Succeeded;
}
// GET api/<controller>/authenticate // GET api/<controller>/authenticate
[HttpGet("authenticate")] [HttpGet("authenticate")]
public User Authenticate() public User Authenticate()