From ebfafa2f29a0dbd3228e72ae50e4369a3b098fd5 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 25 Dec 2024 22:50:04 +0100 Subject: [PATCH] [ADD] Implementing services/handlers --- .../Attributes/ProtectAttribute.cs | 2 +- .../AuthorityProviderExtensions.cs | 33 +++++-- .../Crypto/Cryptographer.cs | 14 +++ .../Crypto/ICryptographer.cs | 7 ++ .../Crypto/IPasswordHasher.cs | 6 ++ .../Crypto/PasswordHasher.cs | 9 ++ .../DotBased.AspNet.Authority.csproj | 1 - .../Interfaces/IAttributeRepository.cs | 6 -- .../Interfaces/IAuthorityRepository.cs | 6 -- .../Interfaces/IRoleRepository.cs | 6 -- .../Models/Options/AuthorityOptions.cs | 1 + .../Models/Options/RepositoryOptions.cs | 10 ++ .../Models/Validation/ValidationError.cs | 24 +++++ .../Models/Validation/ValidationResult.cs | 21 ++++ .../Repositories/AuthorityRepository.cs | 6 -- .../Repositories/IAttributeRepository.cs | 6 ++ .../Repositories/IAuthorityRepository.cs | 7 ++ .../Repositories/IGroupRepository.cs | 6 ++ .../Repositories/IRoleRepository.cs | 6 ++ .../IUserRepository.cs | 2 +- .../Services/AuthorityGroupManager.cs | 6 ++ .../Services/AuthorityManager.cs | 96 ++++++++++++++++++- .../Services/AuthorityRoleManager.cs | 6 ++ .../Services/AuthorityUserManager.cs | 28 ++++++ .../Validators/IPasswordValidator.cs | 5 +- .../Validators/IUserValidator.cs | 2 +- .../Validators/PasswordOptionsValidator.cs | 66 +++++++++++++ .../Validators/PasswordValidator.cs | 6 -- .../Validators/UserValidator.cs | 2 +- .../Verifiers/IUserVerifier.cs | 2 +- TestWebApi/Program.cs | 6 ++ 31 files changed, 360 insertions(+), 44 deletions(-) create mode 100644 DotBased.AspNet.Authority/Crypto/Cryptographer.cs create mode 100644 DotBased.AspNet.Authority/Crypto/ICryptographer.cs create mode 100644 DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs create mode 100644 DotBased.AspNet.Authority/Crypto/PasswordHasher.cs delete mode 100644 DotBased.AspNet.Authority/Interfaces/IAttributeRepository.cs delete mode 100644 DotBased.AspNet.Authority/Interfaces/IAuthorityRepository.cs delete mode 100644 DotBased.AspNet.Authority/Interfaces/IRoleRepository.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs create mode 100644 DotBased.AspNet.Authority/Models/Validation/ValidationError.cs create mode 100644 DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs delete mode 100644 DotBased.AspNet.Authority/Repositories/AuthorityRepository.cs create mode 100644 DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs create mode 100644 DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs create mode 100644 DotBased.AspNet.Authority/Repositories/IGroupRepository.cs create mode 100644 DotBased.AspNet.Authority/Repositories/IRoleRepository.cs rename DotBased.AspNet.Authority/{Interfaces => Repositories}/IUserRepository.cs (85%) create mode 100644 DotBased.AspNet.Authority/Services/AuthorityGroupManager.cs create mode 100644 DotBased.AspNet.Authority/Services/AuthorityRoleManager.cs create mode 100644 DotBased.AspNet.Authority/Services/AuthorityUserManager.cs create mode 100644 DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs delete mode 100644 DotBased.AspNet.Authority/Validators/PasswordValidator.cs diff --git a/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs b/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs index 58775b1..1d1749e 100644 --- a/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs +++ b/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs @@ -1,7 +1,7 @@ namespace DotBased.AspNet.Authority.Attributes; /// -/// Indicates to protect the property before saving to the repository. +/// Indicates to protect the property before saving/loading to the repository. /// [AttributeUsage(AttributeTargets.Property)] public class ProtectAttribute : Attribute diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index 82326b0..b99a7ec 100644 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -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(this IServiceCollection services, Action optionsAction) where TModel : class + public static AuthorityBuilder AddAuthority(this IServiceCollection services, Action? optionsAction = null) + => services.AddAuthority(optionsAction); + + public static AuthorityBuilder AddAuthority(this IServiceCollection services, Action? optionsAction = null) + where TUser : class where TGroup : class where TRole : class { - services.AddOptions(); - // Configure required classes, services, etc. - services.Configure(optionsAction); + if (optionsAction != null) + { + services.AddOptions(); + services.Configure(optionsAction); + } + services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped, PasswordOptionsValidator>(); + services.TryAddScoped, UserValidator>(); + /*services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped();*/ + services.TryAddScoped(); + services.TryAddScoped>(); + services.TryAddScoped>(); + services.TryAddScoped>(); return new AuthorityBuilder(services); } - public static AuthorityBuilder AddAuthorityStore(this AuthorityBuilder authorityBuilder) where TStore : IAuthorityRepository + public static AuthorityBuilder AddAuthorityRepository(this AuthorityBuilder authorityBuilder) where TRepository : class { return authorityBuilder; } diff --git a/DotBased.AspNet.Authority/Crypto/Cryptographer.cs b/DotBased.AspNet.Authority/Crypto/Cryptographer.cs new file mode 100644 index 0000000..d6a3416 --- /dev/null +++ b/DotBased.AspNet.Authority/Crypto/Cryptographer.cs @@ -0,0 +1,14 @@ +namespace DotBased.AspNet.Authority.Crypto; + +public class Cryptographer : ICryptographer +{ + public Task EncryptAsync(string data) + { + throw new NotImplementedException(); + } + + public Task DecryptAsync(string data) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Crypto/ICryptographer.cs b/DotBased.AspNet.Authority/Crypto/ICryptographer.cs new file mode 100644 index 0000000..a043271 --- /dev/null +++ b/DotBased.AspNet.Authority/Crypto/ICryptographer.cs @@ -0,0 +1,7 @@ +namespace DotBased.AspNet.Authority.Crypto; + +public interface ICryptographer +{ + public Task EncryptAsync(string data); + public Task DecryptAsync(string data); +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs b/DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs new file mode 100644 index 0000000..23cd3ef --- /dev/null +++ b/DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Crypto; + +public interface IPasswordHasher +{ + public Task HashPasswordAsync(string password); +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Crypto/PasswordHasher.cs b/DotBased.AspNet.Authority/Crypto/PasswordHasher.cs new file mode 100644 index 0000000..38ad043 --- /dev/null +++ b/DotBased.AspNet.Authority/Crypto/PasswordHasher.cs @@ -0,0 +1,9 @@ +namespace DotBased.AspNet.Authority.Crypto; + +public class PasswordHasher : IPasswordHasher +{ + public async Task HashPasswordAsync(string password) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj index f056f35..53d8b22 100644 --- a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj +++ b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj @@ -17,7 +17,6 @@ - diff --git a/DotBased.AspNet.Authority/Interfaces/IAttributeRepository.cs b/DotBased.AspNet.Authority/Interfaces/IAttributeRepository.cs deleted file mode 100644 index d0f90d1..0000000 --- a/DotBased.AspNet.Authority/Interfaces/IAttributeRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DotBased.AspNet.Authority.Interfaces; - -public interface IAttributeRepository -{ - -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Interfaces/IAuthorityRepository.cs b/DotBased.AspNet.Authority/Interfaces/IAuthorityRepository.cs deleted file mode 100644 index 3f09635..0000000 --- a/DotBased.AspNet.Authority/Interfaces/IAuthorityRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DotBased.AspNet.Authority.Interfaces; - -public interface IAuthorityRepository -{ - -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Interfaces/IRoleRepository.cs b/DotBased.AspNet.Authority/Interfaces/IRoleRepository.cs deleted file mode 100644 index 9ad9dc8..0000000 --- a/DotBased.AspNet.Authority/Interfaces/IRoleRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DotBased.AspNet.Authority.Interfaces; - -public interface IRoleRepository -{ - -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs b/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs index 77f631c..6ab3e04 100644 --- a/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs +++ b/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs @@ -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(); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs b/DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs new file mode 100644 index 0000000..454af3e --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs @@ -0,0 +1,10 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public class RepositoryOptions +{ + /// + /// Use data encryption when a property has the defined. + /// Default: true + /// + public bool UseDataProtection { get; set; } = true; +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Validation/ValidationError.cs b/DotBased.AspNet.Authority/Models/Validation/ValidationError.cs new file mode 100644 index 0000000..d65c178 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Validation/ValidationError.cs @@ -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; + } + + /// + /// The validator name that generated this error. + /// + public string Validator { get; } + /// + /// The error code + /// + public string ErrorCode { get; } + /// + /// Error description + /// + public string Description { get; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs b/DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs new file mode 100644 index 0000000..aea2d80 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs @@ -0,0 +1,21 @@ +namespace DotBased.AspNet.Authority.Models.Validation; + +public class ValidationResult +{ + public ValidationResult(bool success, IEnumerable? errors = null) + { + if (errors != null) + { + Errors = errors.ToList(); + } + Success = success; + } + + public bool Success { get; } + public IReadOnlyList Errors { get; } = []; + + public static ValidationResult Failed(IEnumerable errors) => new(false, errors); + public static ValidationResult Ok() => new(true); + + public override string ToString() => Success ? "Success" : $"Failed ({Errors.Count} errors)"; +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/AuthorityRepository.cs b/DotBased.AspNet.Authority/Repositories/AuthorityRepository.cs deleted file mode 100644 index 239c114..0000000 --- a/DotBased.AspNet.Authority/Repositories/AuthorityRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DotBased.AspNet.Authority.Repositories; - -public class AuthorityRepository // Inherit the repository interfaces? -{ - -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs new file mode 100644 index 0000000..92272f3 --- /dev/null +++ b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Repositories; + +public interface IAttributeRepository where TAttribute : class where TId : IEquatable +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs b/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs new file mode 100644 index 0000000..8c0af84 --- /dev/null +++ b/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs @@ -0,0 +1,7 @@ +namespace DotBased.AspNet.Authority.Repositories; + +public interface IAuthorityRepository +{ + public Task GetVersion(); + public Task SetVersion(int version); +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs new file mode 100644 index 0000000..667f839 --- /dev/null +++ b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Repositories; + +public interface IGroupRepository where TGroup : class where TId : IEquatable +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs new file mode 100644 index 0000000..e3c9b4c --- /dev/null +++ b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Repositories; + +public interface IRoleRepository where TRole : class where TId : IEquatable +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs similarity index 85% rename from DotBased.AspNet.Authority/Interfaces/IUserRepository.cs rename to DotBased.AspNet.Authority/Repositories/IUserRepository.cs index c2c420c..23c4c6e 100644 --- a/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -1,4 +1,4 @@ -namespace DotBased.AspNet.Authority.Interfaces; +namespace DotBased.AspNet.Authority.Repositories; public interface IUserRepository where TUser : class where TId : IEquatable { diff --git a/DotBased.AspNet.Authority/Services/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Services/AuthorityGroupManager.cs new file mode 100644 index 0000000..b8db5ea --- /dev/null +++ b/DotBased.AspNet.Authority/Services/AuthorityGroupManager.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Services; + +public class AuthorityGroupManager +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityManager.cs b/DotBased.AspNet.Authority/Services/AuthorityManager.cs index 3cb5eb7..2cf9d19 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityManager.cs @@ -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 +public class AuthorityManager { + public AuthorityManager( + IOptions options, + IServiceProvider services, + IAuthorityRepository repository, + ICryptographer cryptographer) + { + _logger = LogService.RegisterLogger(); + 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(); + + /// + /// Protect or unprotect the properties with the + /// + /// The data model + /// True for protection false for unprotection. + /// The class with the properties to protect. + public async Task HandlePropertyProtection(TModel data, bool protection) + { + var props = GetProtectedPropertiesValues(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(string propertieName) + { + var protectedProperties = GetProtectedProperties(); + var propertieFound = protectedProperties.Where(propInfo => propInfo.Name == propertieName); + return propertieFound.Any(); + } + + public List GetProtectedPropertiesValues(TModel model) + { + var protectedProperties = GetProtectedProperties(); + return protectedProperties.Count != 0 ? protectedProperties : []; + } + + public List GetProtectedProperties() + => typeof(TModel).GetProperties().Where(p => Attribute.IsDefined(p, typeof(ProtectAttribute))).ToList(); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Services/AuthorityRoleManager.cs new file mode 100644 index 0000000..d3b8e7d --- /dev/null +++ b/DotBased.AspNet.Authority/Services/AuthorityRoleManager.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Services; + +public class AuthorityRoleManager +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs new file mode 100644 index 0000000..a02c8f3 --- /dev/null +++ b/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs @@ -0,0 +1,28 @@ +using DotBased.AspNet.Authority.Validators; +using DotBased.Logging; + +namespace DotBased.AspNet.Authority.Services; + +public class AuthorityUserManager +{ + public AuthorityUserManager( + AuthorityManager manager, + IEnumerable>? passwordValidators, + IEnumerable>? userValidators) + { + _logger = LogService.RegisterLogger>(); + AuthorityManager = manager; + if (passwordValidators != null) + PasswordValidators = passwordValidators; + if (userValidators != null) + UserValidators = userValidators; + } + + private readonly ILogger _logger; + public AuthorityManager AuthorityManager { get; } + + public IEnumerable> PasswordValidators { get; } = []; + public IEnumerable> UserValidators { get; } = []; + + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs index 2fe5b5c..07590ae 100644 --- a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs @@ -1,6 +1,9 @@ +using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Services; + namespace DotBased.AspNet.Authority.Validators; public interface IPasswordValidator { - + public Task ValidatePasswordAsync(AuthorityUserManager userManager, string password); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IUserValidator.cs b/DotBased.AspNet.Authority/Validators/IUserValidator.cs index cb7e245..96511d6 100644 --- a/DotBased.AspNet.Authority/Validators/IUserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IUserValidator.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Validators; -public interface IUserValidator +public interface IUserValidator { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs new file mode 100644 index 0000000..1360d3e --- /dev/null +++ b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs @@ -0,0 +1,66 @@ +using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Services; +using DotBased.Extensions; + +namespace DotBased.AspNet.Authority.Validators; + +/// +/// Validates the password against the options that is configured. +/// +/// The user model used. +public class PasswordOptionsValidator : IPasswordValidator +{ + private const string ValidatorId = "Authority.Validator.Password.Options"; + private const string ValidationBase = "Authority.Validation.Password"; + + public async Task ValidatePasswordAsync(AuthorityUserManager 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(); + + 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); +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordValidator.cs deleted file mode 100644 index 33ce063..0000000 --- a/DotBased.AspNet.Authority/Validators/PasswordValidator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DotBased.AspNet.Authority.Validators; - -public class PasswordValidator -{ - -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/UserValidator.cs b/DotBased.AspNet.Authority/Validators/UserValidator.cs index 1175fc8..219bccb 100644 --- a/DotBased.AspNet.Authority/Validators/UserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/UserValidator.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Validators; -public class UserValidator +public class UserValidator : IUserValidator { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs b/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs index c41347f..d6e5120 100644 --- a/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs +++ b/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Verifiers; -public class IUserVerifier +public interface IUserVerifier { } \ No newline at end of file diff --git a/TestWebApi/Program.cs b/TestWebApi/Program.cs index f18c0ae..323ca0c 100644 --- a/TestWebApi/Program.cs +++ b/TestWebApi/Program.cs @@ -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;