diff --git a/website/MyWebApp/Controllers/AdminBlockTemplateController.cs b/website/MyWebApp/Controllers/AdminBlockTemplateController.cs index 0afa0de..294481d 100644 --- a/website/MyWebApp/Controllers/AdminBlockTemplateController.cs +++ b/website/MyWebApp/Controllers/AdminBlockTemplateController.cs @@ -26,6 +26,7 @@ public AdminBlockTemplateController(ApplicationDbContext db, HtmlSanitizerServic private async Task LoadPagesAsync() { ViewBag.Pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync(); + ViewBag.Roles = await _db.Roles.AsNoTracking().OrderBy(r => r.Name).ToListAsync(); } public async Task Index() @@ -159,7 +160,7 @@ public async Task AddToPage(int id) [HttpPost] [ValidateAntiForgeryToken] - public async Task AddToPage(int id, int pageId, string zone) + public async Task AddToPage(int id, int pageId, string zone, int? roleId) { var template = await _db.BlockTemplates.FindAsync(id); var page = await _db.Pages.FindAsync(pageId); @@ -186,7 +187,8 @@ public async Task AddToPage(int id, int pageId, string zone) Zone = zone, SortOrder = sort, Html = template.Html, - Type = PageSectionType.Html + Type = PageSectionType.Html, + RoleId = roleId }; _db.PageSections.Add(section); await _db.SaveChangesAsync(); diff --git a/website/MyWebApp/Controllers/AdminContentController.cs b/website/MyWebApp/Controllers/AdminContentController.cs index 21f53d4..99aef70 100644 --- a/website/MyWebApp/Controllers/AdminContentController.cs +++ b/website/MyWebApp/Controllers/AdminContentController.cs @@ -35,6 +35,8 @@ private async Task LoadTemplatesAsync() .OrderBy(t => t.Name).ToListAsync(); ViewBag.Permissions = await _db.Permissions.AsNoTracking() .OrderBy(p => p.Name).ToListAsync(); + ViewBag.Roles = await _db.Roles.AsNoTracking() + .OrderBy(r => r.Name).ToListAsync(); } public async Task Index() diff --git a/website/MyWebApp/Controllers/AdminPageSectionController.cs b/website/MyWebApp/Controllers/AdminPageSectionController.cs index 05f74ff..ecfa844 100644 --- a/website/MyWebApp/Controllers/AdminPageSectionController.cs +++ b/website/MyWebApp/Controllers/AdminPageSectionController.cs @@ -39,6 +39,7 @@ private async Task LoadPagesAsync() { ViewBag.Pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync(); ViewBag.Permissions = await _db.Permissions.AsNoTracking().OrderBy(p => p.Name).ToListAsync(); + ViewBag.Roles = await _db.Roles.AsNoTracking().OrderBy(r => r.Name).ToListAsync(); } public async Task Create() diff --git a/website/MyWebApp/Controllers/PagesController.cs b/website/MyWebApp/Controllers/PagesController.cs index 8d5cae0..e4c62b8 100644 --- a/website/MyWebApp/Controllers/PagesController.cs +++ b/website/MyWebApp/Controllers/PagesController.cs @@ -29,6 +29,15 @@ public async Task Show(string? slug) { return NotFound(); } + var roles = HttpContext.Session.GetString("Roles")?.Split(',') ?? Array.Empty(); + if (page.RoleId != null) + { + var allowed = await Db.Roles.AsNoTracking().Where(r => roles.Contains(r.Name)).Select(r => r.Id).ToListAsync(); + if (!allowed.Contains(page.RoleId.Value)) + { + return Unauthorized(); + } + } var header = await _layout.GetSectionAsync(Db, page.Id, "header"); if (string.IsNullOrEmpty(header)) diff --git a/website/MyWebApp/Data/ApplicationDbContext.cs b/website/MyWebApp/Data/ApplicationDbContext.cs index a64914a..8b12015 100644 --- a/website/MyWebApp/Data/ApplicationDbContext.cs +++ b/website/MyWebApp/Data/ApplicationDbContext.cs @@ -90,14 +90,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) Id = 1, Slug = "layout", Title = "Layout", - Layout = "single-column" + Layout = "single-column", + RoleId = null }, new Page { Id = 2, Slug = "home", Title = "Home", - Layout = "single-column" + Layout = "single-column", + RoleId = null }); modelBuilder.Entity().HasData( diff --git a/website/MyWebApp/Models/Page.cs b/website/MyWebApp/Models/Page.cs index ee8a599..71545c5 100644 --- a/website/MyWebApp/Models/Page.cs +++ b/website/MyWebApp/Models/Page.cs @@ -45,6 +45,10 @@ public class Page [MaxLength(256)] public string? FeaturedImage { get; set; } + public int? RoleId { get; set; } + + public Role? Role { get; set; } + public ICollection Sections { get; set; } = new List(); } } diff --git a/website/MyWebApp/Models/PageSection.cs b/website/MyWebApp/Models/PageSection.cs index 2e3600e..8b97aa4 100644 --- a/website/MyWebApp/Models/PageSection.cs +++ b/website/MyWebApp/Models/PageSection.cs @@ -37,8 +37,12 @@ public class PageSection public int? PermissionId { get; set; } + public int? RoleId { get; set; } + public Page? Page { get; set; } public Permission? Permission { get; set; } + + public Role? Role { get; set; } } diff --git a/website/MyWebApp/Program.cs b/website/MyWebApp/Program.cs index 817dbe8..3902050 100644 --- a/website/MyWebApp/Program.cs +++ b/website/MyWebApp/Program.cs @@ -355,6 +355,8 @@ FOREIGN KEY(PageId) REFERENCES Pages(Id) ON DELETE CASCADE db.Database.ExecuteSqlRaw("ALTER TABLE PageSections ADD COLUMN EndDate TEXT"); if (!columns.Contains("PermissionId")) db.Database.ExecuteSqlRaw("ALTER TABLE PageSections ADD COLUMN PermissionId INTEGER"); + if (!columns.Contains("RoleId")) + db.Database.ExecuteSqlRaw("ALTER TABLE PageSections ADD COLUMN RoleId INTEGER"); cmd.CommandText = "PRAGMA index_list('PageSections')"; using var idx = cmd.ExecuteReader(); @@ -438,6 +440,10 @@ static void UpgradePagesTable(ApplicationDbContext db) { db.Database.ExecuteSqlRaw("ALTER TABLE Pages ADD COLUMN FeaturedImage TEXT"); } + if (!columns.Contains("RoleId")) + { + db.Database.ExecuteSqlRaw("ALTER TABLE Pages ADD COLUMN RoleId INTEGER"); + } } catch (Exception ex) { diff --git a/website/MyWebApp/Services/LayoutService.cs b/website/MyWebApp/Services/LayoutService.cs index 09ffa7a..1d16cf2 100644 --- a/website/MyWebApp/Services/LayoutService.cs +++ b/website/MyWebApp/Services/LayoutService.cs @@ -52,16 +52,26 @@ private async Task> GetAllowedPermissionsAsync(ApplicationDbContext db .ToListAsync(); } + private async Task> GetRoleIdsAsync(ApplicationDbContext db, string[] roles) + { + if (roles.Length == 0) return new List(); + return await db.Roles.AsNoTracking() + .Where(r => roles.Contains(r.Name)) + .Select(r => r.Id) + .ToListAsync(); + } + public async Task GetHeaderAsync(ApplicationDbContext db) { var roles = GetRoles(); + var roleIds = await GetRoleIdsAsync(db, roles); if (roles.Length == 0) { return await _cache.GetOrCreateAsync(HeaderKey, async e => { e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); var parts = await db.PageSections.AsNoTracking() - .Where(s => s.Page.Slug == "layout" && s.Zone == "header" && s.PermissionId == null) + .Where(s => s.Page.Slug == "layout" && s.Zone == "header" && s.PermissionId == null && s.RoleId == null) .OrderBy(s => s.SortOrder) .Select(s => s.Html) .ToListAsync(); @@ -73,7 +83,9 @@ public async Task GetHeaderAsync(ApplicationDbContext db) var allowed = await GetAllowedPermissionsAsync(db, roles); var query = db.PageSections.AsNoTracking() .Where(s => s.Page.Slug == "layout" && s.Zone == "header"); - query = query.Where(s => s.PermissionId == null || allowed.Contains(s.PermissionId.Value)); + query = query.Where(s => + (s.PermissionId == null || allowed.Contains(s.PermissionId.Value)) && + (s.RoleId == null || roleIds.Contains(s.RoleId.Value))); var parts2 = await query.OrderBy(s => s.SortOrder).Select(s => s.Html).ToListAsync(); var html2 = string.Join(System.Environment.NewLine, parts2); return await _tokens.RenderAsync(db, html2); @@ -82,13 +94,14 @@ public async Task GetHeaderAsync(ApplicationDbContext db) public async Task GetFooterAsync(ApplicationDbContext db) { var roles = GetRoles(); + var roleIds = await GetRoleIdsAsync(db, roles); if (roles.Length == 0) { return await _cache.GetOrCreateAsync(FooterKey, async e => { e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); var parts = await db.PageSections.AsNoTracking() - .Where(s => s.Page.Slug == "layout" && s.Zone == "footer" && s.PermissionId == null) + .Where(s => s.Page.Slug == "layout" && s.Zone == "footer" && s.PermissionId == null && s.RoleId == null) .OrderBy(s => s.SortOrder) .Select(s => s.Html) .ToListAsync(); @@ -100,7 +113,9 @@ public async Task GetFooterAsync(ApplicationDbContext db) var allowed = await GetAllowedPermissionsAsync(db, roles); var query = db.PageSections.AsNoTracking() .Where(s => s.Page.Slug == "layout" && s.Zone == "footer"); - query = query.Where(s => s.PermissionId == null || allowed.Contains(s.PermissionId.Value)); + query = query.Where(s => + (s.PermissionId == null || allowed.Contains(s.PermissionId.Value)) && + (s.RoleId == null || roleIds.Contains(s.RoleId.Value))); var parts2 = await query.OrderBy(s => s.SortOrder).Select(s => s.Html).ToListAsync(); var html2 = string.Join(System.Environment.NewLine, parts2); return await _tokens.RenderAsync(db, html2); @@ -109,13 +124,16 @@ public async Task GetFooterAsync(ApplicationDbContext db) public async Task GetSectionAsync(ApplicationDbContext db, int pageId, string zone) { var roles = GetRoles(); + var roleIds = await GetRoleIdsAsync(db, roles); var allowed = await GetAllowedPermissionsAsync(db, roles); var query = db.PageSections.AsNoTracking() .Where(s => s.PageId == pageId && s.Zone == zone); - if (allowed.Count == 0) - query = query.Where(s => s.PermissionId == null); + if (allowed.Count == 0 && roleIds.Count == 0) + query = query.Where(s => s.PermissionId == null && s.RoleId == null); else - query = query.Where(s => s.PermissionId == null || allowed.Contains(s.PermissionId.Value)); + query = query.Where(s => + (s.PermissionId == null || allowed.Contains(s.PermissionId.Value)) && + (s.RoleId == null || roleIds.Contains(s.RoleId.Value))); var parts = await query.OrderBy(s => s.SortOrder).Select(s => s.Html).ToListAsync(); var html = string.Join(System.Environment.NewLine, parts); return await _tokens.RenderAsync(db, html); diff --git a/website/MyWebApp/Services/TokenRenderService.cs b/website/MyWebApp/Services/TokenRenderService.cs index 9d0396d..da635ad 100644 --- a/website/MyWebApp/Services/TokenRenderService.cs +++ b/website/MyWebApp/Services/TokenRenderService.cs @@ -32,6 +32,15 @@ private async Task> GetAllowedPermissionsAsync(ApplicationDbContext db .ToListAsync(); } + private async Task> GetRoleIdsAsync(ApplicationDbContext db, string[] roles) + { + if (roles.Length == 0) return new List(); + return await db.Roles.AsNoTracking() + .Where(r => roles.Contains(r.Name)) + .Select(r => r.Id) + .ToListAsync(); + } + public Task RenderAsync(ApplicationDbContext db, string html) { return RenderAsync(db, html, new HashSet()); @@ -43,8 +52,10 @@ async Task Replace(Match match) { if (match.Value.StartsWith("{{nav", StringComparison.OrdinalIgnoreCase)) { + var roles = GetRoles(); + var roleIds = await GetRoleIdsAsync(db, roles); var pages = await db.Pages.AsNoTracking() - .Where(p => p.IsPublished && p.Slug != "layout" && p.Slug != "home") + .Where(p => p.IsPublished && p.Slug != "layout" && p.Slug != "home" && (p.RoleId == null || roleIds.Contains(p.RoleId.Value))) .OrderBy(p => p.Title) .Select(p => new { p.Slug, p.Title }) .ToListAsync(); @@ -75,13 +86,16 @@ async Task Replace(Match match) { var zone = parts[1]; var roles = GetRoles(); + var roleIds = await GetRoleIdsAsync(db, roles); var allowed = await GetAllowedPermissionsAsync(db, roles); var query = db.PageSections.AsNoTracking() .Where(s => s.PageId == pageId && s.Zone == zone); - if (allowed.Count == 0) - query = query.Where(s => s.PermissionId == null); + if (allowed.Count == 0 && roleIds.Count == 0) + query = query.Where(s => s.PermissionId == null && s.RoleId == null); else - query = query.Where(s => s.PermissionId == null || allowed.Contains(s.PermissionId.Value)); + query = query.Where(s => + (s.PermissionId == null || allowed.Contains(s.PermissionId.Value)) && + (s.RoleId == null || roleIds.Contains(s.RoleId.Value))); var htmlParts = await query .OrderBy(s => s.SortOrder) .Select(s => s.Html) diff --git a/website/MyWebApp/Views/AdminBlockTemplate/AddToPage.cshtml b/website/MyWebApp/Views/AdminBlockTemplate/AddToPage.cshtml index 8e06142..c902368 100644 --- a/website/MyWebApp/Views/AdminBlockTemplate/AddToPage.cshtml +++ b/website/MyWebApp/Views/AdminBlockTemplate/AddToPage.cshtml @@ -19,5 +19,15 @@ +
+ + +
diff --git a/website/MyWebApp/Views/AdminContent/PageEditor.cshtml b/website/MyWebApp/Views/AdminContent/PageEditor.cshtml index 83ba5fd..847a8e2 100644 --- a/website/MyWebApp/Views/AdminContent/PageEditor.cshtml +++ b/website/MyWebApp/Views/AdminContent/PageEditor.cshtml @@ -3,6 +3,7 @@ @using Microsoft.AspNetCore.Mvc.ViewFeatures @{ var sections = ViewBag.Sections as List ?? new List(); + var roles = ViewBag.Roles as List ?? new List(); Layout = "../Admin/_AdminLayout"; var isNew = Model.Id == 0; ViewData["Title"] = isNew ? "Create Page" : "Edit Page"; @@ -40,6 +41,16 @@
+
+ + +
@@ -56,7 +67,7 @@
@for (int i = 0; i < sections.Count; i++) { - var vd = new ViewDataDictionary(ViewData) { ["Index"] = i }; + var vd = new ViewDataDictionary(ViewData) { ["Index"] = i, ["Roles"] = roles }; @await Html.PartialAsync("~/Views/Shared/_SectionEditor.cshtml", sections[i], vd) }
@@ -74,7 +85,7 @@ } diff --git a/website/MyWebApp/Views/AdminPageSection/Create.cshtml b/website/MyWebApp/Views/AdminPageSection/Create.cshtml index 9fa8510..9550599 100644 --- a/website/MyWebApp/Views/AdminPageSection/Create.cshtml +++ b/website/MyWebApp/Views/AdminPageSection/Create.cshtml @@ -4,6 +4,7 @@ Layout = "../Admin/_AdminLayout"; var pages = ViewBag.Pages as List; var permissions = ViewBag.Permissions as List; + var roles = ViewBag.Roles as List ?? new List(); }

Create Section

@@ -16,7 +17,7 @@ -@await Html.PartialAsync("~/Views/Shared/_SectionEditor.cshtml", Model) +@await Html.PartialAsync("~/Views/Shared/_SectionEditor.cshtml", Model, new ViewDataDictionary(ViewData) { ["Roles"] = roles })
diff --git a/website/MyWebApp/Views/AdminPageSection/Edit.cshtml b/website/MyWebApp/Views/AdminPageSection/Edit.cshtml index 5707297..05822c0 100644 --- a/website/MyWebApp/Views/AdminPageSection/Edit.cshtml +++ b/website/MyWebApp/Views/AdminPageSection/Edit.cshtml @@ -4,6 +4,7 @@ Layout = "../Admin/_AdminLayout"; var pages = ViewBag.Pages as List; var permissions = ViewBag.Permissions as List; + var roles = ViewBag.Roles as List ?? new List(); }

Edit Section

@@ -17,7 +18,7 @@ -@await Html.PartialAsync("~/Views/Shared/_SectionEditor.cshtml", Model) +@await Html.PartialAsync("~/Views/Shared/_SectionEditor.cshtml", Model, new ViewDataDictionary(ViewData) { ["Roles"] = roles })
diff --git a/website/MyWebApp/Views/Shared/_SectionEditor.cshtml b/website/MyWebApp/Views/Shared/_SectionEditor.cshtml index 082477b..a5dbee2 100644 --- a/website/MyWebApp/Views/Shared/_SectionEditor.cshtml +++ b/website/MyWebApp/Views/Shared/_SectionEditor.cshtml @@ -4,6 +4,7 @@ var idxObj = ViewData["Index"]; var idx = idxObj?.ToString() ?? "0"; var prefix = idxObj != null ? $"Sections[{idx}]." : string.Empty; + var roles = ViewData["Roles"] as List ?? new List(); }
@@ -12,6 +13,16 @@
+
+ + +