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

View File

@ -113,6 +113,15 @@ namespace Oqtane.Services
/// <returns></returns>
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>
/// Validate a users password against the password policy
/// </summary>

View File

@ -89,6 +89,11 @@ namespace Oqtane.Services
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)
{
return await GetJsonAsync<bool>($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}");

View File

@ -347,6 +347,13 @@ namespace Oqtane.Controllers
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
[HttpGet("validate/{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);
User VerifyTwoFactor(User user, string token);
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<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 IMemoryCache _cache;
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;
_roles = roles;
@ -51,6 +84,15 @@ namespace Oqtane.Managers
_logger = logger;
_cache = cache;
_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)
@ -540,6 +582,40 @@ namespace Oqtane.Managers
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)
{
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>();
}
}