From 611f695db69b262abd32ecba7704703f9808f915 Mon Sep 17 00:00:00 2001 From: Chris McInnes Date: Sat, 11 Apr 2026 19:03:45 +0930 Subject: [PATCH] Granular permission back end done --- .../Areas/Admin/Controllers/HomeController.cs | 17 + .../Admin/Controllers/PermissionController.cs | 23 + FJPSite/Areas/Admin/Views/Home/Index.cshtml | 6 + .../Areas/Admin/Views/Shared/_Layout.cshtml | 14 + FJPSite/Areas/Admin/Views/_ViewImports.cshtml | 3 + FJPSite/Areas/Admin/Views/_ViewStart.cshtml | 3 + FJPSite/Attributes/HasPermissionAttribute.cs | 20 + FJPSite/Attributes/SeedMigrationAttribute.cs | 7 + FJPSite/Controllers/HomeController.cs | 30 +- FJPSite/Data/ApplicationDbContext.cs | 29 +- FJPSite/Data/Authorisation/Feature.cs | 14 + FJPSite/Data/Authorisation/FeatureAction.cs | 22 + FJPSite/Data/Authorisation/Module.cs | 14 + FJPSite/Data/Authorisation/ModuleFeature.cs | 21 + .../Data/Authorisation/PermissionAction.cs | 14 + FJPSite/Data/Authorisation/RolePermission.cs | 21 + FJPSite/Data/Authorisation/UserPermission.cs | 22 + FJPSite/Data/Identity/RoleEntity.cs | 9 + FJPSite/Data/Identity/UserEntity.cs | 10 + ...000000000_CreateIdentitySchema.Designer.cs | 277 --------- .../00000000000000_CreateIdentitySchema.cs | 220 ------- .../ApplicationDbContextModelSnapshot.cs | 275 --------- FJPSite/Data/SoftDeleteEntityBase.cs | 11 + FJPSite/Enums/ActionEnum.cs | 16 + FJPSite/Enums/FeatureEnum.cs | 11 + FJPSite/Enums/ModuleEnum.cs | 8 + FJPSite/Enums/RoleEnum.cs | 8 + FJPSite/Extensions/DbContextExtension.cs | 11 + FJPSite/Factories/DbSeederFactory.cs | 39 ++ .../EntitySeeders/EntitySeederFactory.cs | 13 + .../EntitySeeders/IdentitySeederFactory.cs | 90 +++ .../EntitySeeders/PermissionSeederFactory.cs | 174 ++++++ FJPSite/Factories/UserClaimFactory.cs | 23 + .../PermissionAuthorizationHandler.cs | 45 ++ FJPSite/Helpers/PermissionRequirement.cs | 15 + FJPSite/Interfaces/IEntitySeederFactory.cs | 9 + FJPSite/Interfaces/IPermissionService.cs | 13 + FJPSite/Interfaces/ISoftDeleteEntity.cs | 10 + .../20260409010423_CreateIdentity.Designer.cs | 499 ++++++++++++++++ .../20260409010423_CreateIdentity.cs | 392 +++++++++++++ ...260409123724_CreatePermissions.Designer.cs | 542 ++++++++++++++++++ .../20260409123724_CreatePermissions.cs | 423 ++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 539 +++++++++++++++++ FJPSite/Models/ActionPermissionModel.cs | 9 + FJPSite/Models/ErrorViewModel.cs | 13 +- FJPSite/Models/FeaturePermissionModel.cs | 8 + FJPSite/Models/PermissionModel.cs | 9 + FJPSite/Models/PermissionStructureModel.cs | 8 + FJPSite/Program.cs | 32 +- FJPSite/Services/PermissionService.cs | 73 +++ FJPSite/Views/Shared/_LoginPartial.cshtml | 11 +- 51 files changed, 3322 insertions(+), 803 deletions(-) create mode 100644 FJPSite/Areas/Admin/Controllers/HomeController.cs create mode 100644 FJPSite/Areas/Admin/Controllers/PermissionController.cs create mode 100644 FJPSite/Areas/Admin/Views/Home/Index.cshtml create mode 100644 FJPSite/Areas/Admin/Views/Shared/_Layout.cshtml create mode 100644 FJPSite/Areas/Admin/Views/_ViewImports.cshtml create mode 100644 FJPSite/Areas/Admin/Views/_ViewStart.cshtml create mode 100644 FJPSite/Attributes/HasPermissionAttribute.cs create mode 100644 FJPSite/Attributes/SeedMigrationAttribute.cs create mode 100644 FJPSite/Data/Authorisation/Feature.cs create mode 100644 FJPSite/Data/Authorisation/FeatureAction.cs create mode 100644 FJPSite/Data/Authorisation/Module.cs create mode 100644 FJPSite/Data/Authorisation/ModuleFeature.cs create mode 100644 FJPSite/Data/Authorisation/PermissionAction.cs create mode 100644 FJPSite/Data/Authorisation/RolePermission.cs create mode 100644 FJPSite/Data/Authorisation/UserPermission.cs create mode 100644 FJPSite/Data/Identity/RoleEntity.cs create mode 100644 FJPSite/Data/Identity/UserEntity.cs delete mode 100644 FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs delete mode 100644 FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.cs delete mode 100644 FJPSite/Data/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 FJPSite/Data/SoftDeleteEntityBase.cs create mode 100644 FJPSite/Enums/ActionEnum.cs create mode 100644 FJPSite/Enums/FeatureEnum.cs create mode 100644 FJPSite/Enums/ModuleEnum.cs create mode 100644 FJPSite/Enums/RoleEnum.cs create mode 100644 FJPSite/Extensions/DbContextExtension.cs create mode 100644 FJPSite/Factories/DbSeederFactory.cs create mode 100644 FJPSite/Factories/EntitySeeders/EntitySeederFactory.cs create mode 100644 FJPSite/Factories/EntitySeeders/IdentitySeederFactory.cs create mode 100644 FJPSite/Factories/EntitySeeders/PermissionSeederFactory.cs create mode 100644 FJPSite/Factories/UserClaimFactory.cs create mode 100644 FJPSite/Handlers/PermissionAuthorizationHandler.cs create mode 100644 FJPSite/Helpers/PermissionRequirement.cs create mode 100644 FJPSite/Interfaces/IEntitySeederFactory.cs create mode 100644 FJPSite/Interfaces/IPermissionService.cs create mode 100644 FJPSite/Interfaces/ISoftDeleteEntity.cs create mode 100644 FJPSite/Migrations/20260409010423_CreateIdentity.Designer.cs create mode 100644 FJPSite/Migrations/20260409010423_CreateIdentity.cs create mode 100644 FJPSite/Migrations/20260409123724_CreatePermissions.Designer.cs create mode 100644 FJPSite/Migrations/20260409123724_CreatePermissions.cs create mode 100644 FJPSite/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 FJPSite/Models/ActionPermissionModel.cs create mode 100644 FJPSite/Models/FeaturePermissionModel.cs create mode 100644 FJPSite/Models/PermissionModel.cs create mode 100644 FJPSite/Models/PermissionStructureModel.cs create mode 100644 FJPSite/Services/PermissionService.cs diff --git a/FJPSite/Areas/Admin/Controllers/HomeController.cs b/FJPSite/Areas/Admin/Controllers/HomeController.cs new file mode 100644 index 0000000..918f109 --- /dev/null +++ b/FJPSite/Areas/Admin/Controllers/HomeController.cs @@ -0,0 +1,17 @@ +using FJPSite.Data.Identity; +using FJPSite.Enums; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace FJPSite.Areas.Admin.Controllers; + +[Area("Admin")] +[Authorize] +public class HomeController : Controller +{ + public IActionResult Index() + { + return View(); + } +} diff --git a/FJPSite/Areas/Admin/Controllers/PermissionController.cs b/FJPSite/Areas/Admin/Controllers/PermissionController.cs new file mode 100644 index 0000000..96eb6ae --- /dev/null +++ b/FJPSite/Areas/Admin/Controllers/PermissionController.cs @@ -0,0 +1,23 @@ +using FJPSite.Attributes; +using FJPSite.Enums; +using FJPSite.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace FJPSite.Areas.Admin.Controllers; + +public class PermissionController : Controller +{ + private readonly IPermissionService _permissionService; + + public PermissionController(IPermissionService permissionService) + { + _permissionService = permissionService; + } + + [HasPermission(FeatureEnum.Permissions, ActionEnum.View)] + public IActionResult Index() + { + + return View(); + } +} diff --git a/FJPSite/Areas/Admin/Views/Home/Index.cshtml b/FJPSite/Areas/Admin/Views/Home/Index.cshtml new file mode 100644 index 0000000..067b0b1 --- /dev/null +++ b/FJPSite/Areas/Admin/Views/Home/Index.cshtml @@ -0,0 +1,6 @@ +@* + For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 +*@ +@{ +} +THIS IS ADMIN HOME \ No newline at end of file diff --git a/FJPSite/Areas/Admin/Views/Shared/_Layout.cshtml b/FJPSite/Areas/Admin/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..c014aec --- /dev/null +++ b/FJPSite/Areas/Admin/Views/Shared/_Layout.cshtml @@ -0,0 +1,14 @@ + + + + + + @ViewBag.Title + + + DOPE +
+ @RenderBody() +
+ + diff --git a/FJPSite/Areas/Admin/Views/_ViewImports.cshtml b/FJPSite/Areas/Admin/Views/_ViewImports.cshtml new file mode 100644 index 0000000..107cd3a --- /dev/null +++ b/FJPSite/Areas/Admin/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using FJPSite +@using FJPSite.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/FJPSite/Areas/Admin/Views/_ViewStart.cshtml b/FJPSite/Areas/Admin/Views/_ViewStart.cshtml new file mode 100644 index 0000000..a5f1004 --- /dev/null +++ b/FJPSite/Areas/Admin/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/FJPSite/Attributes/HasPermissionAttribute.cs b/FJPSite/Attributes/HasPermissionAttribute.cs new file mode 100644 index 0000000..17c5207 --- /dev/null +++ b/FJPSite/Attributes/HasPermissionAttribute.cs @@ -0,0 +1,20 @@ +using FJPSite.Enums; +using Microsoft.AspNetCore.Authorization; + +namespace FJPSite.Attributes; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class HasPermissionAttribute : AuthorizeAttribute +{ + public FeatureEnum Feature { get; } + public ActionEnum Action { get; } + public HasPermissionAttribute(FeatureEnum feature, ActionEnum action) + { + Feature = feature; + Action = action; + Policy = BuildPolicyName(feature, action); + } + private static string BuildPolicyName(FeatureEnum feature, ActionEnum action) + { + return $"Permission.{feature}.{action}"; + } \ No newline at end of file diff --git a/FJPSite/Attributes/SeedMigrationAttribute.cs b/FJPSite/Attributes/SeedMigrationAttribute.cs new file mode 100644 index 0000000..8cc677a --- /dev/null +++ b/FJPSite/Attributes/SeedMigrationAttribute.cs @@ -0,0 +1,7 @@ +namespace FJPSite.Attributes; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class SeedMigrationAttribute(string migration) : Attribute +{ + public string Migration { get; set; } = migration; +} diff --git a/FJPSite/Controllers/HomeController.cs b/FJPSite/Controllers/HomeController.cs index ff97c00..294c377 100644 --- a/FJPSite/Controllers/HomeController.cs +++ b/FJPSite/Controllers/HomeController.cs @@ -1,25 +1,25 @@ +using FJPSite.Enums; using FJPSite.Models; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; -namespace FJPSite.Controllers +namespace FJPSite.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller + public IActionResult Index() { - public IActionResult Index() - { - return View(); - } + return View(); + } - public IActionResult Privacy() - { - return View(); - } + public IActionResult Privacy() + { + return View(); + } - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); - } + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } diff --git a/FJPSite/Data/ApplicationDbContext.cs b/FJPSite/Data/ApplicationDbContext.cs index 62af6cc..41556c0 100644 --- a/FJPSite/Data/ApplicationDbContext.cs +++ b/FJPSite/Data/ApplicationDbContext.cs @@ -1,9 +1,32 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using FJPSite.Data.Authorisation; +using FJPSite.Data.Identity; +using FJPSite.Factories; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -namespace FJPSite.Data +namespace FJPSite.Data; + +public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) { - public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) + public DbSet Features { get; set; } + public DbSet FeatureActions { get; set; } + public DbSet Modules { get; set; } + public DbSet ModuleFeatures { get; set; } + public DbSet Actions { get; set; } + public DbSet RolePermissions { get; set; } + public DbSet UserPermissions { get; set; } + protected override void OnModelCreating(ModelBuilder builder) { + base.OnModelCreating(builder); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSeeding((context, _) => + { + DbSeederFactory.SeedDatabase(this); + context.SaveChanges(); + }); } } + diff --git a/FJPSite/Data/Authorisation/Feature.cs b/FJPSite/Data/Authorisation/Feature.cs new file mode 100644 index 0000000..daacbef --- /dev/null +++ b/FJPSite/Data/Authorisation/Feature.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace FJPSite.Data.Authorisation; + +public class Feature +{ + [Key] + public int Id { get; set; } + + [Required] + public required string Name { get; set; } + public ICollection Actions { get; set; } = new List(); + public ICollection Modules { get; set; } = new List(); +} diff --git a/FJPSite/Data/Authorisation/FeatureAction.cs b/FJPSite/Data/Authorisation/FeatureAction.cs new file mode 100644 index 0000000..efd4e31 --- /dev/null +++ b/FJPSite/Data/Authorisation/FeatureAction.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace FJPSite.Data.Authorisation; + +public class FeatureAction +{ + [Key] + public int Id { get; set; } + + [Required, ForeignKey(nameof(Feature))] + public int FeatureId { get; set; } + + public Feature Feature { get; set; } + + [Required, ForeignKey(nameof(Action))] + public int ActionId { get; set; } + + public PermissionAction Action { get; set; } + + public ICollection RolePermissions { get; set; } = new List(); +} diff --git a/FJPSite/Data/Authorisation/Module.cs b/FJPSite/Data/Authorisation/Module.cs new file mode 100644 index 0000000..2eabd70 --- /dev/null +++ b/FJPSite/Data/Authorisation/Module.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace FJPSite.Data.Authorisation; + +public class Module : SoftDeleteEntityBase +{ + [Key] + public int Id { get; set; } + + [Required] + public required string Name { get; set; } + + public ICollection Features { get; set; } = new List(); +} diff --git a/FJPSite/Data/Authorisation/ModuleFeature.cs b/FJPSite/Data/Authorisation/ModuleFeature.cs new file mode 100644 index 0000000..9f89354 --- /dev/null +++ b/FJPSite/Data/Authorisation/ModuleFeature.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace FJPSite.Data.Authorisation; + +public class ModuleFeature +{ + [Key] + public int Id { get; set; } + + [Required, ForeignKey(nameof(Module))] + public int ModuleId { get; set; } + + public Module Module { get; set; } + + [ForeignKey(nameof(Feature))] + public int FeatureId { get; set; } + + public Feature Feature { get; set; } +} diff --git a/FJPSite/Data/Authorisation/PermissionAction.cs b/FJPSite/Data/Authorisation/PermissionAction.cs new file mode 100644 index 0000000..ba8cb30 --- /dev/null +++ b/FJPSite/Data/Authorisation/PermissionAction.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace FJPSite.Data.Authorisation; + +public class PermissionAction +{ + [Key] + public int Id { get; set; } + + [Required] + public required string Name { get; set; } + + public ICollection Features { get; set; } = new List(); +} diff --git a/FJPSite/Data/Authorisation/RolePermission.cs b/FJPSite/Data/Authorisation/RolePermission.cs new file mode 100644 index 0000000..ae4dd29 --- /dev/null +++ b/FJPSite/Data/Authorisation/RolePermission.cs @@ -0,0 +1,21 @@ +using FJPSite.Data.Identity; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace FJPSite.Data.Authorisation; + +public class RolePermission +{ + [Key] + public int Id { get; set; } + + [Required, ForeignKey(nameof(Role))] + public string RoleId { get; set; } + + public RoleEntity Role { get; set; } + + [Required, ForeignKey(nameof(FeatureAction))] + public int FeatureActionId { get; set; } + + public FeatureAction FeatureAction { get; set; } +} diff --git a/FJPSite/Data/Authorisation/UserPermission.cs b/FJPSite/Data/Authorisation/UserPermission.cs new file mode 100644 index 0000000..78dae00 --- /dev/null +++ b/FJPSite/Data/Authorisation/UserPermission.cs @@ -0,0 +1,22 @@ +using FJPSite.Data.Identity; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace FJPSite.Data.Authorisation; + +public class UserPermission +{ + [Key] + public int Id { get; set; } + + [Required, ForeignKey(nameof(User))] + public string UserId { get; set; } + + public UserEntity User { get; set; } + + [Required, ForeignKey(nameof(FeatureAction))] + public int FeatureActionId { get; set; } + + public FeatureAction FeatureAction { get; set; } +} + diff --git a/FJPSite/Data/Identity/RoleEntity.cs b/FJPSite/Data/Identity/RoleEntity.cs new file mode 100644 index 0000000..2b14886 --- /dev/null +++ b/FJPSite/Data/Identity/RoleEntity.cs @@ -0,0 +1,9 @@ +using FJPSite.Data.Authorisation; +using Microsoft.AspNetCore.Identity; + +namespace FJPSite.Data.Identity; + +public class RoleEntity : IdentityRole +{ + public ICollection Permissions { get; set; } = new List(); +} diff --git a/FJPSite/Data/Identity/UserEntity.cs b/FJPSite/Data/Identity/UserEntity.cs new file mode 100644 index 0000000..85ffe05 --- /dev/null +++ b/FJPSite/Data/Identity/UserEntity.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Identity; +using System.ComponentModel.DataAnnotations; + +namespace FJPSite.Data.Identity; + +public class UserEntity : IdentityUser +{ + [Required] public required string Firstname { get; set; } + [Required] public required string Surname { get; set; } +} diff --git a/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs b/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs deleted file mode 100644 index d379412..0000000 --- a/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs +++ /dev/null @@ -1,277 +0,0 @@ -// -using FJPSite.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using System; - -namespace FJPSite.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("00000000000000_CreateIdentitySchema")] - partial class CreateIdentitySchema - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Name") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.cs deleted file mode 100644 index ce0cff0..0000000 --- a/FJPSite/Data/Migrations/00000000000000_CreateIdentitySchema.cs +++ /dev/null @@ -1,220 +0,0 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using System; - -namespace FJPSite.Data.Migrations -{ - public partial class CreateIdentitySchema : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(nullable: false), - Name = table.Column(maxLength: 256, nullable: true), - NormalizedName = table.Column(maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(nullable: false), - UserName = table.Column(maxLength: 256, nullable: true), - NormalizedUserName = table.Column(maxLength: 256, nullable: true), - Email = table.Column(maxLength: 256, nullable: true), - NormalizedEmail = table.Column(maxLength: 256, nullable: true), - EmailConfirmed = table.Column(nullable: false), - PasswordHash = table.Column(nullable: true), - SecurityStamp = table.Column(nullable: true), - ConcurrencyStamp = table.Column(nullable: true), - PhoneNumber = table.Column(nullable: true), - PhoneNumberConfirmed = table.Column(nullable: false), - TwoFactorEnabled = table.Column(nullable: false), - LockoutEnd = table.Column(nullable: true), - LockoutEnabled = table.Column(nullable: false), - AccessFailedCount = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - RoleId = table.Column(nullable: false), - ClaimType = table.Column(nullable: true), - ClaimValue = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - UserId = table.Column(nullable: false), - ClaimType = table.Column(nullable: true), - ClaimValue = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(maxLength: 128, nullable: false), - ProviderKey = table.Column(maxLength: 128, nullable: false), - ProviderDisplayName = table.Column(nullable: true), - UserId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(nullable: false), - RoleId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(nullable: false), - LoginProvider = table.Column(maxLength: 128, nullable: false), - Name = table.Column(maxLength: 128, nullable: false), - Value = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/FJPSite/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/FJPSite/Data/Migrations/ApplicationDbContextModelSnapshot.cs deleted file mode 100644 index 9fbdc5c..0000000 --- a/FJPSite/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ /dev/null @@ -1,275 +0,0 @@ -// -using FJPSite.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using System; - -namespace FJPSite.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - partial class ApplicationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Name") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/FJPSite/Data/SoftDeleteEntityBase.cs b/FJPSite/Data/SoftDeleteEntityBase.cs new file mode 100644 index 0000000..0fe1d18 --- /dev/null +++ b/FJPSite/Data/SoftDeleteEntityBase.cs @@ -0,0 +1,11 @@ +using FJPSite.Interfaces; +using System.ComponentModel.DataAnnotations; + +namespace FJPSite.Data +{ + public class SoftDeleteEntityBase : ISoftDeleteEntity + { + [Required] + public bool IsActive { get; set; } = true; + } +} diff --git a/FJPSite/Enums/ActionEnum.cs b/FJPSite/Enums/ActionEnum.cs new file mode 100644 index 0000000..a82fd82 --- /dev/null +++ b/FJPSite/Enums/ActionEnum.cs @@ -0,0 +1,16 @@ +namespace FJPSite.Enums; + +public enum ActionEnum +{ + Denied, + View, + Create, + Edit, + Delete, + Export, + Import, + Assign, + Review, + Approve, + Reject +} diff --git a/FJPSite/Enums/FeatureEnum.cs b/FJPSite/Enums/FeatureEnum.cs new file mode 100644 index 0000000..5d64385 --- /dev/null +++ b/FJPSite/Enums/FeatureEnum.cs @@ -0,0 +1,11 @@ +namespace FJPSite.Enums; + +public enum FeatureEnum +{ + Users, + Roles, + Permissions, + Pages, + Settings, + Funerals, +} diff --git a/FJPSite/Enums/ModuleEnum.cs b/FJPSite/Enums/ModuleEnum.cs new file mode 100644 index 0000000..1b94d7a --- /dev/null +++ b/FJPSite/Enums/ModuleEnum.cs @@ -0,0 +1,8 @@ +namespace FJPSite.Enums; + +public enum ModuleEnum +{ + Authentication, + Site, + Funerals +} diff --git a/FJPSite/Enums/RoleEnum.cs b/FJPSite/Enums/RoleEnum.cs new file mode 100644 index 0000000..a3dabb9 --- /dev/null +++ b/FJPSite/Enums/RoleEnum.cs @@ -0,0 +1,8 @@ +namespace FJPSite.Enums; + +public enum RoleEnum +{ + Admin, + Director, + User +} diff --git a/FJPSite/Extensions/DbContextExtension.cs b/FJPSite/Extensions/DbContextExtension.cs new file mode 100644 index 0000000..e3d991b --- /dev/null +++ b/FJPSite/Extensions/DbContextExtension.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; + +namespace FJPSite.Extensions; + +public static class DbContextExtension +{ + public static bool MigrationApplied(this DbContext context, string migrationName) + { + return context.Database.GetAppliedMigrations().FirstOrDefault(f => f == migrationName) != null; + } +} diff --git a/FJPSite/Factories/DbSeederFactory.cs b/FJPSite/Factories/DbSeederFactory.cs new file mode 100644 index 0000000..51c6dae --- /dev/null +++ b/FJPSite/Factories/DbSeederFactory.cs @@ -0,0 +1,39 @@ +using FJPSite.Attributes; +using FJPSite.Data; +using FJPSite.Extensions; +using FJPSite.Interfaces; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using System.Reflection; + +namespace FJPSite.Factories; + +public static class DbSeederFactory +{ + public static void SeedDatabase(ApplicationDbContext context) + { + //TODO: Code works, just not sure if want to auto load/inject due to some needing to be done in a specific order. + var serviceDescriptors = typeof(Program).Assembly//.GetTypes().Where(type => type is {IsAbstract: false, IsInterface: false } && type.IsAssignableFrom(typeof(IEntitySeederFactory))).OrderBy(o => o.GetCustomAttribute().Order) + .DefinedTypes + .Where(type => type is { IsAbstract: false, IsInterface: false } && + type.IsAssignableTo(typeof(IEntitySeederFactory))).OrderBy(o => o.GetCustomAttribute().Migration) + .ToArray(); + // + foreach (var serviceDescriptor in serviceDescriptors) + { + if (context.MigrationApplied(serviceDescriptor.GetCustomAttribute().Migration)) + { + IEntitySeederFactory entitySeederFactory = Activator.CreateInstance(serviceDescriptor) as IEntitySeederFactory; + entitySeederFactory.Seed(context); + } + } + + /*Reflection + IdentitySeederFactory identitySeeder = new IdentitySeederFactory(); + identitySeeder.Seed(context); + + PermissionSeederFactory permissionSeeder = new PermissionSeederFactory(); + permissionSeeder.Seed(context);*/ + } +} diff --git a/FJPSite/Factories/EntitySeeders/EntitySeederFactory.cs b/FJPSite/Factories/EntitySeeders/EntitySeederFactory.cs new file mode 100644 index 0000000..926e5a1 --- /dev/null +++ b/FJPSite/Factories/EntitySeeders/EntitySeederFactory.cs @@ -0,0 +1,13 @@ +using FJPSite.Attributes; +using FJPSite.Data; +using FJPSite.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace FJPSite.Factories.EntitySeeders; + + +[SeedMigration("NotImplemented")] +public abstract class EntitySeederFactory : IEntitySeederFactory +{ + public abstract void Seed(ApplicationDbContext context); +} diff --git a/FJPSite/Factories/EntitySeeders/IdentitySeederFactory.cs b/FJPSite/Factories/EntitySeeders/IdentitySeederFactory.cs new file mode 100644 index 0000000..bc34d7c --- /dev/null +++ b/FJPSite/Factories/EntitySeeders/IdentitySeederFactory.cs @@ -0,0 +1,90 @@ +using FJPSite.Attributes; +using FJPSite.Data; +using FJPSite.Data.Identity; +using FJPSite.Enums; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace FJPSite.Factories.EntitySeeders; + +[SeedMigration("20260409010423_CreateIdentity")] +public class IdentitySeederFactory : EntitySeederFactory +{ + private struct UserSeed + { + public string Email { get; set; } + public string Firstname { get; set; } + public string Surname { get; set; } + + public RoleEnum Role { get; set; } + } + private static string DefaultPassword = "Potters144A!"; + private static UserSeed[] Users = [ + new UserSeed {Email = "chris@fjp.com.au", Firstname = "Chris", Surname = "McInnes", Role = RoleEnum.Admin}, + new UserSeed {Email = "kim@fjp.com.au", Firstname = "Kim", Surname = "Fell", Role = RoleEnum.Admin}, + new UserSeed {Email = "amanda@fjp.com.au", Firstname = "Amanda", Surname = "Schenk", Role = RoleEnum.Director}, + new UserSeed {Email = "reception@fjp.com.au", Firstname = "Lucy", Surname = "Fox", Role = RoleEnum.User} + ]; + + private static void SeedRoles(RoleManager roleManager) + { + foreach (var role in (RoleEnum[])Enum.GetValues(typeof(RoleEnum))) + { + var rolename = Enum.GetName(role); + var roleEntity = roleManager.FindByNameAsync(rolename).Result; + if (roleEntity == null) + { + var result = roleManager.CreateAsync(new RoleEntity { Name = rolename }).Result; + if (result.Succeeded == false) + { + throw new Exception("Failed while seeding roles in database"); + } + } + } + } + + private static void SeedUsers(UserManager userManager) + { + foreach (UserSeed userSeed in Users) + { + var user = userManager.FindByNameAsync(userSeed.Email).Result; + if (user == null) + { + var result = userManager.CreateAsync(new UserEntity() + { + UserName = userSeed.Email, + Email = userSeed.Email, + Surname = userSeed.Surname, + Firstname = userSeed.Firstname, + EmailConfirmed = true + }, DefaultPassword).Result; + if (result.Succeeded == false) + { + throw new Exception("Failed while seeding users in database"); + } + user = userManager.FindByNameAsync(userSeed.Email).Result; + result = userManager.SetLockoutEnabledAsync(user, false).Result; + } + + if (userManager.IsInRoleAsync(user, Enum.GetName(userSeed.Role)).Result == false) + { + var roles = userManager.GetRolesAsync(user).Result; + userManager.RemoveFromRolesAsync(user, roles).Wait(); + userManager.AddToRoleAsync(user, Enum.GetName(userSeed.Role)).Wait(); + } + + } + } + public override void Seed(ApplicationDbContext context) + { + var userManager = context.GetService>(); + var roleManager = context.GetService>(); + + SeedRoles(roleManager); + SeedUsers(userManager); + + context.SaveChanges(); + } +} diff --git a/FJPSite/Factories/EntitySeeders/PermissionSeederFactory.cs b/FJPSite/Factories/EntitySeeders/PermissionSeederFactory.cs new file mode 100644 index 0000000..f154bef --- /dev/null +++ b/FJPSite/Factories/EntitySeeders/PermissionSeederFactory.cs @@ -0,0 +1,174 @@ +using FJPSite.Attributes; +using FJPSite.Data; +using FJPSite.Data.Authorisation; +using FJPSite.Data.Identity; +using FJPSite.Enums; +using Microsoft.Build.Framework; + +namespace FJPSite.Factories.EntitySeeders; + +[SeedMigration("20260409123724_CreatePermissions")] +public class PermissionSeederFactory : EntitySeederFactory +{ + private struct FeatureActionLink + { + public FeatureEnum linkFeature { get; set; } + public IList linkActions { get; set; } + } + + private void SeedModules(ApplicationDbContext context) + { + foreach (var moduleName in Enum.GetNames(typeof(ModuleEnum))) + { + if (context.Modules.FirstOrDefault(m => m.Name == moduleName) == null) + { + context.Modules.Add(new Module { Name = moduleName, IsActive = true }); + } + } + context.SaveChanges(); + } + + private void SeedFeatures(ApplicationDbContext context) + { + foreach (var featureName in Enum.GetNames(typeof(FeatureEnum))) + { + if (context.Features.FirstOrDefault(m => m.Name == featureName) == null) + { + context.Features.Add(new Feature { Name = featureName }); + } + } + context.SaveChanges(); + } + + private void SeedActions(ApplicationDbContext context) + { + foreach (var actionName in Enum.GetNames(typeof(ActionEnum))) + { + if (context.Actions.FirstOrDefault(m => m.Name == actionName) == null) + { + context.Actions.Add(new PermissionAction { Name = actionName }); + } + } + context.SaveChanges(); + } + + private void SeedModuleFeatures(ApplicationDbContext context) + { + Dictionary> moduleFeatureList = new Dictionary> + { + {ModuleEnum.Authentication, new List{FeatureEnum.Permissions, FeatureEnum.Users} }, + {ModuleEnum.Funerals, new List{FeatureEnum.Funerals} }, + {ModuleEnum.Site, new List{FeatureEnum.Settings, FeatureEnum.Pages} } + }; + + foreach (var module in moduleFeatureList) + { + var moduleEntity = context.Modules.First(f => f.Name == Enum.GetName(module.Key)); + foreach (var feature in module.Value) + { + var featureEntity = context.Features.First(f => f.Name == Enum.GetName(feature)); + if (context.ModuleFeatures.FirstOrDefault(f => f.ModuleId == moduleEntity.Id && f.FeatureId == featureEntity.Id) == null) + { + context.ModuleFeatures.Add(new ModuleFeature + { + ModuleId = moduleEntity.Id, + FeatureId = featureEntity.Id, + }); + } + } + } + context.SaveChanges(); + } + + private void SeedFeatureAction(ApplicationDbContext context) + { + Dictionary> featureActionList = new Dictionary> + { + {FeatureEnum.Permissions, new List{ActionEnum.View, ActionEnum.Assign } }, + {FeatureEnum.Roles, new List{ ActionEnum.View, ActionEnum.Assign} }, + {FeatureEnum.Pages, new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } }, + {FeatureEnum.Users, new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } }, + {FeatureEnum.Settings, new List{ ActionEnum.View, ActionEnum.Edit } }, + {FeatureEnum.Funerals, new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } }, + }; + + foreach (var feature in featureActionList) + { + var featureEntity = context.Features.First(f => f.Name == Enum.GetName(feature.Key)); + foreach (var action in feature.Value) + { + var actionEntity = context.Actions.First(f => f.Name == Enum.GetName(action)); + if (context.FeatureActions.FirstOrDefault(f => f.FeatureId == featureEntity.Id && f.ActionId == actionEntity.Id) == null) + { + context.FeatureActions.Add(new FeatureAction + { + ActionId = actionEntity.Id, + FeatureId = featureEntity.Id, + }); + } + } + } + + context.SaveChanges(); + } + + private void SeedRolePermission(ApplicationDbContext context) + { + Dictionary> rolePermissions = new Dictionary> + { + { RoleEnum.Admin, new List + { + new FeatureActionLink {linkFeature = FeatureEnum.Permissions, linkActions = new List { ActionEnum.View, ActionEnum.Assign } }, + new FeatureActionLink {linkFeature = FeatureEnum.Roles, linkActions = new List { ActionEnum.View, ActionEnum.Assign } }, + new FeatureActionLink {linkFeature = FeatureEnum.Pages, linkActions = new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } }, + new FeatureActionLink {linkFeature = FeatureEnum.Users, linkActions = new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } }, + new FeatureActionLink {linkFeature = FeatureEnum.Settings, linkActions = new List{ ActionEnum.View, ActionEnum.Edit } }, + new FeatureActionLink {linkFeature = FeatureEnum.Funerals, linkActions = new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } }, + } + }, + { RoleEnum.Director, new List + { + new FeatureActionLink {linkFeature = FeatureEnum.Funerals, linkActions = new List{ ActionEnum.View, ActionEnum.Create, ActionEnum.Edit, ActionEnum.Delete } }, + } + }, + { RoleEnum.User, new List + { + new FeatureActionLink {linkFeature = FeatureEnum.Funerals, linkActions = new List{ ActionEnum.View, ActionEnum.Create } }, + } + }, + }; + + foreach (var roleFeatures in rolePermissions) + { + var roleEntity = context.Roles.First(f => f.Name == Enum.GetName(roleFeatures.Key)); + foreach (var feature in roleFeatures.Value) + { + var featureEntity = context.Features.First(f => f.Name == Enum.GetName(feature.linkFeature)); + foreach (var action in feature.linkActions) + { + var actionEntity = context.Actions.First(f => f.Name == Enum.GetName(action)); + var featureActionEntity = context.FeatureActions.First(f => f.FeatureId == featureEntity.Id && f.ActionId == actionEntity.Id); + if (context.RolePermissions.FirstOrDefault(f => f.RoleId == roleEntity.Id && f.FeatureActionId == featureActionEntity.Id) == null) + { + context.RolePermissions.Add(new RolePermission + { + FeatureActionId = featureActionEntity.Id, + RoleId = roleEntity.Id + }); + } + } + } + } + context.SaveChanges(); + } + + public override void Seed(ApplicationDbContext context) + { + SeedModules(context); + SeedFeatures(context); + SeedActions(context); + SeedModuleFeatures(context); + SeedFeatureAction(context); + SeedRolePermission(context); + } +} diff --git a/FJPSite/Factories/UserClaimFactory.cs b/FJPSite/Factories/UserClaimFactory.cs new file mode 100644 index 0000000..051abbb --- /dev/null +++ b/FJPSite/Factories/UserClaimFactory.cs @@ -0,0 +1,23 @@ +using FJPSite.Data.Identity; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using System.Security.Claims; + +namespace FJPSite.Factories; + +public class UserClaimFactory : UserClaimsPrincipalFactory +{ + public UserClaimFactory(UserManager userManager, RoleManager roleManager, IOptions options) : base(userManager, roleManager, options) + { + } + + public async override Task CreateAsync(UserEntity user) + { + var principal = await base.CreateAsync(user); + ((ClaimsIdentity)principal.Identity).AddClaims(new[] { + new Claim(ClaimTypes.GivenName, user.Firstname), + new Claim(ClaimTypes.Surname, user.Surname) + }); + return principal; + } +} diff --git a/FJPSite/Handlers/PermissionAuthorizationHandler.cs b/FJPSite/Handlers/PermissionAuthorizationHandler.cs new file mode 100644 index 0000000..72b0642 --- /dev/null +++ b/FJPSite/Handlers/PermissionAuthorizationHandler.cs @@ -0,0 +1,45 @@ +using FJPSite.Helpers; +using Microsoft.AspNetCore.Authorization; +using System.Security.Claims; + +namespace FJPSite.Handlers; + +public class PermissionAuthorizationHandler : AuthorizationHandler +{ + private readonly IPermissionService _permissionService; + private readonly IHttpContextAccessor _httpContextAccessor; + public PermissionAuthorizationHandler( + IPermissionService permissionService, + IHttpContextAccessor httpContextAccessor) + { + _permissionService = permissionService; + _httpContextAccessor = httpContextAccessor; + } + protected override async Task HandleRequirementAsync( + AuthorizationHandlerContext context, + PermissionRequirement requirement) + { + if (context.User == null) + { + context.Fail(); + return; + } + // Get user ID from claims + var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + if (string.IsNullOrEmpty(userId)) + { + context.Fail(); + return; + } + // Check if user has the required permission + bool hasPermission = await _permissionService + .UserHasPermissionAsync(userId, requirement.Feature, requirement.Action); + if (hasPermission) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + } \ No newline at end of file diff --git a/FJPSite/Helpers/PermissionRequirement.cs b/FJPSite/Helpers/PermissionRequirement.cs new file mode 100644 index 0000000..2794d89 --- /dev/null +++ b/FJPSite/Helpers/PermissionRequirement.cs @@ -0,0 +1,15 @@ +using FJPSite.Enums; +using Microsoft.AspNetCore.Authorization; + +namespace FJPSite.Helpers; + +public class PermissionRequirement : IAuthorizationRequirement +{ + public FeatureEnum Feature { get; } + public ActionEnum Action { get; } + public PermissionRequirement(FeatureEnum feature, ActionEnum action) + { + Feature = feature; + Action = action; + } +} diff --git a/FJPSite/Interfaces/IEntitySeederFactory.cs b/FJPSite/Interfaces/IEntitySeederFactory.cs new file mode 100644 index 0000000..4af2cb2 --- /dev/null +++ b/FJPSite/Interfaces/IEntitySeederFactory.cs @@ -0,0 +1,9 @@ +using FJPSite.Data; +using Microsoft.EntityFrameworkCore; + +namespace FJPSite.Interfaces; + +public interface IEntitySeederFactory +{ + public void Seed(ApplicationDbContext context); +} diff --git a/FJPSite/Interfaces/IPermissionService.cs b/FJPSite/Interfaces/IPermissionService.cs new file mode 100644 index 0000000..d079afa --- /dev/null +++ b/FJPSite/Interfaces/IPermissionService.cs @@ -0,0 +1,13 @@ +using FJPSite.Enums; +using FJPSite.Models; + +namespace FJPSite.Interfaces; + +public interface IPermissionService +{ + Task UserHasPermissionAsync(string userId, FeatureEnum feature, ActionEnum action); + /*Task> GetUserPermissionsAsync(string userId); + Task RoleHasPermissionAsync(string roleId, FeatureEnum feature, ActionEnum action); + Task AssignPermissionsToRoleAsync(string roleId, List assignments);*/ + Task> GetPermissionStructureAsync(int moduleId); +} \ No newline at end of file diff --git a/FJPSite/Interfaces/ISoftDeleteEntity.cs b/FJPSite/Interfaces/ISoftDeleteEntity.cs new file mode 100644 index 0000000..f9e96f6 --- /dev/null +++ b/FJPSite/Interfaces/ISoftDeleteEntity.cs @@ -0,0 +1,10 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace FJPSite.Interfaces; + +public interface ISoftDeleteEntity +{ + [Required, DefaultValue(true)] + public bool IsActive { get; set; } +} diff --git a/FJPSite/Migrations/20260409010423_CreateIdentity.Designer.cs b/FJPSite/Migrations/20260409010423_CreateIdentity.Designer.cs new file mode 100644 index 0000000..dd29ce2 --- /dev/null +++ b/FJPSite/Migrations/20260409010423_CreateIdentity.Designer.cs @@ -0,0 +1,499 @@ +// +using System; +using FJPSite.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace FJPSite.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260409010423_CreateIdentity")] + partial class CreateIdentity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Action", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Action"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Feature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Feature"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActionId") + .HasColumnType("int"); + + b.Property("FeatureId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ActionId"); + + b.HasIndex("FeatureId"); + + b.ToTable("FeatureAction"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Module", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Module"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.ModuleFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FeatureId") + .HasColumnType("int"); + + b.Property("ModuleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FeatureId"); + + b.HasIndex("ModuleId"); + + b.ToTable("ModuleFeature"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.RolePermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FeatureActionId") + .HasColumnType("int"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("FeatureActionId"); + + b.HasIndex("RoleId"); + + b.ToTable("RolePermission"); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.UserEntity", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("Firstname") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.UserRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.HasOne("FJPSite.Data.Authorisation.Action", "Action") + .WithMany("Features") + .HasForeignKey("ActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Authorisation.Feature", "Feature") + .WithMany("Actions") + .HasForeignKey("FeatureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Action"); + + b.Navigation("Feature"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.ModuleFeature", b => + { + b.HasOne("FJPSite.Data.Authorisation.Feature", "Feature") + .WithMany("Modules") + .HasForeignKey("FeatureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Authorisation.Module", "Module") + .WithMany("Features") + .HasForeignKey("ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Feature"); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.RolePermission", b => + { + b.HasOne("FJPSite.Data.Authorisation.FeatureAction", "FeatureAction") + .WithMany("RolePermissions") + .HasForeignKey("FeatureActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Identity.UserRole", "Role") + .WithMany("Permissions") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeatureAction"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("FJPSite.Data.Identity.UserRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("FJPSite.Data.Identity.UserRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Action", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Feature", b => + { + b.Navigation("Actions"); + + b.Navigation("Modules"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.Navigation("RolePermissions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Module", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.UserRole", b => + { + b.Navigation("Permissions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FJPSite/Migrations/20260409010423_CreateIdentity.cs b/FJPSite/Migrations/20260409010423_CreateIdentity.cs new file mode 100644 index 0000000..8bc8dbe --- /dev/null +++ b/FJPSite/Migrations/20260409010423_CreateIdentity.cs @@ -0,0 +1,392 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace FJPSite.Migrations +{ + /// + public partial class CreateIdentity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Action", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Action", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Firstname = table.Column(type: "nvarchar(max)", nullable: false), + Surname = table.Column(type: "nvarchar(max)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Feature", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Feature", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Module", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + IsActive = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Module", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ProviderKey = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "FeatureAction", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + FeatureId = table.Column(type: "int", nullable: false), + ActionId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FeatureAction", x => x.Id); + table.ForeignKey( + name: "FK_FeatureAction_Action_ActionId", + column: x => x.ActionId, + principalTable: "Action", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_FeatureAction_Feature_FeatureId", + column: x => x.FeatureId, + principalTable: "Feature", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ModuleFeature", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ModuleId = table.Column(type: "int", nullable: false), + FeatureId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ModuleFeature", x => x.Id); + table.ForeignKey( + name: "FK_ModuleFeature_Feature_FeatureId", + column: x => x.FeatureId, + principalTable: "Feature", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ModuleFeature_Module_ModuleId", + column: x => x.ModuleId, + principalTable: "Module", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RolePermission", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + FeatureActionId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RolePermission", x => x.Id); + table.ForeignKey( + name: "FK_RolePermission_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RolePermission_FeatureAction_FeatureActionId", + column: x => x.FeatureActionId, + principalTable: "FeatureAction", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_FeatureAction_ActionId", + table: "FeatureAction", + column: "ActionId"); + + migrationBuilder.CreateIndex( + name: "IX_FeatureAction_FeatureId", + table: "FeatureAction", + column: "FeatureId"); + + migrationBuilder.CreateIndex( + name: "IX_ModuleFeature_FeatureId", + table: "ModuleFeature", + column: "FeatureId"); + + migrationBuilder.CreateIndex( + name: "IX_ModuleFeature_ModuleId", + table: "ModuleFeature", + column: "ModuleId"); + + migrationBuilder.CreateIndex( + name: "IX_RolePermission_FeatureActionId", + table: "RolePermission", + column: "FeatureActionId"); + + migrationBuilder.CreateIndex( + name: "IX_RolePermission_RoleId", + table: "RolePermission", + column: "RoleId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "ModuleFeature"); + + migrationBuilder.DropTable( + name: "RolePermission"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + + migrationBuilder.DropTable( + name: "Module"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "FeatureAction"); + + migrationBuilder.DropTable( + name: "Action"); + + migrationBuilder.DropTable( + name: "Feature"); + } + } +} diff --git a/FJPSite/Migrations/20260409123724_CreatePermissions.Designer.cs b/FJPSite/Migrations/20260409123724_CreatePermissions.Designer.cs new file mode 100644 index 0000000..d868dc5 --- /dev/null +++ b/FJPSite/Migrations/20260409123724_CreatePermissions.Designer.cs @@ -0,0 +1,542 @@ +// +using System; +using FJPSite.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace FJPSite.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260409123724_CreatePermissions")] + partial class CreatePermissions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Feature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Features"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActionId") + .HasColumnType("int"); + + b.Property("FeatureId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ActionId"); + + b.HasIndex("FeatureId"); + + b.ToTable("FeatureActions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Module", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Modules"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.ModuleFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FeatureId") + .HasColumnType("int"); + + b.Property("ModuleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FeatureId"); + + b.HasIndex("ModuleId"); + + b.ToTable("ModuleFeatures"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.PermissionAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Actions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.RolePermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FeatureActionId") + .HasColumnType("int"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("FeatureActionId"); + + b.HasIndex("RoleId"); + + b.ToTable("RolePermissions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.UserPermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FeatureActionId") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("FeatureActionId"); + + b.HasIndex("UserId"); + + b.ToTable("UserPermissions"); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.RoleEntity", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.UserEntity", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("Firstname") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.HasOne("FJPSite.Data.Authorisation.PermissionAction", "Action") + .WithMany("Features") + .HasForeignKey("ActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Authorisation.Feature", "Feature") + .WithMany("Actions") + .HasForeignKey("FeatureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Action"); + + b.Navigation("Feature"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.ModuleFeature", b => + { + b.HasOne("FJPSite.Data.Authorisation.Feature", "Feature") + .WithMany("Modules") + .HasForeignKey("FeatureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Authorisation.Module", "Module") + .WithMany("Features") + .HasForeignKey("ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Feature"); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.RolePermission", b => + { + b.HasOne("FJPSite.Data.Authorisation.FeatureAction", "FeatureAction") + .WithMany("RolePermissions") + .HasForeignKey("FeatureActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Identity.RoleEntity", "Role") + .WithMany("Permissions") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeatureAction"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.UserPermission", b => + { + b.HasOne("FJPSite.Data.Authorisation.FeatureAction", "FeatureAction") + .WithMany() + .HasForeignKey("FeatureActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Identity.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeatureAction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("FJPSite.Data.Identity.RoleEntity", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("FJPSite.Data.Identity.RoleEntity", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Feature", b => + { + b.Navigation("Actions"); + + b.Navigation("Modules"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.Navigation("RolePermissions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Module", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.PermissionAction", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.RoleEntity", b => + { + b.Navigation("Permissions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FJPSite/Migrations/20260409123724_CreatePermissions.cs b/FJPSite/Migrations/20260409123724_CreatePermissions.cs new file mode 100644 index 0000000..00bb080 --- /dev/null +++ b/FJPSite/Migrations/20260409123724_CreatePermissions.cs @@ -0,0 +1,423 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace FJPSite.Migrations +{ + /// + public partial class CreatePermissions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_FeatureAction_Action_ActionId", + table: "FeatureAction"); + + migrationBuilder.DropForeignKey( + name: "FK_FeatureAction_Feature_FeatureId", + table: "FeatureAction"); + + migrationBuilder.DropForeignKey( + name: "FK_ModuleFeature_Feature_FeatureId", + table: "ModuleFeature"); + + migrationBuilder.DropForeignKey( + name: "FK_ModuleFeature_Module_ModuleId", + table: "ModuleFeature"); + + migrationBuilder.DropForeignKey( + name: "FK_RolePermission_AspNetRoles_RoleId", + table: "RolePermission"); + + migrationBuilder.DropForeignKey( + name: "FK_RolePermission_FeatureAction_FeatureActionId", + table: "RolePermission"); + + migrationBuilder.DropTable( + name: "Action"); + + migrationBuilder.DropPrimaryKey( + name: "PK_RolePermission", + table: "RolePermission"); + + migrationBuilder.DropPrimaryKey( + name: "PK_ModuleFeature", + table: "ModuleFeature"); + + migrationBuilder.DropPrimaryKey( + name: "PK_Module", + table: "Module"); + + migrationBuilder.DropPrimaryKey( + name: "PK_FeatureAction", + table: "FeatureAction"); + + migrationBuilder.DropPrimaryKey( + name: "PK_Feature", + table: "Feature"); + + migrationBuilder.RenameTable( + name: "RolePermission", + newName: "RolePermissions"); + + migrationBuilder.RenameTable( + name: "ModuleFeature", + newName: "ModuleFeatures"); + + migrationBuilder.RenameTable( + name: "Module", + newName: "Modules"); + + migrationBuilder.RenameTable( + name: "FeatureAction", + newName: "FeatureActions"); + + migrationBuilder.RenameTable( + name: "Feature", + newName: "Features"); + + migrationBuilder.RenameIndex( + name: "IX_RolePermission_RoleId", + table: "RolePermissions", + newName: "IX_RolePermissions_RoleId"); + + migrationBuilder.RenameIndex( + name: "IX_RolePermission_FeatureActionId", + table: "RolePermissions", + newName: "IX_RolePermissions_FeatureActionId"); + + migrationBuilder.RenameIndex( + name: "IX_ModuleFeature_ModuleId", + table: "ModuleFeatures", + newName: "IX_ModuleFeatures_ModuleId"); + + migrationBuilder.RenameIndex( + name: "IX_ModuleFeature_FeatureId", + table: "ModuleFeatures", + newName: "IX_ModuleFeatures_FeatureId"); + + migrationBuilder.RenameIndex( + name: "IX_FeatureAction_FeatureId", + table: "FeatureActions", + newName: "IX_FeatureActions_FeatureId"); + + migrationBuilder.RenameIndex( + name: "IX_FeatureAction_ActionId", + table: "FeatureActions", + newName: "IX_FeatureActions_ActionId"); + + migrationBuilder.AddPrimaryKey( + name: "PK_RolePermissions", + table: "RolePermissions", + column: "Id"); + + migrationBuilder.AddPrimaryKey( + name: "PK_ModuleFeatures", + table: "ModuleFeatures", + column: "Id"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Modules", + table: "Modules", + column: "Id"); + + migrationBuilder.AddPrimaryKey( + name: "PK_FeatureActions", + table: "FeatureActions", + column: "Id"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Features", + table: "Features", + column: "Id"); + + migrationBuilder.CreateTable( + name: "Actions", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Actions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserPermissions", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + FeatureActionId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserPermissions", x => x.Id); + table.ForeignKey( + name: "FK_UserPermissions_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserPermissions_FeatureActions_FeatureActionId", + column: x => x.FeatureActionId, + principalTable: "FeatureActions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserPermissions_FeatureActionId", + table: "UserPermissions", + column: "FeatureActionId"); + + migrationBuilder.CreateIndex( + name: "IX_UserPermissions_UserId", + table: "UserPermissions", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_FeatureActions_Actions_ActionId", + table: "FeatureActions", + column: "ActionId", + principalTable: "Actions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_FeatureActions_Features_FeatureId", + table: "FeatureActions", + column: "FeatureId", + principalTable: "Features", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ModuleFeatures_Features_FeatureId", + table: "ModuleFeatures", + column: "FeatureId", + principalTable: "Features", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ModuleFeatures_Modules_ModuleId", + table: "ModuleFeatures", + column: "ModuleId", + principalTable: "Modules", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_RolePermissions_AspNetRoles_RoleId", + table: "RolePermissions", + column: "RoleId", + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_RolePermissions_FeatureActions_FeatureActionId", + table: "RolePermissions", + column: "FeatureActionId", + principalTable: "FeatureActions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_FeatureActions_Actions_ActionId", + table: "FeatureActions"); + + migrationBuilder.DropForeignKey( + name: "FK_FeatureActions_Features_FeatureId", + table: "FeatureActions"); + + migrationBuilder.DropForeignKey( + name: "FK_ModuleFeatures_Features_FeatureId", + table: "ModuleFeatures"); + + migrationBuilder.DropForeignKey( + name: "FK_ModuleFeatures_Modules_ModuleId", + table: "ModuleFeatures"); + + migrationBuilder.DropForeignKey( + name: "FK_RolePermissions_AspNetRoles_RoleId", + table: "RolePermissions"); + + migrationBuilder.DropForeignKey( + name: "FK_RolePermissions_FeatureActions_FeatureActionId", + table: "RolePermissions"); + + migrationBuilder.DropTable( + name: "Actions"); + + migrationBuilder.DropTable( + name: "UserPermissions"); + + migrationBuilder.DropPrimaryKey( + name: "PK_RolePermissions", + table: "RolePermissions"); + + migrationBuilder.DropPrimaryKey( + name: "PK_Modules", + table: "Modules"); + + migrationBuilder.DropPrimaryKey( + name: "PK_ModuleFeatures", + table: "ModuleFeatures"); + + migrationBuilder.DropPrimaryKey( + name: "PK_Features", + table: "Features"); + + migrationBuilder.DropPrimaryKey( + name: "PK_FeatureActions", + table: "FeatureActions"); + + migrationBuilder.RenameTable( + name: "RolePermissions", + newName: "RolePermission"); + + migrationBuilder.RenameTable( + name: "Modules", + newName: "Module"); + + migrationBuilder.RenameTable( + name: "ModuleFeatures", + newName: "ModuleFeature"); + + migrationBuilder.RenameTable( + name: "Features", + newName: "Feature"); + + migrationBuilder.RenameTable( + name: "FeatureActions", + newName: "FeatureAction"); + + migrationBuilder.RenameIndex( + name: "IX_RolePermissions_RoleId", + table: "RolePermission", + newName: "IX_RolePermission_RoleId"); + + migrationBuilder.RenameIndex( + name: "IX_RolePermissions_FeatureActionId", + table: "RolePermission", + newName: "IX_RolePermission_FeatureActionId"); + + migrationBuilder.RenameIndex( + name: "IX_ModuleFeatures_ModuleId", + table: "ModuleFeature", + newName: "IX_ModuleFeature_ModuleId"); + + migrationBuilder.RenameIndex( + name: "IX_ModuleFeatures_FeatureId", + table: "ModuleFeature", + newName: "IX_ModuleFeature_FeatureId"); + + migrationBuilder.RenameIndex( + name: "IX_FeatureActions_FeatureId", + table: "FeatureAction", + newName: "IX_FeatureAction_FeatureId"); + + migrationBuilder.RenameIndex( + name: "IX_FeatureActions_ActionId", + table: "FeatureAction", + newName: "IX_FeatureAction_ActionId"); + + migrationBuilder.AddPrimaryKey( + name: "PK_RolePermission", + table: "RolePermission", + column: "Id"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Module", + table: "Module", + column: "Id"); + + migrationBuilder.AddPrimaryKey( + name: "PK_ModuleFeature", + table: "ModuleFeature", + column: "Id"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Feature", + table: "Feature", + column: "Id"); + + migrationBuilder.AddPrimaryKey( + name: "PK_FeatureAction", + table: "FeatureAction", + column: "Id"); + + migrationBuilder.CreateTable( + name: "Action", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Action", x => x.Id); + }); + + migrationBuilder.AddForeignKey( + name: "FK_FeatureAction_Action_ActionId", + table: "FeatureAction", + column: "ActionId", + principalTable: "Action", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_FeatureAction_Feature_FeatureId", + table: "FeatureAction", + column: "FeatureId", + principalTable: "Feature", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ModuleFeature_Feature_FeatureId", + table: "ModuleFeature", + column: "FeatureId", + principalTable: "Feature", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ModuleFeature_Module_ModuleId", + table: "ModuleFeature", + column: "ModuleId", + principalTable: "Module", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_RolePermission_AspNetRoles_RoleId", + table: "RolePermission", + column: "RoleId", + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_RolePermission_FeatureAction_FeatureActionId", + table: "RolePermission", + column: "FeatureActionId", + principalTable: "FeatureAction", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/FJPSite/Migrations/ApplicationDbContextModelSnapshot.cs b/FJPSite/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..d23501b --- /dev/null +++ b/FJPSite/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,539 @@ +// +using System; +using FJPSite.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace FJPSite.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Feature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Features"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActionId") + .HasColumnType("int"); + + b.Property("FeatureId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ActionId"); + + b.HasIndex("FeatureId"); + + b.ToTable("FeatureActions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Module", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Modules"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.ModuleFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FeatureId") + .HasColumnType("int"); + + b.Property("ModuleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FeatureId"); + + b.HasIndex("ModuleId"); + + b.ToTable("ModuleFeatures"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.PermissionAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Actions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.RolePermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FeatureActionId") + .HasColumnType("int"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("FeatureActionId"); + + b.HasIndex("RoleId"); + + b.ToTable("RolePermissions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.UserPermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FeatureActionId") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("FeatureActionId"); + + b.HasIndex("UserId"); + + b.ToTable("UserPermissions"); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.RoleEntity", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.UserEntity", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("Firstname") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.HasOne("FJPSite.Data.Authorisation.PermissionAction", "Action") + .WithMany("Features") + .HasForeignKey("ActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Authorisation.Feature", "Feature") + .WithMany("Actions") + .HasForeignKey("FeatureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Action"); + + b.Navigation("Feature"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.ModuleFeature", b => + { + b.HasOne("FJPSite.Data.Authorisation.Feature", "Feature") + .WithMany("Modules") + .HasForeignKey("FeatureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Authorisation.Module", "Module") + .WithMany("Features") + .HasForeignKey("ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Feature"); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.RolePermission", b => + { + b.HasOne("FJPSite.Data.Authorisation.FeatureAction", "FeatureAction") + .WithMany("RolePermissions") + .HasForeignKey("FeatureActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Identity.RoleEntity", "Role") + .WithMany("Permissions") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeatureAction"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.UserPermission", b => + { + b.HasOne("FJPSite.Data.Authorisation.FeatureAction", "FeatureAction") + .WithMany() + .HasForeignKey("FeatureActionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Identity.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeatureAction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("FJPSite.Data.Identity.RoleEntity", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("FJPSite.Data.Identity.RoleEntity", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("FJPSite.Data.Identity.UserEntity", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Feature", b => + { + b.Navigation("Actions"); + + b.Navigation("Modules"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.FeatureAction", b => + { + b.Navigation("RolePermissions"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.Module", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("FJPSite.Data.Authorisation.PermissionAction", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("FJPSite.Data.Identity.RoleEntity", b => + { + b.Navigation("Permissions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FJPSite/Models/ActionPermissionModel.cs b/FJPSite/Models/ActionPermissionModel.cs new file mode 100644 index 0000000..cd2358b --- /dev/null +++ b/FJPSite/Models/ActionPermissionModel.cs @@ -0,0 +1,9 @@ +namespace FJPSite.Models; + +public class ActionPermissionModel +{ + public int ActionId { get; set; } + public string ActionName { get; set; } + public bool IsSelected { get; set; } + public bool IsDisabled { get; set; } +} diff --git a/FJPSite/Models/ErrorViewModel.cs b/FJPSite/Models/ErrorViewModel.cs index 12036d5..2c9e450 100644 --- a/FJPSite/Models/ErrorViewModel.cs +++ b/FJPSite/Models/ErrorViewModel.cs @@ -1,9 +1,8 @@ -namespace FJPSite.Models -{ - public class ErrorViewModel - { - public string? RequestId { get; set; } +namespace FJPSite.Models; - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } +public class ErrorViewModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); } diff --git a/FJPSite/Models/FeaturePermissionModel.cs b/FJPSite/Models/FeaturePermissionModel.cs new file mode 100644 index 0000000..33b5f43 --- /dev/null +++ b/FJPSite/Models/FeaturePermissionModel.cs @@ -0,0 +1,8 @@ +namespace FJPSite.Models; + +public class FeaturePermissionModel +{ + public int FeatureId { get; set; } + public string FeatureName { get; set; } + public List Actions { get; set; } = new List(); +} diff --git a/FJPSite/Models/PermissionModel.cs b/FJPSite/Models/PermissionModel.cs new file mode 100644 index 0000000..edad74e --- /dev/null +++ b/FJPSite/Models/PermissionModel.cs @@ -0,0 +1,9 @@ +using FJPSite.Enums; + +namespace FJPSite.Models; + +public class PermissionModel +{ + public FeatureEnum Feature { get; set; } + public ActionEnum Action { get; set; } +} diff --git a/FJPSite/Models/PermissionStructureModel.cs b/FJPSite/Models/PermissionStructureModel.cs new file mode 100644 index 0000000..c0c3cfc --- /dev/null +++ b/FJPSite/Models/PermissionStructureModel.cs @@ -0,0 +1,8 @@ +namespace FJPSite.Models; + +public class PermissionStructureModel +{ + public int ModuleId { get; set; } + public string ModuleName { get; set; } + public List Features { get; set; } = new List(); +} diff --git a/FJPSite/Program.cs b/FJPSite/Program.cs index 75839fb..10dacde 100644 --- a/FJPSite/Program.cs +++ b/FJPSite/Program.cs @@ -1,4 +1,11 @@ using FJPSite.Data; +using FJPSite.Data.Identity; +using FJPSite.Enums; +using FJPSite.Factories; +using FJPSite.Factories.EntitySeeders; +using FJPSite.Helpers; +using FJPSite.Interfaces; +using FJPSite.Services; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -10,10 +17,27 @@ builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); -builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) - .AddEntityFrameworkStores(); +builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddRoles() + .AddEntityFrameworkStores() + .AddClaimsPrincipalFactory(); builder.Services.AddControllersWithViews(); +builder.Services.AddTransient, UserClaimFactory>(); +builder.Services.AddScoped(); +builder.Services.AddAuthorization(options => +{ + foreach (FeatureEnum feature in Enum.GetValues(typeof(FeatureEnum))) + { + foreach (ActionEnum action in Enum.GetValues(typeof(ActionEnum))) + { + string policyName = $"Permission.{feature}.{action}"; + options.AddPolicy(policyName, policy => + policy.Requirements.Add(new PermissionRequirement(feature, action))); + } + } +}); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -35,6 +59,10 @@ app.UseAuthorization(); app.MapStaticAssets(); +app.MapControllerRoute( + name: "Admin", + pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); + app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}") diff --git a/FJPSite/Services/PermissionService.cs b/FJPSite/Services/PermissionService.cs new file mode 100644 index 0000000..2cd88cf --- /dev/null +++ b/FJPSite/Services/PermissionService.cs @@ -0,0 +1,73 @@ +using FJPSite.Data; +using FJPSite.Enums; +using FJPSite.Interfaces; +using FJPSite.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; + +namespace FJPSite.Services; + +public class PermissionService : IPermissionService +{ + private readonly ApplicationDbContext _context; + private readonly IMemoryCache _cache; + public PermissionService( + ApplicationDbContext context, + IMemoryCache cache) + { + _context = context; + _cache = cache; + } + public async Task UserHasPermissionAsync(string userId, FeatureEnum feature, ActionEnum action) + { + // First check cache + var cacheKey = $"permissions_{userId}"; + if (!_cache.TryGetValue(cacheKey, out List permissions)) + { + // Get user roles + var userRoles = await _context.UserRoles.Where(ur => ur.UserId == userId).Select(ur => ur.RoleId).ToListAsync(); + // Get all permissions for these roles + var rolePermissions = await _context.RolePermissions + .Where(rp => userRoles.Contains(rp.RoleId)) + .Include(rp => rp.FeatureAction) + .ThenInclude(fa => fa.Feature) + .Include(rp => rp.FeatureAction) + .ThenInclude(fa => fa.Action) + .ToListAsync(); + // Map to PermissionDto + permissions = rolePermissions.Select(rp => new PermissionModel + { + Feature = Enum.Parse(rp.FeatureAction.Feature.Name), + Action = Enum.Parse(rp.FeatureAction.Action.Name) + }).ToList(); + // Cache for 10 minutes + var cacheEntryOptions = new MemoryCacheEntryOptions() + .SetAbsoluteExpiration(TimeSpan.FromMinutes(10)); + _cache.Set(cacheKey, permissions, cacheEntryOptions); + } + return permissions.Any(p => + p.Feature == feature && + p.Action == action); + } + + Task> IPermissionService.GetPermissionStructureAsync(int moduleId) + { + // First check cache + var cacheKey = $"permission_structure_{moduleId}"; + if (!_cache.TryGetValue(cacheKey, out List permissionStructure)) + { + permissionStructure = _context.Modules.Include(i => i.Features).ThenInclude(i => i.Feature).ThenInclude(i=> i.Actions).ThenInclude(i => i.Action).Where(w => w.Id == moduleId && w.IsActive == true).Select(s => new PermissionStructureModel { + ModuleId = s.Id, + ModuleName = s.Name, + Features = s.Features.Select(b => new FeaturePermissionModel + { + FeatureId = b.FeatureId, + FeatureName = s.Feature.Name + }) + }); + + //_context.Features.Include(i => i.Modules).Include(i => i.Actions).Where(w => w.Modules. == moduleId); + } + return permissionStructure; + } +} \ No newline at end of file diff --git a/FJPSite/Views/Shared/_LoginPartial.cshtml b/FJPSite/Views/Shared/_LoginPartial.cshtml index 921dbe1..9df96c2 100644 --- a/FJPSite/Views/Shared/_LoginPartial.cshtml +++ b/FJPSite/Views/Shared/_LoginPartial.cshtml @@ -1,12 +1,15 @@ -@using Microsoft.AspNetCore.Identity -@inject SignInManager SignInManager -@inject UserManager UserManager +@using FJPSite.Data +@using FJPSite.Data.Identity +@using Microsoft.AspNetCore.Identity +@using System.Security.Claims +@inject SignInManager SignInManager +@inject UserManager UserManager