diff --git a/Oqtane.Server/Controllers/RoleController.cs b/Oqtane.Server/Controllers/RoleController.cs index b5779500..82c1aadb 100644 --- a/Oqtane.Server/Controllers/RoleController.cs +++ b/Oqtane.Server/Controllers/RoleController.cs @@ -28,7 +28,7 @@ namespace Oqtane.Controllers // GET: api/?siteid=x&global=true/false [HttpGet] - [Authorize(Roles = RoleNames.Registered)] + [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Read}:{RoleNames.Registered}")] public IEnumerable Get(string siteid, string global) { int SiteId; @@ -50,7 +50,7 @@ namespace Oqtane.Controllers // GET api//5 [HttpGet("{id}")] - [Authorize(Roles = RoleNames.Registered)] + [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Read}:{RoleNames.Registered}")] public Role Get(int id) { var role = _roles.GetRole(id); @@ -68,7 +68,7 @@ namespace Oqtane.Controllers // POST api/ [HttpPost] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}")] public Role Post([FromBody] Role role) { if (ModelState.IsValid && role.SiteId == _alias.SiteId) @@ -88,7 +88,7 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}")] public Role Put(int id, [FromBody] Role role) { if (ModelState.IsValid && role.SiteId == _alias.SiteId && _roles.GetRole(role.RoleId, false) != null) @@ -108,7 +108,7 @@ namespace Oqtane.Controllers // DELETE api//5 [HttpDelete("{id}")] - [Authorize(Roles = RoleNames.Admin)] + [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}")] public void Delete(int id) { var role = _roles.GetRole(id); diff --git a/Oqtane.Server/Security/AuthorizationPolicyProvider.cs b/Oqtane.Server/Security/AuthorizationPolicyProvider.cs index cdfd48aa..56dda318 100644 --- a/Oqtane.Server/Security/AuthorizationPolicyProvider.cs +++ b/Oqtane.Server/Security/AuthorizationPolicyProvider.cs @@ -9,12 +9,10 @@ namespace Oqtane.Security public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider { private readonly AuthorizationOptions _options; - private readonly IConfiguration _configuration; - public AuthorizationPolicyProvider(IOptions options, IConfiguration configuration) : base(options) + public AuthorizationPolicyProvider(IOptions options) : base(options) { _options = options.Value; - _configuration = configuration; } public override async Task GetPolicyAsync(string policyName) @@ -25,18 +23,25 @@ namespace Oqtane.Security if (policy == null) { - // policy names must be in the form of "Entity:Permission" ie. "User:Read" - if (policyName.Contains(":")) + if (policyName.Contains(':')) { - var entityName = policyName.Split(':')[0]; - var permissionName = policyName.Split(':')[1]; + // policy names must be in the form of "EntityName:PermissionName:Roles" ie. "Module:Edit:Administrators" (roles are comma delimited) + var policySegments = policyName.Split(':'); + if (policySegments.Length >= 3) + { + // check for optional RequireEntityId segment + var requireEntityId = false; + if (policySegments.Length == 4 && policySegments[3] == Constants.RequireEntityId) + { + requireEntityId = true; + } + policy = new AuthorizationPolicyBuilder() + .AddRequirements(new PermissionRequirement(policySegments[0], policySegments[1], policySegments[2], requireEntityId)) + .Build(); - policy = new AuthorizationPolicyBuilder() - .AddRequirements(new PermissionRequirement(entityName, permissionName)) - .Build(); - - // add policy to the AuthorizationOptions - _options.AddPolicy(policyName, policy); + // add policy to the AuthorizationOptions + _options.AddPolicy(policyName, policy); + } } } @@ -46,8 +51,8 @@ namespace Oqtane.Security private string GetPolicyName(string policyName) { // backward compatibility for legacy static policy names - if (policyName == PolicyNames.ViewModule) policyName = "Module:View"; - if (policyName == PolicyNames.EditModule) policyName = "Module:Edit"; + 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}"; return policyName; } } diff --git a/Oqtane.Server/Security/PermissionHandler.cs b/Oqtane.Server/Security/PermissionHandler.cs index e171df6a..bc05c3f5 100644 --- a/Oqtane.Server/Security/PermissionHandler.cs +++ b/Oqtane.Server/Security/PermissionHandler.cs @@ -33,30 +33,33 @@ namespace Oqtane.Security siteId = ctx.GetAlias().SiteId; } - // get entityid from querystring based on a parameter format of auth{entityname}id (ie. authmoduleid ) int entityId = -1; - if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id")) + if (requirement.RequireEntityId) { - if (!int.TryParse(ctx.Request.Query["auth" + requirement.EntityName.ToLower() + "id"], out entityId)) + // 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")) { - 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)) + 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; + } + } + } } // validate permissions - if (_userPermissions.IsAuthorized(context.User, siteId, requirement.EntityName, entityId, requirement.PermissionName)) + if (_userPermissions.IsAuthorized(context.User, siteId, requirement.EntityName, entityId, requirement.PermissionName, requirement.Roles)) { context.Succeed(requirement); } diff --git a/Oqtane.Server/Security/PermissionRequirement.cs b/Oqtane.Server/Security/PermissionRequirement.cs index 7d5477ee..e937f647 100644 --- a/Oqtane.Server/Security/PermissionRequirement.cs +++ b/Oqtane.Server/Security/PermissionRequirement.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; namespace Oqtane.Security { @@ -8,10 +8,16 @@ namespace Oqtane.Security public string PermissionName { get; } - public PermissionRequirement(string entityName, string permissionName) + public string Roles { get; } + + public bool RequireEntityId { get; } + + public PermissionRequirement(string entityName, string permissionName, string roles, bool requireEntityId) { EntityName = entityName; PermissionName = permissionName; + Roles = roles; + RequireEntityId = requireEntityId; } } } diff --git a/Oqtane.Server/Security/UserPermissions.cs b/Oqtane.Server/Security/UserPermissions.cs index a40e9e84..0f795ec5 100644 --- a/Oqtane.Server/Security/UserPermissions.cs +++ b/Oqtane.Server/Security/UserPermissions.cs @@ -10,6 +10,7 @@ namespace Oqtane.Security { public interface IUserPermissions { + bool IsAuthorized(ClaimsPrincipal user, int siteId, string entityName, int entityId, string permissionName, string roles); bool IsAuthorized(ClaimsPrincipal user, int siteId, string entityName, int entityId, string permissionName); bool IsAuthorized(ClaimsPrincipal user, string permissionName, string permissions); User GetUser(ClaimsPrincipal user); @@ -30,6 +31,19 @@ namespace Oqtane.Security _accessor = accessor; } + public bool IsAuthorized(ClaimsPrincipal principal, int siteId, string entityName, int entityId, string permissionName, string roles) + { + var permissions = _permissions.GetPermissions(siteId, entityName, entityId, permissionName).ToList(); + if (permissions != null && permissions.Count != 0) + { + return IsAuthorized(principal, permissionName, permissions.EncodePermissions()); + } + else + { + return UserSecurity.IsAuthorized(GetUser(principal), roles.Replace(",",";")); + } + } + public bool IsAuthorized(ClaimsPrincipal principal, int siteId, string entityName, int entityId, string permissionName) { return IsAuthorized(principal, permissionName, _permissions.GetPermissions(siteId, entityName, entityId, permissionName)?.EncodePermissions()); diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 2d1a7980..2931b58a 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -74,6 +74,8 @@ namespace Oqtane.Shared public static readonly string MauiUserAgent = "MAUI"; + public static readonly string RequireEntityId = "RequireEntityId"; + // Obsolete constants const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames"; diff --git a/Oqtane.Shared/Shared/PermissionNames.cs b/Oqtane.Shared/Shared/PermissionNames.cs index a0ec3c93..f5ece6a0 100644 --- a/Oqtane.Shared/Shared/PermissionNames.cs +++ b/Oqtane.Shared/Shared/PermissionNames.cs @@ -1,11 +1,15 @@ -namespace Oqtane.Shared +namespace Oqtane.Shared { public class PermissionNames { - public const string Browse = "Browse"; + // UI permissions public const string View = "View"; public const string Edit = "Edit"; + public const string Browse = "Browse"; public const string Utilize = "Utilize"; + // API permissions + public const string Read = "Read"; + public const string Write = "Write"; } }