From 88a08c886341f139d7f54bdc8353dc594973e7b0 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Fri, 30 Aug 2019 10:05:13 -0400 Subject: [PATCH] Permission grid control, refactor permission string serialization --- .../Modules/Admin/ModuleSettings/Index.razor | 108 +++++----- Oqtane.Client/Modules/Admin/Pages/Add.razor | 24 +-- .../Modules/Admin/Pages/Delete.razor | 20 +- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 22 +- Oqtane.Client/Modules/Admin/Sites/Add.razor | 9 +- .../Modules/Controls/ActionLink.razor | 4 +- .../Modules/Controls/PermissionGrid.razor | 196 ++++++++++++++++++ Oqtane.Client/Shared/Pane.razor | 4 +- Oqtane.Server/Controllers/AliasController.cs | 7 +- Oqtane.Server/Controllers/ModuleController.cs | 7 +- Oqtane.Server/Controllers/PageController.cs | 7 +- .../Controllers/PageModuleController.cs | 7 +- .../Controllers/PermissionController.cs | 7 +- Oqtane.Server/Controllers/RoleController.cs | 7 +- .../Controllers/SettingController.cs | 7 +- Oqtane.Server/Controllers/SiteController.cs | 7 +- Oqtane.Server/Controllers/TenantController.cs | 7 +- Oqtane.Server/Controllers/UserController.cs | 4 +- .../Controllers/UserRoleController.cs | 7 +- .../Repository/PermissionRepository.cs | 67 +++--- Oqtane.Server/Security/IUserPermissions.cs | 9 + Oqtane.Server/Security/PermissionHandler.cs | 35 +--- Oqtane.Server/Security/UserPermissions.cs | 42 ++++ Oqtane.Server/Startup.cs | 2 + Oqtane.Shared/Models/PermissionString.cs | 8 + Oqtane.Shared/Oqtane.Shared.csproj | 1 + Oqtane.Shared/Security/UserSecurity.cs | 51 +++-- 27 files changed, 460 insertions(+), 216 deletions(-) create mode 100644 Oqtane.Client/Modules/Controls/PermissionGrid.razor create mode 100644 Oqtane.Server/Security/IUserPermissions.cs create mode 100644 Oqtane.Server/Security/UserPermissions.cs create mode 100644 Oqtane.Shared/Models/PermissionString.cs diff --git a/Oqtane.Client/Modules/Admin/ModuleSettings/Index.razor b/Oqtane.Client/Modules/Admin/ModuleSettings/Index.razor index 7eb5154e..80ebca10 100644 --- a/Oqtane.Client/Modules/Admin/ModuleSettings/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleSettings/Index.razor @@ -11,59 +11,51 @@ @inject IModuleService ModuleService @inject IPageModuleService PageModuleService - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - -
- - - -
- - - -
- - - -
+ + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
@DynamicComponent @@ -78,10 +70,11 @@ Dictionary containers = new Dictionary(); string title; string containertype; - string viewpermissions; - string editpermissions; + string permissions; string pageid; + PermissionGrid permissiongrid; + RenderFragment DynamicComponent { get; set; } object settings; @@ -90,8 +83,7 @@ title = ModuleState.Title; containers = ThemeService.GetContainerTypes(await ThemeService.GetThemesAsync()); containertype = ModuleState.ContainerType; - viewpermissions = UserSecurity.GetPermissions("View", ModuleState.Permissions); - editpermissions = UserSecurity.GetPermissions("Edit", ModuleState.Permissions); + permissions = ModuleState.Permissions; pageid = ModuleState.PageId.ToString(); DynamicComponent = builder => @@ -109,7 +101,7 @@ private async Task SaveModule() { Module module = ModuleState; - module.Permissions = UserSecurity.SetPermissions("View", viewpermissions) + UserSecurity.SetPermissions("Edit", editpermissions); + module.Permissions = permissiongrid.GetPermissions(); await ModuleService.UpdateModuleAsync(module); PageModule pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index de65c5ae..14c3db9c 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -99,18 +99,10 @@ - + - - - - - - - - - + @@ -133,8 +125,9 @@ string themetype; string layouttype = ""; string icon = ""; - string viewpermissions = "All Users"; - string editpermissions = "Administrators"; + string permissions = ""; // need to set default permissions + + PermissionGrid permissiongrid; protected override void OnInitialized() { @@ -142,6 +135,11 @@ { themes = ThemeService.GetThemeTypes(PageState.Themes); panelayouts = ThemeService.GetPaneLayoutTypes(PageState.Themes); + + List permissionstrings = new List(); + permissionstrings.Add(new PermissionString { PermissionName = "View", Permissions = Constants.AdminRole }); + permissionstrings.Add(new PermissionString { PermissionName = "Edit", Permissions = Constants.AdminRole }); + permissions = UserSecurity.SetPermissionStrings(permissionstrings); } catch (Exception ex) { @@ -181,7 +179,7 @@ } System.Reflection.PropertyInfo property = type.GetProperty("Panes"); page.Panes = (string)property.GetValue(Activator.CreateInstance(type), null); - page.Permissions = UserSecurity.SetPermissions("View", viewpermissions) + UserSecurity.SetPermissions("Edit", editpermissions); + page.Permissions = permissiongrid.GetPermissions(); await PageService.AddPageAsync(page); PageState.Reload = Constants.ReloadSite; diff --git a/Oqtane.Client/Modules/Admin/Pages/Delete.razor b/Oqtane.Client/Modules/Admin/Pages/Delete.razor index bfa1473f..5c5da1ff 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Delete.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Delete.razor @@ -100,18 +100,10 @@ - + - - - - - - - - - + @@ -138,13 +130,14 @@ string themetype; string layouttype; string icon; - string viewpermissions; - string editpermissions; + string permissions; string createdby; DateTime createdon; string modifiedby; DateTime modifiedon; + PermissionGrid permissiongrid; + protected override void OnInitialized() { try @@ -164,8 +157,7 @@ themetype = page.ThemeType; layouttype = page.LayoutType; icon = page.Icon; - viewpermissions = UserSecurity.GetPermissions("View", page.Permissions); - editpermissions = UserSecurity.GetPermissions("Edit", page.Permissions); + permissions = page.Permissions; createdby = page.CreatedBy; createdon = page.CreatedOn; modifiedby = page.ModifiedBy; diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 043c9c1f..43c64ee5 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -100,18 +100,10 @@ - + - - - - - - - - - + @@ -138,13 +130,14 @@ string themetype; string layouttype; string icon; - string viewpermissions; - string editpermissions; + string permissions; string createdby; DateTime createdon; string modifiedby; DateTime modifiedon; + PermissionGrid permissiongrid; + protected override void OnInitialized() { try @@ -171,8 +164,7 @@ themetype = page.ThemeType; layouttype = page.LayoutType; icon = page.Icon; - viewpermissions = UserSecurity.GetPermissions("View", page.Permissions); - editpermissions = UserSecurity.GetPermissions("Edit", page.Permissions); + permissions = page.Permissions; createdby = page.CreatedBy; createdon = page.CreatedOn; modifiedby = page.ModifiedBy; @@ -217,7 +209,7 @@ } System.Reflection.PropertyInfo property = type.GetProperty("Panes"); page.Panes = (string)property.GetValue(Activator.CreateInstance(type), null); - page.Permissions = UserSecurity.SetPermissions("View", viewpermissions) + UserSecurity.SetPermissions("Edit", editpermissions); + page.Permissions = permissiongrid.GetPermissions(); await PageService.UpdatePageAsync(page); PageState.Reload = Constants.ReloadSite; diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 06165cbb..48fc1dc5 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -98,13 +98,18 @@ else p.Path = ""; p.Order = 1; p.IsNavigation = true; - p.ThemeType = "Oqtane.Client.Themes.Theme1.Theme1, Oqtane.Client"; + p.ThemeType = "Oqtane.Client.Themes.Theme1.Theme1, Oqtane.Client"; // TODO: should not hardcode p.LayoutType = ""; p.Icon = ""; Type type = Type.GetType(p.ThemeType); System.Reflection.PropertyInfo property = type.GetProperty("Panes"); p.Panes = (string)property.GetValue(Activator.CreateInstance(type), null); - p.Permissions = UserSecurity.SetPermissions("View", Constants.AllUsersRole) + UserSecurity.SetPermissions("Edit", Constants.AdminRole); + + List permissionstrings = new List(); + permissionstrings.Add(new PermissionString { PermissionName = "View", Permissions = Constants.AllUsersRole }); + permissionstrings.Add(new PermissionString { PermissionName = "Edit", Permissions = Constants.AdminRole }); + p.Permissions = UserSecurity.SetPermissionStrings(permissionstrings); + await PageService.AddPageAsync(p); UriHelper.NavigateTo(url, true); diff --git a/Oqtane.Client/Modules/Controls/ActionLink.razor b/Oqtane.Client/Modules/Controls/ActionLink.razor index deb120c0..d894894b 100644 --- a/Oqtane.Client/Modules/Controls/ActionLink.razor +++ b/Oqtane.Client/Modules/Controls/ActionLink.razor @@ -77,10 +77,10 @@ authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", ModuleState.Permissions); break; case SecurityAccessLevel.Admin: - authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", UserSecurity.SetPermissions("Edit", Constants.AdminRole)); + authorized = UserSecurity.IsAuthorized(PageState.User, Constants.AdminRole); break; case SecurityAccessLevel.Host: - authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", UserSecurity.SetPermissions("Edit", Constants.HostRole)); + authorized = UserSecurity.IsAuthorized(PageState.User, Constants.HostRole); break; } } diff --git a/Oqtane.Client/Modules/Controls/PermissionGrid.razor b/Oqtane.Client/Modules/Controls/PermissionGrid.razor new file mode 100644 index 00000000..d330a9b6 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/PermissionGrid.razor @@ -0,0 +1,196 @@ +@using Oqtane.Services +@using Oqtane.Modules +@using Oqtane.Models +@using Oqtane.Security +@using Oqtane.Shared +@inherits ModuleBase +@inject IRoleService RoleService +@inject IUserService UserService + +@if (roles != null) +{ +
+
+
+ + + + + @foreach (PermissionString permission in permissions) + { + + } + + @foreach (Role role in roles) + { + + + @foreach (PermissionString permission in permissions) + { + var p = permission; + + } + + } + +
Role@permission.PermissionName @EntityName
@role.Name
+
+ @if (@users.Count != 0) + { +
+ + + + + @foreach (PermissionString permission in permissions) + { + + } + + + + @foreach (User user in users) + { + + + @foreach (PermissionString permission in permissions) + { + var p = permission; + + } + + } + +
User@permission.PermissionName @EntityName
@user.DisplayName
+
+ } +
+ + + +
+
+ +
+} + +@code { + [Parameter] + public string EntityName { get; set; } + + [Parameter] + public string Permissions { get; set; } + + [Parameter] + public string PermissionNames { get; set; } // optional - can be used to specify permissions order or add custom permissions + + List roles; + List permissions = new List(); + List users = new List(); + string username = ""; + string message = ""; + + protected override async Task OnInitializedAsync() + { + if (string.IsNullOrEmpty(PermissionNames)) + { + PermissionNames = "View,Edit"; + } + roles = await RoleService.GetRolesAsync(ModuleState.SiteId); + roles.Insert(0, new Role { Name = Constants.AllUsersRole }); + + foreach (string permissionname in PermissionNames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + permissions.Add(new PermissionString { PermissionName = permissionname, Permissions = "" }); + } + foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions)) + { + if (permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null) + { + permissions[permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions; + } + if (permissionstring.Permissions.Contains("[")) + { + foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (user.Contains("]")) + { + int userid = int.Parse(user.Substring(0, user.IndexOf("]"))); + if (users.Where(item => item.UserId == userid).FirstOrDefault() == null) + { + users.Add(await UserService.GetUserAsync(userid, ModuleState.SiteId)); + } + } + } + } + } + } + + private bool GetPermissionValue(string Permissions, string SecurityKey) + { + if ((";" + Permissions + ";").Contains(";" + SecurityKey + ";")) + { + return true; + } + else + { + return false; + } + } + + private bool GetPermissionDisabled(string RoleName) + { + if (RoleName == Constants.AdminRole) + { + return true; + } + else + { + return false; + } + } + + private async Task AddUser() + { + if (users.Where(item => item.Username == username).FirstOrDefault() == null) + { + try + { + User user = await UserService.GetUserAsync(username, ModuleState.SiteId); + if (user != null) + { + users.Add(user); + } + } + catch + { + message = "Username Does Not Exist"; + } + } + username = ""; + } + + private void PermissionChanged(UIChangeEventArgs e, string PermissionName, string SecurityId) + { + bool selected = (bool)e.Value; + PermissionString permission = permissions.Find(item => item.PermissionName == PermissionName); + if (permission != null) + { + List ids = permission.Permissions.Split(';').ToList(); + if (selected) + { + ids.Add(SecurityId); + } + else + { + ids.Remove(SecurityId); + } + permissions[permissions.FindIndex(item => item.PermissionName == PermissionName)].Permissions = string.Join(";", ids.ToArray()); + } + } + + public string GetPermissions() + { + return UserSecurity.SetPermissionStrings(permissions); + } +} diff --git a/Oqtane.Client/Shared/Pane.razor b/Oqtane.Client/Shared/Pane.razor index 3f245bf7..93b27c41 100644 --- a/Oqtane.Client/Shared/Pane.razor +++ b/Oqtane.Client/Shared/Pane.razor @@ -67,10 +67,10 @@ authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", module.Permissions); break; case SecurityAccessLevel.Admin: - authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", UserSecurity.SetPermissions("Edit", Constants.AdminRole)); + authorized = UserSecurity.IsAuthorized(PageState.User, Constants.AdminRole); break; case SecurityAccessLevel.Host: - authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", UserSecurity.SetPermissions("Edit", Constants.HostRole)); + authorized = UserSecurity.IsAuthorized(PageState.User, Constants.HostRole); break; } if (authorized) diff --git a/Oqtane.Server/Controllers/AliasController.cs b/Oqtane.Server/Controllers/AliasController.cs index 9e9e4bae..b559c41e 100644 --- a/Oqtane.Server/Controllers/AliasController.cs +++ b/Oqtane.Server/Controllers/AliasController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -32,7 +33,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Alias Post([FromBody] Alias Alias) { if (ModelState.IsValid) @@ -44,7 +45,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Alias Put(int id, [FromBody] Alias Alias) { if (ModelState.IsValid) @@ -56,7 +57,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id) { Aliases.DeleteAlias(id); diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index a29e5059..1c60310a 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -54,7 +55,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Module Post([FromBody] Module Module) { if (ModelState.IsValid) @@ -66,7 +67,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Module Put(int id, [FromBody] Module Module) { if (ModelState.IsValid) @@ -78,7 +79,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id) { Modules.DeleteModule(id); diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index 03c140e6..8f75bcf0 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -39,7 +40,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Page Post([FromBody] Page Page) { if (ModelState.IsValid) @@ -51,7 +52,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Page Put(int id, [FromBody] Page Page) { if (ModelState.IsValid) @@ -63,7 +64,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id) { Pages.DeletePage(id); diff --git a/Oqtane.Server/Controllers/PageModuleController.cs b/Oqtane.Server/Controllers/PageModuleController.cs index 8ae9d032..061f60ac 100644 --- a/Oqtane.Server/Controllers/PageModuleController.cs +++ b/Oqtane.Server/Controllers/PageModuleController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -32,7 +33,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public PageModule Post([FromBody] PageModule PageModule) { if (ModelState.IsValid) @@ -44,7 +45,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public PageModule Put(int id, [FromBody] PageModule PageModule) { if (ModelState.IsValid) @@ -56,7 +57,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id) { PageModules.DeletePageModule(id); diff --git a/Oqtane.Server/Controllers/PermissionController.cs b/Oqtane.Server/Controllers/PermissionController.cs index 59466930..dc2bc614 100644 --- a/Oqtane.Server/Controllers/PermissionController.cs +++ b/Oqtane.Server/Controllers/PermissionController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -32,7 +33,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Permission Post([FromBody] Permission Permission) { if (ModelState.IsValid) @@ -44,7 +45,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Permission Put(int id, [FromBody] Permission Permission) { if (ModelState.IsValid) @@ -56,7 +57,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id) { Permissions.DeletePermission(id); diff --git a/Oqtane.Server/Controllers/RoleController.cs b/Oqtane.Server/Controllers/RoleController.cs index 7401b2bf..8ac711a3 100644 --- a/Oqtane.Server/Controllers/RoleController.cs +++ b/Oqtane.Server/Controllers/RoleController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -39,7 +40,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Role Post([FromBody] Role Role) { if (ModelState.IsValid) @@ -51,7 +52,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public Role Put(int id, [FromBody] Role Role) { if (ModelState.IsValid) @@ -63,7 +64,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id) { Roles.DeleteRole(id); diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 4c674138..5dcec2d8 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -32,7 +33,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize] + [Authorize(Roles = Constants.AdminRole)] public Setting Post([FromBody] Setting Setting) { if (ModelState.IsValid) @@ -44,7 +45,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize] + [Authorize(Roles = Constants.AdminRole)] public Setting Put(int id, [FromBody] Setting Setting) { if (ModelState.IsValid) @@ -56,7 +57,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id) { Settings.DeleteSetting(id); diff --git a/Oqtane.Server/Controllers/SiteController.cs b/Oqtane.Server/Controllers/SiteController.cs index 4377b67a..802cac83 100644 --- a/Oqtane.Server/Controllers/SiteController.cs +++ b/Oqtane.Server/Controllers/SiteController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -32,7 +33,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize] + [Authorize(Roles = Constants.HostRole)] public Site Post([FromBody] Site Site) { if (ModelState.IsValid) @@ -44,7 +45,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize] + [Authorize(Roles = Constants.HostRole)] public Site Put(int id, [FromBody] Site Site) { if (ModelState.IsValid) @@ -56,7 +57,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize] + [Authorize(Roles = Constants.HostRole)] public void Delete(int id) { Sites.DeleteSite(id); diff --git a/Oqtane.Server/Controllers/TenantController.cs b/Oqtane.Server/Controllers/TenantController.cs index c3f41289..1711aae1 100644 --- a/Oqtane.Server/Controllers/TenantController.cs +++ b/Oqtane.Server/Controllers/TenantController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; using System.Collections.Generic; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -32,7 +33,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize] + [Authorize(Roles = Constants.HostRole)] public Tenant Post([FromBody] Tenant Tenant) { if (ModelState.IsValid) @@ -44,7 +45,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize] + [Authorize(Roles = Constants.HostRole)] public Tenant Put(int id, [FromBody] Tenant Tenant) { if (ModelState.IsValid) @@ -56,7 +57,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize] + [Authorize(Roles = Constants.HostRole)] public void Delete(int id) { Tenants.DeleteTenant(id); diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 535b2d38..3ad60d0c 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -178,7 +178,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public User Put(int id, [FromBody] User User) { if (ModelState.IsValid) @@ -190,7 +190,7 @@ namespace Oqtane.Controllers // DELETE api//5?siteid=x [HttpDelete("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id, string siteid) { SiteUser siteuser = SiteUsers.GetSiteUser(id, int.Parse(siteid)); diff --git a/Oqtane.Server/Controllers/UserRoleController.cs b/Oqtane.Server/Controllers/UserRoleController.cs index 4bdfba30..752adddb 100644 --- a/Oqtane.Server/Controllers/UserRoleController.cs +++ b/Oqtane.Server/Controllers/UserRoleController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Repository; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -39,7 +40,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public UserRole Post([FromBody] UserRole UserRole) { if (ModelState.IsValid) @@ -51,7 +52,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public UserRole Put(int id, [FromBody] UserRole UserRole) { if (ModelState.IsValid) @@ -63,7 +64,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = "Administrators")] + [Authorize(Roles = Constants.AdminRole)] public void Delete(int id) { UserRoles.DeleteUserRole(id); diff --git a/Oqtane.Server/Repository/PermissionRepository.cs b/Oqtane.Server/Repository/PermissionRepository.cs index ba2aeee3..67a72b0d 100644 --- a/Oqtane.Server/Repository/PermissionRepository.cs +++ b/Oqtane.Server/Repository/PermissionRepository.cs @@ -4,6 +4,8 @@ using System.Linq; using Oqtane.Models; using System.Text; using System; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Oqtane.Repository { @@ -136,18 +138,22 @@ namespace Oqtane.Repository // permissions are stored in the format "{permissionname:!rolename1;![userid1];rolename2;rolename3;[userid2];[userid3]}" where "!" designates Deny permissions public string EncodePermissions(int EntityId, List Permissions) { - string permissions = ""; + List permissionstrings = new List(); string permissionname = ""; + string permissions = ""; StringBuilder permissionsbuilder = new StringBuilder(); - string perm = ""; + string securityid = ""; foreach (Permission permission in Permissions.Where(item => item.EntityId == EntityId).OrderBy(item => item.PermissionName)) { // permission collections are grouped by permissionname if (permissionname != permission.PermissionName) { + permissions = permissionsbuilder.ToString(); + if (permissions != "") + { + permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); + } permissionname = permission.PermissionName; - permissions += permissionsbuilder.ToString(); - permissions += ((permissions != "") ? "}" : "") + "{" + permissionname + ":"; permissionsbuilder = new StringBuilder(); } @@ -157,77 +163,76 @@ namespace Oqtane.Repository // encode permission if (permission.UserId == null) { - perm = prefix + permission.Role.Name + ";"; + securityid = prefix + permission.Role.Name + ";"; } else { - perm = prefix + "[" + permission.UserId.ToString() + "];"; + securityid = prefix + "[" + permission.UserId.ToString() + "];"; } - // insert Deny permissions at the beginning and append Grant permissions at the end + // insert deny permissions at the beginning and append grant permissions at the end if (prefix == "!") { - permissionsbuilder.Insert(0, perm); + permissionsbuilder.Insert(0, securityid); } else { - permissionsbuilder.Append(perm); + permissionsbuilder.Append(securityid); } } - if (permissionsbuilder.ToString() != "") + permissions = permissionsbuilder.ToString(); + if (permissions != "") { - permissions += permissionsbuilder.ToString() + "}"; + permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); } - - return permissions; + return JsonSerializer.Serialize(permissionstrings); } - public List DecodePermissions(string Permissions, int SiteId, string EntityName, int EntityId) + public List DecodePermissions(string PermissionStrings, int SiteId, string EntityName, int EntityId) { - List roles = Roles.GetRoles(SiteId).ToList(); List permissions = new List(); - string perm = ""; - string permissionname; - string permissionstring; - foreach (string PermissionString in Permissions.Split(new char[] { '{' }, StringSplitOptions.RemoveEmptyEntries)) + List roles = Roles.GetRoles(SiteId).ToList(); + string securityid = ""; + foreach (PermissionString permissionstring in JsonSerializer.Deserialize>(PermissionStrings)) { - permissionname = PermissionString.Substring(0, PermissionString.IndexOf(":")); - permissionstring = PermissionString.Replace(permissionname + ":", "").Replace("}", ""); - foreach (string Perm in permissionstring.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (string id in permissionstring.Permissions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) { - perm = Perm; + securityid = id; Permission permission = new Permission(); permission.SiteId = SiteId; permission.EntityName = EntityName; permission.EntityId = EntityId; - permission.PermissionName = permissionname; + permission.PermissionName = permissionstring.PermissionName; permission.RoleId = null; permission.UserId = null; permission.IsAuthorized = true; - if (perm.StartsWith("!")) + if (securityid.StartsWith("!")) { // deny permission - perm.Replace("!", ""); + securityid.Replace("!", ""); permission.IsAuthorized = false; } - if (perm.StartsWith("[") && perm.EndsWith("]")) + if (securityid.StartsWith("[") && securityid.EndsWith("]")) { // user id - perm = perm.Replace("[", "").Replace("]", ""); - permission.UserId = int.Parse(perm); + securityid = securityid.Replace("[", "").Replace("]", ""); + permission.UserId = int.Parse(securityid); } else { // role name - Role role = roles.Where(item => item.Name == perm).SingleOrDefault(); + Role role = roles.Where(item => item.Name == securityid).SingleOrDefault(); if (role != null) { permission.RoleId = role.RoleId; } } - permissions.Add(permission); + if (permission.UserId != null || permission.RoleId != null) + { + permissions.Add(permission); + } } } return permissions; diff --git a/Oqtane.Server/Security/IUserPermissions.cs b/Oqtane.Server/Security/IUserPermissions.cs new file mode 100644 index 00000000..587c68ea --- /dev/null +++ b/Oqtane.Server/Security/IUserPermissions.cs @@ -0,0 +1,9 @@ +using System.Security.Claims; + +namespace Oqtane.Security +{ + public interface IUserPermissions + { + bool IsAuthorized(ClaimsPrincipal User, string EntityName, int EntityId, string PermissionName); + } +} diff --git a/Oqtane.Server/Security/PermissionHandler.cs b/Oqtane.Server/Security/PermissionHandler.cs index 95d8d615..8332900d 100644 --- a/Oqtane.Server/Security/PermissionHandler.cs +++ b/Oqtane.Server/Security/PermissionHandler.cs @@ -1,22 +1,19 @@ -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using Oqtane.Models; -using Oqtane.Repository; + namespace Oqtane.Security { public class PermissionHandler : AuthorizationHandler { private readonly IHttpContextAccessor HttpContextAccessor; - private readonly IPermissionRepository Permissions; + private readonly IUserPermissions UserPermissions; - public PermissionHandler(IHttpContextAccessor HttpContextAccessor, IPermissionRepository Permissions) + public PermissionHandler(IHttpContextAccessor HttpContextAccessor, IUserPermissions UserPermissions) { this.HttpContextAccessor = HttpContextAccessor; - this.Permissions = Permissions; + this.UserPermissions = UserPermissions; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) @@ -26,27 +23,7 @@ namespace Oqtane.Security if (ctx != null && ctx.Request.Query.ContainsKey("entityid")) { int EntityId = int.Parse(ctx.Request.Query["entityid"]); - string permissions = Permissions.EncodePermissions(EntityId, Permissions.GetPermissions(requirement.EntityName, EntityId, requirement.PermissionName).ToList()); - - User user = new User(); - user.UserId = -1; - user.Roles = ""; - - if (context.User != null) - { - var idclaim = context.User.Claims.Where(item => item.Type == ClaimTypes.PrimarySid).FirstOrDefault(); - if (idclaim != null) - { - user.UserId = int.Parse(idclaim.Value); - foreach (var claim in context.User.Claims.Where(item => item.Type == ClaimTypes.Role)) - { - user.Roles += claim.Value + ";"; - } - if (user.Roles != "") user.Roles = ";" + user.Roles; - } - } - - if (UserSecurity.IsAuthorized(user, requirement.PermissionName, permissions)) + if (UserPermissions.IsAuthorized(context.User, requirement.EntityName, EntityId, requirement.PermissionName)) { context.Succeed(requirement); } diff --git a/Oqtane.Server/Security/UserPermissions.cs b/Oqtane.Server/Security/UserPermissions.cs new file mode 100644 index 00000000..6b28d6fd --- /dev/null +++ b/Oqtane.Server/Security/UserPermissions.cs @@ -0,0 +1,42 @@ +using Oqtane.Models; +using Oqtane.Repository; +using System.Linq; +using System.Security.Claims; + +namespace Oqtane.Security +{ + public class UserPermissions : IUserPermissions + { + private readonly IPermissionRepository Permissions; + + public UserPermissions(IPermissionRepository Permissions) + { + this.Permissions = Permissions; + } + + public bool IsAuthorized(ClaimsPrincipal User, string EntityName, int EntityId, string PermissionName) + { + string permissionstrings = Permissions.EncodePermissions(EntityId, Permissions.GetPermissions(EntityName, EntityId, PermissionName).ToList()); + + User user = new User(); + user.UserId = -1; + user.Roles = ""; + + if (User != null) + { + var idclaim = User.Claims.Where(item => item.Type == ClaimTypes.PrimarySid).FirstOrDefault(); + if (idclaim != null) + { + user.UserId = int.Parse(idclaim.Value); + foreach (var claim in User.Claims.Where(item => item.Type == ClaimTypes.Role)) + { + user.Roles += claim.Value + ";"; + } + if (user.Roles != "") user.Roles = ";" + user.Roles; + } + } + + return UserSecurity.IsAuthorized(user, PermissionName, permissionstrings); + } + } +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index f8b8fd87..4b441aff 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -169,6 +169,7 @@ namespace Oqtane.Server services.AddSingleton(); // register transient scoped core services + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -334,6 +335,7 @@ namespace Oqtane.Server services.AddSingleton(); // register transient scoped core services + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Oqtane.Shared/Models/PermissionString.cs b/Oqtane.Shared/Models/PermissionString.cs new file mode 100644 index 00000000..1a68e2ef --- /dev/null +++ b/Oqtane.Shared/Models/PermissionString.cs @@ -0,0 +1,8 @@ +namespace Oqtane.Models +{ + public class PermissionString + { + public string PermissionName { get; set; } + public string Permissions { get; set; } + } +} diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 95593fdd..5f73f172 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -22,6 +22,7 @@ + diff --git a/Oqtane.Shared/Security/UserSecurity.cs b/Oqtane.Shared/Security/UserSecurity.cs index 412a6907..8a1854fa 100644 --- a/Oqtane.Shared/Security/UserSecurity.cs +++ b/Oqtane.Shared/Security/UserSecurity.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; using Oqtane.Models; using Oqtane.Shared; @@ -6,38 +9,50 @@ namespace Oqtane.Security { public class UserSecurity { - // permission collections are stored in format {permissionname1:permissions}{permissionname2:permissions}... - public static string GetPermissions(string PermissionName, string Permissions) + public static List GetPermissionStrings(string PermissionStrings) + { + return JsonSerializer.Deserialize>(PermissionStrings); + } + + public static string SetPermissionStrings(List PermissionStrings) + { + return JsonSerializer.Serialize(PermissionStrings); + } + + public static string GetPermissions(string PermissionName, string PermissionStrings) { string permissions = ""; - foreach(string permission in Permissions.Split(new char[] { '{' }, StringSplitOptions.RemoveEmptyEntries)) + List permissionstrings = JsonSerializer.Deserialize>(PermissionStrings); + PermissionString permissionstring = permissionstrings.Where(item => item.PermissionName == PermissionName).FirstOrDefault(); + if (permissionstring != null) { - if (permission.StartsWith(PermissionName + ":")) - { - permissions = permission.Replace(PermissionName + ":", "").Replace("}", ""); - break; - } + permissions = permissionstring.Permissions; } return permissions; } - public static string SetPermissions(string PermissionName, string Permissions) + public static bool IsAuthorized(User User, string PermissionName, string PermissionStrings) { - return "{" + PermissionName + ":" + Permissions + "}"; + return IsAuthorized(User, GetPermissions(PermissionName, PermissionStrings)); } // permissions are stored in the format "!rolename1;![userid1];rolename2;rolename3;[userid2];[userid3]" where "!" designates Deny permissions - public static bool IsAuthorized(User User, string PermissionName, string Permissions) + public static bool IsAuthorized(User User, string Permissions) { - Permissions = GetPermissions(PermissionName, Permissions); - if (User == null) + bool authorized = false; + if (Permissions != "") { - return IsAuthorized(-1, "", Permissions); // user is not authenticated but may have access to resource - } - else - { - return IsAuthorized(User.UserId, User.Roles, Permissions); + if (User == null) + { + authorized = IsAuthorized(-1, "", Permissions); // user is not authenticated but may have access to resource + } + else + { + authorized = IsAuthorized(User.UserId, User.Roles, Permissions); + } + } + return authorized; } private static bool IsAuthorized(int UserId, string Roles, string Permissions)