From 2361e12847bf682ada1fc42c5e384527d4ced0fb Mon Sep 17 00:00:00 2001 From: max Date: Sat, 21 Dec 2024 15:30:17 +0100 Subject: [PATCH 01/38] [ADD] Base Authority initial commit --- .../DotBasedAuthDependencyInjection.cs | 2 +- DotBased.AspNet.Auth/BasedAuthExtensions.cs | 11 ------- .../Attributes/ProtectAttribute.cs | 10 ++++++ DotBased.AspNet.Authority/AuthorityBuilder.cs | 13 ++++++++ .../AuthorityDefaults.cs | 11 +++++++ .../AuthorityProviderExtensions.cs | 17 ++++++++++ .../DotBased.AspNet.Authority.csproj | 18 ++++------- .../Interfaces/IAttributeRepository.cs | 6 ++++ .../Interfaces/IAuthorityRepository.cs | 6 ++++ .../Interfaces/IRoleRepository.cs | 6 ++++ .../Interfaces/IUserRepository.cs | 6 ++++ .../Models/Authority/AuthorityUser.cs | 10 ++++++ .../Models/Authority/AuthorityUserBase.cs | 32 +++++++++++++++++++ .../Services/AuthorityService.cs | 6 ++++ DotBased.sln | 12 +++---- TestWebApi/Program.cs | 9 ++++++ TestWebApi/SeedAuthorityData.cs | 9 ++++++ TestWebApi/TestWebApi.csproj | 1 + 18 files changed, 155 insertions(+), 30 deletions(-) delete mode 100644 DotBased.AspNet.Auth/BasedAuthExtensions.cs create mode 100644 DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs create mode 100644 DotBased.AspNet.Authority/AuthorityBuilder.cs create mode 100644 DotBased.AspNet.Authority/AuthorityDefaults.cs create mode 100644 DotBased.AspNet.Authority/AuthorityProviderExtensions.cs rename DotBased.AspNet.Auth/DotBased.AspNet.Auth.csproj => DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj (64%) create mode 100644 DotBased.AspNet.Authority/Interfaces/IAttributeRepository.cs create mode 100644 DotBased.AspNet.Authority/Interfaces/IAuthorityRepository.cs create mode 100644 DotBased.AspNet.Authority/Interfaces/IRoleRepository.cs create mode 100644 DotBased.AspNet.Authority/Interfaces/IUserRepository.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs create mode 100644 DotBased.AspNet.Authority/Services/AuthorityService.cs create mode 100644 TestWebApi/SeedAuthorityData.cs diff --git a/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs b/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs index 59fa9e0..b060926 100644 --- a/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs +++ b/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs @@ -30,7 +30,7 @@ public static class DotBasedAuthDependencyInjection services.AddAuthentication(options => { options.DefaultScheme = BasedAuthDefaults.AuthenticationScheme; - });/*.AddScheme(BasedAuthDefaults.AuthenticationScheme, null);*/ + }); services.AddAuthorization(); services.AddCascadingAuthenticationState(); return services; diff --git a/DotBased.AspNet.Auth/BasedAuthExtensions.cs b/DotBased.AspNet.Auth/BasedAuthExtensions.cs deleted file mode 100644 index 023a35f..0000000 --- a/DotBased.AspNet.Auth/BasedAuthExtensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace DotBased.AspNet.Auth; - -public static class BasedAuthExtensions -{ - public static IServiceCollection AddBasedAuthentication(this IServiceCollection services) - { - return services; - } -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs b/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs new file mode 100644 index 0000000..13394b8 --- /dev/null +++ b/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs @@ -0,0 +1,10 @@ +namespace DotBased.AspNet.Authority.Attributes; + +/// +/// Indicates that the property should be protected. +/// +[AttributeUsage(AttributeTargets.Property)] +public class ProtectAttribute : Attribute +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/AuthorityBuilder.cs b/DotBased.AspNet.Authority/AuthorityBuilder.cs new file mode 100644 index 0000000..e778248 --- /dev/null +++ b/DotBased.AspNet.Authority/AuthorityBuilder.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace DotBased.AspNet.Authority; + +public class AuthorityBuilder +{ + public AuthorityBuilder(IServiceCollection services) + { + Services = services; + } + + public IServiceCollection Services { get; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/AuthorityDefaults.cs b/DotBased.AspNet.Authority/AuthorityDefaults.cs new file mode 100644 index 0000000..a0b4726 --- /dev/null +++ b/DotBased.AspNet.Authority/AuthorityDefaults.cs @@ -0,0 +1,11 @@ +namespace DotBased.AspNet.Authority; + +public static class AuthorityDefaults +{ + public static class Scheme + { + public const string AuthenticationScheme = "Authority.Scheme.Authentication"; + public const string ExternalScheme = "Authority.Scheme.External"; + } + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs new file mode 100644 index 0000000..71f7730 --- /dev/null +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -0,0 +1,17 @@ +using DotBased.AspNet.Authority.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace DotBased.AspNet.Authority; + +public static class AuthorityProviderExtensions +{ + public static AuthorityBuilder AddAuthorityProvider(this IServiceCollection services) where TModel : class + { + return new AuthorityBuilder(services); + } + + public static AuthorityBuilder AddAuthorityStore(this AuthorityBuilder authorityBuilder) where TStore : IAuthorityRepository + { + return authorityBuilder; + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Auth/DotBased.AspNet.Auth.csproj b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj similarity index 64% rename from DotBased.AspNet.Auth/DotBased.AspNet.Auth.csproj rename to DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj index 3af943e..14ddfd0 100644 --- a/DotBased.AspNet.Auth/DotBased.AspNet.Auth.csproj +++ b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj @@ -7,8 +7,9 @@ - - + + ..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\8.0.2\lib\net8.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + @@ -16,16 +17,9 @@ - - ..\..\..\..\..\usr\lib64\dotnet\shared\Microsoft.AspNetCore.App\8.0.11\Microsoft.AspNetCore.Authentication.dll - - - ..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\8.0.2\lib\net8.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - - - + + + diff --git a/DotBased.AspNet.Authority/Interfaces/IAttributeRepository.cs b/DotBased.AspNet.Authority/Interfaces/IAttributeRepository.cs new file mode 100644 index 0000000..d0f90d1 --- /dev/null +++ b/DotBased.AspNet.Authority/Interfaces/IAttributeRepository.cs @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000..3f09635 --- /dev/null +++ b/DotBased.AspNet.Authority/Interfaces/IAuthorityRepository.cs @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000..9ad9dc8 --- /dev/null +++ b/DotBased.AspNet.Authority/Interfaces/IRoleRepository.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Interfaces; + +public interface IRoleRepository +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs b/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs new file mode 100644 index 0000000..0bc7ad3 --- /dev/null +++ b/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Interfaces; + +public interface IUserRepository +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs new file mode 100644 index 0000000..6c4f8a5 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -0,0 +1,10 @@ +namespace DotBased.AspNet.Authority.Models.Authority; + +public class AuthorityUser : AuthorityUserBase +{ + public AuthorityUser() + { + Id = Guid.NewGuid(); + CreatedDate = DateTime.Now; + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs new file mode 100644 index 0000000..184bd7f --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs @@ -0,0 +1,32 @@ +using DotBased.AspNet.Authority.Attributes; + +namespace DotBased.AspNet.Authority.Models.Authority; + +public abstract class AuthorityUserBase where TKey : IEquatable +{ + public TKey Id { get; set; } + + public bool Enabled { get; set; } + + public bool Locked { get; set; } + + public string UserName { get; set; } + + public string PasswordHash { get; set; } + + public DateTime CreatedDate { get; set; } + + public bool TwoFactorEnabled { get; set; } + + + [Protect] + public string EmailAddress { get; set; } + + public bool EmailConfirmed { get; set; } + + [Protect] + public string PhoneNumber { get; set; } + + public bool PhoneNumberConfirmed { get; set; } + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityService.cs b/DotBased.AspNet.Authority/Services/AuthorityService.cs new file mode 100644 index 0000000..9f90f61 --- /dev/null +++ b/DotBased.AspNet.Authority/Services/AuthorityService.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Services; + +public class AuthorityService +{ + +} \ No newline at end of file diff --git a/DotBased.sln b/DotBased.sln index 4221777..0275d48 100755 --- a/DotBased.sln +++ b/DotBased.sln @@ -20,7 +20,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazor.Wasm", "Blazor.Wasm\ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNet", "AspNet", "{624E7B11-8A18-46E5-AB1F-6AF6097F9D4D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.AspNet.Auth", "DotBased.AspNet.Auth\DotBased.AspNet.Auth.csproj", "{6F407D81-DFAC-4936-ACDD-D75E9FDE2E7B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.AspNet.Authority", "DotBased.AspNet.Authority\DotBased.AspNet.Authority.csproj", "{A3ADC9AF-39B7-4EC4-8022-946118A8C322}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -56,10 +56,10 @@ Global {AC8343A5-7953-4E1D-A926-406BE4D7E819}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC8343A5-7953-4E1D-A926-406BE4D7E819}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC8343A5-7953-4E1D-A926-406BE4D7E819}.Release|Any CPU.Build.0 = Release|Any CPU - {6F407D81-DFAC-4936-ACDD-D75E9FDE2E7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F407D81-DFAC-4936-ACDD-D75E9FDE2E7B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F407D81-DFAC-4936-ACDD-D75E9FDE2E7B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F407D81-DFAC-4936-ACDD-D75E9FDE2E7B}.Release|Any CPU.Build.0 = Release|Any CPU + {A3ADC9AF-39B7-4EC4-8022-946118A8C322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3ADC9AF-39B7-4EC4-8022-946118A8C322}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3ADC9AF-39B7-4EC4-8022-946118A8C322}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3ADC9AF-39B7-4EC4-8022-946118A8C322}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {EBBDAF9A-BFC7-4BDC-8C51-0501B59A1DDC} = {2156FB93-C252-4B33-8A0C-73C82FABB163} @@ -68,6 +68,6 @@ Global {BADA4BAF-142B-47A8-95FC-B25E1D3D0020} = {DBDB4538-85D4-45AC-9E0A-A684467AEABA} {AC8343A5-7953-4E1D-A926-406BE4D7E819} = {DBDB4538-85D4-45AC-9E0A-A684467AEABA} {624E7B11-8A18-46E5-AB1F-6AF6097F9D4D} = {2156FB93-C252-4B33-8A0C-73C82FABB163} - {6F407D81-DFAC-4936-ACDD-D75E9FDE2E7B} = {624E7B11-8A18-46E5-AB1F-6AF6097F9D4D} + {A3ADC9AF-39B7-4EC4-8022-946118A8C322} = {624E7B11-8A18-46E5-AB1F-6AF6097F9D4D} EndGlobalSection EndGlobal diff --git a/TestWebApi/Program.cs b/TestWebApi/Program.cs index 32c7f05..f18c0ae 100644 --- a/TestWebApi/Program.cs +++ b/TestWebApi/Program.cs @@ -2,6 +2,7 @@ using DotBased.Logging; using DotBased.Logging.MEL; using DotBased.Logging.Serilog; using Serilog; +using TestWebApi; using ILogger = Serilog.ILogger; var builder = WebApplication.CreateBuilder(args); @@ -18,6 +19,12 @@ LogService.AddLogAdapter(new BasedSerilogAdapter(serilogLogger)); builder.Logging.ClearProviders(); builder.Logging.AddDotBasedLoggerProvider(LogService.Options); +/*builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = BasedAuthenticationDefaults.BasedAuthenticationScheme; + options.DefaultChallengeScheme = BasedAuthenticationDefaults.BasedAuthenticationScheme; +}).AddCookie();*/ + // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); @@ -25,6 +32,8 @@ builder.Services.AddSwaggerGen(); var app = builder.Build(); +await SeedAuthorityData.InitializeData(app.Services); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { diff --git a/TestWebApi/SeedAuthorityData.cs b/TestWebApi/SeedAuthorityData.cs new file mode 100644 index 0000000..5233886 --- /dev/null +++ b/TestWebApi/SeedAuthorityData.cs @@ -0,0 +1,9 @@ +namespace TestWebApi; + +public class SeedAuthorityData +{ + public static async Task InitializeData(IServiceProvider serviceProvider) + { + // Get the needed services and create users, roles, attributes. etc. + } +} \ No newline at end of file diff --git a/TestWebApi/TestWebApi.csproj b/TestWebApi/TestWebApi.csproj index c3c9828..ef97892 100644 --- a/TestWebApi/TestWebApi.csproj +++ b/TestWebApi/TestWebApi.csproj @@ -13,6 +13,7 @@ + From 7ebe1e1752562ea67a12ef575fcd832daa37050e Mon Sep 17 00:00:00 2001 From: max Date: Sat, 21 Dec 2024 16:14:27 +0100 Subject: [PATCH 02/38] [CHANGE] Extended base user model --- .../Models/Authority/AuthorityUserBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs index 184bd7f..83b8166 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs @@ -17,6 +17,9 @@ public abstract class AuthorityUserBase where TKey : IEquatable public DateTime CreatedDate { get; set; } public bool TwoFactorEnabled { get; set; } + + public string ConcurrencyStamp { get; set; } + public string SecurityStamp { get; set; } [Protect] From 44e64793b7ddca23e49a05a6d9b4a5ab9bb29ec9 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 22 Dec 2024 02:15:34 +0100 Subject: [PATCH 03/38] [ADD] Adding models, repositories. Implementing business logic. --- .../Attributes/ProtectAttribute.cs | 2 +- .../DotBased.AspNet.Authority.csproj | 1 - .../Interfaces/ISecurityVersionRepository.cs | 7 +++ .../Interfaces/IUserRepository.cs | 6 ++- .../Interfaces/IVersionRepository.cs | 6 +++ .../Models/Authority/AuthorityAttribute.cs | 25 +++++++++++ .../Models/Authority/AuthorityGroup.cs | 26 +++++++++++ .../Models/Authority/AuthorityRole.cs | 28 ++++++++++++ .../Models/Authority/AuthorityUser.cs | 44 ++++++++++++++++++- .../Models/Authority/AuthorityUserBase.cs | 35 --------------- .../Repositories/AuthorityRepository.cs | 6 +++ .../Services/AuthorityService.cs | 2 +- 12 files changed, 147 insertions(+), 41 deletions(-) create mode 100644 DotBased.AspNet.Authority/Interfaces/ISecurityVersionRepository.cs create mode 100644 DotBased.AspNet.Authority/Interfaces/IVersionRepository.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs delete mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs create mode 100644 DotBased.AspNet.Authority/Repositories/AuthorityRepository.cs diff --git a/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs b/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs index 13394b8..58775b1 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 that the property should be protected. +/// Indicates to protect the property before saving to the repository. /// [AttributeUsage(AttributeTargets.Property)] public class ProtectAttribute : Attribute diff --git a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj index 14ddfd0..b99e760 100644 --- a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj +++ b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj @@ -19,7 +19,6 @@ - diff --git a/DotBased.AspNet.Authority/Interfaces/ISecurityVersionRepository.cs b/DotBased.AspNet.Authority/Interfaces/ISecurityVersionRepository.cs new file mode 100644 index 0000000..866d22c --- /dev/null +++ b/DotBased.AspNet.Authority/Interfaces/ISecurityVersionRepository.cs @@ -0,0 +1,7 @@ +namespace DotBased.AspNet.Authority.Interfaces; + +public interface ISecurityVersionRepository +{ + public Task GetSecurityVersionAsync(TRepositoryObject obj); + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs b/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs index 0bc7ad3..c7035a3 100644 --- a/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs @@ -1,6 +1,8 @@ namespace DotBased.AspNet.Authority.Interfaces; -public interface IUserRepository +public interface IUserRepository : IVersionRepository, ISecurityVersionRepository where TUser : class where TId : IEquatable { - + public Task GetUserByIdAsync(TId id); + + public Task GetUserIdAsync(TUser user); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Interfaces/IVersionRepository.cs b/DotBased.AspNet.Authority/Interfaces/IVersionRepository.cs new file mode 100644 index 0000000..b88ec89 --- /dev/null +++ b/DotBased.AspNet.Authority/Interfaces/IVersionRepository.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Interfaces; + +public interface IVersionRepository +{ + public Task GetVersionAsync(TRepositoryObject obj); +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs new file mode 100644 index 0000000..dc4e5b5 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs @@ -0,0 +1,25 @@ +namespace DotBased.AspNet.Authority.Models.Authority; + +public class AuthorityAttribute +{ + public AuthorityAttribute(string attributeKey, string bound) : this() + { + AttributeKey = attributeKey; + BoundId = bound; + } + + public AuthorityAttribute() + { + + } + + public string AttributeKey { get; set; } // ClaimType/Authority.attribute.enabled + + public string BoundId { get; set; } // Bound to User, Group, Role id + + public string? AttributeValue { get; set; } + + public string? Type { get; set; } // AspNet.Claim.Role/Property/Data.JSON, Data.Raw, Data.Base64 etc. + + public long Version { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs new file mode 100644 index 0000000..df010e8 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs @@ -0,0 +1,26 @@ +namespace DotBased.AspNet.Authority.Models.Authority; + +public class AuthorityGroup : AuthorityGroup +{ + public AuthorityGroup(string name) : this() + { + Name = name; + } + + public AuthorityGroup() + { + Id = Guid.NewGuid(); + CreatedDate = DateTime.Now; + } +} + +public abstract class AuthorityGroup where TKey : IEquatable +{ + public TKey Id { get; set; } + + public string? Name { get; set; } + + public long Version { get; set; } + + public DateTime CreatedDate { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs new file mode 100644 index 0000000..90ccee5 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs @@ -0,0 +1,28 @@ +namespace DotBased.AspNet.Authority.Models.Authority; + +public class AuthorityRole : AuthorityRole +{ + public AuthorityRole(string name) : this() + { + Name = name; + } + + public AuthorityRole() + { + Id = Guid.NewGuid(); + CreatedDate = DateTime.Now; + } +} + +public abstract class AuthorityRole where TKey : IEquatable +{ + public TKey Id { get; set; } + + public string? Name { get; set; } + + public long Version { get; set; } + + public DateTime CreatedDate { get; set; } + + public override string ToString() => Name ?? string.Empty; +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs index 6c4f8a5..7ed6f9b 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -1,10 +1,52 @@ +using DotBased.AspNet.Authority.Attributes; + namespace DotBased.AspNet.Authority.Models.Authority; -public class AuthorityUser : AuthorityUserBase +public class AuthorityUser : AuthorityUser { + public AuthorityUser(string userName) : this() + { + UserName = userName; + } + public AuthorityUser() { Id = Guid.NewGuid(); CreatedDate = DateTime.Now; } +} + +public abstract class AuthorityUser where TKey : IEquatable +{ + public TKey Id { get; set; } + + public bool Enabled { get; set; } + + public bool Locked { get; set; } + + public DateTime LockedDate { get; set; } + + public string? UserName { get; set; } + + public string? PasswordHash { get; set; } + + public DateTime CreatedDate { get; set; } + + public bool TwoFactorEnabled { get; set; } + + public long Version { get; set; } + + public long SecurityVersion { get; set; } + + [Protect] + public string? EmailAddress { get; set; } + + public bool EmailConfirmed { get; set; } + + [Protect] + public string? PhoneNumber { get; set; } + + public bool PhoneNumberConfirmed { get; set; } + + public override string ToString() => UserName ?? EmailAddress ?? string.Empty; } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs deleted file mode 100644 index 83b8166..0000000 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUserBase.cs +++ /dev/null @@ -1,35 +0,0 @@ -using DotBased.AspNet.Authority.Attributes; - -namespace DotBased.AspNet.Authority.Models.Authority; - -public abstract class AuthorityUserBase where TKey : IEquatable -{ - public TKey Id { get; set; } - - public bool Enabled { get; set; } - - public bool Locked { get; set; } - - public string UserName { get; set; } - - public string PasswordHash { get; set; } - - public DateTime CreatedDate { get; set; } - - public bool TwoFactorEnabled { get; set; } - - public string ConcurrencyStamp { get; set; } - public string SecurityStamp { get; set; } - - - [Protect] - public string EmailAddress { get; set; } - - public bool EmailConfirmed { get; set; } - - [Protect] - public string PhoneNumber { get; set; } - - public bool PhoneNumberConfirmed { get; set; } - -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/AuthorityRepository.cs b/DotBased.AspNet.Authority/Repositories/AuthorityRepository.cs new file mode 100644 index 0000000..239c114 --- /dev/null +++ b/DotBased.AspNet.Authority/Repositories/AuthorityRepository.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Repositories; + +public class AuthorityRepository // Inherit the repository interfaces? +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityService.cs b/DotBased.AspNet.Authority/Services/AuthorityService.cs index 9f90f61..2c61b55 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityService.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityService.cs @@ -2,5 +2,5 @@ namespace DotBased.AspNet.Authority.Services; public class AuthorityService { - + public long GenerateVersion() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } \ No newline at end of file From 797323789ecb0bb85bd461f888cf960d1ce7ea9e Mon Sep 17 00:00:00 2001 From: max Date: Mon, 23 Dec 2024 00:59:13 +0100 Subject: [PATCH 04/38] [ADD] Added base options --- .../Interfaces/ISecurityVersionRepository.cs | 7 ------- .../Interfaces/IUserRepository.cs | 5 +++-- .../Interfaces/IVersionRepository.cs | 6 ------ .../Models/Authority/AuthorityAttribute.cs | 2 +- .../Models/Authority/AuthorityUser.cs | 2 ++ .../Models/Options/AuthorityOptions.cs | 10 ++++++++++ .../Models/Options/LockdownOptions.cs | 6 ++++++ .../Models/Options/LockoutOptions.cs | 8 ++++++++ .../Models/Options/PasswordOptions.cs | 14 ++++++++++++++ .../Models/Options/ProviderOptions.cs | 6 ++++++ .../Models/Options/SignInOptions.cs | 8 ++++++++ .../Models/Options/UserOptions.cs | 11 +++++++++++ 12 files changed, 69 insertions(+), 16 deletions(-) delete mode 100644 DotBased.AspNet.Authority/Interfaces/ISecurityVersionRepository.cs delete mode 100644 DotBased.AspNet.Authority/Interfaces/IVersionRepository.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/SignInOptions.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/UserOptions.cs diff --git a/DotBased.AspNet.Authority/Interfaces/ISecurityVersionRepository.cs b/DotBased.AspNet.Authority/Interfaces/ISecurityVersionRepository.cs deleted file mode 100644 index 866d22c..0000000 --- a/DotBased.AspNet.Authority/Interfaces/ISecurityVersionRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace DotBased.AspNet.Authority.Interfaces; - -public interface ISecurityVersionRepository -{ - public Task GetSecurityVersionAsync(TRepositoryObject obj); - -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs b/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs index c7035a3..c2c420c 100644 --- a/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Interfaces/IUserRepository.cs @@ -1,8 +1,9 @@ namespace DotBased.AspNet.Authority.Interfaces; -public interface IUserRepository : IVersionRepository, ISecurityVersionRepository where TUser : class where TId : IEquatable +public interface IUserRepository where TUser : class where TId : IEquatable { public Task GetUserByIdAsync(TId id); - public Task GetUserIdAsync(TUser user); + public Task SetVersion(TUser user, long version); + public Task SetSecurityVersion(TUser user, long version); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Interfaces/IVersionRepository.cs b/DotBased.AspNet.Authority/Interfaces/IVersionRepository.cs deleted file mode 100644 index b88ec89..0000000 --- a/DotBased.AspNet.Authority/Interfaces/IVersionRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DotBased.AspNet.Authority.Interfaces; - -public interface IVersionRepository -{ - public Task GetVersionAsync(TRepositoryObject obj); -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs index dc4e5b5..1057db7 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs @@ -17,7 +17,7 @@ public class AuthorityAttribute public string BoundId { get; set; } // Bound to User, Group, Role id - public string? AttributeValue { get; set; } + public object? AttributeValue { get; set; } public string? Type { get; set; } // AspNet.Claim.Role/Property/Data.JSON, Data.Raw, Data.Base64 etc. diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs index 7ed6f9b..bbc919f 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -22,6 +22,8 @@ public abstract class AuthorityUser where TKey : IEquatable public bool Enabled { get; set; } + public bool Confirmed { get; set; } + public bool Locked { get; set; } public DateTime LockedDate { get; set; } diff --git a/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs b/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs new file mode 100644 index 0000000..77f631c --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs @@ -0,0 +1,10 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public class AuthorityOptions +{ + public LockdownOptions Lockdown { get; set; } = new(); + public LockoutOptions Lockout { get; set; } = new(); + public PasswordOptions Password { get; set; } = new(); + public ProviderOptions Provider { get; set; } = new(); + public UserOptions User { get; set; } = new(); +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs b/DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs new file mode 100644 index 0000000..aefbf76 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public class LockdownOptions +{ + public bool EnableLockout { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs b/DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs new file mode 100644 index 0000000..6debdbd --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs @@ -0,0 +1,8 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public class LockoutOptions +{ + public bool EnableLockout { get; set; } = true; + public int FailedAttempts { get; set; } = 3; + public TimeSpan LockoutTimeout { get; set; } = TimeSpan.FromMinutes(30); +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs b/DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs new file mode 100644 index 0000000..28000ba --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs @@ -0,0 +1,14 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public class PasswordOptions +{ + public int RequiredLength { get; set; } = 10; + public int MinimalUniqueChars { get; set; } = 1; + public bool RequireLowercase { get; set; } + public bool RequireUppercase { get; set; } + public bool RequireDigit { get; set; } + public bool RequireNonAlphanumeric { get; set; } + + public List PasswordBlackList { get; set; } = ["password", "1234"]; + public StringComparer PasswordBlackListComparer { get; set; } = StringComparer.OrdinalIgnoreCase; +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs b/DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs new file mode 100644 index 0000000..ba11c01 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public class ProviderOptions +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs b/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs new file mode 100644 index 0000000..f25e2be --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs @@ -0,0 +1,8 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public class SignInOptions +{ + public bool RequireValidatedEmail { get; set; } + public bool RequireValidatedPhoneNumber { get; set; } + public bool RequireConfirmedAccount { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/UserOptions.cs b/DotBased.AspNet.Authority/Models/Options/UserOptions.cs new file mode 100644 index 0000000..54011e7 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/UserOptions.cs @@ -0,0 +1,11 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public class UserOptions +{ + public bool EnableRegister { get; set; } + public bool RequireUniqueEmail { get; set; } + public string AllowedCharacters { get; set; } = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@"; + + public List UserNameBlackList { get; set; } = ["admin", "administrator", "dev", "developer"]; + public StringComparer UserNameBlackListComparer { get; set; } = StringComparer.OrdinalIgnoreCase; +} \ No newline at end of file From 5c4ebd2b32e65b4eb0236b4c5107f10825129c20 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 23 Dec 2024 01:26:21 +0100 Subject: [PATCH 05/38] [ADD] Added verifiers, validators & config. --- DotBased.AspNet.Authority/AuthorityProviderExtensions.cs | 6 +++++- DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj | 4 ++++ .../Models/Authority/AuthorityAttribute.cs | 5 +++-- DotBased.AspNet.Authority/Models/Options/SignInOptions.cs | 4 ++-- .../Services/{AuthorityService.cs => AuthorityManager.cs} | 2 +- DotBased.AspNet.Authority/Validators/IPasswordValidator.cs | 6 ++++++ DotBased.AspNet.Authority/Validators/IUserValidator.cs | 6 ++++++ DotBased.AspNet.Authority/Validators/PasswordValidator.cs | 6 ++++++ DotBased.AspNet.Authority/Validators/UserValidator.cs | 6 ++++++ DotBased.AspNet.Authority/Verifiers/IEmailVerifier.cs | 6 ++++++ DotBased.AspNet.Authority/Verifiers/IPhoneNumberVerifier.cs | 6 ++++++ DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs | 6 ++++++ 12 files changed, 57 insertions(+), 6 deletions(-) rename DotBased.AspNet.Authority/Services/{AuthorityService.cs => AuthorityManager.cs} (77%) create mode 100644 DotBased.AspNet.Authority/Validators/IPasswordValidator.cs create mode 100644 DotBased.AspNet.Authority/Validators/IUserValidator.cs create mode 100644 DotBased.AspNet.Authority/Validators/PasswordValidator.cs create mode 100644 DotBased.AspNet.Authority/Validators/UserValidator.cs create mode 100644 DotBased.AspNet.Authority/Verifiers/IEmailVerifier.cs create mode 100644 DotBased.AspNet.Authority/Verifiers/IPhoneNumberVerifier.cs create mode 100644 DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index 71f7730..c67c51a 100644 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -1,12 +1,16 @@ using DotBased.AspNet.Authority.Interfaces; +using DotBased.AspNet.Authority.Models.Options; using Microsoft.Extensions.DependencyInjection; namespace DotBased.AspNet.Authority; public static class AuthorityProviderExtensions { - public static AuthorityBuilder AddAuthorityProvider(this IServiceCollection services) where TModel : class + public static AuthorityBuilder AddAuthorityProvider(this IServiceCollection services, Action optionsAction) where TModel : class { + services.AddOptions(); + // Configure required classes, services, etc. + services.Configure(optionsAction); return new AuthorityBuilder(services); } diff --git a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj index b99e760..f056f35 100644 --- a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj +++ b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs index 1057db7..5bc4d6e 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs @@ -2,7 +2,7 @@ namespace DotBased.AspNet.Authority.Models.Authority; public class AuthorityAttribute { - public AuthorityAttribute(string attributeKey, string bound) : this() + public AuthorityAttribute(string attributeKey, string bound) { AttributeKey = attributeKey; BoundId = bound; @@ -10,7 +10,8 @@ public class AuthorityAttribute public AuthorityAttribute() { - + AttributeKey = string.Empty; + BoundId = string.Empty; } public string AttributeKey { get; set; } // ClaimType/Authority.attribute.enabled diff --git a/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs b/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs index f25e2be..8c142db 100644 --- a/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs +++ b/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs @@ -2,7 +2,7 @@ namespace DotBased.AspNet.Authority.Models.Options; public class SignInOptions { - public bool RequireValidatedEmail { get; set; } - public bool RequireValidatedPhoneNumber { get; set; } + public bool RequireVerifiedEmail { get; set; } + public bool RequireVerifiedPhoneNumber { get; set; } public bool RequireConfirmedAccount { get; set; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityService.cs b/DotBased.AspNet.Authority/Services/AuthorityManager.cs similarity index 77% rename from DotBased.AspNet.Authority/Services/AuthorityService.cs rename to DotBased.AspNet.Authority/Services/AuthorityManager.cs index 2c61b55..3cb5eb7 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityService.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityManager.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Services; -public class AuthorityService +public class AuthorityManager { public long GenerateVersion() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs new file mode 100644 index 0000000..2fe5b5c --- /dev/null +++ b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Validators; + +public interface IPasswordValidator +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IUserValidator.cs b/DotBased.AspNet.Authority/Validators/IUserValidator.cs new file mode 100644 index 0000000..cb7e245 --- /dev/null +++ b/DotBased.AspNet.Authority/Validators/IUserValidator.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Validators; + +public interface IUserValidator +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordValidator.cs new file mode 100644 index 0000000..33ce063 --- /dev/null +++ b/DotBased.AspNet.Authority/Validators/PasswordValidator.cs @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000..1175fc8 --- /dev/null +++ b/DotBased.AspNet.Authority/Validators/UserValidator.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Validators; + +public class UserValidator +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Verifiers/IEmailVerifier.cs b/DotBased.AspNet.Authority/Verifiers/IEmailVerifier.cs new file mode 100644 index 0000000..63172f0 --- /dev/null +++ b/DotBased.AspNet.Authority/Verifiers/IEmailVerifier.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Verifiers; + +public interface IEmailVerifier +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Verifiers/IPhoneNumberVerifier.cs b/DotBased.AspNet.Authority/Verifiers/IPhoneNumberVerifier.cs new file mode 100644 index 0000000..92e25bd --- /dev/null +++ b/DotBased.AspNet.Authority/Verifiers/IPhoneNumberVerifier.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Verifiers; + +public interface IPhoneNumberVerifier +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs b/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs new file mode 100644 index 0000000..c41347f --- /dev/null +++ b/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Verifiers; + +public class IUserVerifier +{ + +} \ No newline at end of file From 361af3403621dcffdfc2ec57b9d35d1ce5e0748a Mon Sep 17 00:00:00 2001 From: max Date: Mon, 23 Dec 2024 15:59:24 +0100 Subject: [PATCH 06/38] [CHANGE] Add extension method --- DotBased.AspNet.Authority/AuthorityProviderExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index c67c51a..82326b0 100644 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -18,4 +18,9 @@ public static class AuthorityProviderExtensions { return authorityBuilder; } + + public static AuthorityBuilder MapAuthorityEndpoints(this AuthorityBuilder builder) + { + return builder; + } } \ No newline at end of file From ebfafa2f29a0dbd3228e72ae50e4369a3b098fd5 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 25 Dec 2024 22:50:04 +0100 Subject: [PATCH 07/38] [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; From 172d5838e74152ceef7dcfb1cae720f831bba6c1 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 26 Dec 2024 20:01:57 +0100 Subject: [PATCH 08/38] [ADD] Pwd validator, reworked classes --- .../AuthorityProviderExtensions.cs | 18 ++++++++++++ .../Models/Authority/AuthorityUser.cs | 7 +++-- .../Repositories/IUserRepository.cs | 6 ++-- .../Services/AuthorityManager.cs | 1 + .../Services/AuthorityUserManager.cs | 28 +++++++++++++++++-- .../Validators/IPasswordValidator.cs | 4 +-- .../Validators/PasswordEqualsValidator.cs | 27 ++++++++++++++++++ .../Validators/PasswordOptionsValidator.cs | 4 +-- 8 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index b99a7ec..7789c7b 100644 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -21,9 +21,11 @@ public static class AuthorityProviderExtensions services.AddOptions(); services.Configure(optionsAction); } + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped, PasswordOptionsValidator>(); + services.TryAddScoped, PasswordEqualsValidator>(); services.TryAddScoped, UserValidator>(); /*services.TryAddScoped(); services.TryAddScoped(); @@ -44,4 +46,20 @@ public static class AuthorityProviderExtensions { return builder; } + + private static Type GetBaseGenericArgumentType(Type baseType) + { + var userGenericBaseTypeDefinition = typeof(TModel).BaseType?.GetGenericTypeDefinition(); + if (userGenericBaseTypeDefinition != null && userGenericBaseTypeDefinition == baseType) + { + var userBaseGenericArguments = userGenericBaseTypeDefinition.GetGenericArguments(); + if (userBaseGenericArguments.Length <= 0) + { + throw new ArgumentException("Base implementation does not have the required generic argument.", nameof(TModel)); + } + + return userBaseGenericArguments[0]; + } + throw new ArgumentException($"Given object {typeof(TModel).Name} does not have the base implementation type of: {baseType.Name}", nameof(TModel)); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs index bbc919f..7f7f87a 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -16,10 +16,13 @@ public class AuthorityUser : AuthorityUser } } -public abstract class AuthorityUser where TKey : IEquatable +public abstract class AuthorityUser : AuthorityUserBase where TKey : IEquatable { public TKey Id { get; set; } - +} + +public abstract class AuthorityUserBase +{ public bool Enabled { get; set; } public bool Confirmed { get; set; } diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index 23c4c6e..0aa9893 100644 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -1,9 +1,9 @@ namespace DotBased.AspNet.Authority.Repositories; -public interface IUserRepository where TUser : class where TId : IEquatable +public interface IUserRepository where TUser : class { - public Task GetUserByIdAsync(TId id); - public Task GetUserIdAsync(TUser user); + public Task GetUserByIdAsync(string id); + public Task GetUserIdAsync(TUser user); public Task SetVersion(TUser user, long version); public Task SetSecurityVersion(TUser user, long version); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityManager.cs b/DotBased.AspNet.Authority/Services/AuthorityManager.cs index 2cf9d19..d5e607a 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityManager.cs @@ -32,6 +32,7 @@ public class AuthorityManager public long GenerateVersion() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + /// /// Protect or unprotect the properties with the diff --git a/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs index a02c8f3..a874760 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs @@ -1,17 +1,24 @@ +using DotBased.AspNet.Authority.Crypto; +using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Repositories; using DotBased.AspNet.Authority.Validators; using DotBased.Logging; namespace DotBased.AspNet.Authority.Services; -public class AuthorityUserManager +public class AuthorityUserManager where TUser : class { public AuthorityUserManager( AuthorityManager manager, + IUserRepository userRepository, + IPasswordHasher passwordHasher, IEnumerable>? passwordValidators, IEnumerable>? userValidators) { _logger = LogService.RegisterLogger>(); AuthorityManager = manager; + UserRepository = userRepository; + PasswordHasher = passwordHasher; if (passwordValidators != null) PasswordValidators = passwordValidators; if (userValidators != null) @@ -20,9 +27,24 @@ public class AuthorityUserManager private readonly ILogger _logger; public AuthorityManager AuthorityManager { get; } + public IUserRepository UserRepository { get; } + + public IPasswordHasher PasswordHasher { get; } public IEnumerable> PasswordValidators { get; } = []; public IEnumerable> UserValidators { get; } = []; - - + + public async Task ValidatePasswordAsync(TUser user, string password) + { + List errors = []; + foreach (var validator in PasswordValidators) + { + var validatorResult = await validator.ValidatePasswordAsync(this, user, password); + if (!validatorResult.Success) + { + errors.AddRange(validatorResult.Errors); + } + } + return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs index 07590ae..035038d 100644 --- a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs @@ -3,7 +3,7 @@ using DotBased.AspNet.Authority.Services; namespace DotBased.AspNet.Authority.Validators; -public interface IPasswordValidator +public interface IPasswordValidator where TUser : class { - public Task ValidatePasswordAsync(AuthorityUserManager userManager, string password); + public Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs new file mode 100644 index 0000000..1d5071a --- /dev/null +++ b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs @@ -0,0 +1,27 @@ +using DotBased.AspNet.Authority.Models.Authority; +using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Services; + +namespace DotBased.AspNet.Authority.Validators; + +public class PasswordEqualsValidator : IPasswordValidator where TUser : class +{ + private const string ValidatorId = "Authority.Validator.Password.Equals"; + private const string ValidationBase = "Authority.Validation.Password"; + public async Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password) + { + if (user == null || user is not AuthorityUserBase authorityUser) + { + throw new ArgumentException("Invalid user given!", nameof(user)); + } + + List errors = []; + var hashedPassword = await userManager.PasswordHasher.HashPasswordAsync(password); + if (authorityUser.PasswordHash != null && authorityUser.PasswordHash.Equals(hashedPassword, StringComparison.Ordinal)) + { + errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.InUse", "User uses this password already!")); + } + + return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs index 1360d3e..6bfcad0 100644 --- a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs @@ -8,12 +8,12 @@ namespace DotBased.AspNet.Authority.Validators; /// Validates the password against the options that is configured. /// /// The user model used. -public class PasswordOptionsValidator : IPasswordValidator +public class PasswordOptionsValidator : IPasswordValidator where TUser : class { private const string ValidatorId = "Authority.Validator.Password.Options"; private const string ValidationBase = "Authority.Validation.Password"; - public async Task ValidatePasswordAsync(AuthorityUserManager userManager, string password) + public async Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password) { if (userManager == null) { From 2d96a259064f54228d8c7ab4ac176bf0c5209e31 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 30 Dec 2024 15:40:52 +0100 Subject: [PATCH 09/38] [CHANGE] Repository manager --- .../Repositories/IAuthorityRepository.cs | 4 ++-- .../Repositories/IGroupRepository.cs | 2 +- .../Repositories/RepositoryManager.cs | 13 +++++++++++++ .../Services/AuthorityManager.cs | 6 +++--- 4 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 DotBased.AspNet.Authority/Repositories/RepositoryManager.cs diff --git a/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs b/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs index 8c0af84..da76f55 100644 --- a/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs @@ -2,6 +2,6 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IAuthorityRepository { - public Task GetVersion(); - public Task SetVersion(int version); + public Task GetVersion(); + public Task SetVersion(long version); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs index 667f839..e3c6369 100644 --- a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Repositories; -public interface IGroupRepository where TGroup : class where TId : IEquatable +public interface IGroupRepository where TGroup : class { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/RepositoryManager.cs b/DotBased.AspNet.Authority/Repositories/RepositoryManager.cs new file mode 100644 index 0000000..952a7b2 --- /dev/null +++ b/DotBased.AspNet.Authority/Repositories/RepositoryManager.cs @@ -0,0 +1,13 @@ +namespace DotBased.AspNet.Authority.Repositories; + +public class RepositoryManager where TUser : class where TGroup : class +{ + public RepositoryManager(IUserRepository userRepository, IGroupRepository groupRepository) + { + UserRepository = userRepository; + GroupRepository = groupRepository; + } + + public IUserRepository UserRepository { get; set; } + public IGroupRepository GroupRepository { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityManager.cs b/DotBased.AspNet.Authority/Services/AuthorityManager.cs index d5e607a..c313772 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityManager.cs @@ -83,11 +83,11 @@ public class AuthorityManager _logger.Debug("{HandledPropCount}/{TotalPropCount} protection properties handled!", handledProperties, props.Count); } - public bool IsPropertieProtected(string propertieName) + public bool IsPropertyProtected(string propertyName) { var protectedProperties = GetProtectedProperties(); - var propertieFound = protectedProperties.Where(propInfo => propInfo.Name == propertieName); - return propertieFound.Any(); + var propertyFound = protectedProperties.Where(propInfo => propInfo.Name == propertyName); + return propertyFound.Any(); } public List GetProtectedPropertiesValues(TModel model) From efc82599304e43ac44b3cb781c90ff1771af9f5b Mon Sep 17 00:00:00 2001 From: max Date: Fri, 3 Jan 2025 00:14:12 +0100 Subject: [PATCH 10/38] [CHANGE] Implementing managers. repositories --- .../AuthorityProviderExtensions.cs | 2 +- .../AuthorityGroupManager.cs | 2 +- .../AuthorityManager.cs | 10 +- .../AuthorityRoleManager.cs | 2 +- .../Managers/AuthorityUserManager.cs | 97 +++++++++++++++++++ .../Models/AuthorityResult.cs | 38 ++++++++ .../Models/Options/ListOption.cs | 7 ++ .../Models/Options/UserOptions.cs | 3 +- .../Repositories/IAuthorityRepository.cs | 7 -- .../Repositories/IUserRepository.cs | 10 +- .../Repositories/RepositoryManager.cs | 13 --- .../Services/AuthorityUserManager.cs | 50 ---------- .../Validators/IPasswordValidator.cs | 2 +- .../Validators/IUserValidator.cs | 7 +- .../Validators/PasswordEqualsValidator.cs | 6 +- .../Validators/PasswordOptionsValidator.cs | 2 +- .../Validators/UserValidator.cs | 86 +++++++++++++++- 17 files changed, 252 insertions(+), 92 deletions(-) rename DotBased.AspNet.Authority/{Services => Managers}/AuthorityGroupManager.cs (52%) rename DotBased.AspNet.Authority/{Services => Managers}/AuthorityManager.cs (89%) rename DotBased.AspNet.Authority/{Services => Managers}/AuthorityRoleManager.cs (51%) create mode 100644 DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs create mode 100644 DotBased.AspNet.Authority/Models/AuthorityResult.cs create mode 100644 DotBased.AspNet.Authority/Models/Options/ListOption.cs delete mode 100644 DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs delete mode 100644 DotBased.AspNet.Authority/Repositories/RepositoryManager.cs delete mode 100644 DotBased.AspNet.Authority/Services/AuthorityUserManager.cs diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index 7789c7b..ab2b8fc 100644 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -1,7 +1,7 @@ using DotBased.AspNet.Authority.Crypto; +using DotBased.AspNet.Authority.Managers; 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; diff --git a/DotBased.AspNet.Authority/Services/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs similarity index 52% rename from DotBased.AspNet.Authority/Services/AuthorityGroupManager.cs rename to DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs index b8db5ea..a4dffbe 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityGroupManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs @@ -1,4 +1,4 @@ -namespace DotBased.AspNet.Authority.Services; +namespace DotBased.AspNet.Authority.Managers; public class AuthorityGroupManager { diff --git a/DotBased.AspNet.Authority/Services/AuthorityManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs similarity index 89% rename from DotBased.AspNet.Authority/Services/AuthorityManager.cs rename to DotBased.AspNet.Authority/Managers/AuthorityManager.cs index c313772..b80e1e3 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs @@ -2,24 +2,21 @@ 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; +namespace DotBased.AspNet.Authority.Managers; 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; } @@ -27,7 +24,6 @@ public class AuthorityManager public IServiceProvider Services { get; } public AuthorityOptions Options { get; } - public IAuthorityRepository Repository { get; } public ICryptographer Cryptographer { get; } @@ -38,7 +34,7 @@ public class AuthorityManager /// Protect or unprotect the properties with the /// /// The data model - /// True for protection false for unprotection. + /// True for protect false for unprotect. /// The class with the properties to protect. public async Task HandlePropertyProtection(TModel data, bool protection) { @@ -74,7 +70,7 @@ public class AuthorityManager if (cryptString == null) { - _logger.Warning("{Protection} failed for property {PropName}", protection ? "Encyption" : "Decyption", property.Name); + _logger.Warning("{Protection} failed for property {PropName}", protection ? "Encryption" : "Decryption", property.Name); continue; } property.SetValue(data, cryptString); diff --git a/DotBased.AspNet.Authority/Services/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs similarity index 51% rename from DotBased.AspNet.Authority/Services/AuthorityRoleManager.cs rename to DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index d3b8e7d..c41b6d2 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -1,4 +1,4 @@ -namespace DotBased.AspNet.Authority.Services; +namespace DotBased.AspNet.Authority.Managers; public class AuthorityRoleManager { diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs new file mode 100644 index 0000000..b5a9003 --- /dev/null +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -0,0 +1,97 @@ +using DotBased.AspNet.Authority.Crypto; +using DotBased.AspNet.Authority.Models; +using DotBased.AspNet.Authority.Models.Authority; +using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Repositories; +using DotBased.AspNet.Authority.Validators; +using DotBased.Logging; + +namespace DotBased.AspNet.Authority.Managers; + +public class AuthorityUserManager where TUser : class +{ + public AuthorityUserManager( + AuthorityManager manager, + IUserRepository userRepository, + IPasswordHasher passwordHasher, + IEnumerable>? passwordValidators, + IEnumerable>? userValidators) + { + _logger = LogService.RegisterLogger>(); + AuthorityManager = manager; + UserRepository = userRepository; + PasswordHasher = passwordHasher; + if (passwordValidators != null) + PasswordValidators = passwordValidators; + if (userValidators != null) + UserValidators = userValidators; + } + + private readonly ILogger _logger; + public AuthorityManager AuthorityManager { get; } + public IUserRepository UserRepository { get; } + + public IPasswordHasher PasswordHasher { get; } + + public IEnumerable> PasswordValidators { get; } = []; + public IEnumerable> UserValidators { get; } = []; + + public async Task ValidatePasswordAsync(TUser user, string password) + { + List errors = []; + foreach (var validator in PasswordValidators) + { + var validatorResult = await validator.ValidatePasswordAsync(this, user, password); + if (!validatorResult.Success) + { + errors.AddRange(validatorResult.Errors); + } + } + return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + } + + public async Task ValidateUserAsync(TUser user) + { + List errors = []; + foreach (var userValidator in UserValidators) + { + var validationResult = await userValidator.ValidateUserAsync(this, user); + if (!validationResult.Success) + { + errors.AddRange(validationResult.Errors); + } + } + return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + } + + public async Task> CreateUserAsync(TUser userModel, string password) + { + if (userModel is not AuthorityUserBase userBase) + { + return AuthorityResult.Error($"Given user is not of base type {nameof(AuthorityUserBase)}!"); + } + + var userValidation = await ValidateUserAsync(userModel); + var passwordValidation = await ValidatePasswordAsync(userModel, password); + if (!userValidation.Success || !passwordValidation.Success) + { + List errors = []; + errors.AddRange(userValidation.Errors); + errors.AddRange(passwordValidation.Errors); + return AuthorityResult.Failed(errors, ResultFailReason.Validation); + } + + var version = AuthorityManager.GenerateVersion(); + userBase.Version = version; + var securityVersion = AuthorityManager.GenerateVersion(); + userBase.SecurityVersion = securityVersion; + var hashedPassword = await PasswordHasher.HashPasswordAsync(password); + userBase.PasswordHash = hashedPassword; + + var userCreationResult = await UserRepository.CreateUserAsync(userModel); + + return userCreationResult != null + ? AuthorityResult.Ok(userCreationResult) + : AuthorityResult.Error("Failed to create user in repository!"); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/AuthorityResult.cs b/DotBased.AspNet.Authority/Models/AuthorityResult.cs new file mode 100644 index 0000000..f6c6d35 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/AuthorityResult.cs @@ -0,0 +1,38 @@ +using DotBased.AspNet.Authority.Models.Validation; + +namespace DotBased.AspNet.Authority.Models; + +public class AuthorityResult +{ + public AuthorityResult(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, List? errors = null) + { + Success = success; + ErrorMessage = errorMessage; + Value = value; + Reason = reason; + ValidationErrors = errors; + } + + public bool Success { get; } + public string ErrorMessage { get; } + public TResultValue? Value { get; } + public ResultFailReason Reason { get; } + public List? ValidationErrors { get; } + + + public static AuthorityResult Ok(TResultValue? value) => new AuthorityResult(true, value:value); + + public static AuthorityResult Error(string errorMessage, ResultFailReason reason = ResultFailReason.Error) => + new AuthorityResult(false, errorMessage, reason:reason); + + public static AuthorityResult Failed(List errors, ResultFailReason reason = ResultFailReason.None) + => new AuthorityResult(false, errors:errors, reason:reason); +} + +public enum ResultFailReason +{ + None, + Unknown, + Validation, + Error +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/ListOption.cs b/DotBased.AspNet.Authority/Models/Options/ListOption.cs new file mode 100644 index 0000000..3c43bf2 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Options/ListOption.cs @@ -0,0 +1,7 @@ +namespace DotBased.AspNet.Authority.Models.Options; + +public enum ListOption +{ + Blacklist, + Whitelist +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Options/UserOptions.cs b/DotBased.AspNet.Authority/Models/Options/UserOptions.cs index 54011e7..c483bab 100644 --- a/DotBased.AspNet.Authority/Models/Options/UserOptions.cs +++ b/DotBased.AspNet.Authority/Models/Options/UserOptions.cs @@ -4,7 +4,8 @@ public class UserOptions { public bool EnableRegister { get; set; } public bool RequireUniqueEmail { get; set; } - public string AllowedCharacters { get; set; } = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@"; + public string UserNameCharacters { get; set; } = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@"; + public ListOption UserNameCharacterListType { get; set; } = ListOption.Whitelist; public List UserNameBlackList { get; set; } = ["admin", "administrator", "dev", "developer"]; public StringComparer UserNameBlackListComparer { get; set; } = StringComparer.OrdinalIgnoreCase; diff --git a/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs b/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs deleted file mode 100644 index da76f55..0000000 --- a/DotBased.AspNet.Authority/Repositories/IAuthorityRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace DotBased.AspNet.Authority.Repositories; - -public interface IAuthorityRepository -{ - public Task GetVersion(); - public Task SetVersion(long version); -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index 0aa9893..8385b26 100644 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -4,6 +4,12 @@ public interface IUserRepository where TUser : class { public Task GetUserByIdAsync(string id); public Task GetUserIdAsync(TUser user); - public Task SetVersion(TUser user, long version); - public Task SetSecurityVersion(TUser user, long version); + public Task GetUserByEmailAsync(string email); + public Task SetVersionAsync(TUser user, long version); + public Task GetVersionAsync(TUser user); + public Task SetSecurityVersionAsync(TUser user, long version); + public Task GetSecurityVersionAsync(TUser user); + public Task CreateUserAsync(TUser user); + public Task UpdateUserAsync(TUser user); + public Task DeleteUserAsync(TUser user); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/RepositoryManager.cs b/DotBased.AspNet.Authority/Repositories/RepositoryManager.cs deleted file mode 100644 index 952a7b2..0000000 --- a/DotBased.AspNet.Authority/Repositories/RepositoryManager.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace DotBased.AspNet.Authority.Repositories; - -public class RepositoryManager where TUser : class where TGroup : class -{ - public RepositoryManager(IUserRepository userRepository, IGroupRepository groupRepository) - { - UserRepository = userRepository; - GroupRepository = groupRepository; - } - - public IUserRepository UserRepository { get; set; } - public IGroupRepository GroupRepository { get; set; } -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs deleted file mode 100644 index a874760..0000000 --- a/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs +++ /dev/null @@ -1,50 +0,0 @@ -using DotBased.AspNet.Authority.Crypto; -using DotBased.AspNet.Authority.Models.Validation; -using DotBased.AspNet.Authority.Repositories; -using DotBased.AspNet.Authority.Validators; -using DotBased.Logging; - -namespace DotBased.AspNet.Authority.Services; - -public class AuthorityUserManager where TUser : class -{ - public AuthorityUserManager( - AuthorityManager manager, - IUserRepository userRepository, - IPasswordHasher passwordHasher, - IEnumerable>? passwordValidators, - IEnumerable>? userValidators) - { - _logger = LogService.RegisterLogger>(); - AuthorityManager = manager; - UserRepository = userRepository; - PasswordHasher = passwordHasher; - if (passwordValidators != null) - PasswordValidators = passwordValidators; - if (userValidators != null) - UserValidators = userValidators; - } - - private readonly ILogger _logger; - public AuthorityManager AuthorityManager { get; } - public IUserRepository UserRepository { get; } - - public IPasswordHasher PasswordHasher { get; } - - public IEnumerable> PasswordValidators { get; } = []; - public IEnumerable> UserValidators { get; } = []; - - public async Task ValidatePasswordAsync(TUser user, string password) - { - List errors = []; - foreach (var validator in PasswordValidators) - { - var validatorResult = await validator.ValidatePasswordAsync(this, user, password); - if (!validatorResult.Success) - { - errors.AddRange(validatorResult.Errors); - } - } - return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); - } -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs index 035038d..1f6304f 100644 --- a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs @@ -1,5 +1,5 @@ +using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Validation; -using DotBased.AspNet.Authority.Services; namespace DotBased.AspNet.Authority.Validators; diff --git a/DotBased.AspNet.Authority/Validators/IUserValidator.cs b/DotBased.AspNet.Authority/Validators/IUserValidator.cs index 96511d6..a2b3f69 100644 --- a/DotBased.AspNet.Authority/Validators/IUserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IUserValidator.cs @@ -1,6 +1,9 @@ +using DotBased.AspNet.Authority.Managers; +using DotBased.AspNet.Authority.Models.Validation; + namespace DotBased.AspNet.Authority.Validators; -public interface IUserValidator +public interface IUserValidator where TUser : class { - + public Task ValidateUserAsync(AuthorityUserManager manager, TUser user); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs index 1d5071a..b91de4a 100644 --- a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs @@ -1,6 +1,6 @@ +using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; -using DotBased.AspNet.Authority.Services; namespace DotBased.AspNet.Authority.Validators; @@ -10,9 +10,9 @@ public class PasswordEqualsValidator : IPasswordValidator where TU private const string ValidationBase = "Authority.Validation.Password"; public async Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password) { - if (user == null || user is not AuthorityUserBase authorityUser) + if (user is not AuthorityUserBase authorityUser) { - throw new ArgumentException("Invalid user given!", nameof(user)); + throw new ArgumentException($"User is not type of {nameof(AuthorityUserBase)}!", nameof(user)); } List errors = []; diff --git a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs index 6bfcad0..65de9b2 100644 --- a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs @@ -1,5 +1,5 @@ +using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Validation; -using DotBased.AspNet.Authority.Services; using DotBased.Extensions; namespace DotBased.AspNet.Authority.Validators; diff --git a/DotBased.AspNet.Authority/Validators/UserValidator.cs b/DotBased.AspNet.Authority/Validators/UserValidator.cs index 219bccb..e23183b 100644 --- a/DotBased.AspNet.Authority/Validators/UserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/UserValidator.cs @@ -1,6 +1,88 @@ +using DotBased.AspNet.Authority.Managers; +using DotBased.AspNet.Authority.Models.Authority; +using DotBased.AspNet.Authority.Models.Options; +using DotBased.AspNet.Authority.Models.Validation; + namespace DotBased.AspNet.Authority.Validators; -public class UserValidator : IUserValidator +public class UserValidator : IUserValidator where TUser : class { - + private const string ValidatorId = "Authority.Validator.User"; + private const string ValidationBase = "Authority.Validation.User"; + + public async Task ValidateUserAsync(AuthorityUserManager manager, TUser user) + { + List errors = []; + + var userOptions = manager.AuthorityManager.Options.User; + + if (user is not AuthorityUserBase userBase) + { + errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.NotAuthorityUser", + $"Given user model is not an type of {nameof(AuthorityUserBase)}")); + return ValidationResult.Failed(errors); + } + + if (userOptions.RequireUniqueEmail) + { + if (string.IsNullOrWhiteSpace(userBase.EmailAddress)) + { + errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.NoEmail", + $"Option {nameof(UserOptions.RequireUniqueEmail)} is set to true but given user does not have an email address!")); + } + else + { + var userEmailResult = await manager.UserRepository.GetUserByEmailAsync(userBase.EmailAddress); + if (userEmailResult != null) + { + errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.EmailExists", + "Given email has already registered an account!")); + } + } + } + + if (!string.IsNullOrWhiteSpace(userBase.UserName)) + { + if (userOptions.UserNameBlackList.Count != 0 && userOptions.UserNameBlackList.Contains(userBase.UserName, userOptions.UserNameBlackListComparer)) + { + errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Blacklisted", "Given username is not allowed (blacklisted)")); + } + + if (!string.IsNullOrWhiteSpace(userOptions.UserNameCharacters)) + { + List chars = []; + if (userOptions.UserNameCharacterListType == ListOption.Whitelist) + { + chars.AddRange(userBase.UserName.Where(userNameChar => !userOptions.UserNameCharacters.Contains(userNameChar))); + } + if (userOptions.UserNameCharacterListType == ListOption.Blacklist) + { + chars.AddRange(userBase.UserName.Where(userNameChar => userOptions.UserNameCharacters.Contains(userNameChar))); + } + + if (chars.Count <= 0) return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + var errorCode = ""; + var description = ""; + switch (userOptions.UserNameCharacterListType) + { + case ListOption.Whitelist: + errorCode = "CharactersNotOnWhitelist"; + description = $"Found characters in username that were not on the whitelist! Chars: [{string.Join(',', chars)}]"; + break; + case ListOption.Blacklist: + errorCode = "CharactersOnBlacklist"; + description = $"Found characters in username that are on the blacklist! Chars: [{string.Join(',', chars)}]"; + break; + } + + errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.UserName.{errorCode}", description)); + } + } + else + { + errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.InvalidUserName", "No username given!")); + } + + return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + } } \ No newline at end of file From 12efc92ac4605a8c68783f217d64c1f33bf08dd1 Mon Sep 17 00:00:00 2001 From: max Date: Sat, 4 Jan 2025 00:52:04 +0100 Subject: [PATCH 11/38] [CHANGE] Implementation updates --- .../Managers/AuthorityUserManager.cs | 48 +++++++++++++++++-- .../Repositories/IUserRepository.cs | 21 ++++---- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index b5a9003..f72af18 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -64,6 +64,34 @@ public class AuthorityUserManager where TUser : class return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); } + public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0) + { + var searchResult = await UserRepository.GetUsersAsync(query, maxResults, offset); + return searchResult.Item1 == null ? ListResult.Failed("No results!") : ListResult.Ok(searchResult.Item1, searchResult.Item2); + } + + public async Task> UpdatePasswordAsync(TUser user, string password) + { + if (user is not AuthorityUserBase userBase) + { + return AuthorityResult.Error($"Given user is not of base type {nameof(AuthorityUserBase)}!"); + } + + var passwordValidation = await ValidatePasswordAsync(user, password); + if (!passwordValidation.Success) + { + List errors = []; + errors.AddRange(passwordValidation.Errors); + return AuthorityResult.Failed(errors, ResultFailReason.Validation); + } + + userBase.PasswordHash = await PasswordHasher.HashPasswordAsync(password); + userBase.SecurityVersion = AuthorityManager.GenerateVersion(); + + var updateResult = await UserRepository.UpdateUserAsync(user); + return updateResult == null ? AuthorityResult.Error("Failed to save updates!") : AuthorityResult.Ok(updateResult); + } + public async Task> CreateUserAsync(TUser userModel, string password) { if (userModel is not AuthorityUserBase userBase) @@ -81,10 +109,8 @@ public class AuthorityUserManager where TUser : class return AuthorityResult.Failed(errors, ResultFailReason.Validation); } - var version = AuthorityManager.GenerateVersion(); - userBase.Version = version; - var securityVersion = AuthorityManager.GenerateVersion(); - userBase.SecurityVersion = securityVersion; + userBase.Version = AuthorityManager.GenerateVersion(); + userBase.SecurityVersion = AuthorityManager.GenerateVersion(); var hashedPassword = await PasswordHasher.HashPasswordAsync(password); userBase.PasswordHash = hashedPassword; @@ -94,4 +120,18 @@ public class AuthorityUserManager where TUser : class ? AuthorityResult.Ok(userCreationResult) : AuthorityResult.Error("Failed to create user in repository!"); } + + public async Task> UpdateUserAsync(TUser model) + { + var updateResult = await UserRepository.UpdateUserAsync(model); + return updateResult != null ? Result.Ok(updateResult) : Result.Failed("Failed to update user in repository!"); + } + + public async Task DeleteUserAsync(TUser model) + { + var deleteResult = await UserRepository.DeleteUserAsync(model); + return deleteResult; + } + + } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index 8385b26..46e833a 100644 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -2,14 +2,15 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IUserRepository where TUser : class { - public Task GetUserByIdAsync(string id); - public Task GetUserIdAsync(TUser user); - public Task GetUserByEmailAsync(string email); - public Task SetVersionAsync(TUser user, long version); - public Task GetVersionAsync(TUser user); - public Task SetSecurityVersionAsync(TUser user, long version); - public Task GetSecurityVersionAsync(TUser user); - public Task CreateUserAsync(TUser user); - public Task UpdateUserAsync(TUser user); - public Task DeleteUserAsync(TUser user); + public Task GetUserByIdAsync(string id, CancellationToken? cancellationToken = null); + public Task GetUserIdAsync(TUser user, CancellationToken? cancellationToken = null); + public Task?, int>> GetUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken? cancellationToken = null); + public Task GetUserByEmailAsync(string email, CancellationToken? cancellationToken = null); + public Task SetVersionAsync(TUser user, long version, CancellationToken? cancellationToken = null); + public Task GetVersionAsync(TUser user, CancellationToken? cancellationToken = null); + public Task SetSecurityVersionAsync(TUser user, long version, CancellationToken? cancellationToken = null); + public Task GetSecurityVersionAsync(TUser user, CancellationToken? cancellationToken = null); + public Task CreateUserAsync(TUser user, CancellationToken? cancellationToken = null); + public Task UpdateUserAsync(TUser user, CancellationToken? cancellationToken = null); + public Task DeleteUserAsync(TUser user, CancellationToken? cancellationToken = null); } \ No newline at end of file From 90cd0a2828cd5802077b305b2ad206abf06f7686 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 8 Jan 2025 15:55:00 +0100 Subject: [PATCH 12/38] [CHANGE] Removed generics and using base classes --- .../AuthorityProviderExtensions.cs | 17 ++--- .../Managers/AuthorityGroupManager.cs | 2 +- .../Managers/AuthorityRoleManager.cs | 2 +- .../Managers/AuthorityUserManager.cs | 66 ++++++++----------- .../Models/Authority/AuthorityUser.cs | 14 ++-- .../Repositories/IAttributeRepository.cs | 2 +- .../Repositories/IGroupRepository.cs | 2 +- .../Repositories/IRoleRepository.cs | 2 +- .../Repositories/IUserRepository.cs | 26 ++++---- .../Validators/IPasswordValidator.cs | 5 +- .../Validators/IUserValidator.cs | 5 +- .../Validators/PasswordEqualsValidator.cs | 11 +--- .../Validators/PasswordOptionsValidator.cs | 5 +- .../Validators/UserValidator.cs | 23 +++---- 14 files changed, 77 insertions(+), 105 deletions(-) diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index ab2b8fc..88c02f2 100644 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -1,6 +1,5 @@ using DotBased.AspNet.Authority.Crypto; using DotBased.AspNet.Authority.Managers; -using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Options; using DotBased.AspNet.Authority.Validators; using Microsoft.Extensions.DependencyInjection; @@ -11,10 +10,6 @@ namespace DotBased.AspNet.Authority; public static class AuthorityProviderExtensions { 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 { if (optionsAction != null) { @@ -24,16 +19,16 @@ public static class AuthorityProviderExtensions services.TryAddScoped(); services.TryAddScoped(); - services.TryAddScoped, PasswordOptionsValidator>(); - services.TryAddScoped, PasswordEqualsValidator>(); - services.TryAddScoped, UserValidator>(); + services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped(); /*services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped();*/ services.TryAddScoped(); - services.TryAddScoped>(); - services.TryAddScoped>(); - services.TryAddScoped>(); + services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped(); return new AuthorityBuilder(services); } diff --git a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs index a4dffbe..7da7e73 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Managers; -public class AuthorityGroupManager +public class AuthorityGroupManager { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index c41b6d2..420b639 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Managers; -public class AuthorityRoleManager +public class AuthorityRoleManager { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index f72af18..6cc0974 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -8,16 +8,16 @@ using DotBased.Logging; namespace DotBased.AspNet.Authority.Managers; -public class AuthorityUserManager where TUser : class +public class AuthorityUserManager { public AuthorityUserManager( AuthorityManager manager, - IUserRepository userRepository, + IUserRepository userRepository, IPasswordHasher passwordHasher, - IEnumerable>? passwordValidators, - IEnumerable>? userValidators) + IEnumerable? passwordValidators, + IEnumerable? userValidators) { - _logger = LogService.RegisterLogger>(); + _logger = LogService.RegisterLogger(); AuthorityManager = manager; UserRepository = userRepository; PasswordHasher = passwordHasher; @@ -29,14 +29,14 @@ public class AuthorityUserManager where TUser : class private readonly ILogger _logger; public AuthorityManager AuthorityManager { get; } - public IUserRepository UserRepository { get; } + public IUserRepository UserRepository { get; } public IPasswordHasher PasswordHasher { get; } - public IEnumerable> PasswordValidators { get; } = []; - public IEnumerable> UserValidators { get; } = []; + public IEnumerable PasswordValidators { get; } = []; + public IEnumerable UserValidators { get; } = []; - public async Task ValidatePasswordAsync(TUser user, string password) + public async Task ValidatePasswordAsync(AuthorityUser user, string password) { List errors = []; foreach (var validator in PasswordValidators) @@ -50,7 +50,7 @@ public class AuthorityUserManager where TUser : class return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); } - public async Task ValidateUserAsync(TUser user) + public async Task ValidateUserAsync(AuthorityUser user) { List errors = []; foreach (var userValidator in UserValidators) @@ -64,41 +64,31 @@ public class AuthorityUserManager where TUser : class return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); } - public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0) + public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0) { - var searchResult = await UserRepository.GetUsersAsync(query, maxResults, offset); - return searchResult.Item1 == null ? ListResult.Failed("No results!") : ListResult.Ok(searchResult.Item1, searchResult.Item2); + var searchResult = await UserRepository.GetAuthorityUsersAsync(query, maxResults, offset); + return searchResult.Item1 == null ? ListResult.Failed("No results!") : ListResult.Ok(searchResult.Item1, searchResult.Item2); } - public async Task> UpdatePasswordAsync(TUser user, string password) + public async Task> UpdatePasswordAsync(AuthorityUser user, string password) { - if (user is not AuthorityUserBase userBase) - { - return AuthorityResult.Error($"Given user is not of base type {nameof(AuthorityUserBase)}!"); - } - var passwordValidation = await ValidatePasswordAsync(user, password); if (!passwordValidation.Success) { List errors = []; errors.AddRange(passwordValidation.Errors); - return AuthorityResult.Failed(errors, ResultFailReason.Validation); + return AuthorityResult.Failed(errors, ResultFailReason.Validation); } - userBase.PasswordHash = await PasswordHasher.HashPasswordAsync(password); - userBase.SecurityVersion = AuthorityManager.GenerateVersion(); + user.PasswordHash = await PasswordHasher.HashPasswordAsync(password); + user.SecurityVersion = AuthorityManager.GenerateVersion(); var updateResult = await UserRepository.UpdateUserAsync(user); - return updateResult == null ? AuthorityResult.Error("Failed to save updates!") : AuthorityResult.Ok(updateResult); + return updateResult == null ? AuthorityResult.Error("Failed to save updates!") : AuthorityResult.Ok(updateResult); } - public async Task> CreateUserAsync(TUser userModel, string password) + public async Task> CreateUserAsync(AuthorityUser userModel, string password) { - if (userModel is not AuthorityUserBase userBase) - { - return AuthorityResult.Error($"Given user is not of base type {nameof(AuthorityUserBase)}!"); - } - var userValidation = await ValidateUserAsync(userModel); var passwordValidation = await ValidatePasswordAsync(userModel, password); if (!userValidation.Success || !passwordValidation.Success) @@ -106,28 +96,28 @@ public class AuthorityUserManager where TUser : class List errors = []; errors.AddRange(userValidation.Errors); errors.AddRange(passwordValidation.Errors); - return AuthorityResult.Failed(errors, ResultFailReason.Validation); + return AuthorityResult.Failed(errors, ResultFailReason.Validation); } - userBase.Version = AuthorityManager.GenerateVersion(); - userBase.SecurityVersion = AuthorityManager.GenerateVersion(); + userModel.Version = AuthorityManager.GenerateVersion(); + userModel.SecurityVersion = AuthorityManager.GenerateVersion(); var hashedPassword = await PasswordHasher.HashPasswordAsync(password); - userBase.PasswordHash = hashedPassword; + userModel.PasswordHash = hashedPassword; var userCreationResult = await UserRepository.CreateUserAsync(userModel); return userCreationResult != null - ? AuthorityResult.Ok(userCreationResult) - : AuthorityResult.Error("Failed to create user in repository!"); + ? AuthorityResult.Ok(userCreationResult) + : AuthorityResult.Error("Failed to create user in repository!"); } - public async Task> UpdateUserAsync(TUser model) + public async Task> UpdateUserAsync(AuthorityUser model) { var updateResult = await UserRepository.UpdateUserAsync(model); - return updateResult != null ? Result.Ok(updateResult) : Result.Failed("Failed to update user in repository!"); + return updateResult != null ? Result.Ok(updateResult) : Result.Failed("Failed to update user in repository!"); } - public async Task DeleteUserAsync(TUser model) + public async Task DeleteUserAsync(AuthorityUser model) { var deleteResult = await UserRepository.DeleteUserAsync(model); return deleteResult; diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs index 7f7f87a..9f52b58 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -2,7 +2,7 @@ using DotBased.AspNet.Authority.Attributes; namespace DotBased.AspNet.Authority.Models.Authority; -public class AuthorityUser : AuthorityUser +public class AuthorityUser { public AuthorityUser(string userName) : this() { @@ -14,15 +14,9 @@ public class AuthorityUser : AuthorityUser Id = Guid.NewGuid(); CreatedDate = DateTime.Now; } -} - -public abstract class AuthorityUser : AuthorityUserBase where TKey : IEquatable -{ - public TKey Id { get; set; } -} - -public abstract class AuthorityUserBase -{ + + public Guid Id { get; set; } + public bool Enabled { get; set; } public bool Confirmed { get; set; } diff --git a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs index 92272f3..7e22f88 100644 --- a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Repositories; -public interface IAttributeRepository where TAttribute : class where TId : IEquatable +public interface IAttributeRepository { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs index e3c6369..f16b7d2 100644 --- a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Repositories; -public interface IGroupRepository where TGroup : class +public interface IGroupRepository { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs index e3c9b4c..436076b 100644 --- a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Repositories; -public interface IRoleRepository where TRole : class where TId : IEquatable +public interface IRoleRepository { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index 46e833a..f86cdb5 100644 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -1,16 +1,18 @@ +using DotBased.AspNet.Authority.Models.Authority; + namespace DotBased.AspNet.Authority.Repositories; -public interface IUserRepository where TUser : class +public interface IUserRepository { - public Task GetUserByIdAsync(string id, CancellationToken? cancellationToken = null); - public Task GetUserIdAsync(TUser user, CancellationToken? cancellationToken = null); - public Task?, int>> GetUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken? cancellationToken = null); - public Task GetUserByEmailAsync(string email, CancellationToken? cancellationToken = null); - public Task SetVersionAsync(TUser user, long version, CancellationToken? cancellationToken = null); - public Task GetVersionAsync(TUser user, CancellationToken? cancellationToken = null); - public Task SetSecurityVersionAsync(TUser user, long version, CancellationToken? cancellationToken = null); - public Task GetSecurityVersionAsync(TUser user, CancellationToken? cancellationToken = null); - public Task CreateUserAsync(TUser user, CancellationToken? cancellationToken = null); - public Task UpdateUserAsync(TUser user, CancellationToken? cancellationToken = null); - public Task DeleteUserAsync(TUser user, CancellationToken? cancellationToken = null); + public Task GetAuthorityUserByIdAsync(string id, CancellationToken? cancellationToken = null); + public Task GetAuthorityUserIdAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task?, int>> GetAuthorityUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken? cancellationToken = null); + public Task GetAuthorityUserByEmailAsync(string email, CancellationToken? cancellationToken = null); + public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); + public Task GetVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); + public Task GetSecurityVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task CreateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task UpdateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task DeleteUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs index 1f6304f..ff7c770 100644 --- a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs @@ -1,9 +1,10 @@ using DotBased.AspNet.Authority.Managers; +using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; namespace DotBased.AspNet.Authority.Validators; -public interface IPasswordValidator where TUser : class +public interface IPasswordValidator { - public Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password); + public Task ValidatePasswordAsync(AuthorityUserManager userManager, AuthorityUser user, 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 a2b3f69..da07405 100644 --- a/DotBased.AspNet.Authority/Validators/IUserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IUserValidator.cs @@ -1,9 +1,10 @@ using DotBased.AspNet.Authority.Managers; +using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; namespace DotBased.AspNet.Authority.Validators; -public interface IUserValidator where TUser : class +public interface IUserValidator { - public Task ValidateUserAsync(AuthorityUserManager manager, TUser user); + public Task ValidateUserAsync(AuthorityUserManager manager, AuthorityUser user); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs index b91de4a..a95c55d 100644 --- a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs @@ -4,20 +4,15 @@ using DotBased.AspNet.Authority.Models.Validation; namespace DotBased.AspNet.Authority.Validators; -public class PasswordEqualsValidator : IPasswordValidator where TUser : class +public class PasswordEqualsValidator : IPasswordValidator { private const string ValidatorId = "Authority.Validator.Password.Equals"; private const string ValidationBase = "Authority.Validation.Password"; - public async Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password) + public async Task ValidatePasswordAsync(AuthorityUserManager userManager, AuthorityUser user, string password) { - if (user is not AuthorityUserBase authorityUser) - { - throw new ArgumentException($"User is not type of {nameof(AuthorityUserBase)}!", nameof(user)); - } - List errors = []; var hashedPassword = await userManager.PasswordHasher.HashPasswordAsync(password); - if (authorityUser.PasswordHash != null && authorityUser.PasswordHash.Equals(hashedPassword, StringComparison.Ordinal)) + if (user.PasswordHash != null && user.PasswordHash.Equals(hashedPassword, StringComparison.Ordinal)) { errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.InUse", "User uses this password already!")); } diff --git a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs index 65de9b2..bd38b25 100644 --- a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs @@ -1,4 +1,5 @@ using DotBased.AspNet.Authority.Managers; +using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; using DotBased.Extensions; @@ -8,12 +9,12 @@ namespace DotBased.AspNet.Authority.Validators; /// Validates the password against the options that is configured. /// /// The user model used. -public class PasswordOptionsValidator : IPasswordValidator where TUser : class +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, TUser user, string password) + public async Task ValidatePasswordAsync(AuthorityUserManager userManager, AuthorityUser user, string password) { if (userManager == null) { diff --git a/DotBased.AspNet.Authority/Validators/UserValidator.cs b/DotBased.AspNet.Authority/Validators/UserValidator.cs index e23183b..317327f 100644 --- a/DotBased.AspNet.Authority/Validators/UserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/UserValidator.cs @@ -5,34 +5,27 @@ using DotBased.AspNet.Authority.Models.Validation; namespace DotBased.AspNet.Authority.Validators; -public class UserValidator : IUserValidator where TUser : class +public class UserValidator : IUserValidator { private const string ValidatorId = "Authority.Validator.User"; private const string ValidationBase = "Authority.Validation.User"; - public async Task ValidateUserAsync(AuthorityUserManager manager, TUser user) + public async Task ValidateUserAsync(AuthorityUserManager manager, AuthorityUser user) { List errors = []; var userOptions = manager.AuthorityManager.Options.User; - if (user is not AuthorityUserBase userBase) - { - errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.NotAuthorityUser", - $"Given user model is not an type of {nameof(AuthorityUserBase)}")); - return ValidationResult.Failed(errors); - } - if (userOptions.RequireUniqueEmail) { - if (string.IsNullOrWhiteSpace(userBase.EmailAddress)) + if (string.IsNullOrWhiteSpace(user.EmailAddress)) { errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.NoEmail", $"Option {nameof(UserOptions.RequireUniqueEmail)} is set to true but given user does not have an email address!")); } else { - var userEmailResult = await manager.UserRepository.GetUserByEmailAsync(userBase.EmailAddress); + var userEmailResult = await manager.UserRepository.GetAuthorityUserByEmailAsync(user.EmailAddress); if (userEmailResult != null) { errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.EmailExists", @@ -41,9 +34,9 @@ public class UserValidator : IUserValidator where TUser : class } } - if (!string.IsNullOrWhiteSpace(userBase.UserName)) + if (!string.IsNullOrWhiteSpace(user.UserName)) { - if (userOptions.UserNameBlackList.Count != 0 && userOptions.UserNameBlackList.Contains(userBase.UserName, userOptions.UserNameBlackListComparer)) + if (userOptions.UserNameBlackList.Count != 0 && userOptions.UserNameBlackList.Contains(user.UserName, userOptions.UserNameBlackListComparer)) { errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.Blacklisted", "Given username is not allowed (blacklisted)")); } @@ -53,11 +46,11 @@ public class UserValidator : IUserValidator where TUser : class List chars = []; if (userOptions.UserNameCharacterListType == ListOption.Whitelist) { - chars.AddRange(userBase.UserName.Where(userNameChar => !userOptions.UserNameCharacters.Contains(userNameChar))); + chars.AddRange(user.UserName.Where(userNameChar => !userOptions.UserNameCharacters.Contains(userNameChar))); } if (userOptions.UserNameCharacterListType == ListOption.Blacklist) { - chars.AddRange(userBase.UserName.Where(userNameChar => userOptions.UserNameCharacters.Contains(userNameChar))); + chars.AddRange(user.UserName.Where(userNameChar => userOptions.UserNameCharacters.Contains(userNameChar))); } if (chars.Count <= 0) return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); From 28fcd74acf04ee20fbfcb84c24fd4ed2928645f4 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 8 Jan 2025 16:06:25 +0100 Subject: [PATCH 13/38] [REFACTOR] Split manager class, refactored/cleaned classes --- .../AuthorityProviderExtensions.cs | 3 -- .../Managers/AuthorityGroupManager.cs | 2 +- .../Managers/AuthorityManager.cs | 26 ++++++++----- .../Managers/AuthorityRoleManager.cs | 2 +- .../Managers/AuthorityUserManager.cs | 38 ++----------------- .../Validators/IPasswordValidator.cs | 2 +- .../Validators/IUserValidator.cs | 2 +- .../Validators/PasswordEqualsValidator.cs | 2 +- .../Validators/PasswordOptionsValidator.cs | 5 +-- .../Validators/UserValidator.cs | 4 +- 10 files changed, 30 insertions(+), 56 deletions(-) diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index 88c02f2..9018d7a 100644 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -26,9 +26,6 @@ public static class AuthorityProviderExtensions services.TryAddScoped(); services.TryAddScoped();*/ services.TryAddScoped(); - services.TryAddScoped(); - services.TryAddScoped(); - services.TryAddScoped(); return new AuthorityBuilder(services); } diff --git a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs index 7da7e73..22e70cb 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Managers; -public class AuthorityGroupManager +public partial class AuthorityManager { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs index b80e1e3..4b78e03 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs @@ -2,22 +2,28 @@ 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.AspNet.Authority.Validators; using DotBased.Logging; using Microsoft.Extensions.Options; namespace DotBased.AspNet.Authority.Managers; -public class AuthorityManager +public partial class AuthorityManager { public AuthorityManager( IOptions options, IServiceProvider services, - ICryptographer cryptographer) + ICryptographer cryptographer, + IUserRepository userRepository, + IPasswordHasher passwordHasher) { _logger = LogService.RegisterLogger(); - Options = options.Value ?? new AuthorityOptions(); + Options = options.Value; Services = services; Cryptographer = cryptographer; + UserRepository = userRepository; + PasswordHasher = passwordHasher; } private readonly ILogger _logger; @@ -25,6 +31,13 @@ public class AuthorityManager public IServiceProvider Services { get; } public AuthorityOptions Options { get; } public ICryptographer Cryptographer { get; } + + public IUserRepository UserRepository { get; } + + public IPasswordHasher PasswordHasher { get; } + + public IEnumerable PasswordValidators { get; } = []; + public IEnumerable UserValidators { get; } = []; public long GenerateVersion() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); @@ -38,12 +51,7 @@ public class AuthorityManager /// 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; - } + var props = GetProtectedPropertiesValues(data); if (props.Count == 0) { return; diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index 420b639..22e70cb 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Managers; -public class AuthorityRoleManager +public partial class AuthorityManager { } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index 6cc0974..94feb6f 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -1,41 +1,11 @@ -using DotBased.AspNet.Authority.Crypto; using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; -using DotBased.AspNet.Authority.Repositories; -using DotBased.AspNet.Authority.Validators; -using DotBased.Logging; namespace DotBased.AspNet.Authority.Managers; -public class AuthorityUserManager +public partial class AuthorityManager { - public AuthorityUserManager( - AuthorityManager manager, - IUserRepository userRepository, - IPasswordHasher passwordHasher, - IEnumerable? passwordValidators, - IEnumerable? userValidators) - { - _logger = LogService.RegisterLogger(); - AuthorityManager = manager; - UserRepository = userRepository; - PasswordHasher = passwordHasher; - if (passwordValidators != null) - PasswordValidators = passwordValidators; - if (userValidators != null) - UserValidators = userValidators; - } - - private readonly ILogger _logger; - public AuthorityManager AuthorityManager { get; } - public IUserRepository UserRepository { get; } - - public IPasswordHasher PasswordHasher { get; } - - public IEnumerable PasswordValidators { get; } = []; - public IEnumerable UserValidators { get; } = []; - public async Task ValidatePasswordAsync(AuthorityUser user, string password) { List errors = []; @@ -81,7 +51,7 @@ public class AuthorityUserManager } user.PasswordHash = await PasswordHasher.HashPasswordAsync(password); - user.SecurityVersion = AuthorityManager.GenerateVersion(); + user.SecurityVersion = GenerateVersion(); var updateResult = await UserRepository.UpdateUserAsync(user); return updateResult == null ? AuthorityResult.Error("Failed to save updates!") : AuthorityResult.Ok(updateResult); @@ -99,8 +69,8 @@ public class AuthorityUserManager return AuthorityResult.Failed(errors, ResultFailReason.Validation); } - userModel.Version = AuthorityManager.GenerateVersion(); - userModel.SecurityVersion = AuthorityManager.GenerateVersion(); + userModel.Version = GenerateVersion(); + userModel.SecurityVersion = GenerateVersion(); var hashedPassword = await PasswordHasher.HashPasswordAsync(password); userModel.PasswordHash = hashedPassword; diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs index ff7c770..1208a21 100644 --- a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs @@ -6,5 +6,5 @@ namespace DotBased.AspNet.Authority.Validators; public interface IPasswordValidator { - public Task ValidatePasswordAsync(AuthorityUserManager userManager, AuthorityUser user, string password); + public Task ValidatePasswordAsync(AuthorityManager manager, AuthorityUser user, 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 da07405..ef07650 100644 --- a/DotBased.AspNet.Authority/Validators/IUserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IUserValidator.cs @@ -6,5 +6,5 @@ namespace DotBased.AspNet.Authority.Validators; public interface IUserValidator { - public Task ValidateUserAsync(AuthorityUserManager manager, AuthorityUser user); + public Task ValidateUserAsync(AuthorityManager manager, AuthorityUser user); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs index a95c55d..8278e13 100644 --- a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs @@ -8,7 +8,7 @@ public class PasswordEqualsValidator : IPasswordValidator { private const string ValidatorId = "Authority.Validator.Password.Equals"; private const string ValidationBase = "Authority.Validation.Password"; - public async Task ValidatePasswordAsync(AuthorityUserManager userManager, AuthorityUser user, string password) + public async Task ValidatePasswordAsync(AuthorityManager userManager, AuthorityUser user, string password) { List errors = []; var hashedPassword = await userManager.PasswordHasher.HashPasswordAsync(password); diff --git a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs index bd38b25..41cab40 100644 --- a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs @@ -8,19 +8,18 @@ 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, AuthorityUser user, string password) + public async Task ValidatePasswordAsync(AuthorityManager userManager, AuthorityUser user, string password) { if (userManager == null) { throw new ArgumentNullException(nameof(userManager), "User manager is not provided!"); } - var passwordOptions = userManager.AuthorityManager.Options.Password; + var passwordOptions = userManager.Options.Password; var errors = new List(); if (password.IsNullOrEmpty() || password.Length < passwordOptions.RequiredLength) diff --git a/DotBased.AspNet.Authority/Validators/UserValidator.cs b/DotBased.AspNet.Authority/Validators/UserValidator.cs index 317327f..1b914bc 100644 --- a/DotBased.AspNet.Authority/Validators/UserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/UserValidator.cs @@ -10,11 +10,11 @@ public class UserValidator : IUserValidator private const string ValidatorId = "Authority.Validator.User"; private const string ValidationBase = "Authority.Validation.User"; - public async Task ValidateUserAsync(AuthorityUserManager manager, AuthorityUser user) + public async Task ValidateUserAsync(AuthorityManager manager, AuthorityUser user) { List errors = []; - var userOptions = manager.AuthorityManager.Options.User; + var userOptions = manager.Options.User; if (userOptions.RequireUniqueEmail) { From fd733b7238d09c18e4550251c77cb0f9c739dc7f Mon Sep 17 00:00:00 2001 From: max Date: Wed, 8 Jan 2025 16:22:59 +0100 Subject: [PATCH 14/38] [ADD] Add cancellation tokens to async functions --- .../Managers/AuthorityManager.cs | 3 +++ .../Managers/AuthorityRoleManager.cs | 22 ++++++++++++++++++- .../Managers/AuthorityUserManager.cs | 20 ++++++++--------- .../Models/Authority/AuthorityRole.cs | 18 ++------------- 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/DotBased.AspNet.Authority/Managers/AuthorityManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs index 4b78e03..50548b1 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs @@ -16,6 +16,7 @@ public partial class AuthorityManager IServiceProvider services, ICryptographer cryptographer, IUserRepository userRepository, + IRoleRepository roleRepository, IPasswordHasher passwordHasher) { _logger = LogService.RegisterLogger(); @@ -23,6 +24,7 @@ public partial class AuthorityManager Services = services; Cryptographer = cryptographer; UserRepository = userRepository; + RoleRepository = roleRepository; PasswordHasher = passwordHasher; } @@ -33,6 +35,7 @@ public partial class AuthorityManager public ICryptographer Cryptographer { get; } public IUserRepository UserRepository { get; } + public IRoleRepository RoleRepository { get; } public IPasswordHasher PasswordHasher { get; } diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index 22e70cb..3500cb4 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -1,6 +1,26 @@ +using DotBased.AspNet.Authority.Models.Authority; + namespace DotBased.AspNet.Authority.Managers; public partial class AuthorityManager { - + public async Task> CreateRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null) + { + return Result.Failed("Not implemented!"); + } + + public async Task DeleteRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null) + { + return Result.Failed("Not implemented!"); + } + + public async Task> UpdateRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null) + { + return Result.Failed("Not implemented!"); + } + + public async Task AddUserToRole(AuthorityUser user, AuthorityRole role, CancellationToken? cancellationToken = null) + { + + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index 94feb6f..5b07cfc 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -34,13 +34,13 @@ public partial class AuthorityManager return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); } - public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0) + public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken? cancellationToken = null) { - var searchResult = await UserRepository.GetAuthorityUsersAsync(query, maxResults, offset); + var searchResult = await UserRepository.GetAuthorityUsersAsync(query, maxResults, offset, cancellationToken); return searchResult.Item1 == null ? ListResult.Failed("No results!") : ListResult.Ok(searchResult.Item1, searchResult.Item2); } - public async Task> UpdatePasswordAsync(AuthorityUser user, string password) + public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken? cancellationToken = null) { var passwordValidation = await ValidatePasswordAsync(user, password); if (!passwordValidation.Success) @@ -53,11 +53,11 @@ public partial class AuthorityManager user.PasswordHash = await PasswordHasher.HashPasswordAsync(password); user.SecurityVersion = GenerateVersion(); - var updateResult = await UserRepository.UpdateUserAsync(user); + var updateResult = await UserRepository.UpdateUserAsync(user, cancellationToken); return updateResult == null ? AuthorityResult.Error("Failed to save updates!") : AuthorityResult.Ok(updateResult); } - public async Task> CreateUserAsync(AuthorityUser userModel, string password) + public async Task> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken? cancellationToken = null) { var userValidation = await ValidateUserAsync(userModel); var passwordValidation = await ValidatePasswordAsync(userModel, password); @@ -74,22 +74,22 @@ public partial class AuthorityManager var hashedPassword = await PasswordHasher.HashPasswordAsync(password); userModel.PasswordHash = hashedPassword; - var userCreationResult = await UserRepository.CreateUserAsync(userModel); + var userCreationResult = await UserRepository.CreateUserAsync(userModel, cancellationToken); return userCreationResult != null ? AuthorityResult.Ok(userCreationResult) : AuthorityResult.Error("Failed to create user in repository!"); } - public async Task> UpdateUserAsync(AuthorityUser model) + public async Task> UpdateUserAsync(AuthorityUser model, CancellationToken? cancellationToken = null) { - var updateResult = await UserRepository.UpdateUserAsync(model); + var updateResult = await UserRepository.UpdateUserAsync(model, cancellationToken); return updateResult != null ? Result.Ok(updateResult) : Result.Failed("Failed to update user in repository!"); } - public async Task DeleteUserAsync(AuthorityUser model) + public async Task DeleteUserAsync(AuthorityUser model, CancellationToken? cancellationToken = null) { - var deleteResult = await UserRepository.DeleteUserAsync(model); + var deleteResult = await UserRepository.DeleteUserAsync(model, cancellationToken); return deleteResult; } diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs index 90ccee5..4fc2600 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs @@ -1,22 +1,8 @@ namespace DotBased.AspNet.Authority.Models.Authority; -public class AuthorityRole : AuthorityRole +public abstract class AuthorityRole { - public AuthorityRole(string name) : this() - { - Name = name; - } - - public AuthorityRole() - { - Id = Guid.NewGuid(); - CreatedDate = DateTime.Now; - } -} - -public abstract class AuthorityRole where TKey : IEquatable -{ - public TKey Id { get; set; } + public Guid Id { get; set; } public string? Name { get; set; } From 3ccd3106c14ac735150e65399ccfdda320d62eb5 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 8 Jan 2025 16:30:51 +0100 Subject: [PATCH 15/38] [CHANGE] Updated models --- .../Managers/AuthorityRoleManager.cs | 10 +++++++++- .../Models/Authority/AuthorityGroup.cs | 9 +++------ .../Models/Authority/AuthorityRole.cs | 11 +++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index 3500cb4..52f09a5 100644 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -19,8 +19,16 @@ public partial class AuthorityManager return Result.Failed("Not implemented!"); } - public async Task AddUserToRole(AuthorityUser user, AuthorityRole role, CancellationToken? cancellationToken = null) + public async Task AddRoleToUserAsync(AuthorityUser user, AuthorityRole role, CancellationToken? cancellationToken = null) { } + + public async Task RemoveRoleFromUserAsync(AuthorityRole role, AuthorityUser user, CancellationToken? cancellationToken = null) + { + } + + public async Task AddRoleToGroupAsync(AuthorityRole role, AuthorityGroup group, CancellationToken? cancellationToken = null) + { + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs index df010e8..c5474df 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Models.Authority; -public class AuthorityGroup : AuthorityGroup +public class AuthorityGroup { public AuthorityGroup(string name) : this() { @@ -12,11 +12,8 @@ public class AuthorityGroup : AuthorityGroup Id = Guid.NewGuid(); CreatedDate = DateTime.Now; } -} - -public abstract class AuthorityGroup where TKey : IEquatable -{ - public TKey Id { get; set; } + + public Guid Id { get; set; } public string? Name { get; set; } diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs index 4fc2600..1e22cb5 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs @@ -2,6 +2,17 @@ namespace DotBased.AspNet.Authority.Models.Authority; public abstract class AuthorityRole { + public AuthorityRole(string name) : this() + { + Name = name; + } + + public AuthorityRole() + { + Id = Guid.NewGuid(); + CreatedDate = DateTime.Now; + } + public Guid Id { get; set; } public string? Name { get; set; } From c27890a31fa8ffb2f0f0c921f3dd18d925cdc7c2 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 27 Jan 2025 01:21:09 +0100 Subject: [PATCH 16/38] [CHANGE] Building data structure --- Blazor.Wasm/App.razor | 0 Blazor.Wasm/Blazor.Wasm.csproj | 0 Blazor.Wasm/Layout/MainLayout.razor | 0 Blazor.Wasm/Layout/MainLayout.razor.css | 0 Blazor.Wasm/Layout/NavMenu.razor | 0 Blazor.Wasm/Layout/NavMenu.razor.css | 0 Blazor.Wasm/Pages/Counter.razor | 0 Blazor.Wasm/Pages/Home.razor | 0 Blazor.Wasm/Pages/Weather.razor | 0 Blazor.Wasm/Program.cs | 0 Blazor.Wasm/Properties/launchSettings.json | 0 Blazor.Wasm/_Imports.razor | 0 Blazor.Wasm/wwwroot/css/app.css | 0 .../wwwroot/css/bootstrap/bootstrap.min.css | 0 .../css/bootstrap/bootstrap.min.css.map | 0 Blazor.Wasm/wwwroot/favicon.png | Bin Blazor.Wasm/wwwroot/icon-192.png | Bin Blazor.Wasm/wwwroot/index.html | 0 Blazor.Wasm/wwwroot/sample-data/weather.json | 0 DotBased.ASP.Auth/AuthDataCache.cs | 0 DotBased.ASP.Auth/AuthenticationService.cs | 0 DotBased.ASP.Auth/BasedAuthConfiguration.cs | 0 DotBased.ASP.Auth/BasedAuthDefaults.cs | 0 .../BasedServerAuthenticationStateProvider.cs | 0 .../Domains/Auth/AuthenticationStateModel.cs | 0 .../Domains/Auth/PermissionModel.cs | 0 DotBased.ASP.Auth/Domains/Auth/RoleModel.cs | 0 .../Domains/Identity/GroupItemModel.cs | 0 .../Domains/Identity/GroupModel.cs | 0 .../Domains/Identity/UserItemModel.cs | 0 .../Domains/Identity/UserModel.cs | 0 DotBased.ASP.Auth/Domains/LoginModel.cs | 0 DotBased.ASP.Auth/Domains/RegisterModel.cs | 0 DotBased.ASP.Auth/DotBased.ASP.Auth.csproj | 0 .../DotBasedAuthDependencyInjection.cs | 0 DotBased.ASP.Auth/IAuthDataRepository.cs | 0 DotBased.ASP.Auth/ISessionStateProvider.cs | 0 DotBased.ASP.Auth/MemoryAuthDataRepository.cs | 0 .../Models/Configuration/AuthConfiguration.cs | 0 .../Configuration/CacheConfiguration.cs | 0 .../Configuration/LockoutConfiguration.cs | 0 .../Configuration/PasswordConfiguration.cs | 0 .../Configuration/ProviderConfiguration.cs | 0 .../Configuration/RepositoryConfiguration.cs | 0 .../Models/Configuration/UserConfiguration.cs | 0 DotBased.ASP.Auth/SecurityManager.cs | 0 DotBased.ASP.Auth/SecurityService.cs | 0 .../Attributes/ProtectAttribute.cs | 0 DotBased.AspNet.Authority/AuthorityBuilder.cs | 0 .../AuthorityDefaults.cs | 0 .../AuthorityProviderExtensions.cs | 0 .../Crypto/Cryptographer.cs | 0 .../Crypto/ICryptographer.cs | 0 .../Crypto/IPasswordHasher.cs | 0 .../Crypto/PasswordHasher.cs | 0 .../DotBased.AspNet.Authority.csproj | 0 .../Managers/AuthorityGroupManager.cs | 6 +- .../Managers/AuthorityManager.cs | 43 ++-- .../Managers/AuthorityRoleManager.cs | 33 +++- .../Managers/AuthorityUserManager.cs | 0 .../Models/Authority/AuthorityAttribute.cs | 0 .../Models/Authority/AuthorityGroup.cs | 0 .../Models/Authority/AuthorityRole.cs | 16 +- .../Models/Authority/AuthorityUser.cs | 16 +- .../Models/AuthorityResult.cs | 0 .../Models/Options/AuthorityOptions.cs | 0 .../Models/Options/ListOption.cs | 0 .../Models/Options/LockdownOptions.cs | 0 .../Models/Options/LockoutOptions.cs | 0 .../Models/Options/PasswordOptions.cs | 0 .../Models/Options/ProviderOptions.cs | 0 .../Models/Options/RepositoryOptions.cs | 0 .../Models/Options/SignInOptions.cs | 0 .../Models/Options/UserOptions.cs | 0 .../Models/Validation/ValidationError.cs | 0 .../Models/Validation/ValidationResult.cs | 0 .../Repositories/IAttributeRepository.cs | 0 .../Repositories/IGroupRepository.cs | 0 .../Repositories/IRoleRepository.cs | 0 .../Repositories/IUserRepository.cs | 0 .../Validators/IPasswordValidator.cs | 0 .../Validators/IUserValidator.cs | 0 .../Validators/PasswordEqualsValidator.cs | 0 .../Validators/PasswordOptionsValidator.cs | 0 .../Validators/UserValidator.cs | 0 .../Verifiers/IEmailVerifier.cs | 0 .../Verifiers/IPhoneNumberVerifier.cs | 0 .../Verifiers/IUserVerifier.cs | 0 DotBased.Data/DotBased.Data.csproj | 12 ++ DotBased.Logging.MEL/BasedLogger.cs | 0 DotBased.Logging.MEL/BasedLoggerProvider.cs | 0 .../DotBased.Logging.MEL.csproj | 0 .../LoggerBuilderExtensions.cs | 0 DotBased.Logging.Serilog/BasedSerilog.cs | 0 .../BasedSerilogEnricher.cs | 0 DotBased.sln | 7 + DotBased/Objects/DbObjectAttribute.cs | 0 DotBased/Objects/IObjectAttribute.cs | 0 DotBased/Objects/ObjectAttribute.cs | 0 TestWebApi/Program.cs | 0 TestWebApi/Properties/launchSettings.json | 0 TestWebApi/SeedAuthorityData.cs | 0 TestWebApi/TestWebApi.csproj | 0 TestWebApi/TestWebApi.http | 0 TestWebApi/appsettings.Development.json | 0 TestWebApi/appsettings.json | 0 obs_DotBased/.obsidian/app.json | 1 + obs_DotBased/.obsidian/appearance.json | 1 + obs_DotBased/.obsidian/core-plugins.json | 30 +++ obs_DotBased/.obsidian/graph.json | 22 +++ obs_DotBased/.obsidian/workspace.json | 186 ++++++++++++++++++ .../DotBased.Authority/Data diagram.canvas | 22 +++ .../Models/AuthorityAttribute.md | 2 + .../Models/AuthorityGroup.md | 2 + .../Models/AuthorityRole.md | 1 + .../Models/AuthorityUser.md | 1 + .../Repositories/UserRepository.md | 6 + .../AspNet/DotBased.Authority/Repository.md | 0 118 files changed, 356 insertions(+), 51 deletions(-) mode change 100644 => 100755 Blazor.Wasm/App.razor mode change 100644 => 100755 Blazor.Wasm/Blazor.Wasm.csproj mode change 100644 => 100755 Blazor.Wasm/Layout/MainLayout.razor mode change 100644 => 100755 Blazor.Wasm/Layout/MainLayout.razor.css mode change 100644 => 100755 Blazor.Wasm/Layout/NavMenu.razor mode change 100644 => 100755 Blazor.Wasm/Layout/NavMenu.razor.css mode change 100644 => 100755 Blazor.Wasm/Pages/Counter.razor mode change 100644 => 100755 Blazor.Wasm/Pages/Home.razor mode change 100644 => 100755 Blazor.Wasm/Pages/Weather.razor mode change 100644 => 100755 Blazor.Wasm/Program.cs mode change 100644 => 100755 Blazor.Wasm/Properties/launchSettings.json mode change 100644 => 100755 Blazor.Wasm/_Imports.razor mode change 100644 => 100755 Blazor.Wasm/wwwroot/css/app.css mode change 100644 => 100755 Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css mode change 100644 => 100755 Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css.map mode change 100644 => 100755 Blazor.Wasm/wwwroot/favicon.png mode change 100644 => 100755 Blazor.Wasm/wwwroot/icon-192.png mode change 100644 => 100755 Blazor.Wasm/wwwroot/index.html mode change 100644 => 100755 Blazor.Wasm/wwwroot/sample-data/weather.json mode change 100644 => 100755 DotBased.ASP.Auth/AuthDataCache.cs mode change 100644 => 100755 DotBased.ASP.Auth/AuthenticationService.cs mode change 100644 => 100755 DotBased.ASP.Auth/BasedAuthConfiguration.cs mode change 100644 => 100755 DotBased.ASP.Auth/BasedAuthDefaults.cs mode change 100644 => 100755 DotBased.ASP.Auth/BasedServerAuthenticationStateProvider.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/Auth/AuthenticationStateModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/Auth/PermissionModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/Auth/RoleModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/Identity/GroupItemModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/Identity/GroupModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/Identity/UserItemModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/Identity/UserModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/LoginModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/Domains/RegisterModel.cs mode change 100644 => 100755 DotBased.ASP.Auth/DotBased.ASP.Auth.csproj mode change 100644 => 100755 DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs mode change 100644 => 100755 DotBased.ASP.Auth/IAuthDataRepository.cs mode change 100644 => 100755 DotBased.ASP.Auth/ISessionStateProvider.cs mode change 100644 => 100755 DotBased.ASP.Auth/MemoryAuthDataRepository.cs mode change 100644 => 100755 DotBased.ASP.Auth/Models/Configuration/AuthConfiguration.cs mode change 100644 => 100755 DotBased.ASP.Auth/Models/Configuration/CacheConfiguration.cs mode change 100644 => 100755 DotBased.ASP.Auth/Models/Configuration/LockoutConfiguration.cs mode change 100644 => 100755 DotBased.ASP.Auth/Models/Configuration/PasswordConfiguration.cs mode change 100644 => 100755 DotBased.ASP.Auth/Models/Configuration/ProviderConfiguration.cs mode change 100644 => 100755 DotBased.ASP.Auth/Models/Configuration/RepositoryConfiguration.cs mode change 100644 => 100755 DotBased.ASP.Auth/Models/Configuration/UserConfiguration.cs mode change 100644 => 100755 DotBased.ASP.Auth/SecurityManager.cs mode change 100644 => 100755 DotBased.ASP.Auth/SecurityService.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs mode change 100644 => 100755 DotBased.AspNet.Authority/AuthorityBuilder.cs mode change 100644 => 100755 DotBased.AspNet.Authority/AuthorityDefaults.cs mode change 100644 => 100755 DotBased.AspNet.Authority/AuthorityProviderExtensions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Crypto/Cryptographer.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Crypto/ICryptographer.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Crypto/PasswordHasher.cs mode change 100644 => 100755 DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj mode change 100644 => 100755 DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Managers/AuthorityManager.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/AuthorityResult.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/ListOption.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/SignInOptions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Options/UserOptions.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Validation/ValidationError.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Repositories/IGroupRepository.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Repositories/IRoleRepository.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Repositories/IUserRepository.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Validators/IPasswordValidator.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Validators/IUserValidator.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Validators/UserValidator.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Verifiers/IEmailVerifier.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Verifiers/IPhoneNumberVerifier.cs mode change 100644 => 100755 DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs create mode 100644 DotBased.Data/DotBased.Data.csproj mode change 100644 => 100755 DotBased.Logging.MEL/BasedLogger.cs mode change 100644 => 100755 DotBased.Logging.MEL/BasedLoggerProvider.cs mode change 100644 => 100755 DotBased.Logging.MEL/DotBased.Logging.MEL.csproj mode change 100644 => 100755 DotBased.Logging.MEL/LoggerBuilderExtensions.cs mode change 100644 => 100755 DotBased.Logging.Serilog/BasedSerilog.cs mode change 100644 => 100755 DotBased.Logging.Serilog/BasedSerilogEnricher.cs mode change 100644 => 100755 DotBased/Objects/DbObjectAttribute.cs mode change 100644 => 100755 DotBased/Objects/IObjectAttribute.cs mode change 100644 => 100755 DotBased/Objects/ObjectAttribute.cs mode change 100644 => 100755 TestWebApi/Program.cs mode change 100644 => 100755 TestWebApi/Properties/launchSettings.json mode change 100644 => 100755 TestWebApi/SeedAuthorityData.cs mode change 100644 => 100755 TestWebApi/TestWebApi.csproj mode change 100644 => 100755 TestWebApi/TestWebApi.http mode change 100644 => 100755 TestWebApi/appsettings.Development.json mode change 100644 => 100755 TestWebApi/appsettings.json create mode 100644 obs_DotBased/.obsidian/app.json create mode 100644 obs_DotBased/.obsidian/appearance.json create mode 100644 obs_DotBased/.obsidian/core-plugins.json create mode 100644 obs_DotBased/.obsidian/graph.json create mode 100644 obs_DotBased/.obsidian/workspace.json create mode 100644 obs_DotBased/Modules/AspNet/DotBased.Authority/Data diagram.canvas create mode 100644 obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityAttribute.md create mode 100644 obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityGroup.md create mode 100644 obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityRole.md create mode 100644 obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityUser.md create mode 100644 obs_DotBased/Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md create mode 100644 obs_DotBased/Modules/AspNet/DotBased.Authority/Repository.md diff --git a/Blazor.Wasm/App.razor b/Blazor.Wasm/App.razor old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Blazor.Wasm.csproj b/Blazor.Wasm/Blazor.Wasm.csproj old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Layout/MainLayout.razor b/Blazor.Wasm/Layout/MainLayout.razor old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Layout/MainLayout.razor.css b/Blazor.Wasm/Layout/MainLayout.razor.css old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Layout/NavMenu.razor b/Blazor.Wasm/Layout/NavMenu.razor old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Layout/NavMenu.razor.css b/Blazor.Wasm/Layout/NavMenu.razor.css old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Pages/Counter.razor b/Blazor.Wasm/Pages/Counter.razor old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Pages/Home.razor b/Blazor.Wasm/Pages/Home.razor old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Pages/Weather.razor b/Blazor.Wasm/Pages/Weather.razor old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Program.cs b/Blazor.Wasm/Program.cs old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/Properties/launchSettings.json b/Blazor.Wasm/Properties/launchSettings.json old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/_Imports.razor b/Blazor.Wasm/_Imports.razor old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/wwwroot/css/app.css b/Blazor.Wasm/wwwroot/css/app.css old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css b/Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css.map b/Blazor.Wasm/wwwroot/css/bootstrap/bootstrap.min.css.map old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/wwwroot/favicon.png b/Blazor.Wasm/wwwroot/favicon.png old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/wwwroot/icon-192.png b/Blazor.Wasm/wwwroot/icon-192.png old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/wwwroot/index.html b/Blazor.Wasm/wwwroot/index.html old mode 100644 new mode 100755 diff --git a/Blazor.Wasm/wwwroot/sample-data/weather.json b/Blazor.Wasm/wwwroot/sample-data/weather.json old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/AuthDataCache.cs b/DotBased.ASP.Auth/AuthDataCache.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/AuthenticationService.cs b/DotBased.ASP.Auth/AuthenticationService.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/BasedAuthConfiguration.cs b/DotBased.ASP.Auth/BasedAuthConfiguration.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/BasedAuthDefaults.cs b/DotBased.ASP.Auth/BasedAuthDefaults.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/BasedServerAuthenticationStateProvider.cs b/DotBased.ASP.Auth/BasedServerAuthenticationStateProvider.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/Auth/AuthenticationStateModel.cs b/DotBased.ASP.Auth/Domains/Auth/AuthenticationStateModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/Auth/PermissionModel.cs b/DotBased.ASP.Auth/Domains/Auth/PermissionModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/Auth/RoleModel.cs b/DotBased.ASP.Auth/Domains/Auth/RoleModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/Identity/GroupItemModel.cs b/DotBased.ASP.Auth/Domains/Identity/GroupItemModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/Identity/GroupModel.cs b/DotBased.ASP.Auth/Domains/Identity/GroupModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/Identity/UserItemModel.cs b/DotBased.ASP.Auth/Domains/Identity/UserItemModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/Identity/UserModel.cs b/DotBased.ASP.Auth/Domains/Identity/UserModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/LoginModel.cs b/DotBased.ASP.Auth/Domains/LoginModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Domains/RegisterModel.cs b/DotBased.ASP.Auth/Domains/RegisterModel.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/DotBased.ASP.Auth.csproj b/DotBased.ASP.Auth/DotBased.ASP.Auth.csproj old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs b/DotBased.ASP.Auth/DotBasedAuthDependencyInjection.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/IAuthDataRepository.cs b/DotBased.ASP.Auth/IAuthDataRepository.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/ISessionStateProvider.cs b/DotBased.ASP.Auth/ISessionStateProvider.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/MemoryAuthDataRepository.cs b/DotBased.ASP.Auth/MemoryAuthDataRepository.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Models/Configuration/AuthConfiguration.cs b/DotBased.ASP.Auth/Models/Configuration/AuthConfiguration.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Models/Configuration/CacheConfiguration.cs b/DotBased.ASP.Auth/Models/Configuration/CacheConfiguration.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Models/Configuration/LockoutConfiguration.cs b/DotBased.ASP.Auth/Models/Configuration/LockoutConfiguration.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Models/Configuration/PasswordConfiguration.cs b/DotBased.ASP.Auth/Models/Configuration/PasswordConfiguration.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Models/Configuration/ProviderConfiguration.cs b/DotBased.ASP.Auth/Models/Configuration/ProviderConfiguration.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Models/Configuration/RepositoryConfiguration.cs b/DotBased.ASP.Auth/Models/Configuration/RepositoryConfiguration.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/Models/Configuration/UserConfiguration.cs b/DotBased.ASP.Auth/Models/Configuration/UserConfiguration.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/SecurityManager.cs b/DotBased.ASP.Auth/SecurityManager.cs old mode 100644 new mode 100755 diff --git a/DotBased.ASP.Auth/SecurityService.cs b/DotBased.ASP.Auth/SecurityService.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs b/DotBased.AspNet.Authority/Attributes/ProtectAttribute.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/AuthorityBuilder.cs b/DotBased.AspNet.Authority/AuthorityBuilder.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/AuthorityDefaults.cs b/DotBased.AspNet.Authority/AuthorityDefaults.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Crypto/Cryptographer.cs b/DotBased.AspNet.Authority/Crypto/Cryptographer.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Crypto/ICryptographer.cs b/DotBased.AspNet.Authority/Crypto/ICryptographer.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs b/DotBased.AspNet.Authority/Crypto/IPasswordHasher.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Crypto/PasswordHasher.cs b/DotBased.AspNet.Authority/Crypto/PasswordHasher.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs old mode 100644 new mode 100755 index 22e70cb..a91e65d --- a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs @@ -2,5 +2,9 @@ namespace DotBased.AspNet.Authority.Managers; public partial class AuthorityManager { - + /* + * - Validate User & Group + * - Check if user is already in group (if already in group return) + * - Add to UsersGroups table + */ } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs old mode 100644 new mode 100755 index 50548b1..a9f83f9 --- a/DotBased.AspNet.Authority/Managers/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs @@ -9,36 +9,25 @@ using Microsoft.Extensions.Options; namespace DotBased.AspNet.Authority.Managers; -public partial class AuthorityManager +public partial class AuthorityManager( + IOptions options, + IServiceProvider services, + ICryptographer cryptographer, + IUserRepository userRepository, + IRoleRepository roleRepository, + IPasswordHasher passwordHasher) { - public AuthorityManager( - IOptions options, - IServiceProvider services, - ICryptographer cryptographer, - IUserRepository userRepository, - IRoleRepository roleRepository, - IPasswordHasher passwordHasher) - { - _logger = LogService.RegisterLogger(); - Options = options.Value; - Services = services; - Cryptographer = cryptographer; - UserRepository = userRepository; - RoleRepository = roleRepository; - PasswordHasher = passwordHasher; - } + private readonly ILogger _logger = LogService.RegisterLogger(); - private readonly ILogger _logger; + public IServiceProvider Services { get; } = services; + public AuthorityOptions Options { get; } = options.Value; + public ICryptographer Cryptographer { get; } = cryptographer; + + public IUserRepository UserRepository { get; } = userRepository; + public IRoleRepository RoleRepository { get; } = roleRepository; + + public IPasswordHasher PasswordHasher { get; } = passwordHasher; - public IServiceProvider Services { get; } - public AuthorityOptions Options { get; } - public ICryptographer Cryptographer { get; } - - public IUserRepository UserRepository { get; } - public IRoleRepository RoleRepository { get; } - - public IPasswordHasher PasswordHasher { get; } - public IEnumerable PasswordValidators { get; } = []; public IEnumerable UserValidators { get; } = []; diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs old mode 100644 new mode 100755 index 52f09a5..2b62e98 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -19,9 +19,22 @@ public partial class AuthorityManager return Result.Failed("Not implemented!"); } + public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken? cancellationToken = null) + { + /* + * Search by role name & id + * Order by name, created date, creator? (paging) + */ + return ListResult.Failed("Not implemented!"); + } + public async Task AddRoleToUserAsync(AuthorityUser user, AuthorityRole role, CancellationToken? cancellationToken = null) { - + /* + - Validate User & Role + - Check if role is already in linked to user (if user already has the role, return) + - Add to UsersRoles table + */ } public async Task RemoveRoleFromUserAsync(AuthorityRole role, AuthorityUser user, CancellationToken? cancellationToken = null) @@ -31,4 +44,22 @@ public partial class AuthorityManager public async Task AddRoleToGroupAsync(AuthorityRole role, AuthorityGroup group, CancellationToken? cancellationToken = null) { } + + /// + /// Get all roles (including group roles) that the user has. + /// + /// The user to get the roles from + /// + public async Task> GetUserRolesAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + { + /* + * - Validate user + * - Get user groups (id) + * - Get roles contained from user + * - Get roles contained from groups (if any) + * - Order by (for paging) + */ + + return ListResult.Failed("Not implemented!"); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs old mode 100644 new mode 100755 index 1e22cb5..f1ef51a --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs @@ -1,25 +1,19 @@ namespace DotBased.AspNet.Authority.Models.Authority; -public abstract class AuthorityRole +public abstract class AuthorityRole() { public AuthorityRole(string name) : this() { Name = name; } - - public AuthorityRole() - { - Id = Guid.NewGuid(); - CreatedDate = DateTime.Now; - } - - public Guid Id { get; set; } - + + public Guid Id { get; set; } = Guid.NewGuid(); + public string? Name { get; set; } public long Version { get; set; } - public DateTime CreatedDate { get; set; } + public DateTime CreatedDate { get; set; } = DateTime.Now; public override string ToString() => Name ?? string.Empty; } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs old mode 100644 new mode 100755 index 9f52b58..bc90356 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -2,21 +2,15 @@ using DotBased.AspNet.Authority.Attributes; namespace DotBased.AspNet.Authority.Models.Authority; -public class AuthorityUser +public class AuthorityUser() { public AuthorityUser(string userName) : this() { UserName = userName; } - - public AuthorityUser() - { - Id = Guid.NewGuid(); - CreatedDate = DateTime.Now; - } - - public Guid Id { get; set; } - + + public Guid Id { get; set; } = Guid.NewGuid(); + public bool Enabled { get; set; } public bool Confirmed { get; set; } @@ -29,7 +23,7 @@ public class AuthorityUser public string? PasswordHash { get; set; } - public DateTime CreatedDate { get; set; } + public DateTime CreatedDate { get; set; } = DateTime.Now; public bool TwoFactorEnabled { get; set; } diff --git a/DotBased.AspNet.Authority/Models/AuthorityResult.cs b/DotBased.AspNet.Authority/Models/AuthorityResult.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs b/DotBased.AspNet.Authority/Models/Options/AuthorityOptions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/ListOption.cs b/DotBased.AspNet.Authority/Models/Options/ListOption.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs b/DotBased.AspNet.Authority/Models/Options/LockdownOptions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs b/DotBased.AspNet.Authority/Models/Options/LockoutOptions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs b/DotBased.AspNet.Authority/Models/Options/PasswordOptions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs b/DotBased.AspNet.Authority/Models/Options/ProviderOptions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs b/DotBased.AspNet.Authority/Models/Options/RepositoryOptions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs b/DotBased.AspNet.Authority/Models/Options/SignInOptions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Options/UserOptions.cs b/DotBased.AspNet.Authority/Models/Options/UserOptions.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Validation/ValidationError.cs b/DotBased.AspNet.Authority/Models/Validation/ValidationError.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs b/DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Validators/IUserValidator.cs b/DotBased.AspNet.Authority/Validators/IUserValidator.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Validators/UserValidator.cs b/DotBased.AspNet.Authority/Validators/UserValidator.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Verifiers/IEmailVerifier.cs b/DotBased.AspNet.Authority/Verifiers/IEmailVerifier.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Verifiers/IPhoneNumberVerifier.cs b/DotBased.AspNet.Authority/Verifiers/IPhoneNumberVerifier.cs old mode 100644 new mode 100755 diff --git a/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs b/DotBased.AspNet.Authority/Verifiers/IUserVerifier.cs old mode 100644 new mode 100755 diff --git a/DotBased.Data/DotBased.Data.csproj b/DotBased.Data/DotBased.Data.csproj new file mode 100644 index 0000000..f5d3ce2 --- /dev/null +++ b/DotBased.Data/DotBased.Data.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + enable + + + + + + + diff --git a/DotBased.Logging.MEL/BasedLogger.cs b/DotBased.Logging.MEL/BasedLogger.cs old mode 100644 new mode 100755 diff --git a/DotBased.Logging.MEL/BasedLoggerProvider.cs b/DotBased.Logging.MEL/BasedLoggerProvider.cs old mode 100644 new mode 100755 diff --git a/DotBased.Logging.MEL/DotBased.Logging.MEL.csproj b/DotBased.Logging.MEL/DotBased.Logging.MEL.csproj old mode 100644 new mode 100755 diff --git a/DotBased.Logging.MEL/LoggerBuilderExtensions.cs b/DotBased.Logging.MEL/LoggerBuilderExtensions.cs old mode 100644 new mode 100755 diff --git a/DotBased.Logging.Serilog/BasedSerilog.cs b/DotBased.Logging.Serilog/BasedSerilog.cs old mode 100644 new mode 100755 diff --git a/DotBased.Logging.Serilog/BasedSerilogEnricher.cs b/DotBased.Logging.Serilog/BasedSerilogEnricher.cs old mode 100644 new mode 100755 diff --git a/DotBased.sln b/DotBased.sln index 0275d48..b867aa7 100755 --- a/DotBased.sln +++ b/DotBased.sln @@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNet", "AspNet", "{624E7B EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.AspNet.Authority", "DotBased.AspNet.Authority\DotBased.AspNet.Authority.csproj", "{A3ADC9AF-39B7-4EC4-8022-946118A8C322}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.Data", "DotBased.Data\DotBased.Data.csproj", "{2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +62,10 @@ Global {A3ADC9AF-39B7-4EC4-8022-946118A8C322}.Debug|Any CPU.Build.0 = Debug|Any CPU {A3ADC9AF-39B7-4EC4-8022-946118A8C322}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3ADC9AF-39B7-4EC4-8022-946118A8C322}.Release|Any CPU.Build.0 = Release|Any CPU + {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {EBBDAF9A-BFC7-4BDC-8C51-0501B59A1DDC} = {2156FB93-C252-4B33-8A0C-73C82FABB163} @@ -69,5 +75,6 @@ Global {AC8343A5-7953-4E1D-A926-406BE4D7E819} = {DBDB4538-85D4-45AC-9E0A-A684467AEABA} {624E7B11-8A18-46E5-AB1F-6AF6097F9D4D} = {2156FB93-C252-4B33-8A0C-73C82FABB163} {A3ADC9AF-39B7-4EC4-8022-946118A8C322} = {624E7B11-8A18-46E5-AB1F-6AF6097F9D4D} + {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4} = {2156FB93-C252-4B33-8A0C-73C82FABB163} EndGlobalSection EndGlobal diff --git a/DotBased/Objects/DbObjectAttribute.cs b/DotBased/Objects/DbObjectAttribute.cs old mode 100644 new mode 100755 diff --git a/DotBased/Objects/IObjectAttribute.cs b/DotBased/Objects/IObjectAttribute.cs old mode 100644 new mode 100755 diff --git a/DotBased/Objects/ObjectAttribute.cs b/DotBased/Objects/ObjectAttribute.cs old mode 100644 new mode 100755 diff --git a/TestWebApi/Program.cs b/TestWebApi/Program.cs old mode 100644 new mode 100755 diff --git a/TestWebApi/Properties/launchSettings.json b/TestWebApi/Properties/launchSettings.json old mode 100644 new mode 100755 diff --git a/TestWebApi/SeedAuthorityData.cs b/TestWebApi/SeedAuthorityData.cs old mode 100644 new mode 100755 diff --git a/TestWebApi/TestWebApi.csproj b/TestWebApi/TestWebApi.csproj old mode 100644 new mode 100755 diff --git a/TestWebApi/TestWebApi.http b/TestWebApi/TestWebApi.http old mode 100644 new mode 100755 diff --git a/TestWebApi/appsettings.Development.json b/TestWebApi/appsettings.Development.json old mode 100644 new mode 100755 diff --git a/TestWebApi/appsettings.json b/TestWebApi/appsettings.json old mode 100644 new mode 100755 diff --git a/obs_DotBased/.obsidian/app.json b/obs_DotBased/.obsidian/app.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/obs_DotBased/.obsidian/app.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/obs_DotBased/.obsidian/appearance.json b/obs_DotBased/.obsidian/appearance.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/obs_DotBased/.obsidian/appearance.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/obs_DotBased/.obsidian/core-plugins.json b/obs_DotBased/.obsidian/core-plugins.json new file mode 100644 index 0000000..436f43c --- /dev/null +++ b/obs_DotBased/.obsidian/core-plugins.json @@ -0,0 +1,30 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false +} \ No newline at end of file diff --git a/obs_DotBased/.obsidian/graph.json b/obs_DotBased/.obsidian/graph.json new file mode 100644 index 0000000..42a46ec --- /dev/null +++ b/obs_DotBased/.obsidian/graph.json @@ -0,0 +1,22 @@ +{ + "collapse-filter": true, + "search": "", + "showTags": false, + "showAttachments": false, + "hideUnresolved": false, + "showOrphans": true, + "collapse-color-groups": true, + "colorGroups": [], + "collapse-display": true, + "showArrow": false, + "textFadeMultiplier": 0, + "nodeSizeMultiplier": 1, + "lineSizeMultiplier": 1, + "collapse-forces": true, + "centerStrength": 0.518713248970312, + "repelStrength": 10, + "linkStrength": 1, + "linkDistance": 250, + "scale": 1, + "close": true +} \ No newline at end of file diff --git a/obs_DotBased/.obsidian/workspace.json b/obs_DotBased/.obsidian/workspace.json new file mode 100644 index 0000000..7819a34 --- /dev/null +++ b/obs_DotBased/.obsidian/workspace.json @@ -0,0 +1,186 @@ +{ + "main": { + "id": "036516b320d15f05", + "type": "split", + "children": [ + { + "id": "68f3abbbf106a47b", + "type": "tabs", + "children": [ + { + "id": "9629cc68ecd8963f", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "UserRepository" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "97d2b7eaa5b0817b", + "type": "split", + "children": [ + { + "id": "4ab704d9f07468d7", + "type": "tabs", + "children": [ + { + "id": "2dcd0dffd753993c", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "4a5d90d4f8e6e8b9", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "26ebe543611d3a9b", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "37b39260b7304344", + "type": "split", + "children": [ + { + "id": "233acbe146cb7e31", + "type": "tabs", + "children": [ + { + "id": "61d17c9ee7d66c50", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks for UserRepository" + } + }, + { + "id": "9edca2501ca8fc8d", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links from UserRepository" + } + }, + { + "id": "a0b5dc7ca04cc4f8", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "25f7df275652ff82", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md" + }, + "icon": "lucide-list", + "title": "Outline of UserRepository" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false + } + }, + "active": "9629cc68ecd8963f", + "lastOpenFiles": [ + "Modules/AspNet/DotBased.Authority/Repository.md", + "Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md", + "Modules/AspNet/DotBased.Authority/Repositories", + "Modules/AspNet/DotBased.Authority/Data diagram.canvas", + "Modules/AspNet/DotBased.Authority/Models/AuthorityAttribute.md", + "Modules/AspNet/DotBased.Authority/Models/AuthorityUser.md", + "Modules/AspNet/DotBased.Authority/Models/AuthorityRole.md", + "Modules/AspNet/DotBased.Authority/Models/AuthorityGroup.md", + "Modules/AspNet/DotBased.Authority/Models", + "Untitled.canvas", + "Modules/AspNet/DotBased.Authority.md", + "Modules/AspNet/DotBased.Authority", + "DotBased.md", + "Modules/Untitled", + "Modules/DotBased.Data", + "Modules/AspNet", + "Modules", + "Welcome.md" + ] +} \ No newline at end of file diff --git a/obs_DotBased/Modules/AspNet/DotBased.Authority/Data diagram.canvas b/obs_DotBased/Modules/AspNet/DotBased.Authority/Data diagram.canvas new file mode 100644 index 0000000..f1b2fb6 --- /dev/null +++ b/obs_DotBased/Modules/AspNet/DotBased.Authority/Data diagram.canvas @@ -0,0 +1,22 @@ +{ + "nodes":[ + {"id":"d06f84f5cb83d5b3","type":"file","file":"Modules/AspNet/DotBased.Authority/Models/AuthorityUser.md","x":-660,"y":-740,"width":400,"height":400}, + {"id":"20469f32c2b97d54","type":"file","file":"Modules/AspNet/DotBased.Authority/Models/AuthorityGroup.md","x":-180,"y":-740,"width":400,"height":400}, + {"id":"54394f02266f386b","x":-180,"y":-1480,"width":400,"height":400,"type":"file","file":"Modules/AspNet/DotBased.Authority/Models/AuthorityAttribute.md"}, + {"id":"e38f59f13e522e10","x":-340,"y":-190,"width":250,"height":60,"color":"5","type":"text","text":"#### UsersGroups"}, + {"id":"cfabf37254bf9b94","x":-340,"y":-40,"width":250,"height":60,"color":"5","type":"text","text":"#### UserRoles"}, + {"id":"d306221d0fc3815f","x":300,"y":-740,"width":400,"height":400,"type":"file","file":"Modules/AspNet/DotBased.Authority/Models/AuthorityRole.md"}, + {"id":"3c9bc17b3a311b43","x":140,"y":-190,"width":250,"height":60,"color":"5","type":"text","text":"#### GroupsRoles"} + ], + "edges":[ + {"id":"591458f8b454aec0","fromNode":"d06f84f5cb83d5b3","fromSide":"bottom","toNode":"e38f59f13e522e10","toSide":"left"}, + {"id":"a5758a4bbb7fe559","fromNode":"20469f32c2b97d54","fromSide":"bottom","toNode":"e38f59f13e522e10","toSide":"right"}, + {"id":"86bf0eb2ae0fcfdc","fromNode":"d06f84f5cb83d5b3","fromSide":"bottom","toNode":"cfabf37254bf9b94","toSide":"left"}, + {"id":"64a58417d746183f","fromNode":"d306221d0fc3815f","fromSide":"bottom","toNode":"cfabf37254bf9b94","toSide":"right"}, + {"id":"e5825aa1415a34c4","fromNode":"20469f32c2b97d54","fromSide":"bottom","toNode":"3c9bc17b3a311b43","toSide":"left"}, + {"id":"0aa9af60a44a83dd","fromNode":"d306221d0fc3815f","fromSide":"bottom","toNode":"3c9bc17b3a311b43","toSide":"right"}, + {"id":"1958b5d5b82c10d7","fromNode":"54394f02266f386b","fromSide":"bottom","toNode":"d06f84f5cb83d5b3","toSide":"top"}, + {"id":"c71f1f3fc3e239f3","fromNode":"54394f02266f386b","fromSide":"bottom","toNode":"20469f32c2b97d54","toSide":"top"}, + {"id":"bd026c0a356725a5","fromNode":"54394f02266f386b","fromSide":"bottom","toNode":"d306221d0fc3815f","toSide":"top"} + ] +} \ No newline at end of file diff --git a/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityAttribute.md b/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityAttribute.md new file mode 100644 index 0000000..a3fa43b --- /dev/null +++ b/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityAttribute.md @@ -0,0 +1,2 @@ +Attributes to store some extra metadata. +One attribute per table entry. \ No newline at end of file diff --git a/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityGroup.md b/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityGroup.md new file mode 100644 index 0000000..0c4ca5a --- /dev/null +++ b/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityGroup.md @@ -0,0 +1,2 @@ +Groups can have multiple [[AuthorityUser]]s and holds [[AuthorityRole]]s . +Can be extended with extra data from [[AuthorityAttribute]]s \ No newline at end of file diff --git a/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityRole.md b/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityRole.md new file mode 100644 index 0000000..f2bf9f3 --- /dev/null +++ b/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityRole.md @@ -0,0 +1 @@ +Roles used for permissions and specific [[AuthorityAttribute]]s. Can get added to [[AuthorityUser]] and [[AuthorityRole]] \ No newline at end of file diff --git a/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityUser.md b/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityUser.md new file mode 100644 index 0000000..2a576e0 --- /dev/null +++ b/obs_DotBased/Modules/AspNet/DotBased.Authority/Models/AuthorityUser.md @@ -0,0 +1 @@ +An user can be in multiple [[AuthorityGroup]]s, can have own [[AuthorityRole]]s and [[AuthorityAttribute]]s. \ No newline at end of file diff --git a/obs_DotBased/Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md b/obs_DotBased/Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md new file mode 100644 index 0000000..62f8751 --- /dev/null +++ b/obs_DotBased/Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md @@ -0,0 +1,6 @@ +Handles the db for user models. + +## GetUsers (list) +- Search +- Paging (limit, offset) +- Returns useritem \ No newline at end of file diff --git a/obs_DotBased/Modules/AspNet/DotBased.Authority/Repository.md b/obs_DotBased/Modules/AspNet/DotBased.Authority/Repository.md new file mode 100644 index 0000000..e69de29 From e914023c5a545b69415b050586cb57347bcdfca9 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 27 Jan 2025 23:15:23 +0100 Subject: [PATCH 17/38] [ADD] Added EF Core project for authority db --- .../AuthorityContext.cs | 12 ++++++++++++ .../DotBased.AspNet.Authority.EFCore.csproj | 17 +++++++++++++++++ .../Repositories/RoleRepository.cs | 12 ++++++++++++ .../Repositories/IRoleRepository.cs | 4 +++- DotBased.sln | 7 +++++++ DotBased/Result.cs | 16 +++++++++++++--- 6 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 DotBased.AspNet.Authority.EFCore/AuthorityContext.cs create mode 100644 DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj create mode 100644 DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs diff --git a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs new file mode 100644 index 0000000..fc470a6 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs @@ -0,0 +1,12 @@ +using DotBased.AspNet.Authority.Models.Authority; +using Microsoft.EntityFrameworkCore; + +namespace DotBased.AspNet.Authority.EFCore; + +public class AuthorityContext : DbContext +{ + public DbSet Attributes { get; set; } + public DbSet Groups { get; set; } + public DbSet Roles { get; set; } + public DbSet Users { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj b/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj new file mode 100644 index 0000000..56e1802 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs new file mode 100644 index 0000000..6a1b1d8 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs @@ -0,0 +1,12 @@ +using DotBased.AspNet.Authority.Models.Authority; +using DotBased.AspNet.Authority.Repositories; + +namespace DotBased.AspNet.Authority.EFCore.Repositories; + +public class RoleRepository : IRoleRepository +{ + public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs index 436076b..69ab90d 100755 --- a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs @@ -1,6 +1,8 @@ +using DotBased.AspNet.Authority.Models.Authority; + namespace DotBased.AspNet.Authority.Repositories; public interface IRoleRepository { - + public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased.sln b/DotBased.sln index b867aa7..bdaa262 100755 --- a/DotBased.sln +++ b/DotBased.sln @@ -24,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.AspNet.Authority", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.Data", "DotBased.Data\DotBased.Data.csproj", "{2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.AspNet.Authority.EFCore", "DotBased.AspNet.Authority.EFCore\DotBased.AspNet.Authority.EFCore.csproj", "{F1F3F60B-911F-4036-8A2B-CEC18A8F59DD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -66,6 +68,10 @@ Global {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}.Debug|Any CPU.Build.0 = Debug|Any CPU {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}.Release|Any CPU.ActiveCfg = Release|Any CPU {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4}.Release|Any CPU.Build.0 = Release|Any CPU + {F1F3F60B-911F-4036-8A2B-CEC18A8F59DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1F3F60B-911F-4036-8A2B-CEC18A8F59DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1F3F60B-911F-4036-8A2B-CEC18A8F59DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1F3F60B-911F-4036-8A2B-CEC18A8F59DD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {EBBDAF9A-BFC7-4BDC-8C51-0501B59A1DDC} = {2156FB93-C252-4B33-8A0C-73C82FABB163} @@ -76,5 +82,6 @@ Global {624E7B11-8A18-46E5-AB1F-6AF6097F9D4D} = {2156FB93-C252-4B33-8A0C-73C82FABB163} {A3ADC9AF-39B7-4EC4-8022-946118A8C322} = {624E7B11-8A18-46E5-AB1F-6AF6097F9D4D} {2DF9FEEF-5A60-4B41-9B5F-F883DCE33EF4} = {2156FB93-C252-4B33-8A0C-73C82FABB163} + {F1F3F60B-911F-4036-8A2B-CEC18A8F59DD} = {624E7B11-8A18-46E5-AB1F-6AF6097F9D4D} EndGlobalSection EndGlobal diff --git a/DotBased/Result.cs b/DotBased/Result.cs index 49e8ba6..e188b77 100755 --- a/DotBased/Result.cs +++ b/DotBased/Result.cs @@ -47,7 +47,7 @@ public class Result : Result public class ListResult : Result { - public ListResult(bool success, string message, int totalCount, IEnumerable? items, Exception? exception) : base(success, message, exception) + public ListResult(bool success, string message, int totalCount, IEnumerable? items, int limit = -1, int offset = -1, Exception? exception = null) : base(success, message, exception) { Items = items != null ? new List(items) : new List(); TotalCount = totalCount; @@ -69,9 +69,19 @@ public class ListResult : Result /// public int TotalCount { get; } + /// + /// The limit this result contains + /// + public int Limit { get; } + + /// + /// The offset this result has the items from. + /// + public int Offset { get; } + public static ListResult Ok(IEnumerable items, int totalCount = -1) => - new(true, string.Empty, totalCount, items, null); + new(true, string.Empty, totalCount, items); public new static ListResult Failed(string message, Exception? exception = null) => - new(false, message, -1, null, exception); + new(false, message, -1, null); } \ No newline at end of file From 5b4509cac30418e78c889c65c2177de0c98095af Mon Sep 17 00:00:00 2001 From: max Date: Sat, 1 Feb 2025 01:02:27 +0100 Subject: [PATCH 18/38] [ADD] Added sqlite to test project, created di for ef core context. Reworked repositories to use result class. --- DotBased.AspNet.Authority.EFCore/DI.cs | 13 ++++++++ .../Repositories/AttributeRepository.cs | 33 +++++++++++++++++++ .../Repositories/GroupRepository.cs | 32 ++++++++++++++++++ .../Repositories/RoleRepository.cs | 22 ++++++++++++- .../Managers/AuthorityUserManager.cs | 18 +++++----- .../Authority/AuthorityAttributeItem.cs | 6 ++++ .../Models/Authority/AuthorityGroupItem.cs | 6 ++++ .../Models/Authority/AuthorityRoleItem.cs | 6 ++++ .../Models/Authority/AuthorityUserItem.cs | 6 ++++ .../Models/AuthorityResult.cs | 17 ++++++---- .../Repositories/IAttributeRepository.cs | 8 ++++- .../Repositories/IGroupRepository.cs | 8 ++++- .../Repositories/IRoleRepository.cs | 6 +++- .../Repositories/IUserRepository.cs | 21 ++++++------ TestWebApi/Program.cs | 7 +++- TestWebApi/TestWebApi.csproj | 2 ++ obs_DotBased/.obsidian/workspace.json | 18 ++++------ 17 files changed, 185 insertions(+), 44 deletions(-) create mode 100644 DotBased.AspNet.Authority.EFCore/DI.cs create mode 100644 DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs create mode 100644 DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityAttributeItem.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityGroupItem.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityRoleItem.cs create mode 100644 DotBased.AspNet.Authority/Models/Authority/AuthorityUserItem.cs diff --git a/DotBased.AspNet.Authority.EFCore/DI.cs b/DotBased.AspNet.Authority.EFCore/DI.cs new file mode 100644 index 0000000..f5baba7 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/DI.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace DotBased.AspNet.Authority.EFCore; + +public static class DI +{ + public static IServiceCollection AddAuthorityContext(this IServiceCollection services, Action options) + { + services.AddDbContextFactory(options); + return services; + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs new file mode 100644 index 0000000..438e75d --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs @@ -0,0 +1,33 @@ +using DotBased.AspNet.Authority.Models.Authority; +using DotBased.AspNet.Authority.Repositories; + +namespace DotBased.AspNet.Authority.EFCore.Repositories; + +public class AttributeRepository : IAttributeRepository +{ + public Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> GetAttributeByIdAsync(string id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs new file mode 100644 index 0000000..a6fb010 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs @@ -0,0 +1,32 @@ +using DotBased.AspNet.Authority.Models.Authority; +using DotBased.AspNet.Authority.Repositories; + +namespace DotBased.AspNet.Authority.EFCore.Repositories; + +public class GroupRepository : IGroupRepository +{ + public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs index 6a1b1d8..bd15ed7 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs @@ -5,7 +5,27 @@ namespace DotBased.AspNet.Authority.EFCore.Repositories; public class RoleRepository : IRoleRepository { - public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index 5b07cfc..363c49c 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -34,10 +34,10 @@ public partial class AuthorityManager return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); } - public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken? cancellationToken = null) + public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken? cancellationToken = null) { - var searchResult = await UserRepository.GetAuthorityUsersAsync(query, maxResults, offset, cancellationToken); - return searchResult.Item1 == null ? ListResult.Failed("No results!") : ListResult.Ok(searchResult.Item1, searchResult.Item2); + var result = await UserRepository.GetAuthorityUsersAsync(maxResults, offset, query, cancellationToken); + return result; } public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken? cancellationToken = null) @@ -54,7 +54,7 @@ public partial class AuthorityManager user.SecurityVersion = GenerateVersion(); var updateResult = await UserRepository.UpdateUserAsync(user, cancellationToken); - return updateResult == null ? AuthorityResult.Error("Failed to save updates!") : AuthorityResult.Ok(updateResult); + return AuthorityResult.FromResult(updateResult); } public async Task> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken? cancellationToken = null) @@ -75,19 +75,17 @@ public partial class AuthorityManager userModel.PasswordHash = hashedPassword; var userCreationResult = await UserRepository.CreateUserAsync(userModel, cancellationToken); - - return userCreationResult != null - ? AuthorityResult.Ok(userCreationResult) - : AuthorityResult.Error("Failed to create user in repository!"); + + return AuthorityResult.FromResult(userCreationResult); } public async Task> UpdateUserAsync(AuthorityUser model, CancellationToken? cancellationToken = null) { var updateResult = await UserRepository.UpdateUserAsync(model, cancellationToken); - return updateResult != null ? Result.Ok(updateResult) : Result.Failed("Failed to update user in repository!"); + return updateResult; } - public async Task DeleteUserAsync(AuthorityUser model, CancellationToken? cancellationToken = null) + public async Task DeleteUserAsync(AuthorityUser model, CancellationToken? cancellationToken = null) { var deleteResult = await UserRepository.DeleteUserAsync(model, cancellationToken); return deleteResult; diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttributeItem.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttributeItem.cs new file mode 100644 index 0000000..44a0211 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttributeItem.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Models.Authority; + +public class AuthorityAttributeItem +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroupItem.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroupItem.cs new file mode 100644 index 0000000..2b9bbcc --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroupItem.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Models.Authority; + +public class AuthorityGroupItem +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRoleItem.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRoleItem.cs new file mode 100644 index 0000000..e285d5a --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRoleItem.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Models.Authority; + +public class AuthorityRoleItem +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUserItem.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserItem.cs new file mode 100644 index 0000000..6afe594 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserItem.cs @@ -0,0 +1,6 @@ +namespace DotBased.AspNet.Authority.Models.Authority; + +public class AuthorityUserItem +{ + +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/AuthorityResult.cs b/DotBased.AspNet.Authority/Models/AuthorityResult.cs index f6c6d35..3cb3340 100755 --- a/DotBased.AspNet.Authority/Models/AuthorityResult.cs +++ b/DotBased.AspNet.Authority/Models/AuthorityResult.cs @@ -2,20 +2,23 @@ using DotBased.AspNet.Authority.Models.Validation; namespace DotBased.AspNet.Authority.Models; -public class AuthorityResult +public class AuthorityResult : Result { - public AuthorityResult(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, List? errors = null) + public static AuthorityResult FromResult(Result result) => new AuthorityResult(result); + + public AuthorityResult(Result result) : base(result) + { + Reason = ResultFailReason.Unknown; + } + + public AuthorityResult(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, List? errors = null) : base(success, errorMessage, value, null) { Success = success; - ErrorMessage = errorMessage; + Message = errorMessage; Value = value; Reason = reason; ValidationErrors = errors; } - - public bool Success { get; } - public string ErrorMessage { get; } - public TResultValue? Value { get; } public ResultFailReason Reason { get; } public List? ValidationErrors { get; } diff --git a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs index 7e22f88..7fcca43 100755 --- a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs @@ -1,6 +1,12 @@ +using DotBased.AspNet.Authority.Models.Authority; + namespace DotBased.AspNet.Authority.Repositories; public interface IAttributeRepository { - + public Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetAttributeByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); + public Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); + public Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs index f16b7d2..f8eb385 100755 --- a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs @@ -1,6 +1,12 @@ +using DotBased.AspNet.Authority.Models.Authority; + namespace DotBased.AspNet.Authority.Repositories; public interface IGroupRepository { - + public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs index 69ab90d..83cec83 100755 --- a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs @@ -4,5 +4,9 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IRoleRepository { - public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); + public Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); + public Task DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index f86cdb5..ca15430 100755 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -4,15 +4,14 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IUserRepository { - public Task GetAuthorityUserByIdAsync(string id, CancellationToken? cancellationToken = null); - public Task GetAuthorityUserIdAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task?, int>> GetAuthorityUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken? cancellationToken = null); - public Task GetAuthorityUserByEmailAsync(string email, CancellationToken? cancellationToken = null); - public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); - public Task GetVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); - public Task GetSecurityVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task CreateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task UpdateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task DeleteUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken? cancellationToken = null); + public Task> GetAuthorityUserByIdAsync(string id, CancellationToken? cancellationToken = null); + public Task> CreateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task> UpdateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task DeleteUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task> GetAuthorityUserByEmailAsync(string email, CancellationToken? cancellationToken = null); + public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); + public Task> GetVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); + public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); } \ No newline at end of file diff --git a/TestWebApi/Program.cs b/TestWebApi/Program.cs index 323ca0c..1c24460 100755 --- a/TestWebApi/Program.cs +++ b/TestWebApi/Program.cs @@ -1,7 +1,9 @@ using DotBased.AspNet.Authority; +using DotBased.AspNet.Authority.EFCore; using DotBased.Logging; using DotBased.Logging.MEL; using DotBased.Logging.Serilog; +using Microsoft.EntityFrameworkCore; using Serilog; using TestWebApi; using ILogger = Serilog.ILogger; @@ -19,7 +21,10 @@ LogService.AddLogAdapter(new BasedSerilogAdapter(serilogLogger)); builder.Logging.ClearProviders(); builder.Logging.AddDotBasedLoggerProvider(LogService.Options); - +builder.Services.AddAuthorityContext(options => +{ + options.UseSqlite("Data Source=dev-dotbased.db"); +}); builder.Services.AddAuthority(options => { diff --git a/TestWebApi/TestWebApi.csproj b/TestWebApi/TestWebApi.csproj index ef97892..6340445 100755 --- a/TestWebApi/TestWebApi.csproj +++ b/TestWebApi/TestWebApi.csproj @@ -8,11 +8,13 @@ + + diff --git a/obs_DotBased/.obsidian/workspace.json b/obs_DotBased/.obsidian/workspace.json index 7819a34..9e458b2 100644 --- a/obs_DotBased/.obsidian/workspace.json +++ b/obs_DotBased/.obsidian/workspace.json @@ -4,21 +4,17 @@ "type": "split", "children": [ { - "id": "68f3abbbf106a47b", + "id": "89533e49f06550fb", "type": "tabs", "children": [ { - "id": "9629cc68ecd8963f", + "id": "65943ca25b411f17", "type": "leaf", "state": { - "type": "markdown", - "state": { - "file": "Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md", - "mode": "source", - "source": false - }, + "type": "empty", + "state": {}, "icon": "lucide-file", - "title": "UserRepository" + "title": "New tab" } } ] @@ -162,10 +158,10 @@ "command-palette:Open command palette": false } }, - "active": "9629cc68ecd8963f", + "active": "65943ca25b411f17", "lastOpenFiles": [ - "Modules/AspNet/DotBased.Authority/Repository.md", "Modules/AspNet/DotBased.Authority/Repositories/UserRepository.md", + "Modules/AspNet/DotBased.Authority/Repository.md", "Modules/AspNet/DotBased.Authority/Repositories", "Modules/AspNet/DotBased.Authority/Data diagram.canvas", "Modules/AspNet/DotBased.Authority/Models/AuthorityAttribute.md", From 0f6b2fec88bb311b9ef5d4d6390c760b62901910 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 2 Feb 2025 01:06:36 +0100 Subject: [PATCH 19/38] DBContext & repository --- .../AuthorityContext.cs | 9 +++ DotBased.AspNet.Authority.EFCore/DI.cs | 6 ++ .../DotBased.AspNet.Authority.EFCore.csproj | 1 + .../Repositories/UserRepository.cs | 58 +++++++++++++++++++ .../Repositories/IUserRepository.cs | 2 +- .../Validators/UserValidator.cs | 2 +- 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs diff --git a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs index fc470a6..5463940 100644 --- a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs +++ b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs @@ -9,4 +9,13 @@ public class AuthorityContext : DbContext public DbSet Groups { get; set; } public DbSet Roles { get; set; } public DbSet Users { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().ToTable("authority_attributes"); + modelBuilder.Entity().ToTable("authority_groups"); + modelBuilder.Entity().ToTable("authority_roles"); + modelBuilder.Entity().ToTable("authority_users"); + base.OnModelCreating(modelBuilder); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/DI.cs b/DotBased.AspNet.Authority.EFCore/DI.cs index f5baba7..999df24 100644 --- a/DotBased.AspNet.Authority.EFCore/DI.cs +++ b/DotBased.AspNet.Authority.EFCore/DI.cs @@ -1,3 +1,5 @@ +using DotBased.AspNet.Authority.EFCore.Repositories; +using DotBased.AspNet.Authority.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -8,6 +10,10 @@ public static class DI public static IServiceCollection AddAuthorityContext(this IServiceCollection services, Action options) { services.AddDbContextFactory(options); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); return services; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj b/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj index 56e1802..64df6fe 100644 --- a/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj +++ b/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj @@ -12,6 +12,7 @@ + diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs new file mode 100644 index 0000000..9e1542f --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs @@ -0,0 +1,58 @@ +using DotBased.AspNet.Authority.Models.Authority; +using DotBased.AspNet.Authority.Repositories; + +namespace DotBased.AspNet.Authority.EFCore.Repositories; + +public class UserRepository : IUserRepository +{ + public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", + CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task> GetAuthorityUserByIdAsync(string id, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task> CreateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task> UpdateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task DeleteUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task> GetUserByEmailAsync(string email, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task> GetVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } + + public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index ca15430..c84448a 100755 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -9,7 +9,7 @@ public interface IUserRepository public Task> CreateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); public Task> UpdateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); public Task DeleteUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task> GetAuthorityUserByEmailAsync(string email, CancellationToken? cancellationToken = null); + public Task> GetUserByEmailAsync(string email, CancellationToken? cancellationToken = null); public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); public Task> GetVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); diff --git a/DotBased.AspNet.Authority/Validators/UserValidator.cs b/DotBased.AspNet.Authority/Validators/UserValidator.cs index 1b914bc..cb60dce 100755 --- a/DotBased.AspNet.Authority/Validators/UserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/UserValidator.cs @@ -25,7 +25,7 @@ public class UserValidator : IUserValidator } else { - var userEmailResult = await manager.UserRepository.GetAuthorityUserByEmailAsync(user.EmailAddress); + var userEmailResult = await manager.UserRepository.GetUserByEmailAsync(user.EmailAddress); if (userEmailResult != null) { errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.EmailExists", From 2938e1311f7e80a4e9f942da72101d3a3984d0f5 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 2 Feb 2025 23:33:00 +0100 Subject: [PATCH 20/38] [DB] DbContext relations --- .../AuthorityContext.cs | 32 ++++++++++++++++--- .../DotBased.AspNet.Authority.EFCore.csproj | 4 +++ DotBased.AspNet.Authority.EFCore/README.md | 23 +++++++++++++ .../DotBased.AspNet.Authority.csproj | 1 + .../Models/Authority/AuthorityAttribute.cs | 20 ++++-------- .../Models/Authority/AuthorityGroup.cs | 1 + .../Models/Authority/AuthorityRole.cs | 2 +- .../Models/Authority/AuthorityUser.cs | 2 ++ TestWebApi/Program.cs | 2 +- TestWebApi/TestWebApi.csproj | 6 +++- 10 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 DotBased.AspNet.Authority.EFCore/README.md diff --git a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs index 5463940..6a0f813 100644 --- a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs +++ b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; namespace DotBased.AspNet.Authority.EFCore; -public class AuthorityContext : DbContext +public class AuthorityContext(DbContextOptions options) : DbContext(options) { public DbSet Attributes { get; set; } public DbSet Groups { get; set; } @@ -12,10 +12,32 @@ public class AuthorityContext : DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().ToTable("authority_attributes"); - modelBuilder.Entity().ToTable("authority_groups"); - modelBuilder.Entity().ToTable("authority_roles"); - modelBuilder.Entity().ToTable("authority_users"); + modelBuilder.Entity(attributeEntity => + { + attributeEntity.ToTable("authority_attributes"); + attributeEntity.HasKey(a => new { a.BoundId, a.AttributeKey }); + }); + + modelBuilder.Entity(groupEntity => + { + groupEntity.ToTable("authority_groups"); + groupEntity.HasKey(x => x.Id); + groupEntity.HasMany(g => g.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(roleEntity => + { + roleEntity.ToTable("authority_roles"); + roleEntity.HasKey(x => x.Id); + }); + + modelBuilder.Entity(userEntity => + { + userEntity.ToTable("authority_users"); + userEntity.HasKey(x => x.Id); + userEntity.HasMany(u => u.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade); + }); + base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj b/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj index 64df6fe..ec7e8bd 100644 --- a/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj +++ b/DotBased.AspNet.Authority.EFCore/DotBased.AspNet.Authority.EFCore.csproj @@ -12,6 +12,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DotBased.AspNet.Authority.EFCore/README.md b/DotBased.AspNet.Authority.EFCore/README.md new file mode 100644 index 0000000..f15acc9 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/README.md @@ -0,0 +1,23 @@ +# EF Core database + +## Add migration project +```csharp +options.UseSqlite("Data Source=dev-dotbased.db", c => c.MigrationsAssembly("PROJECT-NAME")); +``` + +## EF Tool + +Add migration +```shell +dotnet ef migrations add MIGRATION-NAME --project PROJECT-NAME +``` + +Remove migrations +```shell +dotnet ef migrations remove --project PROJECT-NAME +``` + +Update database +```shell +dotnet ef database update --project PROJECT-NAME +``` diff --git a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj index 53d8b22..b0a8901 100755 --- a/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj +++ b/DotBased.AspNet.Authority/DotBased.AspNet.Authority.csproj @@ -17,6 +17,7 @@ + diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs index 5bc4d6e..27c08ca 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs @@ -1,26 +1,18 @@ namespace DotBased.AspNet.Authority.Models.Authority; -public class AuthorityAttribute +public class AuthorityAttribute(string attributeKey, Guid bound) { - public AuthorityAttribute(string attributeKey, string bound) + public AuthorityAttribute() : this(string.Empty, Guid.NewGuid()) { - AttributeKey = attributeKey; - BoundId = bound; } - public AuthorityAttribute() - { - AttributeKey = string.Empty; - BoundId = string.Empty; - } - - public string AttributeKey { get; set; } // ClaimType/Authority.attribute.enabled + public Guid BoundId { get; set; } = bound; - public string BoundId { get; set; } // Bound to User, Group, Role id + public string AttributeKey { get; set; } = attributeKey; - public object? AttributeValue { get; set; } + public string AttributeValue { get; set; } = string.Empty; - public string? Type { get; set; } // AspNet.Claim.Role/Property/Data.JSON, Data.Raw, Data.Base64 etc. + public string? Type { get; set; } public long Version { get; set; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs index c5474df..05f85c9 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs @@ -20,4 +20,5 @@ public class AuthorityGroup public long Version { get; set; } public DateTime CreatedDate { get; set; } + public ICollection Attributes { get; set; } = []; } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs index f1ef51a..1e215aa 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.Models.Authority; -public abstract class AuthorityRole() +public class AuthorityRole() { public AuthorityRole(string name) : this() { diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs index bc90356..03214ca 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -41,5 +41,7 @@ public class AuthorityUser() public bool PhoneNumberConfirmed { get; set; } + public ICollection Attributes { get; set; } = []; + public override string ToString() => UserName ?? EmailAddress ?? string.Empty; } \ No newline at end of file diff --git a/TestWebApi/Program.cs b/TestWebApi/Program.cs index 1c24460..4c03d50 100755 --- a/TestWebApi/Program.cs +++ b/TestWebApi/Program.cs @@ -23,7 +23,7 @@ builder.Logging.ClearProviders(); builder.Logging.AddDotBasedLoggerProvider(LogService.Options); builder.Services.AddAuthorityContext(options => { - options.UseSqlite("Data Source=dev-dotbased.db"); + options.UseSqlite("Data Source=dev-dotbased.db", c => c.MigrationsAssembly("TestWebApi")); }); builder.Services.AddAuthority(options => { diff --git a/TestWebApi/TestWebApi.csproj b/TestWebApi/TestWebApi.csproj index 6340445..673fe48 100755 --- a/TestWebApi/TestWebApi.csproj +++ b/TestWebApi/TestWebApi.csproj @@ -8,7 +8,11 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + From 65d625a30dc579e496616d4ba1e53f0ac3397732 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 10 Feb 2025 02:11:35 +0100 Subject: [PATCH 21/38] [ADD] Join tables. Added attributes to role. --- .../AuthorityContext.cs | 24 +++++++++++++++++++ .../Models/RoleGroup.cs | 7 ++++++ .../Models/RoleUser.cs | 7 ++++++ .../Models/UserGroup.cs | 7 ++++++ .../Models/Authority/AuthorityGroup.cs | 16 ++++--------- .../Models/Authority/AuthorityRole.cs | 2 ++ 6 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs create mode 100644 DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs create mode 100644 DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs diff --git a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs index 6a0f813..57dbaa8 100644 --- a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs +++ b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs @@ -1,3 +1,4 @@ +using DotBased.AspNet.Authority.EFCore.Models; using DotBased.AspNet.Authority.Models.Authority; using Microsoft.EntityFrameworkCore; @@ -9,6 +10,10 @@ public class AuthorityContext(DbContextOptions options) : DbCo public DbSet Groups { get; set; } public DbSet Roles { get; set; } public DbSet Users { get; set; } + + public DbSet RoleGroup { get; set; } + public DbSet RoleUser { get; set; } + public DbSet UserGroup { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -29,6 +34,7 @@ public class AuthorityContext(DbContextOptions options) : DbCo { roleEntity.ToTable("authority_roles"); roleEntity.HasKey(x => x.Id); + roleEntity.HasMany(r => r.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(userEntity => @@ -37,6 +43,24 @@ public class AuthorityContext(DbContextOptions options) : DbCo userEntity.HasKey(x => x.Id); userEntity.HasMany(u => u.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity(rgEntity => + { + rgEntity.ToTable("role_group"); + rgEntity.HasKey(rg => new { rg.RoleId, rg.GroupId }); + }); + + modelBuilder.Entity(ruEntity => + { + ruEntity.ToTable("role_user"); + ruEntity.HasKey(ru => new { ru.RoleId, ru.UserId }); + }); + + modelBuilder.Entity(ugEntity => + { + ugEntity.ToTable("user_group"); + ugEntity.HasKey(ug => new { ug.UserId, ug.GroupId }); + }); base.OnModelCreating(modelBuilder); } diff --git a/DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs b/DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs new file mode 100644 index 0000000..1afe9a7 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs @@ -0,0 +1,7 @@ +namespace DotBased.AspNet.Authority.EFCore.Models; + +public class RoleGroup +{ + public Guid RoleId { get; set; } + public Guid GroupId { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs b/DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs new file mode 100644 index 0000000..c4a0917 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs @@ -0,0 +1,7 @@ +namespace DotBased.AspNet.Authority.EFCore.Models; + +public class RoleUser +{ + public Guid RoleId { get; set; } + public Guid UserId { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs b/DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs new file mode 100644 index 0000000..648b376 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs @@ -0,0 +1,7 @@ +namespace DotBased.AspNet.Authority.EFCore.Models; + +public class UserGroup +{ + public Guid UserId { get; set; } + public Guid GroupId { get; set; } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs index 05f85c9..fdcae2f 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroup.cs @@ -1,24 +1,18 @@ namespace DotBased.AspNet.Authority.Models.Authority; -public class AuthorityGroup +public class AuthorityGroup() { public AuthorityGroup(string name) : this() { Name = name; } - - public AuthorityGroup() - { - Id = Guid.NewGuid(); - CreatedDate = DateTime.Now; - } - - public Guid Id { get; set; } - + + public Guid Id { get; set; } = Guid.NewGuid(); + public string? Name { get; set; } public long Version { get; set; } - public DateTime CreatedDate { get; set; } + public DateTime CreatedDate { get; set; } = DateTime.Now; public ICollection Attributes { get; set; } = []; } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs index 1e215aa..d3efc45 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs @@ -15,5 +15,7 @@ public class AuthorityRole() public DateTime CreatedDate { get; set; } = DateTime.Now; + public IEnumerable Attributes { get; set; } = []; + public override string ToString() => Name ?? string.Empty; } \ No newline at end of file From eef7cfb2b9f45f2f28d66e12d2010331ee50087f Mon Sep 17 00:00:00 2001 From: max Date: Mon, 10 Feb 2025 02:40:27 +0100 Subject: [PATCH 22/38] [ADD] Creating queries --- .../{DI.cs => Extensions.cs} | 2 +- .../Repositories/UserRepository.cs | 22 +++++++++++++++---- .../Models/Authority/AuthorityUser.cs | 17 ++++++++++++-- .../Models/Authority/AuthorityUserItem.cs | 5 ++++- DotBased/Result.cs | 2 +- 5 files changed, 39 insertions(+), 9 deletions(-) rename DotBased.AspNet.Authority.EFCore/{DI.cs => Extensions.cs} (95%) diff --git a/DotBased.AspNet.Authority.EFCore/DI.cs b/DotBased.AspNet.Authority.EFCore/Extensions.cs similarity index 95% rename from DotBased.AspNet.Authority.EFCore/DI.cs rename to DotBased.AspNet.Authority.EFCore/Extensions.cs index 999df24..d48aa30 100644 --- a/DotBased.AspNet.Authority.EFCore/DI.cs +++ b/DotBased.AspNet.Authority.EFCore/Extensions.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection; namespace DotBased.AspNet.Authority.EFCore; -public static class DI +public static class Extensions { public static IServiceCollection AddAuthorityContext(this IServiceCollection services, Action options) { diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs index 9e1542f..bcd8165 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs @@ -1,14 +1,28 @@ using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; +using Microsoft.EntityFrameworkCore; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class UserRepository : IUserRepository +public class UserRepository(IDbContextFactory contextFactory) : IUserRepository { - public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", - CancellationToken? cancellationToken = null) + public async Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken? cancellationToken = null) { - throw new NotImplementedException(); + await using var context = await contextFactory.CreateDbContextAsync(); + var query = context.Users.AsQueryable(); + if (!string.IsNullOrWhiteSpace(search)) + { + query = query.Where(u => $"{u.Id} {u.Name} {u.UserName} {u.EmailAddress} {u.PhoneNumber}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); + } + var totalCount = query.Count(); + var selected = query.Skip(offset).Take(limit).Select(u => new AuthorityUserItem() + { + Id = u.Id, + UserName = u.UserName, + EmailAddress = u.EmailAddress, + PhoneNumber = u.PhoneNumber + }); + return ListResult.Ok(selected, totalCount, limit, offset); } public Task> GetAuthorityUserByIdAsync(string id, CancellationToken? cancellationToken = null) diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs index 03214ca..1d37fa1 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -1,3 +1,4 @@ +using System.Text; using DotBased.AspNet.Authority.Attributes; namespace DotBased.AspNet.Authority.Models.Authority; @@ -19,7 +20,9 @@ public class AuthorityUser() public DateTime LockedDate { get; set; } - public string? UserName { get; set; } + public string UserName { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; public string? PasswordHash { get; set; } @@ -43,5 +46,15 @@ public class AuthorityUser() public ICollection Attributes { get; set; } = []; - public override string ToString() => UserName ?? EmailAddress ?? string.Empty; + public override string ToString() + { + var strBuilder = new StringBuilder(); + strBuilder.Append(!string.IsNullOrWhiteSpace(Name) ? Name : UserName); + + if (string.IsNullOrWhiteSpace(EmailAddress)) return strBuilder.ToString(); + + strBuilder.Append(strBuilder.Length == 0 ? EmailAddress : $" ({EmailAddress})"); + + return strBuilder.ToString(); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUserItem.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserItem.cs index 6afe594..61e116b 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUserItem.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUserItem.cs @@ -2,5 +2,8 @@ namespace DotBased.AspNet.Authority.Models.Authority; public class AuthorityUserItem { - + public Guid Id { get; set; } + public string UserName { get; set; } = string.Empty; + public string? EmailAddress { get; set; } = string.Empty; + public string? PhoneNumber { get; set; } = string.Empty; } \ No newline at end of file diff --git a/DotBased/Result.cs b/DotBased/Result.cs index e188b77..dcf1b28 100755 --- a/DotBased/Result.cs +++ b/DotBased/Result.cs @@ -79,7 +79,7 @@ public class ListResult : Result /// public int Offset { get; } - public static ListResult Ok(IEnumerable items, int totalCount = -1) => + public static ListResult Ok(IEnumerable items, int totalCount = -1, int limit = -1, int offset = -1) => new(true, string.Empty, totalCount, items); public new static ListResult Failed(string message, Exception? exception = null) => From 1f593a364bf32b23e7a872334e4b1f56246c2309 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 10 Feb 2025 16:11:58 +0100 Subject: [PATCH 23/38] [CHANGE] updating queries && parameter update --- .../Repositories/UserRepository.cs | 150 +++++++++++++----- .../Managers/AuthorityUserManager.cs | 10 +- .../Repositories/IUserRepository.cs | 20 +-- DotBased/Result.cs | 5 + 4 files changed, 132 insertions(+), 53 deletions(-) diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs index bcd8165..6a1dcf2 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs @@ -6,66 +6,140 @@ namespace DotBased.AspNet.Authority.EFCore.Repositories; public class UserRepository(IDbContextFactory contextFactory) : IUserRepository { - public async Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken? cancellationToken = null) + public async Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - await using var context = await contextFactory.CreateDbContextAsync(); - var query = context.Users.AsQueryable(); - if (!string.IsNullOrWhiteSpace(search)) + try { - query = query.Where(u => $"{u.Id} {u.Name} {u.UserName} {u.EmailAddress} {u.PhoneNumber}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var query = context.Users.AsQueryable(); + if (!string.IsNullOrWhiteSpace(search)) + { + query = query.Where(u => + $"{u.Id} {u.Name} {u.UserName} {u.EmailAddress} {u.PhoneNumber}".Contains(search, + StringComparison.CurrentCultureIgnoreCase)); + } + + var totalCount = query.Count(); + var selected = query.Skip(offset).Take(limit).Select(u => new AuthorityUserItem() + { + Id = u.Id, + UserName = u.UserName, + EmailAddress = u.EmailAddress, + PhoneNumber = u.PhoneNumber + }); + return ListResult.Ok(selected, totalCount, limit, offset); } - var totalCount = query.Count(); - var selected = query.Skip(offset).Take(limit).Select(u => new AuthorityUserItem() + catch (Exception e) { - Id = u.Id, - UserName = u.UserName, - EmailAddress = u.EmailAddress, - PhoneNumber = u.PhoneNumber - }); - return ListResult.Ok(selected, totalCount, limit, offset); + return ListResult.Failed("Failed to get users.", e); + } } - public Task> GetAuthorityUserByIdAsync(string id, CancellationToken? cancellationToken = null) + public async Task> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (!Guid.TryParse(id, out var guid)) + { + return Result.Failed("Invalid id!"); + } + + var user = await context.Users.FirstOrDefaultAsync(u => u.Id == guid, cancellationToken: cancellationToken); + return Result.HandleResult(user, "User not found."); + } + catch (Exception e) + { + return Result.Failed("Failed to get user.", e); + } + } + + public async Task> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (user.Id == Guid.Empty) + { + return Result.Failed("Id cannot be empty!"); + } + var entity = context.Users.Add(user); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to create user!") : Result.Ok(entity.Entity); + } + catch (Exception e) + { + return Result.Failed("Failed to create user.", e); + } + } + + public async Task> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usr = context.Users.FirstOrDefault(u => u.Id == user.Id); + if (usr == null) + { + return Result.Failed("User not found!"); + } + + if (usr.Version != user.Version || usr.SecurityVersion != user.SecurityVersion) + { + return Result.Failed("Version validation failed!"); + } + + var entity = context.Users.Update(user); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to save updated user!") : Result.Ok(entity.Entity); + } + catch (Exception e) + { + return Result.Failed("Failed to update user!", e); + } + } + + public async Task DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usr = context.Users.FirstOrDefault(u => u.Id == user.Id); + if (usr == null) + { + return Result.Failed("User not found!"); + } + context.Users.Remove(usr); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to delete user!") : Result.Ok(); + } + catch (Exception e) + { + return Result.Failed("Failed to delete user!", e); + } + } + + public Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public Task> CreateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public Task> UpdateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + public Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public Task DeleteUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public Task> GetUserByEmailAsync(string email, CancellationToken? cancellationToken = null) - { - throw new NotImplementedException(); - } - - public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null) - { - throw new NotImplementedException(); - } - - public Task> GetVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null) - { - throw new NotImplementedException(); - } - - public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null) - { - throw new NotImplementedException(); - } - - public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index 363c49c..cd31157 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -34,13 +34,13 @@ public partial class AuthorityManager return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); } - public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken? cancellationToken = null) + public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken cancellationToken = default) { var result = await UserRepository.GetAuthorityUsersAsync(maxResults, offset, query, cancellationToken); return result; } - public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken? cancellationToken = null) + public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken cancellationToken = default) { var passwordValidation = await ValidatePasswordAsync(user, password); if (!passwordValidation.Success) @@ -57,7 +57,7 @@ public partial class AuthorityManager return AuthorityResult.FromResult(updateResult); } - public async Task> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken? cancellationToken = null) + public async Task> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken cancellationToken = default) { var userValidation = await ValidateUserAsync(userModel); var passwordValidation = await ValidatePasswordAsync(userModel, password); @@ -79,13 +79,13 @@ public partial class AuthorityManager return AuthorityResult.FromResult(userCreationResult); } - public async Task> UpdateUserAsync(AuthorityUser model, CancellationToken? cancellationToken = null) + public async Task> UpdateUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) { var updateResult = await UserRepository.UpdateUserAsync(model, cancellationToken); return updateResult; } - public async Task DeleteUserAsync(AuthorityUser model, CancellationToken? cancellationToken = null) + public async Task DeleteUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) { var deleteResult = await UserRepository.DeleteUserAsync(model, cancellationToken); return deleteResult; diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index c84448a..01c4165 100755 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -4,14 +4,14 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IUserRepository { - public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken? cancellationToken = null); - public Task> GetAuthorityUserByIdAsync(string id, CancellationToken? cancellationToken = null); - public Task> CreateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task> UpdateUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task DeleteUserAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task> GetUserByEmailAsync(string email, CancellationToken? cancellationToken = null); - public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); - public Task> GetVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); - public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken? cancellationToken = null); - public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken? cancellationToken = null); + public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default); + public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default); + public Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default); + public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased/Result.cs b/DotBased/Result.cs index dcf1b28..4669c28 100755 --- a/DotBased/Result.cs +++ b/DotBased/Result.cs @@ -43,6 +43,11 @@ public class Result : Result public new static Result Failed(string message, Exception? exception = null) => new(false, message, default, exception); + + public new static Result HandleResult(TValue? value, string failedMessage, Exception? exception = null) + { + return value == null ? Failed(failedMessage, exception) : Ok(value); + } } public class ListResult : Result From 6c67276dca7ea14bbecc0c0ed075bc987a3d94a0 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 10 Feb 2025 16:25:25 +0100 Subject: [PATCH 24/38] [IMPL] Added base user repository implementation --- .../Repositories/UserRepository.cs | 95 ++++++++++++++++--- .../Repositories/IUserRepository.cs | 2 +- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs index 6a1dcf2..599f5d0 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs @@ -78,7 +78,7 @@ public class UserRepository(IDbContextFactory contextFactory) try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usr = context.Users.FirstOrDefault(u => u.Id == user.Id); + var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken); if (usr == null) { return Result.Failed("User not found!"); @@ -104,7 +104,7 @@ public class UserRepository(IDbContextFactory contextFactory) try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usr = context.Users.FirstOrDefault(u => u.Id == user.Id); + var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken); if (usr == null) { return Result.Failed("User not found!"); @@ -119,28 +119,99 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default) + public async Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usr = await context.Users.FirstOrDefaultAsync(u => u.EmailAddress == email, cancellationToken: cancellationToken); + return Result.HandleResult(usr, "User not found by given email address."); + } + catch (Exception e) + { + return Result.Failed("An error occured while getting the user.", e); + } } - public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) + public async Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken); + if (usr == null) + { + return Result.Failed("Failed to find user with given id!"); + } + + if (usr.Version != user.Version) + { + return Result.Failed("Stored user version doesn't match current user version!"); + } + + usr.Version = version; + context.Users.Update(usr); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to update user!") : Result.Ok(); + } + catch (Exception e) + { + return Result.Failed("An error occured while updating the version.", e); + } } - public Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.Version).FirstOrDefaultAsync(cancellationToken); + return Result.HandleResult(usrVersion, "Failed to get user version!"); + } + catch (Exception e) + { + return Result.Failed("An error occured while getting the user version.", e); + } } - public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) + public async Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken); + if (usr == null) + { + return Result.Failed("Failed to find user with given id!"); + } + + if (usr.SecurityVersion != user.SecurityVersion) + { + return Result.Failed("Stored user version doesn't match current user version!"); + } + + usr.SecurityVersion = securityVersion; + context.Users.Update(usr); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to update user!") : Result.Ok(); + } + catch (Exception e) + { + return Result.Failed("An error occured while updating the security version.", e); + } } - public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.SecurityVersion).FirstOrDefaultAsync(cancellationToken); + return Result.HandleResult(usrVersion, "Failed to get user security version!"); + } + catch (Exception e) + { + return Result.Failed("An error occured while getting the user security version.", e); + } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index 01c4165..0107a76 100755 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -12,6 +12,6 @@ public interface IUserRepository public Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default); public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default); public Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task SetSecurityVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default); + public Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default); public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); } \ No newline at end of file From 13b70c22f2452ad7503362d4633bfda046eb4930 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 17 Feb 2025 00:40:55 +0100 Subject: [PATCH 25/38] [CHANGE] Updated queries, exception handler, role repository. --- .../Repositories/RepositoryBase.cs | 19 ++++ .../Repositories/RoleRepository.cs | 107 ++++++++++++++++-- .../Repositories/UserRepository.cs | 31 +++-- .../Models/Authority/AuthorityRoleItem.cs | 4 +- 4 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs new file mode 100644 index 0000000..1be6d12 --- /dev/null +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs @@ -0,0 +1,19 @@ +namespace DotBased.AspNet.Authority.EFCore.Repositories; + +public abstract class RepositoryBase +{ + protected Result HandleExceptionResult(string message, Exception ex) => new(HandleException(message, ex)); + + protected ListResult HandleExceptionListResult(string message, Exception ex) => + new(HandleException(message, ex)); + + protected Result HandleException(string message, Exception ex) + { + if (ex is OperationCanceledException oce) + { + return Result.Failed("Operation cancelled.", oce); + } + + return Result.Failed(message, ex); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs index bd15ed7..491080e 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs @@ -1,32 +1,117 @@ using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; +using Microsoft.EntityFrameworkCore; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class RoleRepository : IRoleRepository +public class RoleRepository(IDbContextFactory contextFactory) : RepositoryBase, IRoleRepository { - public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var query = context.Roles.AsQueryable(); + if (!string.IsNullOrWhiteSpace(search)) + { + query = query.Where(r => + $"{r.Name} {r.Id}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); + } + + var total = await query.CountAsync(cancellationToken); + var select = await query.OrderBy(r => r.Name).Skip(offset).Take(limit).Select(r => new AuthorityRoleItem() + { + Id = r.Id, + Name = r.Name + }).ToListAsync(cancellationToken: cancellationToken); + return ListResult.Ok(select, total, limit, offset); + } + catch (Exception e) + { + return HandleExceptionListResult("Failed to get roles.", e); + } } - public Task> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default) + public async Task> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (!Guid.TryParse(id, out var guid)) + { + return Result.Failed("Invalid id!"); + } + var role = await context.Roles.Where(r => r.Id == guid).Include(r => r.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); + return Result.HandleResult(role, "Role not found!"); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to get role!", e); + } } - public Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + public async Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (role.Id == Guid.Empty) + { + return Result.Failed("Id cannot be empty!"); + } + var entity = context.Roles.Add(role); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to create role!") : Result.Ok(entity.Entity); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to create role!", e); + } } - public Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + public async Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentRole = await context.Roles.FirstOrDefaultAsync(r => r.Id == role.Id, cancellationToken: cancellationToken); + if (currentRole == null) + { + return Result.Failed("Role not found!"); + } + + if (role.Version != currentRole.Version) + { + return Result.Failed("Role version does not match, version validation failed!"); + } + + var entity = context.Roles.Update(role); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to update role!") : Result.Ok(entity.Entity); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to update role!", e); + } } - public Task DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + public async Task DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentRole = await context.Roles.FirstOrDefaultAsync(r => r.Id == role.Id, cancellationToken: cancellationToken); + if (currentRole == null) + { + return Result.Failed("Role not found, could not delete!"); + } + context.Roles.Remove(currentRole); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to delete role!") : Result.Ok(); + } + catch (Exception e) + { + return HandleException("Failed to delete role!", e); + } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs index 599f5d0..efcc004 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class UserRepository(IDbContextFactory contextFactory) : IUserRepository +public class UserRepository(IDbContextFactory contextFactory) : RepositoryBase, IUserRepository { public async Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { @@ -18,20 +18,19 @@ public class UserRepository(IDbContextFactory contextFactory) $"{u.Id} {u.Name} {u.UserName} {u.EmailAddress} {u.PhoneNumber}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); } - var totalCount = query.Count(); - var selected = query.Skip(offset).Take(limit).Select(u => new AuthorityUserItem() + var selected = await query.OrderBy(u => u.UserName).Skip(offset).Take(limit).Select(u => new AuthorityUserItem() { Id = u.Id, UserName = u.UserName, EmailAddress = u.EmailAddress, PhoneNumber = u.PhoneNumber - }); + }).ToListAsync(cancellationToken: cancellationToken); return ListResult.Ok(selected, totalCount, limit, offset); } catch (Exception e) { - return ListResult.Failed("Failed to get users.", e); + return HandleExceptionListResult("Failed to get users.", e); } } @@ -45,12 +44,12 @@ public class UserRepository(IDbContextFactory contextFactory) return Result.Failed("Invalid id!"); } - var user = await context.Users.FirstOrDefaultAsync(u => u.Id == guid, cancellationToken: cancellationToken); + var user = await context.Users.Where(u => u.Id == guid).Include(u => u.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); return Result.HandleResult(user, "User not found."); } catch (Exception e) { - return Result.Failed("Failed to get user.", e); + return HandleExceptionResult("Failed to get user.", e); } } @@ -69,7 +68,7 @@ public class UserRepository(IDbContextFactory contextFactory) } catch (Exception e) { - return Result.Failed("Failed to create user.", e); + return HandleExceptionResult("Failed to create user.", e); } } @@ -95,7 +94,7 @@ public class UserRepository(IDbContextFactory contextFactory) } catch (Exception e) { - return Result.Failed("Failed to update user!", e); + return HandleExceptionResult("Failed to update user!", e); } } @@ -115,7 +114,7 @@ public class UserRepository(IDbContextFactory contextFactory) } catch (Exception e) { - return Result.Failed("Failed to delete user!", e); + return HandleException("Failed to delete user!", e); } } @@ -124,12 +123,12 @@ public class UserRepository(IDbContextFactory contextFactory) try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usr = await context.Users.FirstOrDefaultAsync(u => u.EmailAddress == email, cancellationToken: cancellationToken); + var usr = await context.Users.Where(u => u.EmailAddress == email).Include(u => u.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); return Result.HandleResult(usr, "User not found by given email address."); } catch (Exception e) { - return Result.Failed("An error occured while getting the user.", e); + return HandleExceptionResult("An error occured while getting the user.", e); } } @@ -156,7 +155,7 @@ public class UserRepository(IDbContextFactory contextFactory) } catch (Exception e) { - return Result.Failed("An error occured while updating the version.", e); + return HandleException("An error occured while updating the version.", e); } } @@ -170,7 +169,7 @@ public class UserRepository(IDbContextFactory contextFactory) } catch (Exception e) { - return Result.Failed("An error occured while getting the user version.", e); + return HandleExceptionResult("An error occured while getting the user version.", e); } } @@ -197,7 +196,7 @@ public class UserRepository(IDbContextFactory contextFactory) } catch (Exception e) { - return Result.Failed("An error occured while updating the security version.", e); + return HandleException("An error occured while updating the security version.", e); } } @@ -211,7 +210,7 @@ public class UserRepository(IDbContextFactory contextFactory) } catch (Exception e) { - return Result.Failed("An error occured while getting the user security version.", e); + return HandleExceptionResult("An error occured while getting the user security version.", e); } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRoleItem.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRoleItem.cs index e285d5a..b8273d8 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityRoleItem.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRoleItem.cs @@ -2,5 +2,7 @@ namespace DotBased.AspNet.Authority.Models.Authority; public class AuthorityRoleItem { - + public Guid Id { get; set; } = Guid.NewGuid(); + + public string? Name { get; set; } } \ No newline at end of file From c6e11efdf207b74f13dad7d0d8475b64290ecc23 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 17 Feb 2025 19:58:50 +0100 Subject: [PATCH 26/38] [ADD] Added repository implementations --- .../Repositories/AttributeRepository.cs | 103 +++++++++++++++-- .../Repositories/GroupRepository.cs | 105 ++++++++++++++++-- .../Authority/AuthorityAttributeItem.cs | 4 + .../Models/Authority/AuthorityGroupItem.cs | 4 +- .../Repositories/IAttributeRepository.cs | 2 +- .../Repositories/IGroupRepository.cs | 6 +- 6 files changed, 197 insertions(+), 27 deletions(-) diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs index 438e75d..b986ccc 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs @@ -1,33 +1,114 @@ using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; +using Microsoft.EntityFrameworkCore; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class AttributeRepository : IAttributeRepository +public class AttributeRepository(IDbContextFactory contextFactory) : RepositoryBase, IAttributeRepository { - public Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", + public async Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var query = context.Attributes.AsQueryable(); + if (!string.IsNullOrEmpty(search)) + { + query = query.Where(a => $"{a.AttributeKey} {a.BoundId} {a.AttributeValue}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); + } + + var total = await query.CountAsync(cancellationToken); + var select = await query.OrderBy(a => a.AttributeKey).Skip(offset).Take(limit).Select(a => new AuthorityAttributeItem() + { + BoundId = a.BoundId, + AttributeKey = a.AttributeKey, + AttributeValue = a.AttributeValue + }).ToListAsync(cancellationToken); + return ListResult.Ok(select, total, limit, offset); + } + catch (Exception e) + { + return HandleExceptionListResult("Failed to get attributes", e); + } } - public Task> GetAttributeByIdAsync(string id, CancellationToken cancellationToken = default) + public async Task> GetAttributeByKeyAsync(string key, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var attribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == key, cancellationToken); + return attribute == null ? Result.Failed("Attribute not found") : Result.Ok(attribute); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to get attribute by id", e); + } } - public Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (string.IsNullOrWhiteSpace(attribute.AttributeKey) || attribute.BoundId == Guid.Empty) + { + return Result.Failed("Attribute key and/or bound id is empty"); + } + var entry = context.Attributes.Add(attribute); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to create attribute") : Result.Ok(entry.Entity); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to create attribute", e); + } } - public Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentAttribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == attribute.AttributeKey, cancellationToken); + if (currentAttribute == null) + { + return Result.Failed("Attribute not found"); + } + + if (currentAttribute.Version != attribute.Version) + { + return Result.Failed("Attribute version doesn't match"); + } + + var entry = context.Attributes.Update(currentAttribute); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to update attribute") : Result.Ok(entry.Entity); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to update attribute", e); + } } - public Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentAttribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == attribute.AttributeKey, cancellationToken); + if (currentAttribute == null) + { + return Result.Failed("Attribute not found"); + } + context.Attributes.Remove(currentAttribute); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to delete attribute") : Result.Ok(); + } + catch (Exception e) + { + return HandleException("Failed to delete attribute", e); + } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs index a6fb010..212ed61 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs @@ -1,32 +1,115 @@ using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; +using Microsoft.EntityFrameworkCore; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class GroupRepository : IGroupRepository +public class GroupRepository(IDbContextFactory contextFactory) : RepositoryBase, IGroupRepository { - public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var query = context.Groups.AsQueryable(); + if (!string.IsNullOrWhiteSpace(search)) + { + query = query.Where(g => $"{g.Name} {g.Id}".Contains(search)); + } + var total = await query.CountAsync(cancellationToken); + var select = await query.OrderBy(g => g.Name).Skip(offset).Take(limit).Select(g => new AuthorityGroupItem() + { + Id = g.Id, + Name = g.Name + }).ToListAsync(cancellationToken); + return ListResult.Ok(select, total, limit, offset); + } + catch (Exception e) + { + return HandleExceptionListResult("Failed to get Groups", e); + } } - public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default) + public async Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (!Guid.TryParse(id, out var groupId)) + { + return Result.Failed("Invalid group id"); + } + var group = await context.Groups.Where(g => g.Id == groupId).Include(g => g.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); + return Result.HandleResult(group, "Group not found"); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to get Group", e); + } } - public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (group.Id == Guid.Empty) + { + return Result.Failed("Id cannot be empty."); + } + var entry = context.Groups.Add(group); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to create group.") : Result.Ok(entry.Entity); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to create group!", e); + } } - public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentGroup = await context.Groups.FirstOrDefaultAsync(g => g.Id == group.Id ,cancellationToken); + if (currentGroup == null) + { + return Result.Failed("Group not found."); + } + + if (currentGroup.Version != group.Version) + { + return Result.Failed("Group version does not match, version validation failed!"); + } + + var entry = context.Groups.Update(group); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to update group.") : Result.Ok(entry.Entity); + } + catch (Exception e) + { + return HandleExceptionResult("Failed to update group!", e); + } } - public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentGroup = await context.Groups.FirstOrDefaultAsync(g => g.Id == group.Id, cancellationToken); + if (currentGroup == null) + { + return Result.Failed("Group not found, cannot delete group!"); + } + context.Groups.Remove(currentGroup); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to delete group.") : Result.Ok(); + } + catch (Exception e) + { + return HandleException("Failed to delete group!", e); + } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttributeItem.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttributeItem.cs index 44a0211..aa802bf 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttributeItem.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttributeItem.cs @@ -2,5 +2,9 @@ namespace DotBased.AspNet.Authority.Models.Authority; public class AuthorityAttributeItem { + public Guid BoundId { get; set; } + public string AttributeKey { get; set; } = string.Empty; + + public string AttributeValue { get; set; } = string.Empty; } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroupItem.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroupItem.cs index 2b9bbcc..f9cbecb 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityGroupItem.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityGroupItem.cs @@ -2,5 +2,7 @@ namespace DotBased.AspNet.Authority.Models.Authority; public class AuthorityGroupItem { - + public Guid Id { get; set; } = Guid.NewGuid(); + + public string? Name { get; set; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs index 7fcca43..bf045f6 100755 --- a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs @@ -5,7 +5,7 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IAttributeRepository { public Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetAttributeByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> GetAttributeByKeyAsync(string id, CancellationToken cancellationToken = default); public Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); public Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); public Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs index f8eb385..3e8e814 100755 --- a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs @@ -5,8 +5,8 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IGroupRepository { public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default); - public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); - public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); } \ No newline at end of file From 79d8fcfb8d6c47b0341bec1330427713d1d7d4ae Mon Sep 17 00:00:00 2001 From: max Date: Mon, 24 Feb 2025 16:07:56 +0100 Subject: [PATCH 27/38] [CHANGE] Update errors to IReadOnlyList --- DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs | 4 +--- DotBased.AspNet.Authority/Models/AuthorityResult.cs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index cd31157..dd4bf6b 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -45,9 +45,7 @@ public partial class AuthorityManager var passwordValidation = await ValidatePasswordAsync(user, password); if (!passwordValidation.Success) { - List errors = []; - errors.AddRange(passwordValidation.Errors); - return AuthorityResult.Failed(errors, ResultFailReason.Validation); + return AuthorityResult.Failed(passwordValidation.Errors, ResultFailReason.Validation); } user.PasswordHash = await PasswordHasher.HashPasswordAsync(password); diff --git a/DotBased.AspNet.Authority/Models/AuthorityResult.cs b/DotBased.AspNet.Authority/Models/AuthorityResult.cs index 3cb3340..d74fcfa 100755 --- a/DotBased.AspNet.Authority/Models/AuthorityResult.cs +++ b/DotBased.AspNet.Authority/Models/AuthorityResult.cs @@ -11,7 +11,7 @@ public class AuthorityResult : Result Reason = ResultFailReason.Unknown; } - public AuthorityResult(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, List? errors = null) : base(success, errorMessage, value, null) + public AuthorityResult(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, IReadOnlyList? errors = null) : base(success, errorMessage, value, null) { Success = success; Message = errorMessage; @@ -20,7 +20,7 @@ public class AuthorityResult : Result ValidationErrors = errors; } public ResultFailReason Reason { get; } - public List? ValidationErrors { get; } + public IReadOnlyList? ValidationErrors { get; } public static AuthorityResult Ok(TResultValue? value) => new AuthorityResult(true, value:value); @@ -28,7 +28,7 @@ public class AuthorityResult : Result public static AuthorityResult Error(string errorMessage, ResultFailReason reason = ResultFailReason.Error) => new AuthorityResult(false, errorMessage, reason:reason); - public static AuthorityResult Failed(List errors, ResultFailReason reason = ResultFailReason.None) + public static AuthorityResult Failed(IReadOnlyList errors, ResultFailReason reason = ResultFailReason.None) => new AuthorityResult(false, errors:errors, reason:reason); } From e65b937128d4c656ca7dceb5443936341de539c3 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 24 Feb 2025 16:35:23 +0100 Subject: [PATCH 28/38] [CHANGE] Managers --- .../Managers/AuthorityGroupManager.cs | 11 ++-- .../Managers/AuthorityRoleManager.cs | 66 ++++++++++++------- .../Managers/AuthorityUserManager.cs | 8 ++- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs index a91e65d..517a836 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs @@ -1,10 +1,11 @@ +using DotBased.AspNet.Authority.Models.Authority; + namespace DotBased.AspNet.Authority.Managers; public partial class AuthorityManager { - /* - * - Validate User & Group - * - Check if user is already in group (if already in group return) - * - Add to UsersGroups table - */ + public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) + { + return ListResult.Failed("Not implemented!"); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index 2b62e98..e63059b 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -1,34 +1,36 @@ +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; namespace DotBased.AspNet.Authority.Managers; public partial class AuthorityManager { - public async Task> CreateRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null) + public async Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - return Result.Failed("Not implemented!"); + role.Version = GenerateVersion(); + var createResult = await roleRepository.CreateRoleAsync(role, cancellationToken); + return AuthorityResult.FromResult(createResult); } - public async Task DeleteRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null) + public async Task DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - return Result.Failed("Not implemented!"); + var result = await roleRepository.DeleteRoleAsync(role, cancellationToken); + return result; } - public async Task> UpdateRoleAsync(AuthorityRole role, CancellationToken? cancellationToken = null) + public async Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - return Result.Failed("Not implemented!"); + var result = await roleRepository.UpdateRoleAsync(role, cancellationToken); + return result; } - public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken? cancellationToken = null) + public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - /* - * Search by role name & id - * Order by name, created date, creator? (paging) - */ - return ListResult.Failed("Not implemented!"); + var searchResult = await roleRepository.GetRolesAsync(limit, offset, search, cancellationToken); + return searchResult; } - public async Task AddRoleToUserAsync(AuthorityUser user, AuthorityRole role, CancellationToken? cancellationToken = null) + public async Task AddRoleToUserAsync(AuthorityUser user, AuthorityRole role, CancellationToken cancellationToken = default) { /* - Validate User & Role @@ -37,11 +39,11 @@ public partial class AuthorityManager */ } - public async Task RemoveRoleFromUserAsync(AuthorityRole role, AuthorityUser user, CancellationToken? cancellationToken = null) + public async Task RemoveRoleFromUserAsync(AuthorityRole role, AuthorityUser user, CancellationToken cancellationToken = default) { } - public async Task AddRoleToGroupAsync(AuthorityRole role, AuthorityGroup group, CancellationToken? cancellationToken = null) + public async Task AddRoleToGroupAsync(AuthorityRole role, AuthorityGroup group, CancellationToken cancellationToken = default) { } @@ -50,16 +52,34 @@ public partial class AuthorityManager /// /// The user to get the roles from /// - public async Task> GetUserRolesAsync(AuthorityUser user, CancellationToken? cancellationToken = null) + public async Task> GetUserRolesAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - /* - * - Validate user - * - Get user groups (id) - * - Get roles contained from user - * - Get roles contained from groups (if any) - * - Order by (for paging) - */ + var usrValidation = await IsValidUserAsync(user, cancellationToken); + if (!usrValidation.Success) + { + return ListResult.Failed("Invalid user"); + } + List roles = []; + var usrRoles = await GetUserRolesAsync(user, cancellationToken); + if (usrRoles.Success) + { + roles.AddRange(usrRoles.Items); + } + var usrGroups = await GetUserGroupsAsync(user, cancellationToken); + if (usrGroups.Success) + { + var groupRolesResult = await GetGroupRolesAsync(usrGroups.Items.Select(g => g.Id).ToList(), cancellationToken); + if (groupRolesResult.Success) + { + roles.AddRange(groupRolesResult.Items); + } + } + return ListResult.Ok(roles, roles.Count); + } + + public async Task> GetGroupRolesAsync(List groupIds, CancellationToken cancellationToken = default) + { return ListResult.Failed("Not implemented!"); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index dd4bf6b..cfb7d9c 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -88,6 +88,10 @@ public partial class AuthorityManager var deleteResult = await UserRepository.DeleteUserAsync(model, cancellationToken); return deleteResult; } - - + + public async Task IsValidUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + { + var usrResult = await userRepository.GetVersionAsync(user, cancellationToken); + return usrResult; + } } \ No newline at end of file From d6c0ad1138721a1a00356345156ab79c313bddb0 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 24 Feb 2025 16:52:57 +0100 Subject: [PATCH 29/38] [CHANGE] Search all roles from users/groups --- .../Repositories/RoleRepository.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs index 491080e..3b8824a 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs @@ -114,4 +114,9 @@ public class RoleRepository(IDbContextFactory contextFactory) return HandleException("Failed to delete role!", e); } } + + public async Task> GetUserRolesAsync(AuthorityUser user, int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + { + return ListResult.Failed("Not implemented!"); + } } \ No newline at end of file From d8b08a763e7ecbc055625f7762e4c99192a7eeb0 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 6 Apr 2025 21:34:40 +0200 Subject: [PATCH 30/38] [CHANGE] Adding functionality to manager --- .../AuthorityContext.cs | 29 ++--- .../Models/{RoleGroup.cs => RoleLink.cs} | 4 +- .../Models/{UserGroup.cs => UserGroups.cs} | 2 +- .../Models/{RoleUser.cs => UserRoles.cs} | 2 +- .../Repositories/AttributeRepository.cs | 6 +- .../Repositories/GroupRepository.cs | 15 +++ .../Repositories/RoleRepository.cs | 102 +++++++++++++++-- .../Managers/AuthorityGroupManager.cs | 4 +- .../Managers/AuthorityManager.cs | 2 + .../Managers/AuthorityRoleManager.cs | 105 +++++++++++++----- .../Managers/AuthorityUserManager.cs | 2 +- .../Models/Authority/AuthorityAttribute.cs | 8 +- .../Models/Authority/AuthorityRole.cs | 4 +- .../Models/AuthorityResult.cs | 2 +- .../Repositories/IGroupRepository.cs | 1 + .../Repositories/IRoleRepository.cs | 14 ++- DotBased/Result.cs | 8 +- 17 files changed, 234 insertions(+), 76 deletions(-) rename DotBased.AspNet.Authority.EFCore/Models/{RoleGroup.cs => RoleLink.cs} (59%) rename DotBased.AspNet.Authority.EFCore/Models/{UserGroup.cs => UserGroups.cs} (83%) rename DotBased.AspNet.Authority.EFCore/Models/{RoleUser.cs => UserRoles.cs} (84%) diff --git a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs index 57dbaa8..92a21c3 100644 --- a/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs +++ b/DotBased.AspNet.Authority.EFCore/AuthorityContext.cs @@ -11,54 +11,47 @@ public class AuthorityContext(DbContextOptions options) : DbCo public DbSet Roles { get; set; } public DbSet Users { get; set; } - public DbSet RoleGroup { get; set; } - public DbSet RoleUser { get; set; } - public DbSet UserGroup { get; set; } + public DbSet RoleLinks { get; set; } + public DbSet UserGroups { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(attributeEntity => { attributeEntity.ToTable("authority_attributes"); - attributeEntity.HasKey(a => new { a.BoundId, a.AttributeKey }); + attributeEntity.HasKey(a => new { a.ForeignKey, a.AttributeKey }); }); modelBuilder.Entity(groupEntity => { groupEntity.ToTable("authority_groups"); groupEntity.HasKey(x => x.Id); - groupEntity.HasMany(g => g.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade); + groupEntity.HasMany(g => g.Attributes).WithOne().HasForeignKey(a => a.ForeignKey).OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(roleEntity => { roleEntity.ToTable("authority_roles"); roleEntity.HasKey(x => x.Id); - roleEntity.HasMany(r => r.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade); + roleEntity.HasMany(r => r.Attributes).WithOne().HasForeignKey(a => a.ForeignKey).OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(userEntity => { userEntity.ToTable("authority_users"); userEntity.HasKey(x => x.Id); - userEntity.HasMany(u => u.Attributes).WithOne().HasForeignKey(a => a.BoundId).OnDelete(DeleteBehavior.Cascade); + userEntity.HasMany(u => u.Attributes).WithOne().HasForeignKey(a => a.ForeignKey).OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity(rgEntity => + modelBuilder.Entity(rgEntity => { - rgEntity.ToTable("role_group"); - rgEntity.HasKey(rg => new { rg.RoleId, rg.GroupId }); + rgEntity.ToTable("role_links"); + rgEntity.HasKey(rg => new { rg.RoleId, rg.LinkId }); }); - modelBuilder.Entity(ruEntity => + modelBuilder.Entity(ugEntity => { - ruEntity.ToTable("role_user"); - ruEntity.HasKey(ru => new { ru.RoleId, ru.UserId }); - }); - - modelBuilder.Entity(ugEntity => - { - ugEntity.ToTable("user_group"); + ugEntity.ToTable("user_groups"); ugEntity.HasKey(ug => new { ug.UserId, ug.GroupId }); }); diff --git a/DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs b/DotBased.AspNet.Authority.EFCore/Models/RoleLink.cs similarity index 59% rename from DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs rename to DotBased.AspNet.Authority.EFCore/Models/RoleLink.cs index 1afe9a7..19fd75d 100644 --- a/DotBased.AspNet.Authority.EFCore/Models/RoleGroup.cs +++ b/DotBased.AspNet.Authority.EFCore/Models/RoleLink.cs @@ -1,7 +1,7 @@ namespace DotBased.AspNet.Authority.EFCore.Models; -public class RoleGroup +public class RoleLink { public Guid RoleId { get; set; } - public Guid GroupId { get; set; } + public Guid LinkId { get; set; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs b/DotBased.AspNet.Authority.EFCore/Models/UserGroups.cs similarity index 83% rename from DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs rename to DotBased.AspNet.Authority.EFCore/Models/UserGroups.cs index 648b376..beea497 100644 --- a/DotBased.AspNet.Authority.EFCore/Models/UserGroup.cs +++ b/DotBased.AspNet.Authority.EFCore/Models/UserGroups.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.EFCore.Models; -public class UserGroup +public class UserGroups { public Guid UserId { get; set; } public Guid GroupId { get; set; } diff --git a/DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs b/DotBased.AspNet.Authority.EFCore/Models/UserRoles.cs similarity index 84% rename from DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs rename to DotBased.AspNet.Authority.EFCore/Models/UserRoles.cs index c4a0917..b7a657b 100644 --- a/DotBased.AspNet.Authority.EFCore/Models/RoleUser.cs +++ b/DotBased.AspNet.Authority.EFCore/Models/UserRoles.cs @@ -1,6 +1,6 @@ namespace DotBased.AspNet.Authority.EFCore.Models; -public class RoleUser +public class UserRoles { public Guid RoleId { get; set; } public Guid UserId { get; set; } diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs index b986ccc..959d10f 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs @@ -15,13 +15,13 @@ public class AttributeRepository(IDbContextFactory contextFact var query = context.Attributes.AsQueryable(); if (!string.IsNullOrEmpty(search)) { - query = query.Where(a => $"{a.AttributeKey} {a.BoundId} {a.AttributeValue}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); + query = query.Where(a => $"{a.AttributeKey} {a.ForeignKey} {a.AttributeValue}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); } var total = await query.CountAsync(cancellationToken); var select = await query.OrderBy(a => a.AttributeKey).Skip(offset).Take(limit).Select(a => new AuthorityAttributeItem() { - BoundId = a.BoundId, + BoundId = a.ForeignKey, AttributeKey = a.AttributeKey, AttributeValue = a.AttributeValue }).ToListAsync(cancellationToken); @@ -52,7 +52,7 @@ public class AttributeRepository(IDbContextFactory contextFact try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if (string.IsNullOrWhiteSpace(attribute.AttributeKey) || attribute.BoundId == Guid.Empty) + if (string.IsNullOrWhiteSpace(attribute.AttributeKey) || attribute.ForeignKey == Guid.Empty) { return Result.Failed("Attribute key and/or bound id is empty"); } diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs index 212ed61..3dce771 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs @@ -48,6 +48,21 @@ public class GroupRepository(IDbContextFactory contextFactory) } } + public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var userJoinGroups = context.UserGroups.Where(ug => ug.UserId == user.Id).Select(ug => ug.GroupId); + var userGroups = context.Groups.Where(g => userJoinGroups.Contains(g.Id)); + return ListResult.Ok(userGroups, userGroups.Count()); + } + catch (Exception e) + { + return HandleExceptionListResult("Failed to get Groups", e); + } + } + public async Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { try diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs index 3b8824a..e6f4d92 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs @@ -1,9 +1,11 @@ +using DotBased.AspNet.Authority.EFCore.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; using Microsoft.EntityFrameworkCore; namespace DotBased.AspNet.Authority.EFCore.Repositories; + public class RoleRepository(IDbContextFactory contextFactory) : RepositoryBase, IRoleRepository { public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) @@ -95,19 +97,18 @@ public class RoleRepository(IDbContextFactory contextFactory) } } - public async Task DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + public async Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var currentRole = await context.Roles.FirstOrDefaultAsync(r => r.Id == role.Id, cancellationToken: cancellationToken); - if (currentRole == null) - { - return Result.Failed("Role not found, could not delete!"); - } - context.Roles.Remove(currentRole); + var roleIds = roles.Select(r => r.Id).ToList(); + + context.Roles.RemoveRange(roles); + context.RoleLinks.RemoveRange(context.RoleLinks.Where(rg => roleIds.Contains(rg.RoleId))); + var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to delete role!") : Result.Ok(); + return saveResult <= 0 ? Result.Failed("Failed to delete roles!") : Result.Ok(); } catch (Exception e) { @@ -117,6 +118,89 @@ public class RoleRepository(IDbContextFactory contextFactory) public async Task> GetUserRolesAsync(AuthorityUser user, int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - return ListResult.Failed("Not implemented!"); + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var roleIds = await context.RoleLinks.Where(r => r.LinkId == user.Id).Select(i => i.RoleId).ToListAsync(cancellationToken: cancellationToken); + var rolesQuery = context.Roles.Where(r => roleIds.Contains(r.Id)); + if (!string.IsNullOrEmpty(search)) + { + rolesQuery = rolesQuery.Where(r => r.Name.Contains(search)); + } + + var roles = rolesQuery.Where(r => roleIds.Contains(r.Id)).Skip(offset).Take(limit).Select(r => new AuthorityRoleItem() + { + Id = r.Id, + Name = r.Name + }); + return ListResult.Ok(roles, limit, offset); + } + catch (Exception e) + { + return HandleExceptionListResult("Failed to get user roles.", e); + } + } + + public async Task AddRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + foreach (var role in roles) + { + context.RoleLinks.Add(new RoleLink() { LinkId = linkId, RoleId = role.Id }); + } + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to ad role link!") : Result.Ok(); + } + catch (Exception e) + { + return HandleException("Failed to add role link!", e); + } + } + + public async Task> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var linkedRoles = context.RoleLinks.Where(r => linkIds.Contains(r.LinkId)).Select(r => r.RoleId); + var roleList = await context.Roles.Where(r => linkedRoles.Contains(r.Id)).ToListAsync(cancellationToken); + return ListResult.Ok(roleList.DistinctBy(r => r.Id)); + } + catch (Exception e) + { + return HandleExceptionListResult("Failed to get linked roles!", e); + } + } + + public async Task DeleteRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var roleIds = roles.Select(r => r.Id).ToList(); + context.RoleLinks.RemoveRange(context.RoleLinks.Where(rg => rg.LinkId == linkId && roleIds.Contains(rg.RoleId))); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult <= 0 ? Result.Failed("Failed to delete role links!") : Result.Ok(); + } + catch (Exception e) + { + return HandleException("Failed to delete role link!", e); + } + } + + public async Task> HasRolesAsync(Guid linkId, List roles, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var hasRoles = await context.RoleLinks.Where(r => r.LinkId == linkId && roles.Any(ar => ar.Id == r.RoleId)).Select(r => r.RoleId).ToListAsync(cancellationToken); + return ListResult.Ok(hasRoles); + } + catch (Exception e) + { + return HandleExceptionListResult("Failed to determine role for user!", e); + } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs index 517a836..0dc7bf4 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs @@ -4,8 +4,8 @@ namespace DotBased.AspNet.Authority.Managers; public partial class AuthorityManager { - public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - return ListResult.Failed("Not implemented!"); + return await GroupRepository.GetUserGroupsAsync(user, cancellationToken); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs index a9f83f9..fc048da 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityManager.cs @@ -14,6 +14,7 @@ public partial class AuthorityManager( IServiceProvider services, ICryptographer cryptographer, IUserRepository userRepository, + IGroupRepository groupRepository, IRoleRepository roleRepository, IPasswordHasher passwordHasher) { @@ -24,6 +25,7 @@ public partial class AuthorityManager( public ICryptographer Cryptographer { get; } = cryptographer; public IUserRepository UserRepository { get; } = userRepository; + public IGroupRepository GroupRepository { get; } = groupRepository; public IRoleRepository RoleRepository { get; } = roleRepository; public IPasswordHasher PasswordHasher { get; } = passwordHasher; diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index e63059b..60332c2 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -8,43 +8,103 @@ public partial class AuthorityManager public async Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { role.Version = GenerateVersion(); - var createResult = await roleRepository.CreateRoleAsync(role, cancellationToken); + var createResult = await RoleRepository.CreateRoleAsync(role, cancellationToken); return AuthorityResult.FromResult(createResult); } - public async Task DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + public async Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default) { - var result = await roleRepository.DeleteRoleAsync(role, cancellationToken); + var result = await RoleRepository.DeleteRolesAsync(roles, cancellationToken); return result; } public async Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - var result = await roleRepository.UpdateRoleAsync(role, cancellationToken); + var result = await RoleRepository.UpdateRoleAsync(role, cancellationToken); return result; } public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - var searchResult = await roleRepository.GetRolesAsync(limit, offset, search, cancellationToken); + var searchResult = await RoleRepository.GetRolesAsync(limit, offset, search, cancellationToken); return searchResult; } - public async Task AddRoleToUserAsync(AuthorityUser user, AuthorityRole role, CancellationToken cancellationToken = default) + public async Task AddRolesToUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) { - /* - - Validate User & Role - - Check if role is already in linked to user (if user already has the role, return) - - Add to UsersRoles table - */ + var usrValidation = await IsValidUserAsync(user, cancellationToken); + if (!usrValidation.Success) + { + _logger.Error(usrValidation.Exception ?? new Exception("Validation for user failed!"), "Invalid user!"); + return; + } + + var checkResult = await RoleRepository.HasRolesAsync(user.Id, roles, cancellationToken); + if (!checkResult.Success) + { + return; + } + + var rolesToAdd = roles; + if (checkResult.Count != 0) + { + rolesToAdd = roles.Where(r => !checkResult.Items.Contains(r.Id)).ToList(); + } + + var addResult = await RoleRepository.AddRolesLinkAsync(rolesToAdd, user.Id, cancellationToken); + if (!addResult.Success) + { + _logger.Error(addResult.Exception ?? new Exception("Adding role to user failed!, No further information available!"),"Failed to add role to user!"); + } } - public async Task RemoveRoleFromUserAsync(AuthorityRole role, AuthorityUser user, CancellationToken cancellationToken = default) + public async Task RemoveRolesFromUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) { + var usrValidation = await IsValidUserAsync(user, cancellationToken); + if (!usrValidation.Success) + { + _logger.Error(usrValidation.Exception ?? new Exception("Validation for user failed!"), "Invalid user!"); + return; + } + + var checkResult = await RoleRepository.HasRolesAsync(user.Id, roles, cancellationToken); + if (!checkResult.Success) + { + return; + } + + var rolesToRemove = roles; + if (checkResult.Count != 0) + { + rolesToRemove = roles.Where(r => !checkResult.Items.Contains(r.Id)).ToList(); + } + + var removeResult = await RoleRepository.DeleteRolesLinkAsync(rolesToRemove, user.Id, cancellationToken); + if (!removeResult.Success) + { + _logger.Error(removeResult.Exception ?? new Exception("Removing roles from user failed!"), "Failed to remove roles from user!"); + } } - public async Task AddRoleToGroupAsync(AuthorityRole role, AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task AddRolesToGroupAsync(List roles, AuthorityGroup group, CancellationToken cancellationToken = default) { + var checkResult = await RoleRepository.HasRolesAsync(group.Id, roles, cancellationToken); + if (!checkResult.Success) + { + return; + } + + var rolesToAdd = roles; + if (checkResult.Count != 0) + { + rolesToAdd = roles.Where(r => !checkResult.Items.Contains(r.Id)).ToList(); + } + + var addResult = await RoleRepository.AddRolesLinkAsync(rolesToAdd, group.Id, cancellationToken); + if (!addResult.Success) + { + _logger.Error(addResult.Exception ?? new Exception("Adding roles to group failed!"), "Failed to add roles to group!"); + } } /// @@ -60,26 +120,19 @@ public partial class AuthorityManager return ListResult.Failed("Invalid user"); } - List roles = []; - var usrRoles = await GetUserRolesAsync(user, cancellationToken); - if (usrRoles.Success) - { - roles.AddRange(usrRoles.Items); - } + var searchIds = new List { user.Id }; + var usrGroups = await GetUserGroupsAsync(user, cancellationToken); if (usrGroups.Success) { - var groupRolesResult = await GetGroupRolesAsync(usrGroups.Items.Select(g => g.Id).ToList(), cancellationToken); - if (groupRolesResult.Success) - { - roles.AddRange(groupRolesResult.Items); - } + searchIds.AddRange(usrGroups.Items.Select(g => g.Id).ToList()); } - return ListResult.Ok(roles, roles.Count); + + return await RoleRepository.GetLinkedRolesAsync(searchIds, cancellationToken); } public async Task> GetGroupRolesAsync(List groupIds, CancellationToken cancellationToken = default) { - return ListResult.Failed("Not implemented!"); + return await RoleRepository.GetLinkedRolesAsync(groupIds, cancellationToken); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index cfb7d9c..88462fa 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -91,7 +91,7 @@ public partial class AuthorityManager public async Task IsValidUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - var usrResult = await userRepository.GetVersionAsync(user, cancellationToken); + var usrResult = await UserRepository.GetVersionAsync(user, cancellationToken); return usrResult; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs index 27c08ca..117d8ff 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityAttribute.cs @@ -1,12 +1,8 @@ namespace DotBased.AspNet.Authority.Models.Authority; -public class AuthorityAttribute(string attributeKey, Guid bound) +public class AuthorityAttribute(string attributeKey, Guid foreignKey) { - public AuthorityAttribute() : this(string.Empty, Guid.NewGuid()) - { - } - - public Guid BoundId { get; set; } = bound; + public Guid ForeignKey { get; set; } = foreignKey; public string AttributeKey { get; set; } = attributeKey; diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs index d3efc45..d8b1edf 100755 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityRole.cs @@ -9,7 +9,7 @@ public class AuthorityRole() public Guid Id { get; set; } = Guid.NewGuid(); - public string? Name { get; set; } + public string Name { get; set; } = string.Empty; public long Version { get; set; } @@ -17,5 +17,5 @@ public class AuthorityRole() public IEnumerable Attributes { get; set; } = []; - public override string ToString() => Name ?? string.Empty; + public override string ToString() => Name; } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/AuthorityResult.cs b/DotBased.AspNet.Authority/Models/AuthorityResult.cs index d74fcfa..108c6b1 100755 --- a/DotBased.AspNet.Authority/Models/AuthorityResult.cs +++ b/DotBased.AspNet.Authority/Models/AuthorityResult.cs @@ -23,7 +23,7 @@ public class AuthorityResult : Result public IReadOnlyList? ValidationErrors { get; } - public static AuthorityResult Ok(TResultValue? value) => new AuthorityResult(true, value:value); + public new static AuthorityResult Ok(TResultValue? value) => new AuthorityResult(true, value:value); public static AuthorityResult Error(string errorMessage, ResultFailReason reason = ResultFailReason.Error) => new AuthorityResult(false, errorMessage, reason:reason); diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs index 3e8e814..c7f8ae5 100755 --- a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs @@ -6,6 +6,7 @@ public interface IGroupRepository { public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default); public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); diff --git a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs index 83cec83..3bbb5cb 100755 --- a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs @@ -8,5 +8,17 @@ public interface IRoleRepository public Task> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default); public Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); public Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); - public Task DeleteRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); + public Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default); + public Task AddRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default); + public Task> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default); + public Task DeleteRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default); + + /// + /// Return the role ids the linkId has. + /// + /// + /// + /// + /// + public Task> HasRolesAsync(Guid linkId, List roles, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased/Result.cs b/DotBased/Result.cs index 4669c28..60a61fa 100755 --- a/DotBased/Result.cs +++ b/DotBased/Result.cs @@ -44,7 +44,7 @@ public class Result : Result public new static Result Failed(string message, Exception? exception = null) => new(false, message, default, exception); - public new static Result HandleResult(TValue? value, string failedMessage, Exception? exception = null) + public static Result HandleResult(TValue? value, string failedMessage, Exception? exception = null) { return value == null ? Failed(failedMessage, exception) : Ok(value); } @@ -56,6 +56,8 @@ public class ListResult : Result { Items = items != null ? new List(items) : new List(); TotalCount = totalCount; + Limit = limit; + Offset = offset; } public ListResult(Result bObj) : base(bObj) @@ -85,8 +87,8 @@ public class ListResult : Result public int Offset { get; } public static ListResult Ok(IEnumerable items, int totalCount = -1, int limit = -1, int offset = -1) => - new(true, string.Empty, totalCount, items); + new(true, string.Empty, totalCount, items, limit, offset); public new static ListResult Failed(string message, Exception? exception = null) => - new(false, message, -1, null); + new(false, message, -1, null, exception: exception); } \ No newline at end of file From bb010b0cea4a144b08712312a4c94fedae7625b2 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 7 Apr 2025 00:11:00 +0200 Subject: [PATCH 31/38] [ADD] Add updated monads --- .../Monads/ValidationResult.cs | 20 +++++ DotBased/Monads/Result.cs | 84 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 DotBased.AspNet.Authority/Monads/ValidationResult.cs create mode 100644 DotBased/Monads/Result.cs diff --git a/DotBased.AspNet.Authority/Monads/ValidationResult.cs b/DotBased.AspNet.Authority/Monads/ValidationResult.cs new file mode 100644 index 0000000..6d4dfc7 --- /dev/null +++ b/DotBased.AspNet.Authority/Monads/ValidationResult.cs @@ -0,0 +1,20 @@ +using DotBased.Monads; + +namespace DotBased.AspNet.Authority.Monads; + +public class ValidationResult : DotBased.Monads.Result +{ + private ValidationResult(T result) : base(result) + { + } + + private ValidationResult(Exception exception) : base(exception) + { + } + + private ValidationResult(ResultInformation information) : base(information) + { + } + + +} \ No newline at end of file diff --git a/DotBased/Monads/Result.cs b/DotBased/Monads/Result.cs new file mode 100644 index 0000000..c94724b --- /dev/null +++ b/DotBased/Monads/Result.cs @@ -0,0 +1,84 @@ +namespace DotBased.Monads; + +public class Result +{ + protected Result() + { + IsSuccess = true; + } + + protected Result(Exception exception) + { + IsSuccess = false; + Information = ResultInformation.Error(exception); + } + + protected Result(ResultInformation information) + { + IsSuccess = false; + Information = information; + } + + public bool IsSuccess { get; } + public ResultInformation? Information { get; set; } + + public static implicit operator Result(Exception exception) => new(exception); + public static implicit operator Result(ResultInformation information) => new(information); + + public static Result Success() => new(); + public static Result Error(ResultInformation information) => new(information); + public static Result Fail(Exception exception) => new(exception); + + + public TMatch Match(Func success, Func failure) => IsSuccess ? success() : failure(Information!); +} + +public class Result : Result +{ + protected Result(TResult result) + { + _result = result; + } + + protected Result(Exception exception) : base(exception) + { + _result = default; + } + + protected Result(ResultInformation information) : base(information) + { + _result = default; + } + + private readonly TResult? _result; + public TResult Value => IsSuccess ? _result! : throw new InvalidOperationException("Result is invalid"); + + public static implicit operator Result(TResult result) => new(result); + public static implicit operator Result(Exception exception) => new(exception); + public static implicit operator Result(ResultInformation information) => new(information); + + public static Result Success(TResult result) => new(result); + public new static Result Error(ResultInformation information) => new(information); + public new static Result Fail(Exception exception) => new(exception); + + public TMatch Match(Func success, Func failure) + { + return IsSuccess && _result != null ? success(_result) : failure(Information ?? ResultInformation.Fail("No error and value is null!")); + } +} + +public class ResultInformation +{ + private ResultInformation(string message, Exception? exception) + { + Message = message; + Exception = exception; + } + + public string Message { get; } + public Exception? Exception { get; } + + public static ResultInformation Info(string message) => new(message, null); + public static ResultInformation Fail(string message) => new(message, null); + public static ResultInformation Error(Exception exception, string message = "") => new(message, exception); +} \ No newline at end of file From 0a5950cfa2e1dacc0e3656782eaf06d2ff610c05 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 7 Apr 2025 14:59:37 +0200 Subject: [PATCH 32/38] [CHANGE] Reworking Result monads --- DotBased.ASP.Auth/AuthDataCache.cs | 10 +-- DotBased.ASP.Auth/IAuthDataRepository.cs | 28 +++---- DotBased.ASP.Auth/ISessionStateProvider.cs | 4 +- DotBased.ASP.Auth/MemoryAuthDataRepository.cs | 48 +++++------ DotBased.ASP.Auth/SecurityService.cs | 48 +++++------ .../Repositories/AttributeRepository.cs | 28 +++---- .../Repositories/GroupRepository.cs | 34 ++++---- .../Repositories/RepositoryBase.cs | 10 +-- .../Repositories/RoleRepository.cs | 64 ++++++++------- .../Repositories/UserRepository.cs | 58 ++++++------- .../Managers/AuthorityGroupManager.cs | 2 +- .../Managers/AuthorityRoleManager.cs | 81 +++++++++++-------- .../Managers/AuthorityUserManager.cs | 20 ++--- .../Models/AuthorityResult.cs | 41 ---------- .../Models/AuthorityResultOldOld.cs | 41 ++++++++++ .../Models/QueryItems.cs | 21 +++++ .../Monads/ValidationResult.cs | 29 ++++++- .../Repositories/IAttributeRepository.cs | 10 +-- .../Repositories/IGroupRepository.cs | 12 +-- .../Repositories/IRoleRepository.cs | 10 ++- .../Repositories/IUserRepository.cs | 20 ++--- DotBased/Monads/Result.cs | 55 +++++++------ DotBased/{Result.cs => ResultOld.cs} | 32 ++++---- DotBased/Utilities/Cryptography.cs | 6 +- 24 files changed, 390 insertions(+), 322 deletions(-) delete mode 100755 DotBased.AspNet.Authority/Models/AuthorityResult.cs create mode 100755 DotBased.AspNet.Authority/Models/AuthorityResultOldOld.cs create mode 100644 DotBased.AspNet.Authority/Models/QueryItems.cs rename DotBased/{Result.cs => ResultOld.cs} (55%) diff --git a/DotBased.ASP.Auth/AuthDataCache.cs b/DotBased.ASP.Auth/AuthDataCache.cs index 76287e4..e8a1c18 100755 --- a/DotBased.ASP.Auth/AuthDataCache.cs +++ b/DotBased.ASP.Auth/AuthDataCache.cs @@ -15,26 +15,26 @@ public class AuthDataCache private readonly AuthStateCacheCollection _authenticationStateCollection = []; - public Result PurgeSessionState(string id) => _authenticationStateCollection.Remove(id) ? Result.Ok() : Result.Failed("Failed to purge session state from cache! Or the session was not cached..."); + public ResultOld PurgeSessionState(string id) => _authenticationStateCollection.Remove(id) ? ResultOld.Ok() : ResultOld.Failed("Failed to purge session state from cache! Or the session was not cached..."); public void CacheSessionState(AuthenticationStateModel stateModel, AuthenticationState? state = null) => _authenticationStateCollection[stateModel.Id] = new AuthStateCacheNode(stateModel, state); - public Result> RequestSessionState(string id) + public ResultOld> RequestSessionState(string id) { if (!_authenticationStateCollection.TryGetValue(id, out var node)) - return Result>.Failed("No cached object found!"); + return ResultOld>.Failed("No cached object found!"); string failedMsg; if (node.StateModel != null) { if (node.IsValidLifespan(_configuration.CachedAuthSessionLifespan)) - return Result>.Ok(new Tuple(node.StateModel, node.State)); + return ResultOld>.Ok(new Tuple(node.StateModel, node.State)); failedMsg = $"Session has invalid lifespan, removing entry: [{id}] from cache!"; } else failedMsg = $"Returned object is null, removing entry: [{id}] from cache!"; _authenticationStateCollection.Remove(id); - return Result>.Failed(failedMsg); + return ResultOld>.Failed(failedMsg); } } diff --git a/DotBased.ASP.Auth/IAuthDataRepository.cs b/DotBased.ASP.Auth/IAuthDataRepository.cs index f1ed79c..4435324 100755 --- a/DotBased.ASP.Auth/IAuthDataRepository.cs +++ b/DotBased.ASP.Auth/IAuthDataRepository.cs @@ -5,18 +5,18 @@ namespace DotBased.ASP.Auth; public interface IAuthDataRepository { - public Task CreateUserAsync(UserModel user); - public Task UpdateUserAsync(UserModel user); - public Task DeleteUserAsync(UserModel user); - public Task> GetUserAsync(string id, string email, string username); - public Task> GetUsersAsync(int start = 0, int amount = 30, string search = ""); - public Task CreateGroupAsync(GroupModel group); - public Task UpdateGroupAsync(GroupModel group); - public Task DeleteGroupAsync(GroupModel group); - public Task> GetGroupAsync(string id); - public Task> GetGroupsAsync(int start = 0, int amount = 30, string search = ""); - public Task CreateAuthenticationStateAsync(AuthenticationStateModel authenticationState); - public Task UpdateAuthenticationStateAsync(AuthenticationStateModel authenticationState); - public Task DeleteAuthenticationStateAsync(AuthenticationStateModel authenticationState); - public Task> GetAuthenticationStateAsync(string id); + public Task CreateUserAsync(UserModel user); + public Task UpdateUserAsync(UserModel user); + public Task DeleteUserAsync(UserModel user); + public Task> GetUserAsync(string id, string email, string username); + public Task> GetUsersAsync(int start = 0, int amount = 30, string search = ""); + public Task CreateGroupAsync(GroupModel group); + public Task UpdateGroupAsync(GroupModel group); + public Task DeleteGroupAsync(GroupModel group); + public Task> GetGroupAsync(string id); + public Task> GetGroupsAsync(int start = 0, int amount = 30, string search = ""); + public Task CreateAuthenticationStateAsync(AuthenticationStateModel authenticationState); + public Task UpdateAuthenticationStateAsync(AuthenticationStateModel authenticationState); + public Task DeleteAuthenticationStateAsync(AuthenticationStateModel authenticationState); + public Task> GetAuthenticationStateAsync(string id); } \ No newline at end of file diff --git a/DotBased.ASP.Auth/ISessionStateProvider.cs b/DotBased.ASP.Auth/ISessionStateProvider.cs index ee8850a..794429c 100755 --- a/DotBased.ASP.Auth/ISessionStateProvider.cs +++ b/DotBased.ASP.Auth/ISessionStateProvider.cs @@ -3,6 +3,6 @@ namespace DotBased.ASP.Auth; public interface ISessionStateProvider { public const string SessionStateName = "BasedServerSession"; - public Task> GetSessionStateAsync(); - public Task SetSessionStateAsync(string state); + public Task> GetSessionStateAsync(); + public Task SetSessionStateAsync(string state); } \ No newline at end of file diff --git a/DotBased.ASP.Auth/MemoryAuthDataRepository.cs b/DotBased.ASP.Auth/MemoryAuthDataRepository.cs index 708db7a..a2914a7 100755 --- a/DotBased.ASP.Auth/MemoryAuthDataRepository.cs +++ b/DotBased.ASP.Auth/MemoryAuthDataRepository.cs @@ -10,28 +10,28 @@ namespace DotBased.ASP.Auth; [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] public class MemoryAuthDataRepository : IAuthDataRepository { - public async Task CreateUserAsync(UserModel user) + public async Task CreateUserAsync(UserModel user) { if (MemoryData.users.Any(x => x.Id == user.Id || x.Email == user.Email)) - return Result.Failed("User already exists."); + return ResultOld.Failed("User already exists."); MemoryData.users.Add(user); - return Result.Ok(); + return ResultOld.Ok(); } - public async Task UpdateUserAsync(UserModel user) + public async Task UpdateUserAsync(UserModel user) { if (MemoryData.users.All(x => x.Id != user.Id)) - return Result.Failed("User does not exist!"); + return ResultOld.Failed("User does not exist!"); - return Result.Ok(); + return ResultOld.Ok(); } - public Task DeleteUserAsync(UserModel user) + public Task DeleteUserAsync(UserModel user) { throw new NotImplementedException(); } - public async Task> GetUserAsync(string id, string email, string username) + public async Task> GetUserAsync(string id, string email, string username) { UserModel? userModel = null; if (!id.IsNullOrEmpty()) @@ -40,62 +40,62 @@ public class MemoryAuthDataRepository : IAuthDataRepository userModel = MemoryData.users.FirstOrDefault(u => u.Email.Equals(email, StringComparison.OrdinalIgnoreCase)); if (!username.IsNullOrEmpty()) userModel = MemoryData.users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.OrdinalIgnoreCase)); - return userModel != null ? Result.Ok(userModel) : Result.Failed("No user found!"); + return userModel != null ? ResultOld.Ok(userModel) : ResultOld.Failed("No user found!"); } - public Task> GetUsersAsync(int start = 0, int amount = 30, string search = "") + public Task> GetUsersAsync(int start = 0, int amount = 30, string search = "") { throw new NotImplementedException(); } - public Task CreateGroupAsync(GroupModel group) + public Task CreateGroupAsync(GroupModel group) { throw new NotImplementedException(); } - public Task UpdateGroupAsync(GroupModel group) + public Task UpdateGroupAsync(GroupModel group) { throw new NotImplementedException(); } - public Task DeleteGroupAsync(GroupModel group) + public Task DeleteGroupAsync(GroupModel group) { throw new NotImplementedException(); } - public Task> GetGroupAsync(string id) + public Task> GetGroupAsync(string id) { throw new NotImplementedException(); } - public Task> GetGroupsAsync(int start = 0, int amount = 30, string search = "") + public Task> GetGroupsAsync(int start = 0, int amount = 30, string search = "") { throw new NotImplementedException(); } - public async Task CreateAuthenticationStateAsync(AuthenticationStateModel authenticationState) + public async Task CreateAuthenticationStateAsync(AuthenticationStateModel authenticationState) { - if (MemoryData.AuthenticationStates.Contains(authenticationState)) return Result.Failed("Item already exists!"); + if (MemoryData.AuthenticationStates.Contains(authenticationState)) return ResultOld.Failed("Item already exists!"); MemoryData.AuthenticationStates.Add(authenticationState); - return Result.Ok(); + return ResultOld.Ok(); } - public Task UpdateAuthenticationStateAsync(AuthenticationStateModel authenticationState) + public Task UpdateAuthenticationStateAsync(AuthenticationStateModel authenticationState) { throw new NotImplementedException(); } - public async Task DeleteAuthenticationStateAsync(AuthenticationStateModel authenticationState) + public async Task DeleteAuthenticationStateAsync(AuthenticationStateModel authenticationState) { MemoryData.AuthenticationStates.Remove(authenticationState); - return Result.Ok(); + return ResultOld.Ok(); } - public async Task> GetAuthenticationStateAsync(string id) + public async Task> GetAuthenticationStateAsync(string id) { var item = MemoryData.AuthenticationStates.FirstOrDefault(x => x.Id == id); - if (item == null) return Result.Failed("Could not get the session state!"); - return Result.Ok(item); + if (item == null) return ResultOld.Failed("Could not get the session state!"); + return ResultOld.Ok(item); } } diff --git a/DotBased.ASP.Auth/SecurityService.cs b/DotBased.ASP.Auth/SecurityService.cs index 748e0a7..48e1c80 100755 --- a/DotBased.ASP.Auth/SecurityService.cs +++ b/DotBased.ASP.Auth/SecurityService.cs @@ -24,10 +24,10 @@ public class SecurityService private readonly ProtectedLocalStorage _localStorage; private readonly ILogger _logger; - public async Task> GetAuthenticationStateFromSessionAsync(string id) + public async Task> GetAuthenticationStateFromSessionAsync(string id) { if (id.IsNullOrEmpty()) - return Result.Failed("No valid id!"); + return ResultOld.Failed("No valid id!"); AuthenticationStateModel? authStateModel = null; var stateCache = _dataCache.RequestSessionState(id); if (!stateCache.Success || stateCache.Value == null) @@ -42,16 +42,16 @@ public class SecurityService else { if (stateCache.Value.Item2 != null) - return Result.Ok(stateCache.Value.Item2); + return ResultOld.Ok(stateCache.Value.Item2); authStateModel = stateCache.Value.Item1; } if (authStateModel == null) - return Result.Failed("Failed to get auth state!"); + return ResultOld.Failed("Failed to get auth state!"); var userResult = await _authDataRepository.GetUserAsync(authStateModel.UserId, string.Empty, string.Empty); if (userResult is not { Success: true, Value: not null }) - return Result.Failed("Failed to get user from state!"); + return ResultOld.Failed("Failed to get user from state!"); var claims = new List() { new(ClaimTypes.Sid, userResult.Value.Id), @@ -66,56 +66,56 @@ public class SecurityService var claimsIdentity = new ClaimsIdentity(claims, BasedAuthDefaults.AuthenticationScheme); var authState = new AuthenticationState(new ClaimsPrincipal(claimsIdentity)); _dataCache.CacheSessionState(authStateModel, authState); - return Result.Ok(authState); + return ResultOld.Ok(authState); } - public async Task> LoginAsync(LoginModel login) + public async Task> LoginAsync(LoginModel login) { try { UserModel? user = null; - Result usrResult; + ResultOld usrResultOld; if (!login.UserName.IsNullOrEmpty()) { - usrResult = await _authDataRepository.GetUserAsync(string.Empty, string.Empty, login.UserName); - if (usrResult is { Success: true, Value: not null }) - user = usrResult.Value; + usrResultOld = await _authDataRepository.GetUserAsync(string.Empty, string.Empty, login.UserName); + if (usrResultOld is { Success: true, Value: not null }) + user = usrResultOld.Value; } else if (!login.Email.IsNullOrEmpty()) { - usrResult = await _authDataRepository.GetUserAsync(string.Empty, login.Email, string.Empty); - if (usrResult is { Success: true, Value: not null }) - user = usrResult.Value; + usrResultOld = await _authDataRepository.GetUserAsync(string.Empty, login.Email, string.Empty); + if (usrResultOld is { Success: true, Value: not null }) + user = usrResultOld.Value; } else - return Result.Failed("Username & Email is empty, cannot login!"); + return ResultOld.Failed("Username & Email is empty, cannot login!"); - if (user == null || !usrResult.Success) - return Result.Failed("No user found!"); + if (user == null || !usrResultOld.Success) + return ResultOld.Failed("No user found!"); if (user.PasswordHash != login.Password) //TODO: Hash password and compare - return Result.Failed("Login failed, invalid password."); + return ResultOld.Failed("Login failed, invalid password."); var state = new AuthenticationStateModel(user); var authResult = await _authDataRepository.CreateAuthenticationStateAsync(state); if (!authResult.Success) - return Result.Failed("Failed to store session to database!"); + return ResultOld.Failed("Failed to store session to database!"); _dataCache.CacheSessionState(state); await _localStorage.SetAsync(BasedAuthDefaults.StorageKey, state.Id); - return Result.Ok(state); + return ResultOld.Ok(state); } catch (Exception e) { _logger.Error(e, "Failed to login!"); - return Result.Failed("Login failed, exception thrown!"); + return ResultOld.Failed("Login failed, exception thrown!"); } } - public async Task LogoutAsync(string state) + public async Task LogoutAsync(string state) { try { if (state.IsNullOrEmpty()) - return Result.Failed($"Argument {nameof(state)} is empty!"); + return ResultOld.Failed($"Argument {nameof(state)} is empty!"); var stateResult = await _authDataRepository.GetAuthenticationStateAsync(state); if (!stateResult.Success || stateResult.Value == null) @@ -131,7 +131,7 @@ public class SecurityService catch (Exception e) { _logger.Error(e, "Failed to logout!"); - return Result.Failed("Failed to logout, exception thrown!"); + return ResultOld.Failed("Failed to logout, exception thrown!"); } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs index 959d10f..dd25a1f 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs @@ -6,7 +6,7 @@ namespace DotBased.AspNet.Authority.EFCore.Repositories; public class AttributeRepository(IDbContextFactory contextFactory) : RepositoryBase, IAttributeRepository { - public async Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", + public async Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { try @@ -25,7 +25,7 @@ public class AttributeRepository(IDbContextFactory contextFact AttributeKey = a.AttributeKey, AttributeValue = a.AttributeValue }).ToListAsync(cancellationToken); - return ListResult.Ok(select, total, limit, offset); + return ListResultOld.Ok(select, total, limit, offset); } catch (Exception e) { @@ -33,13 +33,13 @@ public class AttributeRepository(IDbContextFactory contextFact } } - public async Task> GetAttributeByKeyAsync(string key, CancellationToken cancellationToken = default) + public async Task> GetAttributeByKeyAsync(string key, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var attribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == key, cancellationToken); - return attribute == null ? Result.Failed("Attribute not found") : Result.Ok(attribute); + return attribute == null ? ResultOld.Failed("Attribute not found") : ResultOld.Ok(attribute); } catch (Exception e) { @@ -47,18 +47,18 @@ public class AttributeRepository(IDbContextFactory contextFact } } - public async Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (string.IsNullOrWhiteSpace(attribute.AttributeKey) || attribute.ForeignKey == Guid.Empty) { - return Result.Failed("Attribute key and/or bound id is empty"); + return ResultOld.Failed("Attribute key and/or bound id is empty"); } var entry = context.Attributes.Add(attribute); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to create attribute") : Result.Ok(entry.Entity); + return saveResult <= 0 ? ResultOld.Failed("Failed to create attribute") : ResultOld.Ok(entry.Entity); } catch (Exception e) { @@ -66,7 +66,7 @@ public class AttributeRepository(IDbContextFactory contextFact } } - public async Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) { try { @@ -74,17 +74,17 @@ public class AttributeRepository(IDbContextFactory contextFact var currentAttribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == attribute.AttributeKey, cancellationToken); if (currentAttribute == null) { - return Result.Failed("Attribute not found"); + return ResultOld.Failed("Attribute not found"); } if (currentAttribute.Version != attribute.Version) { - return Result.Failed("Attribute version doesn't match"); + return ResultOld.Failed("Attribute version doesn't match"); } var entry = context.Attributes.Update(currentAttribute); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to update attribute") : Result.Ok(entry.Entity); + return saveResult <= 0 ? ResultOld.Failed("Failed to update attribute") : ResultOld.Ok(entry.Entity); } catch (Exception e) { @@ -92,7 +92,7 @@ public class AttributeRepository(IDbContextFactory contextFact } } - public async Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) { try { @@ -100,11 +100,11 @@ public class AttributeRepository(IDbContextFactory contextFact var currentAttribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == attribute.AttributeKey, cancellationToken); if (currentAttribute == null) { - return Result.Failed("Attribute not found"); + return ResultOld.Failed("Attribute not found"); } context.Attributes.Remove(currentAttribute); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to delete attribute") : Result.Ok(); + return saveResult <= 0 ? ResultOld.Failed("Failed to delete attribute") : ResultOld.Ok(); } catch (Exception e) { diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs index 3dce771..74e3a0c 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs @@ -6,7 +6,7 @@ namespace DotBased.AspNet.Authority.EFCore.Repositories; public class GroupRepository(IDbContextFactory contextFactory) : RepositoryBase, IGroupRepository { - public async Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { try { @@ -22,7 +22,7 @@ public class GroupRepository(IDbContextFactory contextFactory) Id = g.Id, Name = g.Name }).ToListAsync(cancellationToken); - return ListResult.Ok(select, total, limit, offset); + return ListResultOld.Ok(select, total, limit, offset); } catch (Exception e) { @@ -30,17 +30,17 @@ public class GroupRepository(IDbContextFactory contextFactory) } } - public async Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default) + public async Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!Guid.TryParse(id, out var groupId)) { - return Result.Failed("Invalid group id"); + return ResultOld.Failed("Invalid group id"); } var group = await context.Groups.Where(g => g.Id == groupId).Include(g => g.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); - return Result.HandleResult(group, "Group not found"); + return ResultOld.HandleResult(group, "Group not found"); } catch (Exception e) { @@ -48,14 +48,14 @@ public class GroupRepository(IDbContextFactory contextFactory) } } - public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var userJoinGroups = context.UserGroups.Where(ug => ug.UserId == user.Id).Select(ug => ug.GroupId); var userGroups = context.Groups.Where(g => userJoinGroups.Contains(g.Id)); - return ListResult.Ok(userGroups, userGroups.Count()); + return ListResultOld.Ok(userGroups, userGroups.Count()); } catch (Exception e) { @@ -63,18 +63,18 @@ public class GroupRepository(IDbContextFactory contextFactory) } } - public async Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (group.Id == Guid.Empty) { - return Result.Failed("Id cannot be empty."); + return ResultOld.Failed("Id cannot be empty."); } var entry = context.Groups.Add(group); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to create group.") : Result.Ok(entry.Entity); + return saveResult <= 0 ? ResultOld.Failed("Failed to create group.") : ResultOld.Ok(entry.Entity); } catch (Exception e) { @@ -82,7 +82,7 @@ public class GroupRepository(IDbContextFactory contextFactory) } } - public async Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { try { @@ -90,17 +90,17 @@ public class GroupRepository(IDbContextFactory contextFactory) var currentGroup = await context.Groups.FirstOrDefaultAsync(g => g.Id == group.Id ,cancellationToken); if (currentGroup == null) { - return Result.Failed("Group not found."); + return ResultOld.Failed("Group not found."); } if (currentGroup.Version != group.Version) { - return Result.Failed("Group version does not match, version validation failed!"); + return ResultOld.Failed("Group version does not match, version validation failed!"); } var entry = context.Groups.Update(group); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to update group.") : Result.Ok(entry.Entity); + return saveResult <= 0 ? ResultOld.Failed("Failed to update group.") : ResultOld.Ok(entry.Entity); } catch (Exception e) { @@ -108,7 +108,7 @@ public class GroupRepository(IDbContextFactory contextFactory) } } - public async Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { try { @@ -116,11 +116,11 @@ public class GroupRepository(IDbContextFactory contextFactory) var currentGroup = await context.Groups.FirstOrDefaultAsync(g => g.Id == group.Id, cancellationToken); if (currentGroup == null) { - return Result.Failed("Group not found, cannot delete group!"); + return ResultOld.Failed("Group not found, cannot delete group!"); } context.Groups.Remove(currentGroup); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to delete group.") : Result.Ok(); + return saveResult <= 0 ? ResultOld.Failed("Failed to delete group.") : ResultOld.Ok(); } catch (Exception e) { diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs index 1be6d12..89fbf20 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs @@ -2,18 +2,18 @@ namespace DotBased.AspNet.Authority.EFCore.Repositories; public abstract class RepositoryBase { - protected Result HandleExceptionResult(string message, Exception ex) => new(HandleException(message, ex)); + protected ResultOld HandleExceptionResult(string message, Exception ex) => new(HandleException(message, ex)); - protected ListResult HandleExceptionListResult(string message, Exception ex) => + protected ListResultOld HandleExceptionListResult(string message, Exception ex) => new(HandleException(message, ex)); - protected Result HandleException(string message, Exception ex) + protected ResultOld HandleException(string message, Exception ex) { if (ex is OperationCanceledException oce) { - return Result.Failed("Operation cancelled.", oce); + return ResultOld.Failed("Operation cancelled.", oce); } - return Result.Failed(message, ex); + return ResultOld.Failed(message, ex); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs index e6f4d92..4d2a459 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs @@ -1,6 +1,8 @@ using DotBased.AspNet.Authority.EFCore.Models; +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; +using DotBased.Monads; using Microsoft.EntityFrameworkCore; namespace DotBased.AspNet.Authority.EFCore.Repositories; @@ -8,7 +10,7 @@ namespace DotBased.AspNet.Authority.EFCore.Repositories; public class RoleRepository(IDbContextFactory contextFactory) : RepositoryBase, IRoleRepository { - public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task>> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { try { @@ -26,29 +28,29 @@ public class RoleRepository(IDbContextFactory contextFactory) Id = r.Id, Name = r.Name }).ToListAsync(cancellationToken: cancellationToken); - return ListResult.Ok(select, total, limit, offset); + return QueryItems.Create(select, total, limit, offset); } catch (Exception e) { - return HandleExceptionListResult("Failed to get roles.", e); + return e; } } - public async Task> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default) + public async Task> GetRoleByIdAsync(Guid id, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if (!Guid.TryParse(id, out var guid)) + var role = await context.Roles.Where(r => r.Id == id).Include(r => r.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); + if (role != null) { - return Result.Failed("Invalid id!"); + return role; } - var role = await context.Roles.Where(r => r.Id == guid).Include(r => r.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); - return Result.HandleResult(role, "Role not found!"); + return ResultError.Fail("Role not found!"); } catch (Exception e) { - return HandleExceptionResult("Failed to get role!", e); + return e; } } @@ -59,15 +61,15 @@ public class RoleRepository(IDbContextFactory contextFactory) await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (role.Id == Guid.Empty) { - return Result.Failed("Id cannot be empty!"); + return ResultError.Fail("Id cannot be empty!"); } var entity = context.Roles.Add(role); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to create role!") : Result.Ok(entity.Entity); + return saveResult <= 0 ? ResultError.Fail("Failed to create role!") : entity.Entity; } catch (Exception e) { - return HandleExceptionResult("Failed to create role!", e); + return e; } } @@ -79,21 +81,21 @@ public class RoleRepository(IDbContextFactory contextFactory) var currentRole = await context.Roles.FirstOrDefaultAsync(r => r.Id == role.Id, cancellationToken: cancellationToken); if (currentRole == null) { - return Result.Failed("Role not found!"); + return ResultError.Fail("Role not found!"); } if (role.Version != currentRole.Version) { - return Result.Failed("Role version does not match, version validation failed!"); + return ResultError.Fail("Role version does not match, version validation failed!"); } var entity = context.Roles.Update(role); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to update role!") : Result.Ok(entity.Entity); + return saveResult <= 0 ? ResultError.Fail("Failed to update role!") : entity.Entity; } catch (Exception e) { - return HandleExceptionResult("Failed to update role!", e); + return e; } } @@ -107,16 +109,16 @@ public class RoleRepository(IDbContextFactory contextFactory) context.Roles.RemoveRange(roles); context.RoleLinks.RemoveRange(context.RoleLinks.Where(rg => roleIds.Contains(rg.RoleId))); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to delete roles!") : Result.Ok(); + var removeResult = await context.SaveChangesAsync(cancellationToken); + return removeResult == roles.Count? Result.Success() : ResultError.Fail($"Not all roles have been removed! {removeResult} of {roles.Count} roles removed!"); } catch (Exception e) { - return HandleException("Failed to delete role!", e); + return e; } } - public async Task> GetUserRolesAsync(AuthorityUser user, int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task> GetUserRolesAsync(AuthorityUser user, int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { try { @@ -133,7 +135,7 @@ public class RoleRepository(IDbContextFactory contextFactory) Id = r.Id, Name = r.Name }); - return ListResult.Ok(roles, limit, offset); + return ListResultOld.Ok(roles, limit, offset); } catch (Exception e) { @@ -151,26 +153,26 @@ public class RoleRepository(IDbContextFactory contextFactory) context.RoleLinks.Add(new RoleLink() { LinkId = linkId, RoleId = role.Id }); } var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to ad role link!") : Result.Ok(); + return saveResult == roles.Count ? Result.Success() : ResultError.Fail($"Not all roles have been linked! {saveResult} of {roles.Count} roles linked!"); } catch (Exception e) { - return HandleException("Failed to add role link!", e); + return e; } } - public async Task> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default) + public async Task>> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var linkedRoles = context.RoleLinks.Where(r => linkIds.Contains(r.LinkId)).Select(r => r.RoleId); var roleList = await context.Roles.Where(r => linkedRoles.Contains(r.Id)).ToListAsync(cancellationToken); - return ListResult.Ok(roleList.DistinctBy(r => r.Id)); + return roleList.DistinctBy(r => r.Id).ToList(); } catch (Exception e) { - return HandleExceptionListResult("Failed to get linked roles!", e); + return e; } } @@ -182,25 +184,25 @@ public class RoleRepository(IDbContextFactory contextFactory) var roleIds = roles.Select(r => r.Id).ToList(); context.RoleLinks.RemoveRange(context.RoleLinks.Where(rg => rg.LinkId == linkId && roleIds.Contains(rg.RoleId))); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to delete role links!") : Result.Ok(); + return saveResult == roles.Count ? Result.Success() : ResultError.Fail($"Not all roles have been unlinked! {saveResult} of {roles.Count} roles unlinked!"); } catch (Exception e) { - return HandleException("Failed to delete role link!", e); + return e; } } - public async Task> HasRolesAsync(Guid linkId, List roles, CancellationToken cancellationToken = default) + public async Task>> HasRolesAsync(Guid linkId, List roles, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var hasRoles = await context.RoleLinks.Where(r => r.LinkId == linkId && roles.Any(ar => ar.Id == r.RoleId)).Select(r => r.RoleId).ToListAsync(cancellationToken); - return ListResult.Ok(hasRoles); + return hasRoles; } catch (Exception e) { - return HandleExceptionListResult("Failed to determine role for user!", e); + return e; } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs index efcc004..aefcd2b 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs @@ -6,7 +6,7 @@ namespace DotBased.AspNet.Authority.EFCore.Repositories; public class UserRepository(IDbContextFactory contextFactory) : RepositoryBase, IUserRepository { - public async Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { try { @@ -26,7 +26,7 @@ public class UserRepository(IDbContextFactory contextFactory) EmailAddress = u.EmailAddress, PhoneNumber = u.PhoneNumber }).ToListAsync(cancellationToken: cancellationToken); - return ListResult.Ok(selected, totalCount, limit, offset); + return ListResultOld.Ok(selected, totalCount, limit, offset); } catch (Exception e) { @@ -34,18 +34,18 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default) + public async Task> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!Guid.TryParse(id, out var guid)) { - return Result.Failed("Invalid id!"); + return ResultOld.Failed("Invalid id!"); } var user = await context.Users.Where(u => u.Id == guid).Include(u => u.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); - return Result.HandleResult(user, "User not found."); + return ResultOld.HandleResult(user, "User not found."); } catch (Exception e) { @@ -53,18 +53,18 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (user.Id == Guid.Empty) { - return Result.Failed("Id cannot be empty!"); + return ResultOld.Failed("Id cannot be empty!"); } var entity = context.Users.Add(user); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to create user!") : Result.Ok(entity.Entity); + return saveResult <= 0 ? ResultOld.Failed("Failed to create user!") : ResultOld.Ok(entity.Entity); } catch (Exception e) { @@ -72,7 +72,7 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) { try { @@ -80,17 +80,17 @@ public class UserRepository(IDbContextFactory contextFactory) var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken); if (usr == null) { - return Result.Failed("User not found!"); + return ResultOld.Failed("User not found!"); } if (usr.Version != user.Version || usr.SecurityVersion != user.SecurityVersion) { - return Result.Failed("Version validation failed!"); + return ResultOld.Failed("Version validation failed!"); } var entity = context.Users.Update(user); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to save updated user!") : Result.Ok(entity.Entity); + return saveResult <= 0 ? ResultOld.Failed("Failed to save updated user!") : ResultOld.Ok(entity.Entity); } catch (Exception e) { @@ -98,7 +98,7 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) { try { @@ -106,11 +106,11 @@ public class UserRepository(IDbContextFactory contextFactory) var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken); if (usr == null) { - return Result.Failed("User not found!"); + return ResultOld.Failed("User not found!"); } context.Users.Remove(usr); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to delete user!") : Result.Ok(); + return saveResult <= 0 ? ResultOld.Failed("Failed to delete user!") : ResultOld.Ok(); } catch (Exception e) { @@ -118,13 +118,13 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default) + public async Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var usr = await context.Users.Where(u => u.EmailAddress == email).Include(u => u.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); - return Result.HandleResult(usr, "User not found by given email address."); + return ResultOld.HandleResult(usr, "User not found by given email address."); } catch (Exception e) { @@ -132,7 +132,7 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) + public async Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) { try { @@ -140,18 +140,18 @@ public class UserRepository(IDbContextFactory contextFactory) var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken); if (usr == null) { - return Result.Failed("Failed to find user with given id!"); + return ResultOld.Failed("Failed to find user with given id!"); } if (usr.Version != user.Version) { - return Result.Failed("Stored user version doesn't match current user version!"); + return ResultOld.Failed("Stored user version doesn't match current user version!"); } usr.Version = version; context.Users.Update(usr); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to update user!") : Result.Ok(); + return saveResult <= 0 ? ResultOld.Failed("Failed to update user!") : ResultOld.Ok(); } catch (Exception e) { @@ -159,13 +159,13 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.Version).FirstOrDefaultAsync(cancellationToken); - return Result.HandleResult(usrVersion, "Failed to get user version!"); + return ResultOld.HandleResult(usrVersion, "Failed to get user version!"); } catch (Exception e) { @@ -173,7 +173,7 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default) + public async Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default) { try { @@ -181,18 +181,18 @@ public class UserRepository(IDbContextFactory contextFactory) var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken); if (usr == null) { - return Result.Failed("Failed to find user with given id!"); + return ResultOld.Failed("Failed to find user with given id!"); } if (usr.SecurityVersion != user.SecurityVersion) { - return Result.Failed("Stored user version doesn't match current user version!"); + return ResultOld.Failed("Stored user version doesn't match current user version!"); } usr.SecurityVersion = securityVersion; context.Users.Update(usr); var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? Result.Failed("Failed to update user!") : Result.Ok(); + return saveResult <= 0 ? ResultOld.Failed("Failed to update user!") : ResultOld.Ok(); } catch (Exception e) { @@ -200,13 +200,13 @@ public class UserRepository(IDbContextFactory contextFactory) } } - public async Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.SecurityVersion).FirstOrDefaultAsync(cancellationToken); - return Result.HandleResult(usrVersion, "Failed to get user security version!"); + return ResultOld.HandleResult(usrVersion, "Failed to get user security version!"); } catch (Exception e) { diff --git a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs index 0dc7bf4..aafc5c9 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs @@ -4,7 +4,7 @@ namespace DotBased.AspNet.Authority.Managers; public partial class AuthorityManager { - public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) { return await GroupRepository.GetUserGroupsAsync(user, cancellationToken); } diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index 60332c2..beda419 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -1,15 +1,16 @@ using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; +using DotBased.Monads; namespace DotBased.AspNet.Authority.Managers; public partial class AuthorityManager { - public async Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + public async Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { role.Version = GenerateVersion(); var createResult = await RoleRepository.CreateRoleAsync(role, cancellationToken); - return AuthorityResult.FromResult(createResult); + return createResult; } public async Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default) @@ -24,7 +25,7 @@ public partial class AuthorityManager return result; } - public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task>> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { var searchResult = await RoleRepository.GetRolesAsync(limit, offset, search, cancellationToken); return searchResult; @@ -40,22 +41,22 @@ public partial class AuthorityManager } var checkResult = await RoleRepository.HasRolesAsync(user.Id, roles, cancellationToken); - if (!checkResult.Success) - { - return; - } + var hasRolesList = checkResult.Match>(success: v => v, (_) => []); var rolesToAdd = roles; - if (checkResult.Count != 0) + if (hasRolesList.Count != 0) { - rolesToAdd = roles.Where(r => !checkResult.Items.Contains(r.Id)).ToList(); + rolesToAdd = roles.Where(r => !hasRolesList.Contains(r.Id)).ToList(); } var addResult = await RoleRepository.AddRolesLinkAsync(rolesToAdd, user.Id, cancellationToken); - if (!addResult.Success) + addResult.Match(() => { - _logger.Error(addResult.Exception ?? new Exception("Adding role to user failed!, No further information available!"),"Failed to add role to user!"); - } + _logger.Debug("Role links successfully added!"); + }, e => + { + _logger.Error(e.Exception ?? new Exception("Match failed!"), e.Description); + }); } public async Task RemoveRolesFromUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) @@ -68,43 +69,43 @@ public partial class AuthorityManager } var checkResult = await RoleRepository.HasRolesAsync(user.Id, roles, cancellationToken); - if (!checkResult.Success) - { - return; - } + var hasRolesList = checkResult.Match>(success: v => v, (_) => []); var rolesToRemove = roles; - if (checkResult.Count != 0) + if (hasRolesList.Count != 0) { - rolesToRemove = roles.Where(r => !checkResult.Items.Contains(r.Id)).ToList(); + rolesToRemove = roles.Where(r => !hasRolesList.Contains(r.Id)).ToList(); } var removeResult = await RoleRepository.DeleteRolesLinkAsync(rolesToRemove, user.Id, cancellationToken); - if (!removeResult.Success) + removeResult.Match(() => { - _logger.Error(removeResult.Exception ?? new Exception("Removing roles from user failed!"), "Failed to remove roles from user!"); - } + _logger.Debug("Removed roles from user!"); + }, e => + { + _logger.Error(e.Exception ?? new Exception("Removing roles from user failed!"), e.Description); + }); } public async Task AddRolesToGroupAsync(List roles, AuthorityGroup group, CancellationToken cancellationToken = default) { var checkResult = await RoleRepository.HasRolesAsync(group.Id, roles, cancellationToken); - if (!checkResult.Success) - { - return; - } + var hasRolesList = checkResult.Match>(success: v => v, (_) => []); var rolesToAdd = roles; - if (checkResult.Count != 0) + if (hasRolesList.Count != 0) { - rolesToAdd = roles.Where(r => !checkResult.Items.Contains(r.Id)).ToList(); + rolesToAdd = roles.Where(r => !hasRolesList.Contains(r.Id)).ToList(); } var addResult = await RoleRepository.AddRolesLinkAsync(rolesToAdd, group.Id, cancellationToken); - if (!addResult.Success) + addResult.Match(() => { - _logger.Error(addResult.Exception ?? new Exception("Adding roles to group failed!"), "Failed to add roles to group!"); - } + _logger.Debug("Added roles to group."); + }, e => + { + _logger.Error(e.Exception ?? new Exception("Adding roles to group failed!"), e.Description); + }); } /// @@ -112,12 +113,12 @@ public partial class AuthorityManager /// /// The user to get the roles from /// - public async Task> GetUserRolesAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task>> GetUserRolesAsync(AuthorityUser user, CancellationToken cancellationToken = default) { var usrValidation = await IsValidUserAsync(user, cancellationToken); if (!usrValidation.Success) { - return ListResult.Failed("Invalid user"); + return ResultError.Fail("Invalid user"); } var searchIds = new List { user.Id }; @@ -128,11 +129,21 @@ public partial class AuthorityManager searchIds.AddRange(usrGroups.Items.Select(g => g.Id).ToList()); } - return await RoleRepository.GetLinkedRolesAsync(searchIds, cancellationToken); + var linkedRolesResult = await RoleRepository.GetLinkedRolesAsync(searchIds, cancellationToken); + return linkedRolesResult.Match>(roles => roles, e => + { + _logger.Error(e.Exception ?? new Exception("Failed to get user roles!"), e.Description); + return []; + }); } - public async Task> GetGroupRolesAsync(List groupIds, CancellationToken cancellationToken = default) + public async Task>> GetGroupRolesAsync(List groupIds, CancellationToken cancellationToken = default) { - return await RoleRepository.GetLinkedRolesAsync(groupIds, cancellationToken); + var linkedRolesResult = await RoleRepository.GetLinkedRolesAsync(groupIds, cancellationToken); + return linkedRolesResult.Match>(roles => roles, e => + { + _logger.Error(e.Exception ?? new Exception("Failed to get group roles!"), e.Description); + return []; + }); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index 88462fa..940c100 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -34,28 +34,28 @@ public partial class AuthorityManager return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); } - public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken cancellationToken = default) + public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken cancellationToken = default) { var result = await UserRepository.GetAuthorityUsersAsync(maxResults, offset, query, cancellationToken); return result; } - public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken cancellationToken = default) + public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken cancellationToken = default) { var passwordValidation = await ValidatePasswordAsync(user, password); if (!passwordValidation.Success) { - return AuthorityResult.Failed(passwordValidation.Errors, ResultFailReason.Validation); + return AuthorityResultOldOld.Failed(passwordValidation.Errors, ResultFailReason.Validation); } user.PasswordHash = await PasswordHasher.HashPasswordAsync(password); user.SecurityVersion = GenerateVersion(); var updateResult = await UserRepository.UpdateUserAsync(user, cancellationToken); - return AuthorityResult.FromResult(updateResult); + return AuthorityResultOldOld.FromResult(updateResult); } - public async Task> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken cancellationToken = default) + public async Task> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken cancellationToken = default) { var userValidation = await ValidateUserAsync(userModel); var passwordValidation = await ValidatePasswordAsync(userModel, password); @@ -64,7 +64,7 @@ public partial class AuthorityManager List errors = []; errors.AddRange(userValidation.Errors); errors.AddRange(passwordValidation.Errors); - return AuthorityResult.Failed(errors, ResultFailReason.Validation); + return AuthorityResultOldOld.Failed(errors, ResultFailReason.Validation); } userModel.Version = GenerateVersion(); @@ -74,22 +74,22 @@ public partial class AuthorityManager var userCreationResult = await UserRepository.CreateUserAsync(userModel, cancellationToken); - return AuthorityResult.FromResult(userCreationResult); + return AuthorityResultOldOld.FromResult(userCreationResult); } - public async Task> UpdateUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) + public async Task> UpdateUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) { var updateResult = await UserRepository.UpdateUserAsync(model, cancellationToken); return updateResult; } - public async Task DeleteUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) + public async Task DeleteUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) { var deleteResult = await UserRepository.DeleteUserAsync(model, cancellationToken); return deleteResult; } - public async Task IsValidUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task IsValidUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) { var usrResult = await UserRepository.GetVersionAsync(user, cancellationToken); return usrResult; diff --git a/DotBased.AspNet.Authority/Models/AuthorityResult.cs b/DotBased.AspNet.Authority/Models/AuthorityResult.cs deleted file mode 100755 index 108c6b1..0000000 --- a/DotBased.AspNet.Authority/Models/AuthorityResult.cs +++ /dev/null @@ -1,41 +0,0 @@ -using DotBased.AspNet.Authority.Models.Validation; - -namespace DotBased.AspNet.Authority.Models; - -public class AuthorityResult : Result -{ - public static AuthorityResult FromResult(Result result) => new AuthorityResult(result); - - public AuthorityResult(Result result) : base(result) - { - Reason = ResultFailReason.Unknown; - } - - public AuthorityResult(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, IReadOnlyList? errors = null) : base(success, errorMessage, value, null) - { - Success = success; - Message = errorMessage; - Value = value; - Reason = reason; - ValidationErrors = errors; - } - public ResultFailReason Reason { get; } - public IReadOnlyList? ValidationErrors { get; } - - - public new static AuthorityResult Ok(TResultValue? value) => new AuthorityResult(true, value:value); - - public static AuthorityResult Error(string errorMessage, ResultFailReason reason = ResultFailReason.Error) => - new AuthorityResult(false, errorMessage, reason:reason); - - public static AuthorityResult Failed(IReadOnlyList errors, ResultFailReason reason = ResultFailReason.None) - => new AuthorityResult(false, errors:errors, reason:reason); -} - -public enum ResultFailReason -{ - None, - Unknown, - Validation, - Error -} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/AuthorityResultOldOld.cs b/DotBased.AspNet.Authority/Models/AuthorityResultOldOld.cs new file mode 100755 index 0000000..b12a05d --- /dev/null +++ b/DotBased.AspNet.Authority/Models/AuthorityResultOldOld.cs @@ -0,0 +1,41 @@ +using DotBased.AspNet.Authority.Models.Validation; + +namespace DotBased.AspNet.Authority.Models; + +public class AuthorityResultOldOld : ResultOld +{ + public static AuthorityResultOldOld FromResult(ResultOld resultOld) => new AuthorityResultOldOld(resultOld); + + public AuthorityResultOldOld(ResultOld resultOld) : base(resultOld) + { + Reason = ResultFailReason.Unknown; + } + + public AuthorityResultOldOld(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, IReadOnlyList? errors = null) : base(success, errorMessage, value, null) + { + Success = success; + Message = errorMessage; + Value = value; + Reason = reason; + ValidationErrors = errors; + } + public ResultFailReason Reason { get; } + public IReadOnlyList? ValidationErrors { get; } + + + public new static AuthorityResultOldOld Ok(TResultValue? value) => new AuthorityResultOldOld(true, value:value); + + public static AuthorityResultOldOld Error(string errorMessage, ResultFailReason reason = ResultFailReason.Error) => + new AuthorityResultOldOld(false, errorMessage, reason:reason); + + public static AuthorityResultOldOld Failed(IReadOnlyList errors, ResultFailReason reason = ResultFailReason.None) + => new AuthorityResultOldOld(false, errors:errors, reason:reason); +} + +public enum ResultFailReason +{ + None, + Unknown, + Validation, + Error +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/QueryItems.cs b/DotBased.AspNet.Authority/Models/QueryItems.cs new file mode 100644 index 0000000..d59cf75 --- /dev/null +++ b/DotBased.AspNet.Authority/Models/QueryItems.cs @@ -0,0 +1,21 @@ +namespace DotBased.AspNet.Authority.Models; + +public class QueryItems +{ + private QueryItems(IEnumerable items, int totalCount, int limit, int offset) + { + Items = items.ToList(); + TotalCount = totalCount; + Limit = limit; + Offset = offset; + } + + public readonly IReadOnlyCollection Items; + + public int Count => Items.Count; + public int TotalCount { get; } + public int Limit { get; } + public int Offset { get; } + + public static QueryItems Create(IEnumerable items, int totalCount, int limit, int offset) => new(items, totalCount, limit, offset); +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Monads/ValidationResult.cs b/DotBased.AspNet.Authority/Monads/ValidationResult.cs index 6d4dfc7..ed5edc5 100644 --- a/DotBased.AspNet.Authority/Monads/ValidationResult.cs +++ b/DotBased.AspNet.Authority/Monads/ValidationResult.cs @@ -1,8 +1,9 @@ +using DotBased.AspNet.Authority.Models.Validation; using DotBased.Monads; namespace DotBased.AspNet.Authority.Monads; -public class ValidationResult : DotBased.Monads.Result +public class ValidationResult : Result { private ValidationResult(T result) : base(result) { @@ -12,9 +13,31 @@ public class ValidationResult : DotBased.Monads.Result { } - private ValidationResult(ResultInformation information) : base(information) + private ValidationResult(ResultError error) : base(error) { } + + private ValidationResult(List validationErrors) : base(ResultError.Fail("Validation failed!")) + { + _validationErrors = validationErrors; + } + + private readonly List _validationErrors = []; + public IReadOnlyList ValidationErrors => _validationErrors; - + public static implicit operator ValidationResult(T result) => new(result); + public static implicit operator ValidationResult(Exception exception) => new(exception); + public static implicit operator ValidationResult(ResultError error) => new(error); + public static implicit operator ValidationResult(List validationErrors) => new(validationErrors); + + public static ValidationResult FromResult(Result result) + { + var authorityResult = result.Match>( + r => new ValidationResult(r), + error => new ValidationResult(error)); + return authorityResult; + } + + public TMatch Match(Func success, Func, TMatch> failure) => + IsSuccess && Value != null ? success(Value) : failure(Error ?? ResultError.Fail("No error and value is null!"), ValidationErrors); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs index bf045f6..2a5fd6e 100755 --- a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs @@ -4,9 +4,9 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IAttributeRepository { - public Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetAttributeByKeyAsync(string id, CancellationToken cancellationToken = default); - public Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); - public Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); - public Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); + public Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetAttributeByKeyAsync(string id, CancellationToken cancellationToken = default); + public Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); + public Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); + public Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs index c7f8ae5..7cc8d59 100755 --- a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs @@ -4,10 +4,10 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IGroupRepository { - public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default); - public Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); - public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); - public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs index 3bbb5cb..ba68d4a 100755 --- a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs @@ -1,16 +1,18 @@ +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; +using DotBased.Monads; namespace DotBased.AspNet.Authority.Repositories; public interface IRoleRepository { - public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetRoleByIdAsync(string id, CancellationToken cancellationToken = default); + public Task>> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetRoleByIdAsync(Guid id, CancellationToken cancellationToken = default); public Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); public Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); public Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default); public Task AddRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default); - public Task> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default); + public Task>> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default); public Task DeleteRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default); /// @@ -20,5 +22,5 @@ public interface IRoleRepository /// /// /// - public Task> HasRolesAsync(Guid linkId, List roles, CancellationToken cancellationToken = default); + public Task>> HasRolesAsync(Guid linkId, List roles, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index 0107a76..97cc30c 100755 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -4,14 +4,14 @@ namespace DotBased.AspNet.Authority.Repositories; public interface IUserRepository { - public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default); - public Task> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default); - public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default); - public Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default); - public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default); + public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default); + public Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default); + public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased/Monads/Result.cs b/DotBased/Monads/Result.cs index c94724b..c36423d 100644 --- a/DotBased/Monads/Result.cs +++ b/DotBased/Monads/Result.cs @@ -10,27 +10,39 @@ public class Result protected Result(Exception exception) { IsSuccess = false; - Information = ResultInformation.Error(exception); + Error = ResultError.Error(exception); } - protected Result(ResultInformation information) + protected Result(ResultError error) { IsSuccess = false; - Information = information; + Error = error; } public bool IsSuccess { get; } - public ResultInformation? Information { get; set; } + public ResultError? Error { get; set; } public static implicit operator Result(Exception exception) => new(exception); - public static implicit operator Result(ResultInformation information) => new(information); + public static implicit operator Result(ResultError error) => new(error); public static Result Success() => new(); - public static Result Error(ResultInformation information) => new(information); - public static Result Fail(Exception exception) => new(exception); + public static Result Fail(ResultError error) => new(error); + public static Result Exception(Exception exception) => new(exception); - public TMatch Match(Func success, Func failure) => IsSuccess ? success() : failure(Information!); + public TMatch Match(Func success, Func failure) => IsSuccess ? success() : failure(Error!); + + public void Match(Action success, Action failure) + { + if (IsSuccess) + { + success(); + } + else + { + failure(Error!); + } + } } public class Result : Result @@ -45,7 +57,7 @@ public class Result : Result _result = default; } - protected Result(ResultInformation information) : base(information) + protected Result(ResultError error) : base(error) { _result = default; } @@ -55,30 +67,27 @@ public class Result : Result public static implicit operator Result(TResult result) => new(result); public static implicit operator Result(Exception exception) => new(exception); - public static implicit operator Result(ResultInformation information) => new(information); + public static implicit operator Result(ResultError error) => new(error); public static Result Success(TResult result) => new(result); - public new static Result Error(ResultInformation information) => new(information); - public new static Result Fail(Exception exception) => new(exception); + public new static Result Fail(ResultError error) => new(error); + public new static Result Exception(Exception exception) => new(exception); - public TMatch Match(Func success, Func failure) - { - return IsSuccess && _result != null ? success(_result) : failure(Information ?? ResultInformation.Fail("No error and value is null!")); - } + public TMatch Match(Func success, Func failure) => + IsSuccess && Value != null ? success(Value) : failure(Error ?? ResultError.Fail("No error and value is null!")); } -public class ResultInformation +public class ResultError { - private ResultInformation(string message, Exception? exception) + private ResultError(string description, Exception? exception) { - Message = message; + Description = description; Exception = exception; } - public string Message { get; } + public string Description { get; } public Exception? Exception { get; } - public static ResultInformation Info(string message) => new(message, null); - public static ResultInformation Fail(string message) => new(message, null); - public static ResultInformation Error(Exception exception, string message = "") => new(message, exception); + public static ResultError Fail(string description) => new(description, null); + public static ResultError Error(Exception exception, string description = "") => new(description, exception); } \ No newline at end of file diff --git a/DotBased/Result.cs b/DotBased/ResultOld.cs similarity index 55% rename from DotBased/Result.cs rename to DotBased/ResultOld.cs index 60a61fa..5805d6c 100755 --- a/DotBased/Result.cs +++ b/DotBased/ResultOld.cs @@ -3,16 +3,16 @@ namespace DotBased; /// /// Simple result class for returning a result state or a message and an exception. /// -public class Result +public class ResultOld { - public Result(bool success, string message, Exception? exception) + public ResultOld(bool success, string message, Exception? exception) { Success = success; Message = message; Exception = exception; } - public Result(Result bObj) + public ResultOld(ResultOld bObj) { Success = bObj.Success; Message = bObj.Message; @@ -23,36 +23,36 @@ public class Result public string Message { get; set; } public Exception? Exception { get; set; } - public static Result Ok() => new(true, string.Empty, null); - public static Result Failed(string message, Exception? exception = null) => new(false, message, exception); + public static ResultOld Ok() => new(true, string.Empty, null); + public static ResultOld Failed(string message, Exception? exception = null) => new(false, message, exception); } -public class Result : Result +public class ResultOld : ResultOld { - public Result(bool success, string message, TValue? value, Exception? exception) : base(success, message, exception) + public ResultOld(bool success, string message, TValue? value, Exception? exception) : base(success, message, exception) { Value = value; } - public Result(Result bObj) : base(bObj) + public ResultOld(ResultOld bObj) : base(bObj) { } public TValue? Value { get; set; } - public static Result Ok(TValue value) => new(true, string.Empty, value, null); + public static ResultOld Ok(TValue value) => new(true, string.Empty, value, null); - public new static Result Failed(string message, Exception? exception = null) => + public new static ResultOld Failed(string message, Exception? exception = null) => new(false, message, default, exception); - public static Result HandleResult(TValue? value, string failedMessage, Exception? exception = null) + public static ResultOld HandleResult(TValue? value, string failedMessage, Exception? exception = null) { return value == null ? Failed(failedMessage, exception) : Ok(value); } } -public class ListResult : Result +public class ListResultOld : ResultOld { - public ListResult(bool success, string message, int totalCount, IEnumerable? items, int limit = -1, int offset = -1, Exception? exception = null) : base(success, message, exception) + public ListResultOld(bool success, string message, int totalCount, IEnumerable? items, int limit = -1, int offset = -1, Exception? exception = null) : base(success, message, exception) { Items = items != null ? new List(items) : new List(); TotalCount = totalCount; @@ -60,7 +60,7 @@ public class ListResult : Result Offset = offset; } - public ListResult(Result bObj) : base(bObj) + public ListResultOld(ResultOld bObj) : base(bObj) { Items = new List(); } @@ -86,9 +86,9 @@ public class ListResult : Result /// public int Offset { get; } - public static ListResult Ok(IEnumerable items, int totalCount = -1, int limit = -1, int offset = -1) => + public static ListResultOld Ok(IEnumerable items, int totalCount = -1, int limit = -1, int offset = -1) => new(true, string.Empty, totalCount, items, limit, offset); - public new static ListResult Failed(string message, Exception? exception = null) => + public new static ListResultOld Failed(string message, Exception? exception = null) => new(false, message, -1, null, exception: exception); } \ No newline at end of file diff --git a/DotBased/Utilities/Cryptography.cs b/DotBased/Utilities/Cryptography.cs index f0e6192..c0fd022 100755 --- a/DotBased/Utilities/Cryptography.cs +++ b/DotBased/Utilities/Cryptography.cs @@ -7,12 +7,12 @@ public static class Cryptography /* * https://gist.github.com/therightstuff/aa65356e95f8d0aae888e9f61aa29414 */ - public static Result ExportPublicKeyToPem(RSACryptoServiceProvider csp) + public static ResultOld ExportPublicKeyToPem(RSACryptoServiceProvider csp) { var outputStream = new StringWriter(); var parameters = csp.ExportParameters(false); if (parameters.Exponent == null || parameters.Modulus == null) - return Result.Failed("RSAParameters are empty!"); + return ResultOld.Failed("RSAParameters are empty!"); using (var stream = new MemoryStream()) { var writer = new BinaryWriter(stream); @@ -66,7 +66,7 @@ public static class Cryptography outputStream.Write("-----END PUBLIC KEY-----"); } - return Result.Ok(outputStream.ToString()); + return ResultOld.Ok(outputStream.ToString()); } private static void EncodeLength(BinaryWriter stream, int length) From f0cb7218ac0f10c449ac6f845faefd174a7387e4 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 11 Apr 2025 19:57:20 +0200 Subject: [PATCH 33/38] [CHANGE] Updated role repository tasks --- .../Repositories/RoleRepository.cs | 239 ++++++------------ .../Managers/AuthorityRoleManager.cs | 126 +++++---- .../Repositories/IRoleRepository.cs | 19 +- 3 files changed, 151 insertions(+), 233 deletions(-) diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs index 4d2a459..9da3084 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs @@ -2,207 +2,132 @@ using DotBased.AspNet.Authority.EFCore.Models; using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; -using DotBased.Monads; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class RoleRepository(IDbContextFactory contextFactory) : RepositoryBase, IRoleRepository +public class RoleRepository(IDbContextFactory contextFactory, ILogger logger) : RepositoryBase, IRoleRepository { - public async Task>> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var query = context.Roles.AsQueryable(); + if (!string.IsNullOrWhiteSpace(search)) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var query = context.Roles.AsQueryable(); - if (!string.IsNullOrWhiteSpace(search)) - { - query = query.Where(r => - $"{r.Name} {r.Id}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); - } + query = query.Where(r => + $"{r.Name} {r.Id}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); + } - var total = await query.CountAsync(cancellationToken); - var select = await query.OrderBy(r => r.Name).Skip(offset).Take(limit).Select(r => new AuthorityRoleItem() - { - Id = r.Id, - Name = r.Name - }).ToListAsync(cancellationToken: cancellationToken); - return QueryItems.Create(select, total, limit, offset); - } - catch (Exception e) + var total = await query.CountAsync(cancellationToken); + var select = await query.OrderBy(r => r.Name).Skip(offset).Take(limit).Select(r => new AuthorityRoleItem() { - return e; - } + Id = r.Id, + Name = r.Name + }).ToListAsync(cancellationToken: cancellationToken); + return QueryItems.Create(select, total, limit, offset); } - public async Task> GetRoleByIdAsync(Guid id, CancellationToken cancellationToken = default) + public async Task GetRoleByIdAsync(Guid id, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var role = await context.Roles.Where(r => r.Id == id).Include(r => r.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); - if (role != null) - { - return role; - } - return ResultError.Fail("Role not found!"); - } - catch (Exception e) - { - return e; - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var role = await context.Roles.Where(r => r.Id == id).Include(r => r.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); + return role; } - public async Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + public async Task CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (role.Id == Guid.Empty) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if (role.Id == Guid.Empty) - { - return ResultError.Fail("Id cannot be empty!"); - } - var entity = context.Roles.Add(role); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultError.Fail("Failed to create role!") : entity.Entity; - } - catch (Exception e) - { - return e; + throw new Exception("Role id is required!"); } + var entity = context.Roles.Add(role); + var saveResult = await context.SaveChangesAsync(cancellationToken); + + return saveResult != 0 ? entity.Entity : null; } - public async Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) + public async Task UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentRole = await context.Roles.FirstOrDefaultAsync(r => r.Id == role.Id, cancellationToken: cancellationToken); + if (currentRole == null) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var currentRole = await context.Roles.FirstOrDefaultAsync(r => r.Id == role.Id, cancellationToken: cancellationToken); - if (currentRole == null) - { - return ResultError.Fail("Role not found!"); - } + throw new Exception($"Role with id {role.Id} not found!"); + } - if (role.Version != currentRole.Version) - { - return ResultError.Fail("Role version does not match, version validation failed!"); - } + if (role.Version != currentRole.Version) + { + throw new Exception("Role version does not match!"); + } - var entity = context.Roles.Update(role); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultError.Fail("Failed to update role!") : entity.Entity; - } - catch (Exception e) - { - return e; - } + var entity = context.Roles.Update(role); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0 ? entity.Entity : null; } - public async Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default) + public async Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var roleIds = roles.Select(r => r.Id).ToList(); + + context.Roles.RemoveRange(roles); + context.RoleLinks.RemoveRange(context.RoleLinks.Where(rl => roleIds.Contains(rl.RoleId))); + + var removedRoles = await context.SaveChangesAsync(cancellationToken); + if (removedRoles == roles.Count) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var roleIds = roles.Select(r => r.Id).ToList(); - - context.Roles.RemoveRange(roles); - context.RoleLinks.RemoveRange(context.RoleLinks.Where(rg => roleIds.Contains(rg.RoleId))); - - var removeResult = await context.SaveChangesAsync(cancellationToken); - return removeResult == roles.Count? Result.Success() : ResultError.Fail($"Not all roles have been removed! {removeResult} of {roles.Count} roles removed!"); - } - catch (Exception e) - { - return e; + return true; } + logger.LogError("Failed to remove all roles, {removedRoles}/{totalRoles} roles removed!", removedRoles, roles.Count); + return false; } - public async Task> GetUserRolesAsync(AuthorityUser user, int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task AddRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + foreach (var role in roles) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var roleIds = await context.RoleLinks.Where(r => r.LinkId == user.Id).Select(i => i.RoleId).ToListAsync(cancellationToken: cancellationToken); - var rolesQuery = context.Roles.Where(r => roleIds.Contains(r.Id)); - if (!string.IsNullOrEmpty(search)) - { - rolesQuery = rolesQuery.Where(r => r.Name.Contains(search)); - } + context.RoleLinks.Add(new RoleLink { LinkId = linkId, RoleId = role.Id }); + } + var linkedRoles = await context.SaveChangesAsync(cancellationToken); + if (linkedRoles == roles.Count) + { + return true; + } - var roles = rolesQuery.Where(r => roleIds.Contains(r.Id)).Skip(offset).Take(limit).Select(r => new AuthorityRoleItem() - { - Id = r.Id, - Name = r.Name - }); - return ListResultOld.Ok(roles, limit, offset); - } - catch (Exception e) - { - return HandleExceptionListResult("Failed to get user roles.", e); - } + logger.LogError("Failed to link all given roles, {linkedRoles}/{totalRoles} roles linked!", linkedRoles, roles.Count); + return false; } - public async Task AddRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default) + public async Task> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - foreach (var role in roles) - { - context.RoleLinks.Add(new RoleLink() { LinkId = linkId, RoleId = role.Id }); - } - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult == roles.Count ? Result.Success() : ResultError.Fail($"Not all roles have been linked! {saveResult} of {roles.Count} roles linked!"); - } - catch (Exception e) - { - return e; - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var linkedRoles = context.RoleLinks.Where(r => linkIds.Contains(r.LinkId)).Select(r => r.RoleId); + var roleList = await context.Roles.Where(r => linkedRoles.Contains(r.Id)).ToListAsync(cancellationToken); + return roleList.DistinctBy(r => r.Id).ToList(); } - public async Task>> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default) + public async Task UnlinkRolesAsync(List roles, Guid linkId, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var roleIds = roles.Select(r => r.Id).ToList(); + context.RoleLinks.RemoveRange(context.RoleLinks.Where(rg => rg.LinkId == linkId && roleIds.Contains(rg.RoleId))); + var unlinkedRoles = await context.SaveChangesAsync(cancellationToken); + if (unlinkedRoles == roles.Count) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var linkedRoles = context.RoleLinks.Where(r => linkIds.Contains(r.LinkId)).Select(r => r.RoleId); - var roleList = await context.Roles.Where(r => linkedRoles.Contains(r.Id)).ToListAsync(cancellationToken); - return roleList.DistinctBy(r => r.Id).ToList(); - } - catch (Exception e) - { - return e; + return true; } + + logger.LogError("Failed to remove all linked roles, {unlinkedRoles}/{totalRoles} roles unlinked!", unlinkedRoles, roles.Count); + return false; } - public async Task DeleteRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default) + public async Task> GetRolesFromLinkAsync(Guid linkId, List roles, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var roleIds = roles.Select(r => r.Id).ToList(); - context.RoleLinks.RemoveRange(context.RoleLinks.Where(rg => rg.LinkId == linkId && roleIds.Contains(rg.RoleId))); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult == roles.Count ? Result.Success() : ResultError.Fail($"Not all roles have been unlinked! {saveResult} of {roles.Count} roles unlinked!"); - } - catch (Exception e) - { - return e; - } - } - - public async Task>> HasRolesAsync(Guid linkId, List roles, CancellationToken cancellationToken = default) - { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var hasRoles = await context.RoleLinks.Where(r => r.LinkId == linkId && roles.Any(ar => ar.Id == r.RoleId)).Select(r => r.RoleId).ToListAsync(cancellationToken); - return hasRoles; - } - catch (Exception e) - { - return e; - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + return await context.RoleLinks.Where(r => r.LinkId == linkId && roles.Any(ar => ar.Id == r.RoleId)).Select(r => r.RoleId).ToListAsync(cancellationToken); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index beda419..adb54b1 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -10,18 +10,28 @@ public partial class AuthorityManager { role.Version = GenerateVersion(); var createResult = await RoleRepository.CreateRoleAsync(role, cancellationToken); + if (createResult == null) + { + return ResultError.Fail("Failed to create new role."); + } + return createResult; } public async Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default) { - var result = await RoleRepository.DeleteRolesAsync(roles, cancellationToken); - return result; + var success = await RoleRepository.DeleteRolesAsync(roles, cancellationToken); + return success ? Result.Success() : ResultError.Fail("Failed to delete roles."); } public async Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default) { var result = await RoleRepository.UpdateRoleAsync(role, cancellationToken); + if (result == null) + { + return ResultError.Fail("Failed to update role."); + } + return result; } @@ -31,81 +41,59 @@ public partial class AuthorityManager return searchResult; } - public async Task AddRolesToUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) + public async Task AddRolesToUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) { var usrValidation = await IsValidUserAsync(user, cancellationToken); if (!usrValidation.Success) { - _logger.Error(usrValidation.Exception ?? new Exception("Validation for user failed!"), "Invalid user!"); - return; + return ResultError.Fail("Validation for user failed!"); } - var checkResult = await RoleRepository.HasRolesAsync(user.Id, roles, cancellationToken); - var hasRolesList = checkResult.Match>(success: v => v, (_) => []); + var linkedRoles = await RoleRepository.GetRolesFromLinkAsync(user.Id, roles, cancellationToken); var rolesToAdd = roles; - if (hasRolesList.Count != 0) + if (linkedRoles.Count != 0) { - rolesToAdd = roles.Where(r => !hasRolesList.Contains(r.Id)).ToList(); + rolesToAdd = roles.Where(r => !linkedRoles.Contains(r.Id)).ToList(); } - var addResult = await RoleRepository.AddRolesLinkAsync(rolesToAdd, user.Id, cancellationToken); - addResult.Match(() => - { - _logger.Debug("Role links successfully added!"); - }, e => - { - _logger.Error(e.Exception ?? new Exception("Match failed!"), e.Description); - }); + var addSuccess = await RoleRepository.AddRolesLinkAsync(rolesToAdd, user.Id, cancellationToken); + + return addSuccess ? Result.Success() : ResultError.Fail("Failed to add roles."); } - public async Task RemoveRolesFromUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) + public async Task RemoveRolesFromUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) { var usrValidation = await IsValidUserAsync(user, cancellationToken); if (!usrValidation.Success) { - _logger.Error(usrValidation.Exception ?? new Exception("Validation for user failed!"), "Invalid user!"); - return; + return ResultError.Fail("Validation for user failed!"); } - var checkResult = await RoleRepository.HasRolesAsync(user.Id, roles, cancellationToken); - var hasRolesList = checkResult.Match>(success: v => v, (_) => []); + var linkedRoles = await RoleRepository.GetRolesFromLinkAsync(user.Id, roles, cancellationToken); var rolesToRemove = roles; - if (hasRolesList.Count != 0) + if (linkedRoles.Count != 0) { - rolesToRemove = roles.Where(r => !hasRolesList.Contains(r.Id)).ToList(); + rolesToRemove = roles.Where(r => !linkedRoles.Contains(r.Id)).ToList(); } - var removeResult = await RoleRepository.DeleteRolesLinkAsync(rolesToRemove, user.Id, cancellationToken); - removeResult.Match(() => - { - _logger.Debug("Removed roles from user!"); - }, e => - { - _logger.Error(e.Exception ?? new Exception("Removing roles from user failed!"), e.Description); - }); + var removeResult = await RoleRepository.UnlinkRolesAsync(rolesToRemove, user.Id, cancellationToken); + return removeResult ? Result.Success() : ResultError.Fail("Failed to remove roles."); } - public async Task AddRolesToGroupAsync(List roles, AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task AddRolesToGroupAsync(List roles, AuthorityGroup group, CancellationToken cancellationToken = default) { - var checkResult = await RoleRepository.HasRolesAsync(group.Id, roles, cancellationToken); - var hasRolesList = checkResult.Match>(success: v => v, (_) => []); + var linkedRoles = await RoleRepository.GetRolesFromLinkAsync(group.Id, roles, cancellationToken); var rolesToAdd = roles; - if (hasRolesList.Count != 0) + if (linkedRoles.Count != 0) { - rolesToAdd = roles.Where(r => !hasRolesList.Contains(r.Id)).ToList(); + rolesToAdd = roles.Where(r => !linkedRoles.Contains(r.Id)).ToList(); } - var addResult = await RoleRepository.AddRolesLinkAsync(rolesToAdd, group.Id, cancellationToken); - addResult.Match(() => - { - _logger.Debug("Added roles to group."); - }, e => - { - _logger.Error(e.Exception ?? new Exception("Adding roles to group failed!"), e.Description); - }); + var linkResult = await RoleRepository.AddRolesLinkAsync(rolesToAdd, group.Id, cancellationToken); + return linkResult ? Result.Success() : ResultError.Fail("Failed to add roles."); } /// @@ -115,35 +103,41 @@ public partial class AuthorityManager /// public async Task>> GetUserRolesAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - var usrValidation = await IsValidUserAsync(user, cancellationToken); - if (!usrValidation.Success) + try { - return ResultError.Fail("Invalid user"); + var usrValidation = await IsValidUserAsync(user, cancellationToken); + if (!usrValidation.Success) + { + return ResultError.Fail("Invalid user"); + } + + var searchIds = new List { user.Id }; + + var usrGroups = await GetUserGroupsAsync(user, cancellationToken); + if (usrGroups.Success) + { + searchIds.AddRange(usrGroups.Items.Select(g => g.Id).ToList()); + } + + var linkedRolesResult = await RoleRepository.GetLinkedRolesAsync(searchIds, cancellationToken); + return linkedRolesResult; } - - var searchIds = new List { user.Id }; - - var usrGroups = await GetUserGroupsAsync(user, cancellationToken); - if (usrGroups.Success) + catch (Exception e) { - searchIds.AddRange(usrGroups.Items.Select(g => g.Id).ToList()); + return e; } - - var linkedRolesResult = await RoleRepository.GetLinkedRolesAsync(searchIds, cancellationToken); - return linkedRolesResult.Match>(roles => roles, e => - { - _logger.Error(e.Exception ?? new Exception("Failed to get user roles!"), e.Description); - return []; - }); } public async Task>> GetGroupRolesAsync(List groupIds, CancellationToken cancellationToken = default) { - var linkedRolesResult = await RoleRepository.GetLinkedRolesAsync(groupIds, cancellationToken); - return linkedRolesResult.Match>(roles => roles, e => + try { - _logger.Error(e.Exception ?? new Exception("Failed to get group roles!"), e.Description); - return []; - }); + var linkedRolesResult = await RoleRepository.GetLinkedRolesAsync(groupIds, cancellationToken); + return linkedRolesResult; + } + catch (Exception e) + { + return e; + } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs index ba68d4a..24ee00a 100755 --- a/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IRoleRepository.cs @@ -1,19 +1,18 @@ using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; -using DotBased.Monads; namespace DotBased.AspNet.Authority.Repositories; public interface IRoleRepository { - public Task>> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetRoleByIdAsync(Guid id, CancellationToken cancellationToken = default); - public Task> CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); - public Task> UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); - public Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default); - public Task AddRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default); - public Task>> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default); - public Task DeleteRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default); + public Task> GetRolesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task GetRoleByIdAsync(Guid id, CancellationToken cancellationToken = default); + public Task CreateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); + public Task UpdateRoleAsync(AuthorityRole role, CancellationToken cancellationToken = default); + public Task DeleteRolesAsync(List roles, CancellationToken cancellationToken = default); + public Task AddRolesLinkAsync(List roles, Guid linkId, CancellationToken cancellationToken = default); + public Task> GetLinkedRolesAsync(List linkIds, CancellationToken cancellationToken = default); + public Task UnlinkRolesAsync(List roles, Guid linkId, CancellationToken cancellationToken = default); /// /// Return the role ids the linkId has. @@ -22,5 +21,5 @@ public interface IRoleRepository /// /// /// - public Task>> HasRolesAsync(Guid linkId, List roles, CancellationToken cancellationToken = default); + public Task> GetRolesFromLinkAsync(Guid linkId, List roles, CancellationToken cancellationToken = default); } \ No newline at end of file From 21fe08c04f3d2a75f5f3e648192a5180ecbe2b24 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 11 Apr 2025 20:12:53 +0200 Subject: [PATCH 34/38] [CHANGE] Updated ValidationResult to monad version --- .../Managers/AuthorityUserManager.cs | 23 +++++++++--------- .../Models/Validation/ValidationResult.cs | 21 ---------------- .../Monads/ValidationResult.cs | 24 +++++++++---------- .../Validators/IPasswordValidator.cs | 2 +- .../Validators/IUserValidator.cs | 2 +- .../Validators/PasswordEqualsValidator.cs | 3 ++- .../Validators/PasswordOptionsValidator.cs | 3 ++- .../Validators/UserValidator.cs | 5 ++-- 8 files changed, 33 insertions(+), 50 deletions(-) delete mode 100755 DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index 940c100..81a99e4 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -1,6 +1,7 @@ using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; +using ValidationResult = DotBased.AspNet.Authority.Monads.ValidationResult; namespace DotBased.AspNet.Authority.Managers; @@ -12,12 +13,12 @@ public partial class AuthorityManager foreach (var validator in PasswordValidators) { var validatorResult = await validator.ValidatePasswordAsync(this, user, password); - if (!validatorResult.Success) + if (!validatorResult.IsSuccess) { - errors.AddRange(validatorResult.Errors); + errors.AddRange(validatorResult.ValidationErrors); } } - return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + return errors.Count > 0 ? ValidationResult.Fail(errors) : ValidationResult.Success(); } public async Task ValidateUserAsync(AuthorityUser user) @@ -26,12 +27,12 @@ public partial class AuthorityManager foreach (var userValidator in UserValidators) { var validationResult = await userValidator.ValidateUserAsync(this, user); - if (!validationResult.Success) + if (!validationResult.IsSuccess) { - errors.AddRange(validationResult.Errors); + errors.AddRange(validationResult.ValidationErrors); } } - return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + return errors.Count > 0 ? ValidationResult.Fail(errors) : ValidationResult.Success(); } public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken cancellationToken = default) @@ -43,9 +44,9 @@ public partial class AuthorityManager public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken cancellationToken = default) { var passwordValidation = await ValidatePasswordAsync(user, password); - if (!passwordValidation.Success) + if (!passwordValidation.IsSuccess) { - return AuthorityResultOldOld.Failed(passwordValidation.Errors, ResultFailReason.Validation); + return AuthorityResultOldOld.Failed(passwordValidation.ValidationErrors, ResultFailReason.Validation); } user.PasswordHash = await PasswordHasher.HashPasswordAsync(password); @@ -59,11 +60,11 @@ public partial class AuthorityManager { var userValidation = await ValidateUserAsync(userModel); var passwordValidation = await ValidatePasswordAsync(userModel, password); - if (!userValidation.Success || !passwordValidation.Success) + if (!userValidation.IsSuccess || !passwordValidation.IsSuccess) { List errors = []; - errors.AddRange(userValidation.Errors); - errors.AddRange(passwordValidation.Errors); + errors.AddRange(userValidation.ValidationErrors); + errors.AddRange(passwordValidation.ValidationErrors); return AuthorityResultOldOld.Failed(errors, ResultFailReason.Validation); } diff --git a/DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs b/DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs deleted file mode 100755 index aea2d80..0000000 --- a/DotBased.AspNet.Authority/Models/Validation/ValidationResult.cs +++ /dev/null @@ -1,21 +0,0 @@ -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/Monads/ValidationResult.cs b/DotBased.AspNet.Authority/Monads/ValidationResult.cs index ed5edc5..a07ff0c 100644 --- a/DotBased.AspNet.Authority/Monads/ValidationResult.cs +++ b/DotBased.AspNet.Authority/Monads/ValidationResult.cs @@ -3,9 +3,9 @@ using DotBased.Monads; namespace DotBased.AspNet.Authority.Monads; -public class ValidationResult : Result +public class ValidationResult : Result { - private ValidationResult(T result) : base(result) + private ValidationResult() { } @@ -25,19 +25,19 @@ public class ValidationResult : Result private readonly List _validationErrors = []; public IReadOnlyList ValidationErrors => _validationErrors; - public static implicit operator ValidationResult(T result) => new(result); - public static implicit operator ValidationResult(Exception exception) => new(exception); - public static implicit operator ValidationResult(ResultError error) => new(error); - public static implicit operator ValidationResult(List validationErrors) => new(validationErrors); + + public static implicit operator ValidationResult(Exception exception) => new(exception); + public static implicit operator ValidationResult(ResultError error) => new(error); + public static implicit operator ValidationResult(List validationErrors) => new(validationErrors); - public static ValidationResult FromResult(Result result) + public static ValidationResult FromResult(Result result) { - var authorityResult = result.Match>( - r => new ValidationResult(r), - error => new ValidationResult(error)); + var authorityResult = result.Match( + () => new ValidationResult(), + error => new ValidationResult(error)); return authorityResult; } - public TMatch Match(Func success, Func, TMatch> failure) => - IsSuccess && Value != null ? success(Value) : failure(Error ?? ResultError.Fail("No error and value is null!"), ValidationErrors); + public new static ValidationResult Success() => new(); + public static ValidationResult Fail(List validationErrors) => new(validationErrors); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs index 1208a21..8b529f3 100755 --- a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs @@ -1,6 +1,6 @@ using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Authority; -using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Monads; namespace DotBased.AspNet.Authority.Validators; diff --git a/DotBased.AspNet.Authority/Validators/IUserValidator.cs b/DotBased.AspNet.Authority/Validators/IUserValidator.cs index ef07650..afa1429 100755 --- a/DotBased.AspNet.Authority/Validators/IUserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IUserValidator.cs @@ -1,6 +1,6 @@ using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Authority; -using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Monads; namespace DotBased.AspNet.Authority.Validators; diff --git a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs index 8278e13..3033fec 100755 --- a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs @@ -1,6 +1,7 @@ using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Monads; namespace DotBased.AspNet.Authority.Validators; @@ -17,6 +18,6 @@ public class PasswordEqualsValidator : IPasswordValidator errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.InUse", "User uses this password already!")); } - return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + return errors.Count > 0 ? ValidationResult.Fail(errors) : ValidationResult.Success(); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs index 41cab40..0d76cf2 100755 --- a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs @@ -1,6 +1,7 @@ using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Monads; using DotBased.Extensions; namespace DotBased.AspNet.Authority.Validators; @@ -57,7 +58,7 @@ public class PasswordOptionsValidator : IPasswordValidator 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()); + return await Task.FromResult(errors.Count > 0 ? ValidationResult.Fail(errors) : ValidationResult.Success()); } private bool ContainsDigit(string strVal) => strVal.Any(char.IsDigit); diff --git a/DotBased.AspNet.Authority/Validators/UserValidator.cs b/DotBased.AspNet.Authority/Validators/UserValidator.cs index cb60dce..668e73f 100755 --- a/DotBased.AspNet.Authority/Validators/UserValidator.cs +++ b/DotBased.AspNet.Authority/Validators/UserValidator.cs @@ -2,6 +2,7 @@ using DotBased.AspNet.Authority.Managers; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Options; using DotBased.AspNet.Authority.Models.Validation; +using ValidationResult = DotBased.AspNet.Authority.Monads.ValidationResult; namespace DotBased.AspNet.Authority.Validators; @@ -53,7 +54,7 @@ public class UserValidator : IUserValidator chars.AddRange(user.UserName.Where(userNameChar => userOptions.UserNameCharacters.Contains(userNameChar))); } - if (chars.Count <= 0) return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + if (chars.Count <= 0) return errors.Count > 0 ? ValidationResult.Fail(errors) : ValidationResult.Success(); var errorCode = ""; var description = ""; switch (userOptions.UserNameCharacterListType) @@ -76,6 +77,6 @@ public class UserValidator : IUserValidator errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.InvalidUserName", "No username given!")); } - return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + return errors.Count > 0 ? ValidationResult.Fail(errors) : ValidationResult.Success(); } } \ No newline at end of file From ba0de460686b40f4f119792ceea7dbfd0000f1da Mon Sep 17 00:00:00 2001 From: max Date: Fri, 11 Apr 2025 21:07:08 +0200 Subject: [PATCH 35/38] [REFACTOR] Refactored UserRepository to use new monads --- .../Repositories/RoleRepository.cs | 15 +- .../Repositories/UserRepository.cs | 257 +++++++----------- .../Managers/AuthorityRoleManager.cs | 21 +- .../Managers/AuthorityUserManager.cs | 45 ++- .../Monads/AuthorityResult.cs | 40 +++ .../Monads/ValidationResult.cs | 4 +- .../Repositories/IUserRepository.cs | 21 +- 7 files changed, 189 insertions(+), 214 deletions(-) create mode 100644 DotBased.AspNet.Authority/Monads/AuthorityResult.cs diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs index 9da3084..c8ba830 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RoleRepository.cs @@ -77,11 +77,8 @@ public class RoleRepository(IDbContextFactory contextFactory, context.RoleLinks.RemoveRange(context.RoleLinks.Where(rl => roleIds.Contains(rl.RoleId))); var removedRoles = await context.SaveChangesAsync(cancellationToken); - if (removedRoles == roles.Count) - { - return true; - } - logger.LogError("Failed to remove all roles, {removedRoles}/{totalRoles} roles removed!", removedRoles, roles.Count); + if (removedRoles != 0) return true; + logger.LogError("Failed to remove roles"); return false; } @@ -116,12 +113,8 @@ public class RoleRepository(IDbContextFactory contextFactory, var roleIds = roles.Select(r => r.Id).ToList(); context.RoleLinks.RemoveRange(context.RoleLinks.Where(rg => rg.LinkId == linkId && roleIds.Contains(rg.RoleId))); var unlinkedRoles = await context.SaveChangesAsync(cancellationToken); - if (unlinkedRoles == roles.Count) - { - return true; - } - - logger.LogError("Failed to remove all linked roles, {unlinkedRoles}/{totalRoles} roles unlinked!", unlinkedRoles, roles.Count); + if (unlinkedRoles != 0) return true; + logger.LogError("Failed to remove linked roles"); return false; } diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs index aefcd2b..c70fadc 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/UserRepository.cs @@ -1,216 +1,147 @@ +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class UserRepository(IDbContextFactory contextFactory) : RepositoryBase, IUserRepository +public class UserRepository(IDbContextFactory contextFactory, ILogger logger) : RepositoryBase, IUserRepository { - public async Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var query = context.Users.AsQueryable(); + if (!string.IsNullOrWhiteSpace(search)) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var query = context.Users.AsQueryable(); - if (!string.IsNullOrWhiteSpace(search)) - { - query = query.Where(u => - $"{u.Id} {u.Name} {u.UserName} {u.EmailAddress} {u.PhoneNumber}".Contains(search, - StringComparison.CurrentCultureIgnoreCase)); - } - var totalCount = query.Count(); - var selected = await query.OrderBy(u => u.UserName).Skip(offset).Take(limit).Select(u => new AuthorityUserItem() - { - Id = u.Id, - UserName = u.UserName, - EmailAddress = u.EmailAddress, - PhoneNumber = u.PhoneNumber - }).ToListAsync(cancellationToken: cancellationToken); - return ListResultOld.Ok(selected, totalCount, limit, offset); + query = query.Where(u => + $"{u.Id} {u.Name} {u.UserName} {u.EmailAddress} {u.PhoneNumber}".Contains(search, + StringComparison.CurrentCultureIgnoreCase)); } - catch (Exception e) + var totalCount = query.Count(); + var selected = await query.OrderBy(u => u.UserName).Skip(offset).Take(limit).Select(u => new AuthorityUserItem() { - return HandleExceptionListResult("Failed to get users.", e); - } + Id = u.Id, + UserName = u.UserName, + EmailAddress = u.EmailAddress, + PhoneNumber = u.PhoneNumber + }).ToListAsync(cancellationToken: cancellationToken); + return QueryItems.Create(selected, totalCount, limit, offset); } - public async Task> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default) + public async Task GetAuthorityUserByIdAsync(Guid id, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (id == Guid.Empty) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if (!Guid.TryParse(id, out var guid)) - { - return ResultOld.Failed("Invalid id!"); - } + throw new Exception("Id is required!"); + } - var user = await context.Users.Where(u => u.Id == guid).Include(u => u.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); - return ResultOld.HandleResult(user, "User not found."); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to get user.", e); - } + return await context.Users.Where(u => u.Id == id).Include(u => u.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); } - public async Task> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (user.Id == Guid.Empty) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if (user.Id == Guid.Empty) - { - return ResultOld.Failed("Id cannot be empty!"); - } - var entity = context.Users.Add(user); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to create user!") : ResultOld.Ok(entity.Entity); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to create user.", e); + throw new Exception("User id is required!"); } + var entity = context.Users.Add(user); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0 ? entity.Entity : null; } - public async Task> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken); + if (usr == null) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken); - if (usr == null) - { - return ResultOld.Failed("User not found!"); - } + throw new Exception("User not found!"); + } - if (usr.Version != user.Version || usr.SecurityVersion != user.SecurityVersion) - { - return ResultOld.Failed("Version validation failed!"); - } + if (usr.Version != user.Version || usr.SecurityVersion != user.SecurityVersion) + { + throw new Exception("User does not have the correct security version!"); + } - var entity = context.Users.Update(user); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to save updated user!") : ResultOld.Ok(entity.Entity); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to update user!", e); - } + var entity = context.Users.Update(user); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0 ? entity.Entity : null; } - public async Task DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task DeleteUsersAsync(List users, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken: cancellationToken); - if (usr == null) - { - return ResultOld.Failed("User not found!"); - } - context.Users.Remove(usr); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to delete user!") : ResultOld.Ok(); - } - catch (Exception e) - { - return HandleException("Failed to delete user!", e); - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usrIds = users.Select(u => u.Id); + + context.Users.RemoveRange(users); + context.RoleLinks.RemoveRange(context.RoleLinks.Where(rl => usrIds.Contains(rl.LinkId))); + + var removedResult = await context.SaveChangesAsync(cancellationToken); + if (removedResult != 0) return true; + logger.LogError("Failed to delete users"); + return false; } - public async Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default) + public async Task GetUserByEmailAsync(string email, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usr = await context.Users.Where(u => u.EmailAddress == email).Include(u => u.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); - return ResultOld.HandleResult(usr, "User not found by given email address."); - } - catch (Exception e) - { - return HandleExceptionResult("An error occured while getting the user.", e); - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + return await context.Users.Where(u => u.EmailAddress == email).Include(u => u.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); } - public async Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) + public async Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken); + if (usr == null) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken); - if (usr == null) - { - return ResultOld.Failed("Failed to find user with given id!"); - } + throw new Exception("User not found!"); + } - if (usr.Version != user.Version) - { - return ResultOld.Failed("Stored user version doesn't match current user version!"); - } + if (usr.Version != user.Version) + { + throw new Exception("User does not have the correct security version!"); + } - usr.Version = version; - context.Users.Update(usr); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to update user!") : ResultOld.Ok(); - } - catch (Exception e) - { - return HandleException("An error occured while updating the version.", e); - } + usr.Version = version; + context.Users.Update(usr); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0; } - public async Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.Version).FirstOrDefaultAsync(cancellationToken); - return ResultOld.HandleResult(usrVersion, "Failed to get user version!"); - } - catch (Exception e) - { - return HandleExceptionResult("An error occured while getting the user version.", e); - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.Version).FirstOrDefaultAsync(cancellationToken); + return usrVersion; } - public async Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default) + public async Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken); + if (usr == null) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usr = await context.Users.FirstOrDefaultAsync(u => u.Id == user.Id, cancellationToken); - if (usr == null) - { - return ResultOld.Failed("Failed to find user with given id!"); - } + throw new Exception("User not found!"); + } - if (usr.SecurityVersion != user.SecurityVersion) - { - return ResultOld.Failed("Stored user version doesn't match current user version!"); - } + if (usr.SecurityVersion != user.SecurityVersion) + { + throw new Exception("User does not have the correct security version!"); + } - usr.SecurityVersion = securityVersion; - context.Users.Update(usr); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to update user!") : ResultOld.Ok(); - } - catch (Exception e) - { - return HandleException("An error occured while updating the security version.", e); - } + usr.SecurityVersion = securityVersion; + context.Users.Update(usr); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0; } - public async Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.SecurityVersion).FirstOrDefaultAsync(cancellationToken); - return ResultOld.HandleResult(usrVersion, "Failed to get user security version!"); - } - catch (Exception e) - { - return HandleExceptionResult("An error occured while getting the user security version.", e); - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var usrVersion = await context.Users.Where(u => u.Id == user.Id).Select(u => u.SecurityVersion).FirstOrDefaultAsync(cancellationToken); + return usrVersion; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index adb54b1..64e5f65 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -44,9 +44,9 @@ public partial class AuthorityManager public async Task AddRolesToUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) { var usrValidation = await IsValidUserAsync(user, cancellationToken); - if (!usrValidation.Success) + if (!usrValidation.IsSuccess) { - return ResultError.Fail("Validation for user failed!"); + return usrValidation; } var linkedRoles = await RoleRepository.GetRolesFromLinkAsync(user.Id, roles, cancellationToken); @@ -65,9 +65,9 @@ public partial class AuthorityManager public async Task RemoveRolesFromUserAsync(List roles, AuthorityUser user, CancellationToken cancellationToken = default) { var usrValidation = await IsValidUserAsync(user, cancellationToken); - if (!usrValidation.Success) + if (!usrValidation.IsSuccess) { - return ResultError.Fail("Validation for user failed!"); + return usrValidation; } var linkedRoles = await RoleRepository.GetRolesFromLinkAsync(user.Id, roles, cancellationToken); @@ -95,20 +95,15 @@ public partial class AuthorityManager var linkResult = await RoleRepository.AddRolesLinkAsync(rolesToAdd, group.Id, cancellationToken); return linkResult ? Result.Success() : ResultError.Fail("Failed to add roles."); } - - /// - /// Get all roles (including group roles) that the user has. - /// - /// The user to get the roles from - /// - public async Task>> GetUserRolesAsync(AuthorityUser user, CancellationToken cancellationToken = default) + + public async Task>> GetAllUserRolesAsync(AuthorityUser user, CancellationToken cancellationToken = default) { try { var usrValidation = await IsValidUserAsync(user, cancellationToken); - if (!usrValidation.Success) + if (!usrValidation.IsSuccess) { - return ResultError.Fail("Invalid user"); + return usrValidation.Error ?? ResultError.Fail("User validation failed."); } var searchIds = new List { user.Id }; diff --git a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs index 81a99e4..3a15a4c 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityUserManager.cs @@ -1,7 +1,8 @@ using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Models.Validation; -using ValidationResult = DotBased.AspNet.Authority.Monads.ValidationResult; +using DotBased.AspNet.Authority.Monads; +using DotBased.Monads; namespace DotBased.AspNet.Authority.Managers; @@ -35,28 +36,33 @@ public partial class AuthorityManager return errors.Count > 0 ? ValidationResult.Fail(errors) : ValidationResult.Success(); } - public async Task> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken cancellationToken = default) + public async Task>> SearchUsersAsync(string query, int maxResults = 20, int offset = 0, CancellationToken cancellationToken = default) { var result = await UserRepository.GetAuthorityUsersAsync(maxResults, offset, query, cancellationToken); return result; } - public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken cancellationToken = default) + public async Task> UpdatePasswordAsync(AuthorityUser user, string password, CancellationToken cancellationToken = default) { var passwordValidation = await ValidatePasswordAsync(user, password); if (!passwordValidation.IsSuccess) { - return AuthorityResultOldOld.Failed(passwordValidation.ValidationErrors, ResultFailReason.Validation); + return passwordValidation.ValidationErrors.ToList(); } user.PasswordHash = await PasswordHasher.HashPasswordAsync(password); user.SecurityVersion = GenerateVersion(); var updateResult = await UserRepository.UpdateUserAsync(user, cancellationToken); - return AuthorityResultOldOld.FromResult(updateResult); + if (updateResult == null) + { + return ResultError.Fail("Failed to update user password."); + } + + return updateResult; } - public async Task> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken cancellationToken = default) + public async Task> CreateUserAsync(AuthorityUser userModel, string password, CancellationToken cancellationToken = default) { var userValidation = await ValidateUserAsync(userModel); var passwordValidation = await ValidatePasswordAsync(userModel, password); @@ -65,7 +71,7 @@ public partial class AuthorityManager List errors = []; errors.AddRange(userValidation.ValidationErrors); errors.AddRange(passwordValidation.ValidationErrors); - return AuthorityResultOldOld.Failed(errors, ResultFailReason.Validation); + return errors; } userModel.Version = GenerateVersion(); @@ -74,25 +80,34 @@ public partial class AuthorityManager userModel.PasswordHash = hashedPassword; var userCreationResult = await UserRepository.CreateUserAsync(userModel, cancellationToken); - - return AuthorityResultOldOld.FromResult(userCreationResult); + if (userCreationResult == null) + { + return ResultError.Fail("Failed to create user."); + } + + return userCreationResult; } - public async Task> UpdateUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) + public async Task> UpdateUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) { var updateResult = await UserRepository.UpdateUserAsync(model, cancellationToken); + if (updateResult == null) + { + return ResultError.Fail("Failed to update user."); + } + return updateResult; } - public async Task DeleteUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) + public async Task DeleteUserAsync(AuthorityUser model, CancellationToken cancellationToken = default) { - var deleteResult = await UserRepository.DeleteUserAsync(model, cancellationToken); - return deleteResult; + var deleteResult = await UserRepository.DeleteUsersAsync([model], cancellationToken); + return deleteResult ? Result.Success() : ResultError.Fail("Failed to delete user."); } - public async Task IsValidUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task IsValidUserAsync(AuthorityUser user, CancellationToken cancellationToken = default) { var usrResult = await UserRepository.GetVersionAsync(user, cancellationToken); - return usrResult; + return usrResult == 0 ? ResultError.Fail("Invalid user version detected.") : Result.Success(); } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Monads/AuthorityResult.cs b/DotBased.AspNet.Authority/Monads/AuthorityResult.cs new file mode 100644 index 0000000..ee57341 --- /dev/null +++ b/DotBased.AspNet.Authority/Monads/AuthorityResult.cs @@ -0,0 +1,40 @@ +using DotBased.AspNet.Authority.Models.Validation; +using DotBased.Monads; + +namespace DotBased.AspNet.Authority.Monads; + +public class AuthorityResult : Result +{ + protected AuthorityResult(TResult result) : base(result) + { + } + + protected AuthorityResult(Exception exception) : base(exception) + { + } + + protected AuthorityResult(ResultError error) : base(error) + { + } + + protected AuthorityResult(List validationErrors) : base(ResultError.Fail("Validation failed!")) + { + _validationErrors = validationErrors; + } + + private readonly List _validationErrors = []; + public IReadOnlyList ValidationErrors => _validationErrors; + + public static implicit operator AuthorityResult(TResult result) => new(result); + public static implicit operator AuthorityResult(Exception exception) => new(exception); + public static implicit operator AuthorityResult(ResultError error) => new(error); + public static implicit operator AuthorityResult(List validationErrors) => new(validationErrors); + + public static AuthorityResult FromResult(Result result) + { + var authorityResult = result.Match>( + r => new AuthorityResult(r), + error => new AuthorityResult(error)); + return authorityResult; + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Monads/ValidationResult.cs b/DotBased.AspNet.Authority/Monads/ValidationResult.cs index a07ff0c..3a9e679 100644 --- a/DotBased.AspNet.Authority/Monads/ValidationResult.cs +++ b/DotBased.AspNet.Authority/Monads/ValidationResult.cs @@ -32,10 +32,10 @@ public class ValidationResult : Result public static ValidationResult FromResult(Result result) { - var authorityResult = result.Match( + var validationResult = result.Match( () => new ValidationResult(), error => new ValidationResult(error)); - return authorityResult; + return validationResult; } public new static ValidationResult Success() => new(); diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index 97cc30c..809c02a 100755 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -1,17 +1,18 @@ +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; namespace DotBased.AspNet.Authority.Repositories; public interface IUserRepository { - public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetAuthorityUserByIdAsync(string id, CancellationToken cancellationToken = default); - public Task> CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task> UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task DeleteUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default); - public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default); - public Task> GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default); - public Task> GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task> GetAuthorityUsersAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task GetAuthorityUserByIdAsync(Guid id, CancellationToken cancellationToken = default); + public Task CreateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task UpdateUserAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task DeleteUsersAsync(List users, CancellationToken cancellationToken = default); + public Task GetUserByEmailAsync(string email, CancellationToken cancellationToken = default); + public Task SetVersionAsync(AuthorityUser user, long version, CancellationToken cancellationToken = default); + public Task GetVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task SetSecurityVersionAsync(AuthorityUser user, long securityVersion, CancellationToken cancellationToken = default); + public Task GetSecurityVersionAsync(AuthorityUser user, CancellationToken cancellationToken = default); } \ No newline at end of file From ec7e2605114e105e7dc3e2d58b6355e2611d5b64 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 11 Apr 2025 21:07:45 +0200 Subject: [PATCH 36/38] [REMOVE] Remove old AuthorityResult --- .../Models/AuthorityResultOldOld.cs | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100755 DotBased.AspNet.Authority/Models/AuthorityResultOldOld.cs diff --git a/DotBased.AspNet.Authority/Models/AuthorityResultOldOld.cs b/DotBased.AspNet.Authority/Models/AuthorityResultOldOld.cs deleted file mode 100755 index b12a05d..0000000 --- a/DotBased.AspNet.Authority/Models/AuthorityResultOldOld.cs +++ /dev/null @@ -1,41 +0,0 @@ -using DotBased.AspNet.Authority.Models.Validation; - -namespace DotBased.AspNet.Authority.Models; - -public class AuthorityResultOldOld : ResultOld -{ - public static AuthorityResultOldOld FromResult(ResultOld resultOld) => new AuthorityResultOldOld(resultOld); - - public AuthorityResultOldOld(ResultOld resultOld) : base(resultOld) - { - Reason = ResultFailReason.Unknown; - } - - public AuthorityResultOldOld(bool success, string errorMessage = "", TResultValue? value = default, ResultFailReason reason = ResultFailReason.None, IReadOnlyList? errors = null) : base(success, errorMessage, value, null) - { - Success = success; - Message = errorMessage; - Value = value; - Reason = reason; - ValidationErrors = errors; - } - public ResultFailReason Reason { get; } - public IReadOnlyList? ValidationErrors { get; } - - - public new static AuthorityResultOldOld Ok(TResultValue? value) => new AuthorityResultOldOld(true, value:value); - - public static AuthorityResultOldOld Error(string errorMessage, ResultFailReason reason = ResultFailReason.Error) => - new AuthorityResultOldOld(false, errorMessage, reason:reason); - - public static AuthorityResultOldOld Failed(IReadOnlyList errors, ResultFailReason reason = ResultFailReason.None) - => new AuthorityResultOldOld(false, errors:errors, reason:reason); -} - -public enum ResultFailReason -{ - None, - Unknown, - Validation, - Error -} \ No newline at end of file From 7ed219d08a6ac93b711b315c2193a46e9d770224 Mon Sep 17 00:00:00 2001 From: max Date: Sat, 12 Apr 2025 14:20:40 +0200 Subject: [PATCH 37/38] [CHANGE] Moved repositories to new monads --- .../Repositories/AttributeRepository.cs | 134 +++++++--------- .../Repositories/GroupRepository.cs | 150 +++++++----------- .../Managers/AuthorityGroupManager.cs | 12 +- .../Managers/AuthorityRoleManager.cs | 4 +- .../Repositories/IAttributeRepository.cs | 11 +- .../Repositories/IGroupRepository.cs | 13 +- 6 files changed, 135 insertions(+), 189 deletions(-) diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs index dd25a1f..6660de3 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/AttributeRepository.cs @@ -1,114 +1,86 @@ +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class AttributeRepository(IDbContextFactory contextFactory) : RepositoryBase, IAttributeRepository +public class AttributeRepository(IDbContextFactory contextFactory, ILogger logger) : RepositoryBase, IAttributeRepository { - public async Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", + public async Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var query = context.Attributes.AsQueryable(); + if (!string.IsNullOrEmpty(search)) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var query = context.Attributes.AsQueryable(); - if (!string.IsNullOrEmpty(search)) - { - query = query.Where(a => $"{a.AttributeKey} {a.ForeignKey} {a.AttributeValue}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); - } + query = query.Where(a => $"{a.AttributeKey} {a.ForeignKey} {a.AttributeValue}".Contains(search, StringComparison.CurrentCultureIgnoreCase)); + } - var total = await query.CountAsync(cancellationToken); - var select = await query.OrderBy(a => a.AttributeKey).Skip(offset).Take(limit).Select(a => new AuthorityAttributeItem() - { - BoundId = a.ForeignKey, - AttributeKey = a.AttributeKey, - AttributeValue = a.AttributeValue - }).ToListAsync(cancellationToken); - return ListResultOld.Ok(select, total, limit, offset); - } - catch (Exception e) + var total = await query.CountAsync(cancellationToken); + var select = await query.OrderBy(a => a.AttributeKey).Skip(offset).Take(limit).Select(a => new AuthorityAttributeItem() { - return HandleExceptionListResult("Failed to get attributes", e); - } + BoundId = a.ForeignKey, + AttributeKey = a.AttributeKey, + AttributeValue = a.AttributeValue + }).ToListAsync(cancellationToken); + return QueryItems.Create(select, total, limit, offset); } - public async Task> GetAttributeByKeyAsync(string key, CancellationToken cancellationToken = default) + public async Task GetAttributeByKeyAsync(string key, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var attribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == key, cancellationToken); - return attribute == null ? ResultOld.Failed("Attribute not found") : ResultOld.Ok(attribute); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to get attribute by id", e); - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + return await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == key, cancellationToken); } - public async Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task CreateAttributeAsync(AuthorityAttribute attribute, + CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (string.IsNullOrWhiteSpace(attribute.AttributeKey) || attribute.ForeignKey == Guid.Empty) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if (string.IsNullOrWhiteSpace(attribute.AttributeKey) || attribute.ForeignKey == Guid.Empty) - { - return ResultOld.Failed("Attribute key and/or bound id is empty"); - } - var entry = context.Attributes.Add(attribute); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to create attribute") : ResultOld.Ok(entry.Entity); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to create attribute", e); + throw new Exception($"Attribute {attribute.AttributeKey} not found"); } + + var entry = context.Attributes.Add(attribute); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0 ? entry.Entity : null; } - public async Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentAttribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == attribute.AttributeKey, cancellationToken); + if (currentAttribute == null) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var currentAttribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == attribute.AttributeKey, cancellationToken); - if (currentAttribute == null) - { - return ResultOld.Failed("Attribute not found"); - } + return null; + } - if (currentAttribute.Version != attribute.Version) - { - return ResultOld.Failed("Attribute version doesn't match"); - } + if (currentAttribute.Version != attribute.Version) + { + logger.LogError("Attribute version validation failed for attribute {attribute}", currentAttribute.AttributeKey); + return null; + } - var entry = context.Attributes.Update(currentAttribute); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to update attribute") : ResultOld.Ok(entry.Entity); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to update attribute", e); - } + var entry = context.Attributes.Update(currentAttribute); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0 ? entry.Entity : null; } - public async Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) + public async Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentAttribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == attribute.AttributeKey, cancellationToken); + + if (currentAttribute == null) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var currentAttribute = await context.Attributes.FirstOrDefaultAsync(a => a.AttributeKey == attribute.AttributeKey, cancellationToken); - if (currentAttribute == null) - { - return ResultOld.Failed("Attribute not found"); - } - context.Attributes.Remove(currentAttribute); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to delete attribute") : ResultOld.Ok(); - } - catch (Exception e) - { - return HandleException("Failed to delete attribute", e); + logger.LogError("Attribute not found."); + return false; } + + context.Attributes.Remove(currentAttribute); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs index 74e3a0c..3e3afcc 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/GroupRepository.cs @@ -1,130 +1,94 @@ +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; using DotBased.AspNet.Authority.Repositories; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; namespace DotBased.AspNet.Authority.EFCore.Repositories; -public class GroupRepository(IDbContextFactory contextFactory) : RepositoryBase, IGroupRepository +public class GroupRepository(IDbContextFactory contextFactory, ILogger logger) : RepositoryBase, IGroupRepository { - public async Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) + public async Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var query = context.Groups.AsQueryable(); + if (!string.IsNullOrWhiteSpace(search)) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var query = context.Groups.AsQueryable(); - if (!string.IsNullOrWhiteSpace(search)) - { - query = query.Where(g => $"{g.Name} {g.Id}".Contains(search)); - } - var total = await query.CountAsync(cancellationToken); - var select = await query.OrderBy(g => g.Name).Skip(offset).Take(limit).Select(g => new AuthorityGroupItem() - { - Id = g.Id, - Name = g.Name - }).ToListAsync(cancellationToken); - return ListResultOld.Ok(select, total, limit, offset); + query = query.Where(g => $"{g.Name} {g.Id}".Contains(search)); } - catch (Exception e) + var total = await query.CountAsync(cancellationToken); + var select = await query.OrderBy(g => g.Name).Skip(offset).Take(limit).Select(g => new AuthorityGroupItem() { - return HandleExceptionListResult("Failed to get Groups", e); - } + Id = g.Id, + Name = g.Name + }).ToListAsync(cancellationToken); + return QueryItems.Create(select, total, limit, offset); } - public async Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default) + public async Task GetGroupByIdAsync(string id, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!Guid.TryParse(id, out var groupId)) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if (!Guid.TryParse(id, out var groupId)) - { - return ResultOld.Failed("Invalid group id"); - } - var group = await context.Groups.Where(g => g.Id == groupId).Include(g => g.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); - return ResultOld.HandleResult(group, "Group not found"); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to get Group", e); + throw new Exception($"Invalid group id: {id}"); } + + return await context.Groups.Where(g => g.Id == groupId).Include(g => g.Attributes).FirstOrDefaultAsync(cancellationToken: cancellationToken); } - public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - try - { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var userJoinGroups = context.UserGroups.Where(ug => ug.UserId == user.Id).Select(ug => ug.GroupId); - var userGroups = context.Groups.Where(g => userJoinGroups.Contains(g.Id)); - return ListResultOld.Ok(userGroups, userGroups.Count()); - } - catch (Exception e) - { - return HandleExceptionListResult("Failed to get Groups", e); - } + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var userJoinGroups = context.UserGroups.Where(ug => ug.UserId == user.Id).Select(ug => ug.GroupId); + var userGroups = context.Groups.Where(g => userJoinGroups.Contains(g.Id)); + return userGroups.ToList(); } - public async Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + if (group.Id == Guid.Empty) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - if (group.Id == Guid.Empty) - { - return ResultOld.Failed("Id cannot be empty."); - } - var entry = context.Groups.Add(group); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to create group.") : ResultOld.Ok(entry.Entity); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to create group!", e); + throw new Exception($"Invalid group id: {group.Id}"); } + var entry = context.Groups.Add(group); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0 ? entry.Entity : null; } - public async Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentGroup = await context.Groups.FirstOrDefaultAsync(g => g.Id == group.Id ,cancellationToken); + if (currentGroup == null) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var currentGroup = await context.Groups.FirstOrDefaultAsync(g => g.Id == group.Id ,cancellationToken); - if (currentGroup == null) - { - return ResultOld.Failed("Group not found."); - } + logger.LogError("Group with id {groupId} not found.", group.Id); + return null; + } - if (currentGroup.Version != group.Version) - { - return ResultOld.Failed("Group version does not match, version validation failed!"); - } + if (currentGroup.Version != group.Version) + { + logger.LogError("Group version validation failed."); + return null; + } - var entry = context.Groups.Update(group); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to update group.") : ResultOld.Ok(entry.Entity); - } - catch (Exception e) - { - return HandleExceptionResult("Failed to update group!", e); - } + var entry = context.Groups.Update(group); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0 ? entry.Entity : null; } - public async Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) + public async Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default) { - try + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + var currentGroup = await context.Groups.FirstOrDefaultAsync(g => g.Id == group.Id, cancellationToken); + if (currentGroup == null) { - await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var currentGroup = await context.Groups.FirstOrDefaultAsync(g => g.Id == group.Id, cancellationToken); - if (currentGroup == null) - { - return ResultOld.Failed("Group not found, cannot delete group!"); - } - context.Groups.Remove(currentGroup); - var saveResult = await context.SaveChangesAsync(cancellationToken); - return saveResult <= 0 ? ResultOld.Failed("Failed to delete group.") : ResultOld.Ok(); - } - catch (Exception e) - { - return HandleException("Failed to delete group!", e); + logger.LogError("Group with id {groupId} not found.", group.Id); + return false; } + context.Groups.Remove(currentGroup); + var saveResult = await context.SaveChangesAsync(cancellationToken); + return saveResult != 0; } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs index aafc5c9..0f0a174 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityGroupManager.cs @@ -1,11 +1,19 @@ using DotBased.AspNet.Authority.Models.Authority; +using DotBased.Monads; namespace DotBased.AspNet.Authority.Managers; public partial class AuthorityManager { - public async Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) + public async Task>> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default) { - return await GroupRepository.GetUserGroupsAsync(user, cancellationToken); + try + { + return await GroupRepository.GetUserGroupsAsync(user, cancellationToken); + } + catch (Exception e) + { + return e; + } } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs index 64e5f65..9250222 100755 --- a/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs +++ b/DotBased.AspNet.Authority/Managers/AuthorityRoleManager.cs @@ -109,9 +109,9 @@ public partial class AuthorityManager var searchIds = new List { user.Id }; var usrGroups = await GetUserGroupsAsync(user, cancellationToken); - if (usrGroups.Success) + if (usrGroups.IsSuccess) { - searchIds.AddRange(usrGroups.Items.Select(g => g.Id).ToList()); + searchIds.AddRange(usrGroups.Value.Select(g => g.Id).ToList()); } var linkedRolesResult = await RoleRepository.GetLinkedRolesAsync(searchIds, cancellationToken); diff --git a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs index 2a5fd6e..fcf61a5 100755 --- a/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IAttributeRepository.cs @@ -1,12 +1,13 @@ +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; namespace DotBased.AspNet.Authority.Repositories; public interface IAttributeRepository { - public Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetAttributeByKeyAsync(string id, CancellationToken cancellationToken = default); - public Task> CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); - public Task> UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); - public Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); + public Task> GetAttributesAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task GetAttributeByKeyAsync(string id, CancellationToken cancellationToken = default); + public Task CreateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); + public Task UpdateAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); + public Task DeleteAttributeAsync(AuthorityAttribute attribute, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs index 7cc8d59..66ff9c2 100755 --- a/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IGroupRepository.cs @@ -1,13 +1,14 @@ +using DotBased.AspNet.Authority.Models; using DotBased.AspNet.Authority.Models.Authority; namespace DotBased.AspNet.Authority.Repositories; public interface IGroupRepository { - public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); - public Task> GetGroupByIdAsync(string id, CancellationToken cancellationToken = default); - public Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default); - public Task> CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); - public Task> UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); - public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task> GetGroupsAsync(int limit = 20, int offset = 0, string search = "", CancellationToken cancellationToken = default); + public Task GetGroupByIdAsync(string id, CancellationToken cancellationToken = default); + public Task> GetUserGroupsAsync(AuthorityUser user, CancellationToken cancellationToken = default); + public Task CreateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task UpdateGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); + public Task DeleteGroupAsync(AuthorityGroup group, CancellationToken cancellationToken = default); } \ No newline at end of file From 095b66f6f3a9d22e367b6decbc7736f4be12bb7c Mon Sep 17 00:00:00 2001 From: max Date: Sat, 12 Apr 2025 14:26:47 +0200 Subject: [PATCH 38/38] [CHANGE] Removed old result monad --- .../Repositories/RepositoryBase.cs | 15 +-- DotBased/ResultOld.cs | 94 ------------------- DotBased/Utilities/Cryptography.cs | 27 +++--- 3 files changed, 15 insertions(+), 121 deletions(-) delete mode 100755 DotBased/ResultOld.cs diff --git a/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs b/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs index 89fbf20..a32d566 100644 --- a/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs +++ b/DotBased.AspNet.Authority.EFCore/Repositories/RepositoryBase.cs @@ -2,18 +2,5 @@ namespace DotBased.AspNet.Authority.EFCore.Repositories; public abstract class RepositoryBase { - protected ResultOld HandleExceptionResult(string message, Exception ex) => new(HandleException(message, ex)); - - protected ListResultOld HandleExceptionListResult(string message, Exception ex) => - new(HandleException(message, ex)); - - protected ResultOld HandleException(string message, Exception ex) - { - if (ex is OperationCanceledException oce) - { - return ResultOld.Failed("Operation cancelled.", oce); - } - - return ResultOld.Failed(message, ex); - } + } \ No newline at end of file diff --git a/DotBased/ResultOld.cs b/DotBased/ResultOld.cs deleted file mode 100755 index 5805d6c..0000000 --- a/DotBased/ResultOld.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace DotBased; - -/// -/// Simple result class for returning a result state or a message and an exception. -/// -public class ResultOld -{ - public ResultOld(bool success, string message, Exception? exception) - { - Success = success; - Message = message; - Exception = exception; - } - - public ResultOld(ResultOld bObj) - { - Success = bObj.Success; - Message = bObj.Message; - Exception = bObj.Exception; - } - - public bool Success { get; set; } - public string Message { get; set; } - public Exception? Exception { get; set; } - - public static ResultOld Ok() => new(true, string.Empty, null); - public static ResultOld Failed(string message, Exception? exception = null) => new(false, message, exception); -} - -public class ResultOld : ResultOld -{ - public ResultOld(bool success, string message, TValue? value, Exception? exception) : base(success, message, exception) - { - Value = value; - } - public ResultOld(ResultOld bObj) : base(bObj) - { - - } - public TValue? Value { get; set; } - - public static ResultOld Ok(TValue value) => new(true, string.Empty, value, null); - - public new static ResultOld Failed(string message, Exception? exception = null) => - new(false, message, default, exception); - - public static ResultOld HandleResult(TValue? value, string failedMessage, Exception? exception = null) - { - return value == null ? Failed(failedMessage, exception) : Ok(value); - } -} - -public class ListResultOld : ResultOld -{ - public ListResultOld(bool success, string message, int totalCount, IEnumerable? items, int limit = -1, int offset = -1, Exception? exception = null) : base(success, message, exception) - { - Items = items != null ? new List(items) : new List(); - TotalCount = totalCount; - Limit = limit; - Offset = offset; - } - - public ListResultOld(ResultOld bObj) : base(bObj) - { - Items = new List(); - } - - public readonly IReadOnlyList Items; - /// - /// The amount of items that this result contains. - /// - public int Count => Items.Count; - - /// - /// The total amount of item that is available. - /// - public int TotalCount { get; } - - /// - /// The limit this result contains - /// - public int Limit { get; } - - /// - /// The offset this result has the items from. - /// - public int Offset { get; } - - public static ListResultOld Ok(IEnumerable items, int totalCount = -1, int limit = -1, int offset = -1) => - new(true, string.Empty, totalCount, items, limit, offset); - - public new static ListResultOld Failed(string message, Exception? exception = null) => - new(false, message, -1, null, exception: exception); -} \ No newline at end of file diff --git a/DotBased/Utilities/Cryptography.cs b/DotBased/Utilities/Cryptography.cs index c0fd022..31cc651 100755 --- a/DotBased/Utilities/Cryptography.cs +++ b/DotBased/Utilities/Cryptography.cs @@ -1,4 +1,5 @@ using System.Security.Cryptography; +using DotBased.Monads; namespace DotBased.Utilities; @@ -7,12 +8,12 @@ public static class Cryptography /* * https://gist.github.com/therightstuff/aa65356e95f8d0aae888e9f61aa29414 */ - public static ResultOld ExportPublicKeyToPem(RSACryptoServiceProvider csp) + public static Result ExportPublicKeyToPem(RSACryptoServiceProvider csp) { var outputStream = new StringWriter(); var parameters = csp.ExportParameters(false); if (parameters.Exponent == null || parameters.Modulus == null) - return ResultOld.Failed("RSAParameters are empty!"); + return ResultError.Fail("RSAParameters are empty!"); using (var stream = new MemoryStream()) { var writer = new BinaryWriter(stream); @@ -23,7 +24,7 @@ public static class Cryptography innerWriter.Write((byte)0x30); // SEQUENCE EncodeLength(innerWriter, 13); innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER - byte[] rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 }; + var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 }; EncodeLength(innerWriter, rsaEncryptionOid.Length); innerWriter.Write(rsaEncryptionOid); innerWriter.Write((byte)0x05); // NULL @@ -44,20 +45,20 @@ public static class Cryptography bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength); } - int bitStringLength = (int)bitStringStream.Length; + var bitStringLength = (int)bitStringStream.Length; EncodeLength(innerWriter, bitStringLength); innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength); } - int length = (int)innerStream.Length; + var length = (int)innerStream.Length; EncodeLength(writer, length); writer.Write(innerStream.GetBuffer(), 0, length); } - char[] base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray(); + var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray(); // WriteLine terminates with \r\n, we want only \n outputStream.Write("-----BEGIN PUBLIC KEY-----\n"); - for (int i = 0; i < base64.Length; i += 64) + for (var i = 0; i < base64.Length; i += 64) { outputStream.Write(base64, i, Math.Min(64, base64.Length - i)); outputStream.Write("\n"); @@ -66,7 +67,7 @@ public static class Cryptography outputStream.Write("-----END PUBLIC KEY-----"); } - return ResultOld.Ok(outputStream.ToString()); + return outputStream.ToString(); } private static void EncodeLength(BinaryWriter stream, int length) @@ -82,15 +83,15 @@ public static class Cryptography default: { // Long form - int temp = length; - int bytesRequired = 0; + var temp = length; + var bytesRequired = 0; while (temp > 0) { temp >>= 8; bytesRequired++; } stream.Write((byte)(bytesRequired | 0x80)); - for (int i = bytesRequired - 1; i >= 0; i--) + for (var i = bytesRequired - 1; i >= 0; i--) { stream.Write((byte)(length >> (8 * i) & 0xff)); } @@ -102,7 +103,7 @@ public static class Cryptography private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true) { stream.Write((byte)0x02); // INTEGER - int prefixZeros = value.TakeWhile(t => t == 0).Count(); + var prefixZeros = value.TakeWhile(t => t == 0).Count(); if (value.Length - prefixZeros == 0) { EncodeLength(stream, 1); @@ -120,7 +121,7 @@ public static class Cryptography { EncodeLength(stream, value.Length - prefixZeros); } - for (int i = prefixZeros; i < value.Length; i++) + for (var i = prefixZeros; i < value.Length; i++) { stream.Write(value[i]); }