add support for API permissions at the UI layer - including ability to delegate user, role, profile management

This commit is contained in:
Shaun Walker
2023-01-09 11:38:25 -05:00
parent 1616f94b86
commit e136972cd7
50 changed files with 628 additions and 799 deletions

View File

@ -1,172 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using Oqtane.Shared;
using Oqtane.Models;
using Oqtane.Infrastructure;
using Oqtane.Enums;
using System.Net;
using Oqtane.Repository;
using Oqtane.Extensions;
using System.Reflection;
using System;
using System.Linq;
namespace Oqtane.Controllers
{
[Route(ControllerRoutes.ApiRoute)]
public class ApiController : Controller
{
private readonly IPermissionRepository _permissions;
private readonly IRoleRepository _roles;
private readonly ILogManager _logger;
private readonly Alias _alias;
public ApiController(IPermissionRepository permissions, IRoleRepository roles, ILogManager logger, ITenantManager tenantManager)
{
_permissions = permissions;
_roles = roles;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>?siteid=x
[HttpGet]
[Authorize(Roles = RoleNames.Admin)]
public List<Api> Get(string siteid)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
var apis = new List<Api>();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// iterate controllers
foreach (var type in assembly.GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type)))
{
// iterate controller methods with authorize attribute
var actions = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.GetCustomAttributes<AuthorizeAttribute>().Any());
foreach(var action in actions)
{
// get policy
var policy = action.GetCustomAttribute<AuthorizeAttribute>().Policy;
if (!string.IsNullOrEmpty(policy) && policy.Contains(":") && !policy.Contains(Constants.RequireEntityId))
{
// parse policy
var segments = policy.Split(':');
if (!apis.Any(item => item.EntityName == segments[0]))
{
apis.Add(new Api { SiteId = SiteId, EntityName = segments[0], Permissions = segments[1] });
}
else
{
// concatenate permissions
var permissions = apis.SingleOrDefault(item => item.EntityName == segments[0]).Permissions;
if (!permissions.Split(',').Contains(segments[1]))
{
apis.SingleOrDefault(item => item.EntityName == segments[0]).Permissions += "," + segments[1];
}
}
}
}
}
}
return apis;
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Get Attempt {SiteId}", siteid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// GET: api/<controller>/1/user
[HttpGet("{siteid}/{entityname}")]
[Authorize(Roles = RoleNames.Admin)]
public Api Get(int siteid, string entityname)
{
if (siteid == _alias.SiteId)
{
var permissions = _permissions.GetPermissions(siteid, entityname);
if (permissions == null || permissions.ToList().Count == 0)
{
permissions = GetPermissions(siteid, entityname);
}
return new Api { SiteId = siteid, EntityName = entityname, Permissions = permissions.EncodePermissions() };
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Get Attempt {SiteId} {EntityName}", siteid, entityname);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// POST: api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Admin)]
public void Post([FromBody] Api api)
{
if (ModelState.IsValid && api.SiteId == _alias.SiteId)
{
_permissions.UpdatePermissions(api.SiteId, api.EntityName, -1, api.Permissions);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Api Updated {Api}", api);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Post Attempt {Api}", api);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
private List<Permission> GetPermissions(int siteid, string entityname)
{
var permissions = new List<Permission>();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// iterate controllers
foreach (var type in assembly.GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type)))
{
// iterate controller methods with authorize attribute
var actions = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.GetCustomAttributes<AuthorizeAttribute>().Any());
foreach (var action in actions)
{
// get policy
var policy = action.GetCustomAttribute<AuthorizeAttribute>().Policy;
if (!string.IsNullOrEmpty(policy) && policy.Contains(":") && !policy.Contains(Constants.RequireEntityId))
{
// parse policy
var segments = policy.Split(':');
// entity match
if (segments[0] == entityname && segments.Length > 2)
{
var roles = _roles.GetRoles(siteid);
foreach (var rolename in (segments[2]).Split(','))
{
var role = roles.FirstOrDefault(item => item.Name == rolename);
if (role != null)
{
if (!permissions.Any(item => item.EntityName == entityname && item.PermissionName == segments[1] && item.RoleId == role.RoleId))
{
permissions.Add(new Permission { SiteId = siteid, EntityName = entityname, EntityId = -1, PermissionName = segments[1], RoleId = role.RoleId, Role = role, UserId = null, IsAuthorized = true });
}
}
}
}
}
}
}
}
return permissions;
}
}
}

View File

@ -47,7 +47,6 @@ namespace Oqtane.Controllers
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(SiteId).ToList();
List<Setting> settings = _settings.GetSettings(EntityNames.Module).ToList();
foreach (PageModule pagemodule in _pageModules.GetPageModules(SiteId))
@ -75,7 +74,6 @@ namespace Oqtane.Controllers
module.Order = pagemodule.Order;
module.ContainerType = pagemodule.ContainerType;
module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName);
module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);

View File

@ -281,7 +281,7 @@ namespace Oqtane.Controllers
// synchronize module permissions
if (added.Count > 0 || removed.Count > 0)
{
foreach (PageModule pageModule in _pageModules.GetPageModules(page.PageId, "").ToList())
foreach (PageModule pageModule in _pageModules.GetPageModules(page.SiteId).Where(item => item.PageId == page.PageId).ToList())
{
var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList();
// permissions added

View File

@ -120,7 +120,8 @@ namespace Oqtane.Controllers
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageid, PermissionNames.Edit))
{
int order = 1;
List<PageModule> pagemodules = _pageModules.GetPageModules(pageid, pane).OrderBy(item => item.Order).ToList();
List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId)
.Where(item => item.PageId == pageid && item.Pane == pane).OrderBy(item => item.Order).ToList();
foreach (PageModule pagemodule in pagemodules)
{
if (pagemodule.Order != order)

View File

@ -28,7 +28,7 @@ namespace Oqtane.Controllers
// GET: api/<controller>?siteid=x
[HttpGet]
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")]
[Authorize(Roles = RoleNames.Registered)]
public IEnumerable<Profile> Get(string siteid)
{
int SiteId;
@ -46,7 +46,7 @@ namespace Oqtane.Controllers
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")]
[Authorize(Roles = RoleNames.Registered)]
public Profile Get(int id)
{
var profile = _profiles.GetProfile(id);

View File

@ -28,7 +28,7 @@ namespace Oqtane.Controllers
// GET: api/<controller>?siteid=x&global=true/false
[HttpGet]
[Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Read}:{RoleNames.Registered}")]
[Authorize(Roles = RoleNames.Registered)]
public IEnumerable<Role> Get(string siteid, string global)
{
int SiteId;
@ -50,7 +50,7 @@ namespace Oqtane.Controllers
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Read}:{RoleNames.Registered}")]
[Authorize(Roles = RoleNames.Registered)]
public Role Get(int id)
{
var role = _roles.GetRole(id);

View File

@ -212,7 +212,7 @@ namespace Oqtane.Controllers
authorized = true;
if (permissionName == PermissionNames.Edit)
{
authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
}
break;
case EntityNames.Visitor:
@ -226,14 +226,11 @@ namespace Oqtane.Controllers
}
break;
default: // custom entity
authorized = true;
if (permissionName == PermissionNames.Edit)
{
authorized = User.IsInRole(RoleNames.Admin) || _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName);
}
else
{
authorized = true;
}
break;
}
return authorized;

View File

@ -29,23 +29,25 @@ namespace Oqtane.Controllers
private readonly ITenantManager _tenantManager;
private readonly INotificationRepository _notifications;
private readonly IFolderRepository _folders;
private readonly ISyncManager _syncManager;
private readonly ISiteRepository _sites;
private readonly IUserPermissions _userPermissions;
private readonly IJwtManager _jwtManager;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger)
public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ISyncManager syncManager, ILogManager logger)
{
_users = users;
_userRoles = userRoles;
_identityUserManager = identityUserManager;
_identitySignInManager = identitySignInManager;
_tenantManager = tenantManager;
_folders = folders;
_notifications = notifications;
_syncManager = syncManager;
_folders = folders;
_sites = sites;
_userPermissions = userPermissions;
_jwtManager = jwtManager;
_syncManager = syncManager;
_logger = logger;
}
@ -105,7 +107,7 @@ namespace Oqtane.Controllers
user.TwoFactorCode = "";
user.TwoFactorExpiry = null;
if (!User.IsInRole(RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower())
if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower())
{
user.Email = "";
user.PhotoFileId = null;
@ -148,8 +150,8 @@ namespace Oqtane.Controllers
User newUser = null;
bool verified;
bool allowregistration;
if (User.IsInRole(RoleNames.Admin))
bool allowregistration;
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
{
verified = true;
allowregistration = true;
@ -241,7 +243,8 @@ namespace Oqtane.Controllers
[Authorize]
public async Task<User> Put(int id, [FromBody] User user)
{
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null && (User.IsInRole(RoleNames.Admin) || User.Identity.Name == user.Username))
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null
&& (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username))
{
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null)
@ -287,7 +290,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5?siteid=x
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)]
[Authorize(Policy = $"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}")]
public async Task Delete(int id, string siteid)
{
int SiteId;

View File

@ -10,6 +10,7 @@ using System.Linq;
using System.Net;
using Oqtane.Security;
using System;
using Oqtane.Modules.Admin.Roles;
namespace Oqtane.Controllers
{
@ -93,7 +94,7 @@ namespace Oqtane.Controllers
userrole.User.TwoFactorCode = "";
userrole.User.TwoFactorExpiry = null;
if (!User.IsInRole(RoleNames.Admin) && userid != userrole.User.UserId)
if (!_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && userid != userrole.User.UserId)
{
userrole.User.Email = "";
userrole.User.PhotoFileId = null;
@ -115,7 +116,7 @@ namespace Oqtane.Controllers
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Admin)]
[Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public UserRole Post([FromBody] UserRole userRole)
{
var role = _roles.GetRole(userRole.RoleId);
@ -138,7 +139,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)]
[Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public UserRole Put(int id, [FromBody] UserRole userRole)
{
var role = _roles.GetRole(userRole.RoleId);
@ -160,7 +161,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)]
[Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public void Delete(int id)
{
UserRole userrole = _userRoles.GetUserRole(id);

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
@ -11,20 +11,22 @@ namespace Oqtane.Extensions
public static string EncodePermissions(this IEnumerable<Permission> permissionList)
{
List<PermissionString> permissionstrings = new List<PermissionString>();
string entityname = "";
string permissionname = "";
string permissions = "";
StringBuilder permissionsbuilder = new StringBuilder();
string securityid = "";
foreach (Permission permission in permissionList.OrderBy(item => item.PermissionName))
foreach (Permission permission in permissionList.OrderBy(item => item.EntityName).ThenBy(item => item.PermissionName))
{
// permission collections are grouped by permissionname
if (permissionname != permission.PermissionName)
// permission collections are grouped by entityname and permissionname
if (entityname != permission.EntityName || permissionname != permission.PermissionName)
{
permissions = permissionsbuilder.ToString();
if (permissions != "")
{
permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) });
permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) });
}
entityname = permission.EntityName;
permissionname = permission.PermissionName;
permissionsbuilder = new StringBuilder();
}
@ -56,7 +58,7 @@ namespace Oqtane.Extensions
permissions = permissionsbuilder.ToString();
if (permissions != "")
{
permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) });
permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) });
}
return JsonSerializer.Serialize(permissionstrings);
}

View File

@ -308,43 +308,45 @@ namespace Oqtane.Infrastructure
private void Upgrade_3_3_0(Tenant tenant, IServiceScope scope)
{
var pageTemplates = new List<PageTemplate>();
pageTemplates.Add(new PageTemplate
try
{
Name = "API Management",
Parent = "Admin",
Order = 35,
Path = "admin/apis",
Icon = Icons.CloudDownload,
IsNavigation = true,
IsPersonalizable = false,
PagePermissions = new List<Permission>
var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>();
var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>();
var modules = scope.ServiceProvider.GetRequiredService<IModuleRepository>();
var permissions = scope.ServiceProvider.GetRequiredService<IPermissionRepository>();
var siteRepository = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in siteRepository.GetSites().ToList())
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
int roleid = roles.GetRoles(site.SiteId).FirstOrDefault(item => item.Name == RoleNames.Registered).RoleId;
int pageid = pages.GetPages(site.SiteId).FirstOrDefault(item => item.Path == "admin").PageId;
var permission = new Permission
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Default,
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""
}
SiteId = site.SiteId,
EntityName = EntityNames.Page,
EntityId = pageid,
PermissionName = PermissionNames.View,
RoleId = roleid,
IsAuthorized = true
};
permissions.AddPermission(permission);
int moduleid = modules.GetModules(site.SiteId).FirstOrDefault(item => item.ModuleDefinitionName == "Oqtane.Modules.Admin.Dashboard, Oqtane.Client").ModuleId;
permission = new Permission
{
SiteId = site.SiteId,
EntityName = EntityNames.Module,
EntityId = moduleid,
PermissionName = PermissionNames.View,
RoleId = roleid,
IsAuthorized = true
};
permissions.AddPermission(permission);
}
});
var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>();
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in sites.GetSites().ToList())
}
catch (Exception ex)
{
sites.CreatePages(site, pageTemplates);
Debug.WriteLine($"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}");
}
}
}

View File

@ -6,7 +6,6 @@ namespace Oqtane.Repository
public interface IPageModuleRepository
{
IEnumerable<PageModule> GetPageModules(int siteId);
IEnumerable<PageModule> GetPageModules(int pageId, string pane);
PageModule AddPageModule(PageModule pageModule);
PageModule UpdatePageModule(PageModule pageModule);
PageModule GetPageModule(int pageModuleId);

View File

@ -10,46 +10,28 @@ namespace Oqtane.Repository
public class PageModuleRepository : IPageModuleRepository
{
private TenantDBContext _db;
private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly IPermissionRepository _permissions;
public PageModuleRepository(TenantDBContext context, IPermissionRepository permissions)
public PageModuleRepository(TenantDBContext context, IModuleDefinitionRepository moduleDefinitions, IPermissionRepository permissions)
{
_db = context;
_moduleDefinitions = moduleDefinitions;
_permissions = permissions;
}
public IEnumerable<PageModule> GetPageModules(int siteId)
{
IEnumerable<PageModule> pagemodules = _db.PageModule
var pagemodules = _db.PageModule
.Include(item => item.Module) // eager load modules
.Where(item => item.Module.SiteId == siteId);
.Where(item => item.Module.SiteId == siteId).ToList();
if (pagemodules.Any())
{
IEnumerable<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList();
foreach (PageModule pagemodule in pagemodules)
var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(siteId).ToList();
var permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList();
for (int index = 0; index < pagemodules.Count; index++)
{
pagemodule.Module.Permissions = permissions.Where(item => item.EntityId == pagemodule.ModuleId).EncodePermissions();
}
}
return pagemodules;
}
public IEnumerable<PageModule> GetPageModules(int pageId, string pane)
{
IEnumerable<PageModule> pagemodules = _db.PageModule
.Include(item => item.Module) // eager load modules
.Where(item => item.PageId == pageId);
if (pane != "" && pagemodules.Any())
{
pagemodules = pagemodules.Where(item => item.Pane == pane);
}
if (pagemodules.Any())
{
var siteId = pagemodules.FirstOrDefault().Module.SiteId;
IEnumerable<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList();
foreach (PageModule pagemodule in pagemodules)
{
pagemodule.Module.Permissions = permissions.Where(item => item.EntityId == pagemodule.ModuleId).EncodePermissions();
pagemodules[index] = GetPageModule(pagemodules[index], moduledefinitions, permissions);
}
}
return pagemodules;
@ -89,7 +71,9 @@ namespace Oqtane.Repository
}
if (pagemodule != null)
{
pagemodule.Module.Permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module, pagemodule.ModuleId)?.EncodePermissions();
var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(pagemodule.Module.SiteId).ToList();
var permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module).ToList();
pagemodule = GetPageModule(pagemodule, moduledefinitions, permissions);
}
return pagemodule;
}
@ -100,7 +84,9 @@ namespace Oqtane.Repository
.SingleOrDefault(item => item.PageId == pageId && item.ModuleId == moduleId);
if (pagemodule != null)
{
pagemodule.Module.Permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module, pagemodule.ModuleId)?.EncodePermissions();
var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(pagemodule.Module.SiteId).ToList();
var permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module).ToList();
pagemodule = GetPageModule(pagemodule, moduledefinitions, permissions);
}
return pagemodule;
}
@ -111,5 +97,30 @@ namespace Oqtane.Repository
_db.PageModule.Remove(pageModule);
_db.SaveChanges();
}
private PageModule GetPageModule(PageModule pageModule, List<ModuleDefinition> moduleDefinitions, List<Permission> modulePermissions)
{
var permissions = modulePermissions.Where(item => item.EntityId == pageModule.ModuleId).ToList();
// moduledefinition permissionnames can specify permissions for other entities (ie. API permissions)
pageModule.Module.ModuleDefinition = moduleDefinitions.Find(item => item.ModuleDefinitionName == pageModule.Module.ModuleDefinitionName);
if (!string.IsNullOrEmpty(pageModule.Module.ModuleDefinition.PermissionNames) && pageModule.Module.ModuleDefinition.PermissionNames.Contains(":"))
{
foreach (var permissionname in pageModule.Module.ModuleDefinition.PermissionNames.Split(",", System.StringSplitOptions.RemoveEmptyEntries))
{
if (permissionname.Contains(":"))
{
// moduledefinition permissionnames can be in the form of "EntityName:PermissionName:Roles"
var segments = permissionname.Split(':');
if (segments.Length == 3 && segments[0] != EntityNames.Module)
{
permissions.AddRange(_permissions.GetPermissions(pageModule.Module.SiteId, segments[0], segments[1]).Where(item => item.EntityId == -1));
}
}
}
}
pageModule.Module.Permissions = permissions?.EncodePermissions();
return pageModule;
}
}
}

View File

@ -79,23 +79,52 @@ namespace Oqtane.Repository
public void UpdatePermissions(int siteId, string entityName, int entityId, string permissionStrings)
{
// get current permissions and delete
IEnumerable<Permission> permissions = _db.Permission
.Where(item => item.EntityName == entityName)
.Where(item => item.EntityId == entityId)
.Where(item => item.SiteId == siteId);
foreach (Permission permission in permissions)
bool modified = false;
var existing = new List<Permission>();
var permissions = DecodePermissions(permissionStrings, siteId, entityName, entityId);
foreach (var permission in permissions)
{
_db.Permission.Remove(permission);
if (!existing.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName))
{
existing.AddRange(GetPermissions(siteId, permission.EntityName, permission.PermissionName)
.Where(item => item.EntityId == entityId || item.EntityId == -1));
}
var current = existing.FirstOrDefault(item => item.EntityName == permission.EntityName && item.EntityId == permission.EntityId
&& item.PermissionName == permission.PermissionName && item.RoleId == permission.RoleId && item.UserId == permission.UserId);
if (current != null)
{
if (current.IsAuthorized != permission.IsAuthorized)
{
current.IsAuthorized = permission.IsAuthorized;
_db.Entry(current).State = EntityState.Modified;
modified = true;
}
}
else
{
_db.Permission.Add(permission);
modified = true;
}
}
// add permissions
permissions = DecodePermissions(permissionStrings, siteId, entityName, entityId);
foreach (Permission permission in permissions)
foreach (var permission in existing)
{
_db.Permission.Add(permission);
if (!permissions.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName
&& item.EntityId == permission.EntityId && item.RoleId == permission.RoleId && item.UserId == permission.UserId))
{
permission.Role = null; // remove linked reference to Role which can cause errors in EF Core change tracking
_db.Permission.Remove(permission);
modified = true;
}
}
if (modified)
{
_db.SaveChanges();
foreach (var entityname in permissions.Select(item => item.EntityName).Distinct())
{
ClearCache(siteId, entityname);
}
}
_db.SaveChanges();
ClearCache(siteId, entityName);
}
public Permission GetPermission(int permissionId)
@ -200,8 +229,22 @@ namespace Oqtane.Repository
securityid = id;
Permission permission = new Permission();
permission.SiteId = siteId;
permission.EntityName = entityName;
permission.EntityId = entityId;
if (!string.IsNullOrEmpty(permissionstring.EntityName))
{
permission.EntityName = permissionstring.EntityName;
}
else
{
permission.EntityName = entityName;
}
if (permission.EntityName == entityName)
{
permission.EntityId = entityId;
}
else
{
permission.EntityId = -1;
}
permission.PermissionName = permissionstring.PermissionName;
permission.RoleId = null;
permission.UserId = null;

View File

@ -431,6 +431,7 @@ namespace Oqtane.Repository
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
@ -441,6 +442,7 @@ namespace Oqtane.Repository
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""

View File

@ -22,22 +22,15 @@ namespace Oqtane.Security
if (policy == null)
{
// policy names must be in the form of "EntityName:PermissionName:Roles" ie. "Module:Edit:Administrators" (roles are comma delimited)
// policy names must be in the form of "EntityName:PermissionName:Roles"
if (policyName.Contains(':'))
{
var policySegments = policyName.Split(':');
if (policySegments.Length >= 3)
var segments = policyName.Split(':');
if (segments.Length == 3)
{
// check for optional RequireEntityId segment
var requireEntityId = false;
if (policySegments.Length == 4 && policySegments[3] == Constants.RequireEntityId)
{
requireEntityId = true;
}
// create policy
var builder = new AuthorizationPolicyBuilder();
builder.AddRequirements(new PermissionRequirement(policySegments[0], policySegments[1], policySegments[2], requireEntityId));
builder.AddRequirements(new PermissionRequirement(segments[0], segments[1], segments[2]));
policy = builder.Build();
// add policy to the AuthorizationOptions
@ -59,8 +52,8 @@ namespace Oqtane.Security
private string GetPolicyName(string policyName)
{
// backward compatibility for legacy static policy names
if (policyName == PolicyNames.ViewModule) policyName = $"{EntityNames.Module}:{PermissionNames.View}:{RoleNames.Admin}:{Constants.RequireEntityId}";
if (policyName == PolicyNames.EditModule) policyName = $"{EntityNames.Module}:{PermissionNames.Edit}:{RoleNames.Admin}:{Constants.RequireEntityId}";
if (policyName == PolicyNames.ViewModule) policyName = $"{EntityNames.Module}:{PermissionNames.View}:{RoleNames.Admin}";
if (policyName == PolicyNames.EditModule) policyName = $"{EntityNames.Module}:{PermissionNames.Edit}:{RoleNames.Admin}";
return policyName;
}
}

View File

@ -34,28 +34,26 @@ namespace Oqtane.Security
}
int entityId = -1;
if (requirement.RequireEntityId)
// get entityid from querystring based on a parameter format of auth{entityname}id (ie. authmoduleid )
if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id"))
{
// get entityid from querystring based on a parameter format of auth{entityname}id (ie. authmoduleid )
if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id"))
if (!int.TryParse(ctx.Request.Query["auth" + requirement.EntityName.ToLower() + "id"], out entityId))
{
if (!int.TryParse(ctx.Request.Query["auth" + requirement.EntityName.ToLower() + "id"], out entityId))
entityId = -1;
}
}
// legacy support for deprecated CreateAuthorizationPolicyUrl(string url, int entityId)
if (entityId == -1)
{
if (ctx.Request.Query.ContainsKey("entityid"))
{
if (!int.TryParse(ctx.Request.Query["entityid"], out entityId))
{
entityId = -1;
}
}
// legacy support for deprecated CreateAuthorizationPolicyUrl(string url, int entityId)
if (entityId == -1)
{
if (ctx.Request.Query.ContainsKey("entityid"))
{
if (!int.TryParse(ctx.Request.Query["entityid"], out entityId))
{
entityId = -1;
}
}
}
}
// validate permissions

View File

@ -8,16 +8,13 @@ namespace Oqtane.Security
public string PermissionName { get; }
public string Roles { get; }
public string Roles { get; } // semi-colon delimited
public bool RequireEntityId { get; }
public PermissionRequirement(string entityName, string permissionName, string roles, bool requireEntityId)
public PermissionRequirement(string entityName, string permissionName, string roles)
{
EntityName = entityName;
PermissionName = permissionName;
Roles = roles;
RequireEntityId = requireEntityId;
}
}
}

View File

@ -40,7 +40,7 @@ namespace Oqtane.Security
}
else
{
return UserSecurity.IsAuthorized(GetUser(principal), roles.Replace(",",";"));
return UserSecurity.IsAuthorized(GetUser(principal), roles);
}
}