mirror of
https://github.com/hmaxnl/DotBased.git
synced 2025-01-18 10:04:20 +01:00
[ADD] Implementing services/handlers
This commit is contained in:
parent
361af34036
commit
ebfafa2f29
|
@ -1,7 +1,7 @@
|
|||
namespace DotBased.AspNet.Authority.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates to protect the property before saving to the repository.
|
||||
/// Indicates to protect the property before saving/loading to the repository.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ProtectAttribute : Attribute
|
||||
|
|
|
@ -1,20 +1,41 @@
|
|||
using DotBased.AspNet.Authority.Interfaces;
|
||||
using DotBased.AspNet.Authority.Crypto;
|
||||
using DotBased.AspNet.Authority.Models.Authority;
|
||||
using DotBased.AspNet.Authority.Models.Options;
|
||||
using DotBased.AspNet.Authority.Services;
|
||||
using DotBased.AspNet.Authority.Validators;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace DotBased.AspNet.Authority;
|
||||
|
||||
public static class AuthorityProviderExtensions
|
||||
{
|
||||
public static AuthorityBuilder AddAuthorityProvider<TModel>(this IServiceCollection services, Action<AuthorityOptions> optionsAction) where TModel : class
|
||||
public static AuthorityBuilder AddAuthority(this IServiceCollection services, Action<AuthorityOptions>? optionsAction = null)
|
||||
=> services.AddAuthority<AuthorityUser, AuthorityGroup, AuthorityRole>(optionsAction);
|
||||
|
||||
public static AuthorityBuilder AddAuthority<TUser, TGroup, TRole>(this IServiceCollection services, Action<AuthorityOptions>? optionsAction = null)
|
||||
where TUser : class where TGroup : class where TRole : class
|
||||
{
|
||||
if (optionsAction != null)
|
||||
{
|
||||
services.AddOptions();
|
||||
// Configure required classes, services, etc.
|
||||
services.Configure<AuthorityOptions>(optionsAction);
|
||||
}
|
||||
services.TryAddScoped<ICryptographer, Cryptographer>();
|
||||
services.TryAddScoped<IPasswordHasher, PasswordHasher>();
|
||||
services.TryAddScoped<IPasswordValidator<TUser>, PasswordOptionsValidator<TUser>>();
|
||||
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
|
||||
/*services.TryAddScoped<IEmailVerifier, EmailVerifier>();
|
||||
services.TryAddScoped<IPhoneNumberVerifier, PhoneNumberVerifier>();
|
||||
services.TryAddScoped<IUserVerifier, UserVerifier>();*/
|
||||
services.TryAddScoped<AuthorityManager>();
|
||||
services.TryAddScoped<AuthorityUserManager<TUser>>();
|
||||
services.TryAddScoped<AuthorityGroupManager<TGroup>>();
|
||||
services.TryAddScoped<AuthorityRoleManager<TRole>>();
|
||||
return new AuthorityBuilder(services);
|
||||
}
|
||||
|
||||
public static AuthorityBuilder AddAuthorityStore<TStore>(this AuthorityBuilder authorityBuilder) where TStore : IAuthorityRepository
|
||||
public static AuthorityBuilder AddAuthorityRepository<TRepository>(this AuthorityBuilder authorityBuilder) where TRepository : class
|
||||
{
|
||||
return authorityBuilder;
|
||||
}
|
||||
|
|
14
DotBased.AspNet.Authority/Crypto/Cryptographer.cs
Normal file
14
DotBased.AspNet.Authority/Crypto/Cryptographer.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace DotBased.AspNet.Authority.Crypto;
|
||||
|
||||
public class Cryptographer : ICryptographer
|
||||
{
|
||||
public Task<string?> EncryptAsync(string data)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<string?> DecryptAsync(string data)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
7
DotBased.AspNet.Authority/Crypto/ICryptographer.cs
Normal file
7
DotBased.AspNet.Authority/Crypto/ICryptographer.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace DotBased.AspNet.Authority.Crypto;
|
||||
|
||||
public interface ICryptographer
|
||||
{
|
||||
public Task<string?> EncryptAsync(string data);
|
||||
public Task<string?> DecryptAsync(string data);
|
||||
}
|
6
DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs
Normal file
6
DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Crypto;
|
||||
|
||||
public interface IPasswordHasher
|
||||
{
|
||||
public Task<string> HashPasswordAsync(string password);
|
||||
}
|
9
DotBased.AspNet.Authority/Crypto/PasswordHasher.cs
Normal file
9
DotBased.AspNet.Authority/Crypto/PasswordHasher.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace DotBased.AspNet.Authority.Crypto;
|
||||
|
||||
public class PasswordHasher : IPasswordHasher
|
||||
{
|
||||
public async Task<string> HashPasswordAsync(string password)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Authentication\" />
|
||||
<Folder Include="Models\Security\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
namespace DotBased.AspNet.Authority.Interfaces;
|
||||
|
||||
public interface IAttributeRepository
|
||||
{
|
||||
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
namespace DotBased.AspNet.Authority.Interfaces;
|
||||
|
||||
public interface IAuthorityRepository
|
||||
{
|
||||
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
namespace DotBased.AspNet.Authority.Interfaces;
|
||||
|
||||
public interface IRoleRepository
|
||||
{
|
||||
|
||||
}
|
|
@ -6,5 +6,6 @@ public class AuthorityOptions
|
|||
public LockoutOptions Lockout { get; set; } = new();
|
||||
public PasswordOptions Password { get; set; } = new();
|
||||
public ProviderOptions Provider { get; set; } = new();
|
||||
public RepositoryOptions Repository { get; set; } = new();
|
||||
public UserOptions User { get; set; } = new();
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace DotBased.AspNet.Authority.Models.Options;
|
||||
|
||||
public class RepositoryOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Use data encryption when a property has the <see cref="DotBased.AspNet.Authority.Attributes.ProtectAttribute"/> defined.
|
||||
/// <value>Default: true</value>
|
||||
/// </summary>
|
||||
public bool UseDataProtection { get; set; } = true;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
namespace DotBased.AspNet.Authority.Models.Validation;
|
||||
|
||||
public class ValidationError
|
||||
{
|
||||
public ValidationError(string validator, string errorCode, string description)
|
||||
{
|
||||
Validator = validator;
|
||||
ErrorCode = errorCode;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The validator name that generated this error.
|
||||
/// </summary>
|
||||
public string Validator { get; }
|
||||
/// <summary>
|
||||
/// The error code
|
||||
/// </summary>
|
||||
public string ErrorCode { get; }
|
||||
/// <summary>
|
||||
/// Error description
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
namespace DotBased.AspNet.Authority.Models.Validation;
|
||||
|
||||
public class ValidationResult
|
||||
{
|
||||
public ValidationResult(bool success, IEnumerable<ValidationError>? errors = null)
|
||||
{
|
||||
if (errors != null)
|
||||
{
|
||||
Errors = errors.ToList();
|
||||
}
|
||||
Success = success;
|
||||
}
|
||||
|
||||
public bool Success { get; }
|
||||
public IReadOnlyList<ValidationError> Errors { get; } = [];
|
||||
|
||||
public static ValidationResult Failed(IEnumerable<ValidationError> errors) => new(false, errors);
|
||||
public static ValidationResult Ok() => new(true);
|
||||
|
||||
public override string ToString() => Success ? "Success" : $"Failed ({Errors.Count} errors)";
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
namespace DotBased.AspNet.Authority.Repositories;
|
||||
|
||||
public class AuthorityRepository // Inherit the repository interfaces?
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Repositories;
|
||||
|
||||
public interface IAttributeRepository<TAttribute, TId> where TAttribute : class where TId : IEquatable<TId>
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace DotBased.AspNet.Authority.Repositories;
|
||||
|
||||
public interface IAuthorityRepository
|
||||
{
|
||||
public Task<int> GetVersion();
|
||||
public Task SetVersion(int version);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Repositories;
|
||||
|
||||
public interface IGroupRepository<TGroup, TId> where TGroup : class where TId : IEquatable<TId>
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Repositories;
|
||||
|
||||
public interface IRoleRepository<TRole, TId> where TRole : class where TId : IEquatable<TId>
|
||||
{
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace DotBased.AspNet.Authority.Interfaces;
|
||||
namespace DotBased.AspNet.Authority.Repositories;
|
||||
|
||||
public interface IUserRepository<TUser, TId> where TUser : class where TId : IEquatable<TId>
|
||||
{
|
|
@ -0,0 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Services;
|
||||
|
||||
public class AuthorityGroupManager<TGroup>
|
||||
{
|
||||
|
||||
}
|
|
@ -1,6 +1,100 @@
|
|||
using System.Reflection;
|
||||
using DotBased.AspNet.Authority.Attributes;
|
||||
using DotBased.AspNet.Authority.Crypto;
|
||||
using DotBased.AspNet.Authority.Models.Options;
|
||||
using DotBased.AspNet.Authority.Repositories;
|
||||
using DotBased.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace DotBased.AspNet.Authority.Services;
|
||||
|
||||
public class AuthorityManager<TData>
|
||||
public class AuthorityManager
|
||||
{
|
||||
public AuthorityManager(
|
||||
IOptions<AuthorityOptions> options,
|
||||
IServiceProvider services,
|
||||
IAuthorityRepository repository,
|
||||
ICryptographer cryptographer)
|
||||
{
|
||||
_logger = LogService.RegisterLogger<AuthorityManager>();
|
||||
Options = options.Value ?? new AuthorityOptions();
|
||||
Services = services;
|
||||
Repository = repository;
|
||||
Cryptographer = cryptographer;
|
||||
}
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public IServiceProvider Services { get; }
|
||||
public AuthorityOptions Options { get; }
|
||||
public IAuthorityRepository Repository { get; }
|
||||
public ICryptographer Cryptographer { get; }
|
||||
|
||||
|
||||
public long GenerateVersion() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
/// <summary>
|
||||
/// Protect or unprotect the properties with the <see cref="ProtectAttribute"/>
|
||||
/// </summary>
|
||||
/// <param name="data">The data model</param>
|
||||
/// <param name="protection">True for protection false for unprotection.</param>
|
||||
/// <typeparam name="TModel">The class with the properties to protect.</typeparam>
|
||||
public async Task HandlePropertyProtection<TModel>(TModel data, bool protection)
|
||||
{
|
||||
var props = GetProtectedPropertiesValues<TModel>(data);
|
||||
if (Cryptographer == null)
|
||||
{
|
||||
_logger.Warning("No cryptographer specified! Cannot encrypt/decrypt properties.");
|
||||
return;
|
||||
}
|
||||
if (props.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var handledProperties = 0;
|
||||
foreach (var property in props)
|
||||
{
|
||||
if (property.PropertyType != typeof(string))
|
||||
{
|
||||
_logger.Warning("Property({PropName}) with type: {PropType} detected, encrypting only supports strings! Skipping property!", property.Name, property.PropertyType);
|
||||
continue;
|
||||
}
|
||||
|
||||
string? cryptString;
|
||||
if (protection)
|
||||
{
|
||||
cryptString = await Cryptographer.EncryptAsync(property.GetValue(data)?.ToString() ?? string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
cryptString = await Cryptographer.DecryptAsync(property.GetValue(data)?.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
if (cryptString == null)
|
||||
{
|
||||
_logger.Warning("{Protection} failed for property {PropName}", protection ? "Encyption" : "Decyption", property.Name);
|
||||
continue;
|
||||
}
|
||||
property.SetValue(data, cryptString);
|
||||
handledProperties++;
|
||||
}
|
||||
_logger.Debug("{HandledPropCount}/{TotalPropCount} protection properties handled!", handledProperties, props.Count);
|
||||
}
|
||||
|
||||
public bool IsPropertieProtected<TModel>(string propertieName)
|
||||
{
|
||||
var protectedProperties = GetProtectedProperties<TModel>();
|
||||
var propertieFound = protectedProperties.Where(propInfo => propInfo.Name == propertieName);
|
||||
return propertieFound.Any();
|
||||
}
|
||||
|
||||
public List<PropertyInfo> GetProtectedPropertiesValues<TModel>(TModel model)
|
||||
{
|
||||
var protectedProperties = GetProtectedProperties<TModel>();
|
||||
return protectedProperties.Count != 0 ? protectedProperties : [];
|
||||
}
|
||||
|
||||
public List<PropertyInfo> GetProtectedProperties<TModel>()
|
||||
=> typeof(TModel).GetProperties().Where(p => Attribute.IsDefined(p, typeof(ProtectAttribute))).ToList();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Services;
|
||||
|
||||
public class AuthorityRoleManager<TRole>
|
||||
{
|
||||
|
||||
}
|
28
DotBased.AspNet.Authority/Services/AuthorityUserManager.cs
Normal file
28
DotBased.AspNet.Authority/Services/AuthorityUserManager.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using DotBased.AspNet.Authority.Validators;
|
||||
using DotBased.Logging;
|
||||
|
||||
namespace DotBased.AspNet.Authority.Services;
|
||||
|
||||
public class AuthorityUserManager<TUser>
|
||||
{
|
||||
public AuthorityUserManager(
|
||||
AuthorityManager manager,
|
||||
IEnumerable<IPasswordValidator<TUser>>? passwordValidators,
|
||||
IEnumerable<IUserValidator<TUser>>? userValidators)
|
||||
{
|
||||
_logger = LogService.RegisterLogger<AuthorityUserManager<TUser>>();
|
||||
AuthorityManager = manager;
|
||||
if (passwordValidators != null)
|
||||
PasswordValidators = passwordValidators;
|
||||
if (userValidators != null)
|
||||
UserValidators = userValidators;
|
||||
}
|
||||
|
||||
private readonly ILogger _logger;
|
||||
public AuthorityManager AuthorityManager { get; }
|
||||
|
||||
public IEnumerable<IPasswordValidator<TUser>> PasswordValidators { get; } = [];
|
||||
public IEnumerable<IUserValidator<TUser>> UserValidators { get; } = [];
|
||||
|
||||
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
using DotBased.AspNet.Authority.Models.Validation;
|
||||
using DotBased.AspNet.Authority.Services;
|
||||
|
||||
namespace DotBased.AspNet.Authority.Validators;
|
||||
|
||||
public interface IPasswordValidator<TUser>
|
||||
{
|
||||
|
||||
public Task<ValidationResult> ValidatePasswordAsync(AuthorityUserManager<TUser> userManager, string password);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Validators;
|
||||
|
||||
public interface IUserValidator
|
||||
public interface IUserValidator<TUser>
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using DotBased.AspNet.Authority.Models.Validation;
|
||||
using DotBased.AspNet.Authority.Services;
|
||||
using DotBased.Extensions;
|
||||
|
||||
namespace DotBased.AspNet.Authority.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Validates the password against the options that is configured.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUser">The user model used.</typeparam>
|
||||
public class PasswordOptionsValidator<TUser> : IPasswordValidator<TUser>
|
||||
{
|
||||
private const string ValidatorId = "Authority.Validator.Password.Options";
|
||||
private const string ValidationBase = "Authority.Validation.Password";
|
||||
|
||||
public async Task<ValidationResult> ValidatePasswordAsync(AuthorityUserManager<TUser> userManager, string password)
|
||||
{
|
||||
if (userManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userManager), "User manager is not provided!");
|
||||
}
|
||||
var passwordOptions = userManager.AuthorityManager.Options.Password;
|
||||
var errors = new List<ValidationError>();
|
||||
|
||||
if (password.IsNullOrEmpty() || password.Length < passwordOptions.RequiredLength)
|
||||
{
|
||||
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.Length", $"Password needs to have a minimum length of {passwordOptions.RequiredLength}"));
|
||||
}
|
||||
|
||||
if (passwordOptions.RequireDigit && !ContainsDigit(password))
|
||||
{
|
||||
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.Digit", "Password must contain a digit!"));
|
||||
}
|
||||
|
||||
if (passwordOptions.RequireNonAlphanumeric && ContainsNonAlphanumeric(password))
|
||||
{
|
||||
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.NonAlphanumeric", "Password must contain a non alphanumeric character."));
|
||||
}
|
||||
|
||||
if (passwordOptions.RequireLowercase && password.Any(char.IsLower))
|
||||
{
|
||||
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.Lowercase", "Password must contains at least one lowercase character."));
|
||||
}
|
||||
|
||||
if (passwordOptions.RequireUppercase && password.Any(char.IsUpper))
|
||||
{
|
||||
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Required.Uppercase", "Password must contains at least one uppercase character."));
|
||||
}
|
||||
|
||||
if (passwordOptions.PasswordBlackList.Count != 0 && passwordOptions.PasswordBlackList.Contains(password, passwordOptions.PasswordBlackListComparer))
|
||||
{
|
||||
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Blacklisted", "Given password is not allowed (blacklisted)"));
|
||||
}
|
||||
|
||||
if (passwordOptions.MinimalUniqueChars > 0 && password.Distinct().Count() < passwordOptions.MinimalUniqueChars)
|
||||
{
|
||||
errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.UniqueChars", $"Password must contain at least {passwordOptions.MinimalUniqueChars} unique chars."));
|
||||
}
|
||||
|
||||
return await Task.FromResult(errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok());
|
||||
}
|
||||
|
||||
private bool ContainsDigit(string strVal) => strVal.Any(char.IsDigit);
|
||||
|
||||
private bool ContainsNonAlphanumeric(string strVal) => !strVal.Any(char.IsLetterOrDigit);
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
namespace DotBased.AspNet.Authority.Validators;
|
||||
|
||||
public class PasswordValidator
|
||||
{
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Validators;
|
||||
|
||||
public class UserValidator
|
||||
public class UserValidator<TUser> : IUserValidator<TUser>
|
||||
{
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
namespace DotBased.AspNet.Authority.Verifiers;
|
||||
|
||||
public class IUserVerifier
|
||||
public interface IUserVerifier
|
||||
{
|
||||
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using DotBased.AspNet.Authority;
|
||||
using DotBased.Logging;
|
||||
using DotBased.Logging.MEL;
|
||||
using DotBased.Logging.Serilog;
|
||||
|
@ -19,6 +20,11 @@ LogService.AddLogAdapter(new BasedSerilogAdapter(serilogLogger));
|
|||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddDotBasedLoggerProvider(LogService.Options);
|
||||
|
||||
builder.Services.AddAuthority(options =>
|
||||
{
|
||||
|
||||
});
|
||||
|
||||
/*builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = BasedAuthenticationDefaults.BasedAuthenticationScheme;
|
||||
|
|
Loading…
Reference in New Issue
Block a user