ACL 排序和评估。啥应该优先 帐户或组、aco 或父 aco
Posted
技术标签:
【中文标题】ACL 排序和评估。啥应该优先 帐户或组、aco 或父 aco【英文标题】:ACL ordering and evaluation. what should take precedence Account or group, aco or parent acoACL 排序和评估。什么应该优先 帐户或组、aco 或父 aco 【发布时间】:2012-07-23 19:01:44 【问题描述】:我正在关注这篇 cake php acl model 文章来创建我自己的 acl 实现。
我已经了解了 ACO ARO 和 ACO_ARO 的概念。我想实现 Check 方法,该方法将决定 aro 是否可以访问 aco。既然有 ARO 树和 ACO 树,我如何计算 aco 对 a ro 的最有效权限。
我还发现下面的文章已经实现了检查方法,但那是在 php acl implementation
简而言之,应该优先考虑什么帐户或组、aco 或父 aco。
类似article
更新 到现在为止我已经到了这里
我做了一个accessControlEntry类如下
public class AccessControlEntry
public BsonObjectId AccessControlEntryId get; set;
public BsonObjectId AccessRequestObjectId get; set;
public BsonObjectId AccessControlObjectId get; set;
public bool CanView get; set;
public bool CanEdit get; set;
public bool CanDelete get; set;
public bool CanAdministrate get; set;
public bool Check(Usercontext usercontext, BsonObjectId acoId, string permission)
//aco id is accessControlObjectId like in cakephp acl
Account acc = _usercontextService.GetAccountByUserContext(usercontext);
//getting ACE eg X account has CanRead=true on Y object
AccessControlEntry entry = _accessControlEntryRepository.GetAccessControlEntry(acc.AccountId, acoId);
if (entry != null)
bool value = (bool)entry.GetType().GetProperty(permission).GetValue(entry, null);
return value;
//account entry not found ...search in groups
bool groupEntryFound = false;
bool effectiveValue = false;
Group[] groups = _usercontextService.GetGroupsForAccount(acc.AccountId);
foreach (Group group in groups)
AccessControlEntry entryGroup = _accessControlEntryRepository.GetAccessControlEntry(group.GroupId, acoId);
if (entryGroup != null)
groupEntryFound = true;
effectiveValue |= (bool)entryGroup.GetType().GetProperty(permission).GetValue(entryGroup, null);
//ACE found in group ..return most privilged value
if (groupEntryFound)
return effectiveValue;
//entry not found for account nor for group..return false
return false;
我像这样从其他服务调用检查方法
Check(context,44556,"CanRead")
check 方法查找帐户的 AccessControlEntry,如果没有找到帐户的任何条目,则查找组。
【问题讨论】:
【参考方案1】:鉴于上述信息,我们可以编写一些简单的工具,就我而言,我正在使用内容管理系统,因此我的 ACL 是关于页面访问的。
首先我定义了我的 ACL 条目的样子...
using System;
namespace Core.Objects.CMS
/// <summary>
/// Represents a record on an access control list
/// </summary>
public class PageACLEntry
/// <summary>
/// Gets or sets the access control entry id.
/// </summary>
/// <value>
/// The access control entry id.
/// </value>
public int PageACLEntryId get; set;
/// <summary>
/// Gets or sets the page id.
/// </summary>
/// <value>
/// The page id.
/// </value>
public int PageId get; set;
/// <summary>
/// Gets or sets the name of the role.
/// </summary>
/// <value>
/// The name of the role.
/// </value>
public string RoleName get; set;
/// <summary>
/// Gets or sets the read.
/// </summary>
/// <value>
/// The read.
/// </value>
public bool? Read get; set;
/// <summary>
/// Gets or sets the content of the update.
/// </summary>
/// <value>
/// The content of the update.
/// </value>
public bool? UpdateContent get; set;
/// <summary>
/// Gets or sets the update meta.
/// </summary>
/// <value>
/// The update meta.
/// </value>
public bool? UpdateMeta get; set;
/// <summary>
/// Gets or sets the delete.
/// </summary>
/// <value>
/// The delete.
/// </value>
public bool? Delete get; set;
/// <summary>
/// Gets or sets the full control.
/// </summary>
/// <value>
/// The full control.
/// </value>
public bool? FullControl get; set;
然后我创建一个辅助类来处理评估...
using System.Collections.Generic;
using System.Security.Principal;
using Core.Objects.CMS;
namespace Core.Utilities
/// <summary>
/// Tools for permission calculation
/// </summary>
public static class PermissionHelper
/// <summary>
/// Calculates the page permissions the given user has on the given page.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="user">The user.</param>
/// <returns>the effective permissions</returns>
private static PageACLEntry CalculatePagePermissions(Page page, IPrincipal user)
PageACLEntry result = new PageACLEntry();
// start with acl for the current page
List<PageACLEntry> acl = new List<PageACLEntry>(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 =>
// 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;
/// <summary>
/// Evaluates the specified permission level.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="suggestion">The suggestion.</param>
/// <returns>evaluation result</returns>
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;
/// <summary>
/// Appends the tree acl from the tree root up to this point.
/// </summary>
/// <param name="acl">The acl.</param>
/// <param name="page">The page.</param>
/// <param name="user">The user.</param>
/// <returns>the complete acl</returns>
private static List<PageACLEntry> AppendTreePermissions(List<PageACLEntry> acl, Page page, IPrincipal user)
Page currentPage = page.Parent;
while (currentPage != null)
acl.AddRange(currentPage.AclRules);
currentPage = page.Parent;
return acl;
/// <summary>
/// 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.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="user">The user.</param>
/// <returns>
/// access right indication as bool
/// </returns>
public static bool UserCanRead(Page page, IPrincipal user)
PageACLEntry permissions = CalculatePagePermissions(page, user);
if (permissions.Read != false)
return true;
return false;
/// <summary>
/// Determines if the current User can delete the given page.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="user">The user.</param>
/// <returns>
/// access right indication as bool
/// </returns>
public static bool UserCanDelete(Page page, IPrincipal user)
PageACLEntry permissions = CalculatePagePermissions(page, user);
if (permissions.FullControl == true || permissions.Delete == true)
return true;
return false;
/// <summary>
/// Determines if the current User can update the given page.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="user">The user.</param>
/// <returns>
/// access right indication as bool
/// </returns>
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;
/// <summary>
/// Determines if the current User can append children to the given page.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="user">The user.</param>
/// <returns>
/// access right indication as bool
/// </returns>
public static bool UserCanAddChildTo(Page page, IPrincipal user)
PageACLEntry permissions = CalculatePagePermissions(page, user);
if (permissions.FullControl == true || permissions.UpdateMeta == true)
return true;
return false;
现在这给了我所有的控制权,我只需传递一个页面和一个 IPrincipal 对象,然后我会返回一个代表访问级别的 ACLEntry。
我决定通过将 Calculation 方法设为私有而仅将 CanX 方法设为公开来进一步抽象出我的安全实现。
所以现在就这么简单
bool result = PermissionsHelper.UserCanRead(page, user);
看起来有很多代码,但如果你去掉格式和 cmets(以满足编码标准),实际上代码很少,如果你将它复制到 Visual Studio 中的类文件中,实际上非常简单遵循和维护。
您可能还注意到,我没有一次传入存储库或服务类来获取数据,那是因为我使用它来构建使用代码优先 EF 建模编写的对象,并且我正在使用延迟加载,所以你的实现可能需要比 page.parent.AClEntries 更多的内容才能爬上树。
哦,以防万一您需要它,这是我的页面类...
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Core.Objects.CMS
/// <summary>
/// Represents a managed CMS page
/// </summary>
[Table("Pages")]
public class Page
/// <summary>
/// Gets or sets the page id.
/// </summary>
/// <value>
/// The page id.
/// </value>
public int PageId get; set;
/// <summary>
/// Gets or sets the version.
/// </summary>
/// <value>
/// The version.
/// </value>
public int Version get; set;
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>
/// The title.
/// </value>
public string Title get; set;
/// <summary>
/// Gets or sets the template.
/// </summary>
/// <value>
/// The template.
/// </value>
public string Template get; set;
/// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>
/// The path.
/// </value>
public string Path get; set;
/// <summary>
/// Gets or sets the parent.
/// </summary>
/// <value>
/// The parent.
/// </value>
public virtual Page Parent get; set;
/// <summary>
/// Gets or sets the children.
/// </summary>
/// <value>
/// The children.
/// </value>
public virtual List<Page> Children get; set;
/// <summary>
/// Gets or sets the content.
/// </summary>
/// <value>
/// The content.
/// </value>
public virtual List<PageContent> Content get; set;
/// <summary>
/// Gets or sets the component stacks.
/// </summary>
/// <value>
/// The component stacks.
/// </value>
public virtual List<Stack> ComponentStacks get; set;
/// <summary>
/// Gets or sets the acl rules.
/// </summary>
/// <value>
/// The acl rules.
/// </value>
public virtual List<PageACLEntry> AclRules get; set;
非常简单的 poco ... 使用 EF 的强大功能来完成我所有的按需 SQL 爬取。 不利的一面...我怀疑如果您的页面深深嵌套在树中,它会特别有效。
我仍然愿意接受有关改进此功能的建议,但这感觉是当时实施可管理解决方案的最简洁方式。 现在我完全了解了我可以寻求提高效率的问题。
但至少你有一个参考点吧:)
【讨论】:
【参考方案2】:在实施内容管理系统/文档管理系统模型时,我在 .Net 中遇到了同样的问题。
我发现,在最简单的形式中,实际上有 2 棵树,而基于继承权限的动态“有效权限”计算在可扩展性方面对您的应用来说是不健康的,但这并不意味着这是不可能的。
这意味着您基本上可以仅根据当前节点计算有效权限,以简化模型。
例如:(让我们使用“页面”作为节点)
在复杂模型中,要确定用户对第 4 页的权限,您将有效地获取分配给第 1、3 和 4 页的所有权限,然后进行“加法合并”。
在简化模型中,我们只考虑为第 4 页的用户添加权限
Page 1
Page 2
Page 3
Page 4
为了让我的问题尽可能简单,从而尽可能避免错误,我决定采用一个模型,其中只能将角色/组与相关的 acl 条目添加到树节点。
这意味着要找出我想要的权限,我可以有效地运行类似的查询(仅伪代码您的实现可能会有所不同):
var allAcls = "Select all ACL where PageId in (pagesToThisPoint) and Role in (userRoles)"
var resultAcl = new aclEntry();
allAcls.Each(acl =>
resultAcl.Delete = (acl.Delete > resultAcl.Delete ? acl.Delete : resultAcl.Delete);
resultAcl.Update = (acl.Update > resultAcl.Update ? acl.Update : resultAcl.Update);
resultAcl.Read = (acl.Read > resultAcl.Read ? acl.Read : resultAcl.Read);
....
);
剩下 1 个其他考虑因素,拒绝规则,其中规则是拒绝规则,典型约定是它覆盖允许规则。
所以再次返回循环并评估拒绝:
allAcls.Each(acl =>
resultAcl.Delete = (acl.Delete == deny ? resultAcl.Delete == deny);
resultAcl.Update = (acl.Update == deny ? resultAcl.Update == deny);
resultAcl.Read = (acl.Read == deny ? resultAcl.Read == deny);
....
);
因此,您基本上是在说获取用户和页面的所有角色,其中页面具有任何这些角色的 acl 条目,将其添加到结果权限,然后删除定义显式拒绝的任何权限。
如果您想重新运行用户特定权限的过程,我确信可以进一步扩展,匹配适用于当前用户的当前页面的所有权限并覆盖基于角色的设置。
作为一般的经验法则,我倾向于遵循这样的逻辑:规则越具体,它就越相关。
所以你可能会说... 所有管理员都可以访问整个站点 经理被剥夺了对网站管理部分的任何权利 所有销售人员都可以访问销售部分 所有营销都可以访问营销 营销用户“Bob”可以访问销售
上面的逻辑将涵盖所有这些并有效地应用访问,如下所示: 用户获得其部门的部分权限(销售用户 = 销售等) 经理获得额外的权限和访问所有区域的权限接受管理员(可能只有 IT?) Bob 是我们的例外,尽管他从事营销工作,但我们授予他销售权。
这意味着什么: 1.用户可以添加到角色 2. 可以将页面添加到角色中,然后赋予相关角色“acl”权限 这意味着我可以这样说: 如果用户是销售角色,则授予他们“阅读、更新” 3.根据定义,页面本身不是“角色”,而只是知道授予角色的访问权限 4.扩展此模型意味着您可以为特定用户指定acl条目 5.用户acl条目覆盖角色acl条目 6. 到目前为止,考虑的 Acl 条目是针对整个树的 7.拒绝规则覆盖允许规则
如果父页面显示“销售用户被完全拒绝访问”,然后我们所在的页面显示“当前用户 bob 具有完全访问权限”,会发生什么?
这是基于开发人员/企业对如何处理这种情况做出的选择...
我的想法是: 用户在范围内比角色更本地化 拒绝规则适用于父页面,而允许规则适用于页面 我会接受允许规则。
但是,如果 Parent 规则适用于用户,而角色的 Page 规则适用于当前页面,我将使用相同的逻辑采用角色的规则。
这些 ACL 的大部分内容是主观的,我倾向于依靠人们习惯的东西,例如:Windows 中的文件系统权限,这样应用程序就会以用户认为“规范”的方式运行,即让您在未来不会收到 20 个问题。
无论如何。
【讨论】:
我一直在进行一场正确的战斗......如果一切都弄清楚了,让 Ninject 将 IPrincipal 推入 Repository我从PHPGacl 学到了很多东西。现在有点老项目了,但这些概念在今天仍然完全有效。
他们的manual 帮助我了解了从基础到更复杂问题的所有内容。
【讨论】:
从你之前的人那里获取知识并以此为基础进行构建总是好的。【参考方案4】:看到这个网址,这是 cakephp 2.0:-
http://book.cakephp.org/2.0/en/tutorials-and-examples/simple-acl-controlled-application/simple-acl-controlled-application.html
http://book.cakephp.org/2.0/en/tutorials-and-examples/simple-acl-controlled-application/part-two.html
【讨论】:
Cake 1.3 现在有点过时了,但 2.x 的文档仍然几乎没有变化。 book.cakephp.org/2.0/en/core-libraries/components/… pffft,我已经证明用很少的代码就可以轻松实现自己的目标。当实际上你所追求的只是大约 2 或 3 个课程时,为什么还要拉一大堆你不需要的臃肿。以上是关于ACL 排序和评估。啥应该优先 帐户或组、aco 或父 aco的主要内容,如果未能解决你的问题,请参考以下文章