Fix #4752: validate the username and email.

This commit is contained in:
Ben 2024-10-21 23:11:57 +08:00
parent 7f978c7845
commit 4f74962ce2
8 changed files with 258 additions and 103 deletions

View File

@ -156,129 +156,130 @@
private List<SiteTemplate> _templates; private List<SiteTemplate> _templates;
private string _template = Constants.DefaultSiteTemplate; private string _template = Constants.DefaultSiteTemplate;
private bool _register = true; private bool _register = true;
private string _message = string.Empty; private string _message = string.Empty;
private string _loadingDisplay = "display: none;"; private string _loadingDisplay = "display: none;";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// include CSS // include CSS
var content = $"<link rel=\"stylesheet\" href=\"{Constants.BootstrapStylesheetUrl}\" integrity=\"{Constants.BootstrapStylesheetIntegrity}\" crossorigin=\"anonymous\" type=\"text/css\"/>"; var content = $"<link rel=\"stylesheet\" href=\"{Constants.BootstrapStylesheetUrl}\" integrity=\"{Constants.BootstrapStylesheetIntegrity}\" crossorigin=\"anonymous\" type=\"text/css\"/>";
SiteState.AppendHeadContent(content); SiteState.AppendHeadContent(content);
_togglePassword = SharedLocalizer["ShowPassword"]; _togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"]; _toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync(); _databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault)) if (_databases.Exists(item => item.IsDefault))
{ {
_databaseName = _databases.Find(item => item.IsDefault).Name; _databaseName = _databases.Find(item => item.IsDefault).Name;
} }
else else
{ {
_databaseName = "LocalDB"; _databaseName = "LocalDB";
} }
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();
_templates = await SiteTemplateService.GetSiteTemplatesAsync(); _templates = await SiteTemplateService.GetSiteTemplatesAsync();
} }
private void DatabaseChanged(ChangeEventArgs eventArgs) private void DatabaseChanged(ChangeEventArgs eventArgs)
{ {
try try
{ {
_databaseName = (string)eventArgs.Value; _databaseName = (string)eventArgs.Value;
_showConnectionString = false; _showConnectionString = false;
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();
} }
catch catch
{ {
_message = Localizer["Error.DbConfig.Load"]; _message = Localizer["Error.DbConfig.Load"];
} }
} }
private void LoadDatabaseConfigComponent() private void LoadDatabaseConfigComponent()
{ {
var database = _databases.SingleOrDefault(d => d.Name == _databaseName); var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (database != null) if (database != null)
{ {
_databaseConfigType = Type.GetType(database.ControlType); _databaseConfigType = Type.GetType(database.ControlType);
DatabaseConfigComponent = builder => DatabaseConfigComponent = builder =>
{ {
builder.OpenComponent(0, _databaseConfigType); builder.OpenComponent(0, _databaseConfigType);
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); }); builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
{ {
// include JavaScript // include JavaScript
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
await interop.IncludeScript("", Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous", "", "head"); await interop.IncludeScript("", Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous", "", "head");
} }
} }
private async Task Install() private async Task Install()
{ {
var connectionString = String.Empty; var connectionString = String.Empty;
if (_showConnectionString) if (_showConnectionString)
{ {
connectionString = _connectionString; connectionString = _connectionString;
} }
else else
{ {
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl) if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{ {
connectionString = databaseConfigControl.GetConnectionString(); connectionString = databaseConfigControl.GetConnectionString();
} }
} }
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _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)) var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
{ if (result.Succeeded)
_loadingDisplay = ""; {
StateHasChanged(); _loadingDisplay = "";
StateHasChanged();
Uri uri = new Uri(NavigationManager.Uri); Uri uri = new Uri(NavigationManager.Uri);
var database = _databases.SingleOrDefault(d => d.Name == _databaseName); var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var config = new InstallConfig var config = new InstallConfig
{ {
DatabaseType = database.DBType, DatabaseType = database.DBType,
ConnectionString = connectionString, ConnectionString = connectionString,
Aliases = uri.Authority, Aliases = uri.Authority,
HostUsername = _hostUsername, HostUsername = _hostUsername,
HostPassword = _hostPassword, HostPassword = _hostPassword,
HostEmail = _hostEmail, HostEmail = _hostEmail,
HostName = _hostUsername, HostName = _hostUsername,
TenantName = TenantNames.Master, TenantName = TenantNames.Master,
IsNewTenant = true, IsNewTenant = true,
SiteName = Constants.DefaultSite, SiteName = Constants.DefaultSite,
Register = _register, Register = _register,
SiteTemplate = _template, SiteTemplate = _template,
RenderMode = RenderModes.Static, RenderMode = RenderModes.Static,
Runtime = Runtimes.Server Runtime = Runtimes.Server
}; };
var installation = await InstallationService.Install(config); var installation = await InstallationService.Install(config);
if (installation.Success) if (installation.Success)
{ {
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true); NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
} }
else else
{ {
_message = installation.Message; _message = installation.Message;
_loadingDisplay = "display: none;"; _loadingDisplay = "display: none;";
} }
} }
else else
{ {
_message = Localizer["Message.Password.Invalid"]; _message = string.Join("<br />", result.Errors.Select(i => i.Value));
} }
} }
else else

View File

@ -113,6 +113,15 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<User> VerifyTwoFactorAsync(User user, string token); Task<User> VerifyTwoFactorAsync(User user, string token);
/// <summary>
/// Validate identity user info.
/// </summary>
/// <param name="username"></param>
/// <param name="email"></param>
/// <param name="password"></param>
/// <returns></returns>
Task<UserValidateResult> ValidateUserAsync(string username, string email, string password);
/// <summary> /// <summary>
/// Validate a users password against the password policy /// Validate a users password against the password policy
/// </summary> /// </summary>

View File

@ -89,6 +89,11 @@ namespace Oqtane.Services
return await PostJsonAsync<User>($"{Apiurl}/twofactor?token={token}", user); return await PostJsonAsync<User>($"{Apiurl}/twofactor?token={token}", user);
} }
public async Task<UserValidateResult> ValidateUserAsync(string username, string email, string password)
{
return await GetJsonAsync<UserValidateResult>($"{Apiurl}/validateuser?username={WebUtility.UrlEncode(username)}&email={WebUtility.UrlEncode(email)}&password={WebUtility.UrlEncode(password)}");
}
public async Task<bool> ValidatePasswordAsync(string password) public async Task<bool> ValidatePasswordAsync(string password)
{ {
return await GetJsonAsync<bool>($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}"); return await GetJsonAsync<bool>($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}");

View File

@ -347,6 +347,13 @@ namespace Oqtane.Controllers
return user; return user;
} }
// GET api/<controller>/validate/x
[HttpGet("validateuser")]
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
{
return await _userManager.ValidateUser(username, email, password);
}
// GET api/<controller>/validate/x // GET api/<controller>/validate/x
[HttpGet("validate/{password}")] [HttpGet("validate/{password}")]
public async Task<bool> Validate(string password) public async Task<bool> Validate(string password)

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Oqtane.Managers
{
/// <summary>
/// This class is only used for user validation during installation process.
/// </summary>
/// <typeparam name="TUser"></typeparam>
internal class InstallUserManager<TUser> : UserManager<IdentityUser>
{
public InstallUserManager(IUserStore<IdentityUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<IdentityUser> passwordHasher, IEnumerable<IUserValidator<IdentityUser>> userValidators, IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<IdentityUser>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
public override async Task<IdentityUser> FindByNameAsync(string userName)
{
await Task.CompletedTask;
return null;
}
public override async Task<IdentityUser> FindByEmailAsync(string email)
{
await Task.CompletedTask;
return null;
}
public override async Task<string> GetUserIdAsync(IdentityUser user)
{
await Task.CompletedTask;
return null;
}
}
}

View File

@ -19,6 +19,7 @@ namespace Oqtane.Managers
Task<User> ResetPassword(User user, string token); Task<User> ResetPassword(User user, string token);
User VerifyTwoFactor(User user, string token); User VerifyTwoFactor(User user, string token);
Task<User> LinkExternalAccount(User user, string token, string type, string key, string name); Task<User> LinkExternalAccount(User user, string token, string type, string key, string name);
Task<UserValidateResult> ValidateUser(string username, string email, string password);
Task<bool> ValidatePassword(string password); Task<bool> ValidatePassword(string password);
Task<Dictionary<string, string>> ImportUsers(int siteId, string filePath, bool notify); Task<Dictionary<string, string>> ImportUsers(int siteId, string filePath, bool notify);
} }

View File

@ -33,8 +33,41 @@ namespace Oqtane.Managers
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly IMemoryCache _cache; private readonly IMemoryCache _cache;
private readonly IStringLocalizer<UserManager> _localizer; private readonly IStringLocalizer<UserManager> _localizer;
private readonly IUserStore<IdentityUser> _identityStore;
private readonly Microsoft.Extensions.Options.IOptions<IdentityOptions> _identityOptionsAccessor;
private readonly IPasswordHasher<IdentityUser> _passwordHasher;
private readonly IEnumerable<IUserValidator<IdentityUser>> _userValidators;
private readonly IEnumerable<IPasswordValidator<IdentityUser>> _passwordValidators;
private readonly ILookupNormalizer _identityKeyNormalizer;
private readonly IdentityErrorDescriber _identityErrors;
private readonly IServiceProvider _identityServices;
private readonly Microsoft.Extensions.Logging.ILogger<UserManager<IdentityUser>> _identityLogger;
public UserManager(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IProfileRepository profiles, ISettingRepository settings, ISiteRepository sites, ISyncManager syncManager, ILogManager logger, IMemoryCache cache, IStringLocalizer<UserManager> localizer) public UserManager(
IUserRepository users,
IRoleRepository roles,
IUserRoleRepository userRoles,
UserManager<IdentityUser> identityUserManager,
SignInManager<IdentityUser> identitySignInManager,
ITenantManager tenantManager,
INotificationRepository notifications,
IFolderRepository folders,
IProfileRepository profiles,
ISettingRepository settings,
ISiteRepository sites,
ISyncManager syncManager,
ILogManager logger,
IMemoryCache cache,
IStringLocalizer<UserManager> localizer,
IUserStore<IdentityUser> store,
Microsoft.Extensions.Options.IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher,
IEnumerable<IUserValidator<IdentityUser>> userValidators,
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
Microsoft.Extensions.Logging.ILogger<UserManager<IdentityUser>> identityLogger)
{ {
_users = users; _users = users;
_roles = roles; _roles = roles;
@ -51,6 +84,15 @@ namespace Oqtane.Managers
_logger = logger; _logger = logger;
_cache = cache; _cache = cache;
_localizer = localizer; _localizer = localizer;
_identityStore = store;
_identityOptionsAccessor = optionsAccessor;
_passwordHasher = passwordHasher;
_userValidators = userValidators;
_passwordValidators = passwordValidators;
_identityKeyNormalizer = keyNormalizer;
_identityErrors = errors;
_identityServices = services;
_identityLogger = identityLogger;
} }
public User GetUser(int userid, int siteid) public User GetUser(int userid, int siteid)
@ -540,6 +582,40 @@ namespace Oqtane.Managers
return user; return user;
} }
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
{
var validateResult = new UserValidateResult { Succeeded = true };
var installUserManager = new InstallUserManager<IdentityUser>(_identityStore, _identityOptionsAccessor, _passwordHasher, _userValidators, _passwordValidators, _identityKeyNormalizer, _identityErrors, _identityServices, _identityLogger);
var user = new IdentityUser { UserName = username, Email = email, EmailConfirmed = true };
var userValidator = new UserValidator<IdentityUser>();
var userResult = await userValidator.ValidateAsync(installUserManager, user);
if (!userResult.Succeeded)
{
validateResult.Succeeded = false;
if(userResult.Errors != null)
{
validateResult.Errors = userResult.Errors?.ToDictionary(i => i.Code, i => i.Description);
}
}
var passwordValidator = new PasswordValidator<IdentityUser>();
var passwordResult = await passwordValidator.ValidateAsync(installUserManager, null, password);
if (!passwordResult.Succeeded && !validateResult.Errors.ContainsKey("InvalidPassword"))
{
validateResult.Succeeded = false;
if (passwordResult.Errors != null)
{
foreach (var error in passwordResult.Errors)
{
validateResult.Errors.Add(error.Code, error.Description);
}
}
}
return validateResult;
}
public async Task<bool> ValidatePassword(string password) public async Task<bool> ValidatePassword(string password)
{ {
var validator = new PasswordValidator<IdentityUser>(); var validator = new PasswordValidator<IdentityUser>();

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Oqtane.Models
{
public class UserValidateResult
{
public bool Succeeded { get; set; }
public IDictionary<string, string> Errors { get; set; } = new Dictionary<string, string>();
}
}