Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Given the above information we can write some simple tooling, in my case I am working with a content management system so my ACL's are about page access. </p> <p>First I define what my ACL entries look like ...</p> <pre><code>using System; namespace Core.Objects.CMS { /// &lt;summary&gt; /// Represents a record on an access control list /// &lt;/summary&gt; public class PageACLEntry { /// &lt;summary&gt; /// Gets or sets the access control entry id. /// &lt;/summary&gt; /// &lt;value&gt; /// The access control entry id. /// &lt;/value&gt; public int PageACLEntryId { get; set; } /// &lt;summary&gt; /// Gets or sets the page id. /// &lt;/summary&gt; /// &lt;value&gt; /// The page id. /// &lt;/value&gt; public int PageId { get; set; } /// &lt;summary&gt; /// Gets or sets the name of the role. /// &lt;/summary&gt; /// &lt;value&gt; /// The name of the role. /// &lt;/value&gt; public string RoleName { get; set; } /// &lt;summary&gt; /// Gets or sets the read. /// &lt;/summary&gt; /// &lt;value&gt; /// The read. /// &lt;/value&gt; public bool? Read { get; set; } /// &lt;summary&gt; /// Gets or sets the content of the update. /// &lt;/summary&gt; /// &lt;value&gt; /// The content of the update. /// &lt;/value&gt; public bool? UpdateContent { get; set; } /// &lt;summary&gt; /// Gets or sets the update meta. /// &lt;/summary&gt; /// &lt;value&gt; /// The update meta. /// &lt;/value&gt; public bool? UpdateMeta { get; set; } /// &lt;summary&gt; /// Gets or sets the delete. /// &lt;/summary&gt; /// &lt;value&gt; /// The delete. /// &lt;/value&gt; public bool? Delete { get; set; } /// &lt;summary&gt; /// Gets or sets the full control. /// &lt;/summary&gt; /// &lt;value&gt; /// The full control. /// &lt;/value&gt; public bool? FullControl { get; set; } } } </code></pre> <p>Then I create a helper class to handle the evaluations ...</p> <pre><code>using System.Collections.Generic; using System.Security.Principal; using Core.Objects.CMS; namespace Core.Utilities { /// &lt;summary&gt; /// Tools for permission calculation /// &lt;/summary&gt; public static class PermissionHelper { /// &lt;summary&gt; /// Calculates the page permissions the given user has on the given page. /// &lt;/summary&gt; /// &lt;param name="page"&gt;The page.&lt;/param&gt; /// &lt;param name="user"&gt;The user.&lt;/param&gt; /// &lt;returns&gt;the effective permissions&lt;/returns&gt; private static PageACLEntry CalculatePagePermissions(Page page, IPrincipal user) { PageACLEntry result = new PageACLEntry(); // start with acl for the current page List&lt;PageACLEntry&gt; acl = new List&lt;PageACLEntry&gt;(page.AclRules); // append all the way up the tree until parent == null acl = AppendTreePermissions(acl, page, user); // reverse the list so we evaluate root first then work up to here acl.Reverse(); // because of the order in which these are applied the most local rules overrule the less local // the wider the scope the less it applies acl.ForEach(ace =&gt; { // only apply rules that apply to roles that our current user is in if (user.IsInRole(ace.RoleName)) { result.Read = Eval(result.Read, ace.Read); result.Delete = Eval(result.Delete, ace.Delete); result.UpdateMeta = Eval(result.UpdateMeta, ace.UpdateMeta); result.UpdateContent = Eval(result.UpdateContent, ace.UpdateContent); result.FullControl = Eval(result.FullControl, ace.FullControl); } }); return result; } /// &lt;summary&gt; /// Evaluates the specified permission level. /// &lt;/summary&gt; /// &lt;param name="target"&gt;The target.&lt;/param&gt; /// &lt;param name="suggestion"&gt;The suggestion.&lt;/param&gt; /// &lt;returns&gt;evaluation result&lt;/returns&gt; private static bool? Eval(bool? target, bool? suggestion) { bool? result = null; switch (target) { case false: result = false; break; case true: result = true; break; case null: break; } return result; } /// &lt;summary&gt; /// Appends the tree acl from the tree root up to this point. /// &lt;/summary&gt; /// &lt;param name="acl"&gt;The acl.&lt;/param&gt; /// &lt;param name="page"&gt;The page.&lt;/param&gt; /// &lt;param name="user"&gt;The user.&lt;/param&gt; /// &lt;returns&gt;the complete acl&lt;/returns&gt; private static List&lt;PageACLEntry&gt; AppendTreePermissions(List&lt;PageACLEntry&gt; acl, Page page, IPrincipal user) { Page currentPage = page.Parent; while (currentPage != null) { acl.AddRange(currentPage.AclRules); currentPage = page.Parent; } return acl; } /// &lt;summary&gt; /// Determines if the current User can read the given page. /// Unless an explicit deny rule is in place the default is to make everything read only. /// &lt;/summary&gt; /// &lt;param name="page"&gt;The page.&lt;/param&gt; /// &lt;param name="user"&gt;The user.&lt;/param&gt; /// &lt;returns&gt; /// access right indication as bool /// &lt;/returns&gt; public static bool UserCanRead(Page page, IPrincipal user) { PageACLEntry permissions = CalculatePagePermissions(page, user); if (permissions.Read != false) { return true; } return false; } /// &lt;summary&gt; /// Determines if the current User can delete the given page. /// &lt;/summary&gt; /// &lt;param name="page"&gt;The page.&lt;/param&gt; /// &lt;param name="user"&gt;The user.&lt;/param&gt; /// &lt;returns&gt; /// access right indication as bool /// &lt;/returns&gt; public static bool UserCanDelete(Page page, IPrincipal user) { PageACLEntry permissions = CalculatePagePermissions(page, user); if (permissions.FullControl == true || permissions.Delete == true) { return true; } return false; } /// &lt;summary&gt; /// Determines if the current User can update the given page. /// &lt;/summary&gt; /// &lt;param name="page"&gt;The page.&lt;/param&gt; /// &lt;param name="user"&gt;The user.&lt;/param&gt; /// &lt;returns&gt; /// access right indication as bool /// &lt;/returns&gt; public static bool UserCanUpdate(Page page, IPrincipal user) { PageACLEntry permissions = CalculatePagePermissions(page, user); if (permissions.FullControl == true || permissions.UpdateMeta == true) { return true; } return false; } public static bool UserCanUpdateContent(Page page, IPrincipal user) { PageACLEntry permissions = CalculatePagePermissions(page, user); if (permissions.FullControl == true || permissions.UpdateContent == true) { return true; } return false; } /// &lt;summary&gt; /// Determines if the current User can append children to the given page. /// &lt;/summary&gt; /// &lt;param name="page"&gt;The page.&lt;/param&gt; /// &lt;param name="user"&gt;The user.&lt;/param&gt; /// &lt;returns&gt; /// access right indication as bool /// &lt;/returns&gt; public static bool UserCanAddChildTo(Page page, IPrincipal user) { PageACLEntry permissions = CalculatePagePermissions(page, user); if (permissions.FullControl == true || permissions.UpdateMeta == true) { return true; } return false; } } } </code></pre> <p>This now gives me all the control I simply pass that a page and an IPrincipal object and I get back an ACLEntry that represents the level of access. </p> <p>I decided to further abstract away my security implementation by making the Calculation method private and only the CanX methods public.</p> <p>So now it's as simple as </p> <pre><code>bool result = PermissionsHelper.UserCanRead(page, user); </code></pre> <p>It seems like theres a lot of code but if you take out the formatting and comments (to meet coding standards) there's actually very little code, and if you copy this in in to a class file in Visual Studio it's actually very easy to follow and maintain.</p> <p>You may also note, not once do I pass in a repository or service class to get data, that's because I use this to build objects that are written using code first EF modelling an i'm using lazy loading, so your implementation might require a bit more than page.parent.AClEntries to go crawling up the tree. </p> <p>Oh just in case you need it here's my page class ...</p> <pre><code>using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Core.Objects.CMS { /// &lt;summary&gt; /// Represents a managed CMS page /// &lt;/summary&gt; [Table("Pages")] public class Page { /// &lt;summary&gt; /// Gets or sets the page id. /// &lt;/summary&gt; /// &lt;value&gt; /// The page id. /// &lt;/value&gt; public int PageId { get; set; } /// &lt;summary&gt; /// Gets or sets the version. /// &lt;/summary&gt; /// &lt;value&gt; /// The version. /// &lt;/value&gt; public int Version { get; set; } /// &lt;summary&gt; /// Gets or sets the title. /// &lt;/summary&gt; /// &lt;value&gt; /// The title. /// &lt;/value&gt; public string Title { get; set; } /// &lt;summary&gt; /// Gets or sets the template. /// &lt;/summary&gt; /// &lt;value&gt; /// The template. /// &lt;/value&gt; public string Template { get; set; } /// &lt;summary&gt; /// Gets or sets the path. /// &lt;/summary&gt; /// &lt;value&gt; /// The path. /// &lt;/value&gt; public string Path { get; set; } /// &lt;summary&gt; /// Gets or sets the parent. /// &lt;/summary&gt; /// &lt;value&gt; /// The parent. /// &lt;/value&gt; public virtual Page Parent { get; set; } /// &lt;summary&gt; /// Gets or sets the children. /// &lt;/summary&gt; /// &lt;value&gt; /// The children. /// &lt;/value&gt; public virtual List&lt;Page&gt; Children { get; set; } /// &lt;summary&gt; /// Gets or sets the content. /// &lt;/summary&gt; /// &lt;value&gt; /// The content. /// &lt;/value&gt; public virtual List&lt;PageContent&gt; Content { get; set; } /// &lt;summary&gt; /// Gets or sets the component stacks. /// &lt;/summary&gt; /// &lt;value&gt; /// The component stacks. /// &lt;/value&gt; public virtual List&lt;Stack&gt; ComponentStacks { get; set; } /// &lt;summary&gt; /// Gets or sets the acl rules. /// &lt;/summary&gt; /// &lt;value&gt; /// The acl rules. /// &lt;/value&gt; public virtual List&lt;PageACLEntry&gt; AclRules { get; set; } } } </code></pre> <p>Very simple poco ... using the power of EF to do all my on demand SQL crawling. The one down side ... i doubt it's particularly efficient if you have a page deeply nested in a tree. </p> <p>I'm still open to suggestions on improving this but this felt like the cleanest way to implement a managable solution at the time. Now i have total visibility of the problem I can look to make those efficiency gains.</p> <p>But at least you have a point of reference right :)</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload