Merge pull request #1387 from sbwalker/dev

fix #1367 - provides support for multiple entities in auth policy and makes parameter names more intuitive - backward compatible with entityid
This commit is contained in:
Shaun Walker 2021-05-23 10:25:17 -04:00 committed by GitHub
commit 35aaf476d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 126 additions and 42 deletions

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Modules.HtmlText.Models;
using Oqtane.Services;
using Oqtane.Shared;
@ -21,23 +20,23 @@ namespace Oqtane.Modules.HtmlText.Services
public async Task<Models.HtmlText> GetHtmlTextAsync(int moduleId)
{
var htmltext = await GetJsonAsync<List<Models.HtmlText>>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", moduleId));
var htmltext = await GetJsonAsync<List<Models.HtmlText>>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", new Dictionary<string, int>() { { EntityNames.Module, moduleId } }));
return htmltext.FirstOrDefault();
}
public async Task AddHtmlTextAsync(Models.HtmlText htmlText)
{
await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", htmlText.ModuleId), htmlText);
await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", new Dictionary<string, int>() { { EntityNames.Module, htmlText.ModuleId } }), htmlText);
}
public async Task UpdateHtmlTextAsync(Models.HtmlText htmlText)
{
await PutJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlText.HtmlTextId}", htmlText.ModuleId), htmlText);
await PutJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlText.HtmlTextId}", new Dictionary<string, int>() { { EntityNames.Module, htmlText.ModuleId } }), htmlText);
}
public async Task DeleteHtmlTextAsync(int moduleId)
{
await DeleteAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", moduleId));
await DeleteAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", new Dictionary<string, int>() { { EntityNames.Module, moduleId } }));
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
@ -54,10 +55,15 @@ namespace Oqtane.Services
return apiurl;
}
// add entityid parameter to url for custom authorization policy
public string CreateAuthorizationPolicyUrl(string url, int entityId)
// add authentityid parameters to url for custom authorization policy - args in form of entityname = entityid
public string CreateAuthorizationPolicyUrl(string url, Dictionary<string, int> args)
{
string qs = "entityid=" + entityId.ToString();
string qs = "";
foreach (KeyValuePair<string, int> kvp in args)
{
qs += (qs != "") ? "&" : "";
qs += "auth" + kvp.Key.ToLower() + "id=" + kvp.Value.ToString();
}
if (url.Contains("?"))
{
@ -204,5 +210,20 @@ namespace Oqtane.Services
{
return CreateApiUrl(serviceName, alias, ControllerRoutes.Default);
}
[Obsolete("This method is obsolete. Use CreateAuthorizationPolicyUrl(string url, Dictionary<string, int> args) instead - in conjunction with _authEntityId in Server Controller.", false)]
public string CreateAuthorizationPolicyUrl(string url, int entityId)
{
string qs = "entityid=" + entityId.ToString();
if (url.Contains("?"))
{
return url + "&" + qs;
}
else
{
return url + "?" + qs;
}
}
}
}

View File

@ -513,28 +513,7 @@
{
List<PermissionString> permissions;
if (action == "publish")
{
// publish all modules
foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
{
permissions = UserSecurity.GetPermissionStrings(module.Permissions);
foreach (var permissionstring in permissions)
{
if (permissionstring.PermissionName == PermissionNames.View)
{
List<string> ids = permissionstring.Permissions.Split(';').ToList();
if (!ids.Contains(RoleNames.Everyone)) ids.Add(RoleNames.Everyone);
if (!ids.Contains(RoleNames.Registered)) ids.Add(RoleNames.Registered);
permissionstring.Permissions = string.Join(";", ids.ToArray());
}
}
module.Permissions = UserSecurity.SetPermissionStrings(permissions);
await ModuleService.UpdateModuleAsync(module);
}
}
// publish page
// publish/unpublish page
var page = PageState.Page;
permissions = UserSecurity.GetPermissionStrings(page.Permissions);
foreach (var permissionstring in permissions)

View File

@ -1,21 +1,35 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Oqtane.Infrastructure;
using System.Collections.Generic;
using System;
namespace Oqtane.Controllers
{
public class ModuleControllerBase : Controller
{
protected readonly ILogManager _logger;
protected int _entityId = -1; // passed as a querystring parameter for policy authorization and used for validation
// querystring parameters for policy authorization and validation
protected Dictionary<string, int> _authEntityId = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
protected int _entityId = -1; // deprecated
public ModuleControllerBase(ILogManager logger, IHttpContextAccessor accessor)
{
_logger = logger;
int value;
foreach (var param in accessor.HttpContext.Request.Query)
{
if (param.Key.StartsWith("auth") && param.Key.EndsWith("id") && int.TryParse(param.Value, out value))
{
_authEntityId.Add(param.Key.Substring(4, param.Key.Length - 6), int.Parse(param.Value));
}
}
// entityid is deprecated
if (accessor.HttpContext.Request.Query.ContainsKey("entityid"))
{
_entityId = int.Parse(accessor.HttpContext.Request.Query["entityid"]);
}
}
}
}

View File

@ -19,17 +19,19 @@ namespace Oqtane.Controllers
private readonly IPageRepository _pages;
private readonly IModuleRepository _modules;
private readonly IPageModuleRepository _pageModules;
private readonly IPermissionRepository _permissionRepository;
private readonly ISettingRepository _settings;
private readonly IUserPermissions _userPermissions;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public PageController(IPageRepository pages, IModuleRepository modules, IPageModuleRepository pageModules, ISettingRepository settings, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
public PageController(IPageRepository pages, IModuleRepository modules, IPageModuleRepository pageModules, IPermissionRepository permissionRepository, ISettingRepository settings, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
{
_pages = pages;
_modules = modules;
_pageModules = pageModules;
_permissionRepository = permissionRepository;
_settings = settings;
_userPermissions = userPermissions;
_syncManager = syncManager;
@ -227,7 +229,54 @@ namespace Oqtane.Controllers
{
if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit))
{
// preserve page permissions
var oldPermissions = _permissionRepository.GetPermissions(EntityNames.Page, page.PageId).ToList();
page = _pages.UpdatePage(page);
// get differences between old and new page permissions
var newPermissions = _permissionRepository.DecodePermissions(page.Permissions, page.SiteId, EntityNames.Page, page.PageId).ToList();
var added = GetPermissionsDifferences(newPermissions, oldPermissions);
var removed = GetPermissionsDifferences(oldPermissions, newPermissions);
// synchronize module permissions
if (added.Count > 0 || removed.Count > 0)
{
foreach (PageModule pageModule in _pageModules.GetPageModules(page.PageId, "").ToList())
{
var modulePermissions = _permissionRepository.GetPermissions(EntityNames.Module, pageModule.Module.ModuleId).ToList();
//var modulePermissions = _permissionRepository.DecodePermissions(pageModule.Module.Permissions, page.SiteId, EntityNames.Module, pageModule.ModuleId).ToList();
// permissions added
foreach(Permission permission in added)
{
if (!modulePermissions.Any(item => item.PermissionName == permission.PermissionName
&& item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized))
{
_permissionRepository.AddPermission(new Permission
{
SiteId = page.SiteId,
EntityName = EntityNames.Module,
EntityId = pageModule.ModuleId,
PermissionName = permission.PermissionName,
RoleId = permission.RoleId,
UserId = permission.UserId,
IsAuthorized = permission.IsAuthorized
});
}
}
// permissions removed
foreach (Permission permission in removed)
{
var modulePermission = modulePermissions.FirstOrDefault(item => item.PermissionName == permission.PermissionName
&& item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized);
if (modulePermission != null)
{
_permissionRepository.DeletePermission(modulePermission.PermissionId);
}
}
}
}
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page);
}
@ -240,6 +289,19 @@ namespace Oqtane.Controllers
return page;
}
private List<Permission> GetPermissionsDifferences(List<Permission> permissions1, List<Permission> permissions2)
{
var differences = new List<Permission>();
foreach (Permission p in permissions1)
{
if (!permissions2.Any(item => item.PermissionName == p.PermissionName && item.RoleId == p.RoleId && item.UserId == p.UserId && item.IsAuthorized == p.IsAuthorized))
{
differences.Add(p);
}
}
return differences;
}
// PUT api/<controller>/?siteid=x&pageid=y&parentid=z
[HttpPut]
[Authorize(Roles = RoleNames.Registered)]
@ -287,4 +349,5 @@ namespace Oqtane.Controllers
}
}
}
}

View File

@ -30,7 +30,7 @@ namespace Oqtane.Modules.HtmlText.Controllers
try
{
Models.HtmlText htmlText = null;
if (_entityId == id)
if (_authEntityId[EntityNames.Module] == id)
{
htmlText = _htmlText.GetHtmlText(id);
list.Add(htmlText);
@ -51,7 +51,7 @@ namespace Oqtane.Modules.HtmlText.Controllers
{
try
{
if (ModelState.IsValid && htmlText.ModuleId == _entityId)
if (ModelState.IsValid && htmlText.ModuleId == _authEntityId[EntityNames.Module])
{
htmlText = _htmlText.AddHtmlText(htmlText);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Html/Text Added {HtmlText}", htmlText);
@ -72,7 +72,7 @@ namespace Oqtane.Modules.HtmlText.Controllers
{
try
{
if (ModelState.IsValid && htmlText.ModuleId == _entityId)
if (ModelState.IsValid && htmlText.ModuleId == _authEntityId[EntityNames.Module])
{
htmlText = _htmlText.UpdateHtmlText(htmlText);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Html/Text Updated {HtmlText}", htmlText);
@ -93,7 +93,7 @@ namespace Oqtane.Modules.HtmlText.Controllers
{
try
{
if (id == _entityId)
if (id == _authEntityId[EntityNames.Module])
{
_htmlText.DeleteHtmlText(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Html/Text Deleted {HtmlTextId}", id);

View File

@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Oqtane.Enums;
@ -22,11 +22,19 @@ namespace Oqtane.Security
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
// permission is scoped based on EntityId which must be passed as a querystring parameter
// permission is scoped based on auth{entityname}id (ie ?authmoduleid ) which must be passed as a querystring parameter
var ctx = _httpContextAccessor.HttpContext;
if (ctx != null && ctx.Request.Query.ContainsKey("entityid"))
if (ctx != null)
{
int entityId = int.Parse(ctx.Request.Query["entityid"]);
int entityId = -1;
if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id"))
{
entityId = int.Parse(ctx.Request.Query["auth" + requirement.EntityName.ToLower() + "id"]);
}
if (entityId == -1 && ctx.Request.Query.ContainsKey("entityid"))
{
entityId = int.Parse(ctx.Request.Query["entityid"]);
}
if (_userPermissions.IsAuthorized(context.User, requirement.EntityName, entityId, requirement.PermissionName))
{
context.Succeed(requirement);
@ -39,4 +47,4 @@ namespace Oqtane.Security
return Task.CompletedTask;
}
}
}
}