0

我正在关注这篇蛋糕 php acl 模型文章来创建我自己的 acl 实现。

我已经理解了 ACO ARO 和 ACO_ARO 的概念。我想实现 Check 方法,该方法将决定 aro 是否可以访问 aco。由于有 ARO 树和 ACO 树,我如何计算 aco 到 a ro 的最有效权限。

我还发现了下面的文章,它实现了检查方法,但那是在 php acl 实现中

简而言之,应该优先考虑什么帐户或组、aco 或父aco。

类似这篇文章的东西

更新到现在我已经到了这里

我制作了一个 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,如果没有找到帐户的任何条目,则查找组。

4

4 回答 4

0

在实施内容管理系统/文档管理系统模型时,我在 .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);
         ....
});

这留下了另外一个考虑因素,拒绝规则,其中规则是拒绝规则,典型的约定是它覆盖允许规则。

所以再次回到循环并评估拒绝:

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 条目,将其添加到结果权限,然后删除定义显式拒绝的任何权限。

如果您想重新运行用户特定权限的过程,我确信可以进一步扩展,匹配适用于当前用户的当前页面的所有权限并覆盖基于角色的集。

作为一般的经验法则,我倾向于遵循这样的逻辑,即规则越具体,它就越相关。

所以你可能会说... 所有经理都可以访问整个网站 经理被拒绝访问网站的管理部分 所有销售人员都可以访问销售部分 所有营销人员都可以访问营销 营销用户“鲍勃”可以访问销售人员

上面的逻辑将涵盖所有这些并有效地应用以下访问权限:用户获得其部门的部分权限(销售用户 = 销售等)经理获得额外的权限并访问所有区域接受管理员(可能只是 IT 人员?) Bob 是我们是例外,尽管他从事营销工作,但我们授予他销售权。

这意味着: 1. 可以将用户添加到角色 2. 可以将页面添加到角色,然后赋予相关角色“acl”权限 这意味着我可以这样说:如果用户是销售角色,请授予他们“读取,更新” 3. 根据定义,页面本身不是“角色”,而只是知道授予角色的访问权限 4. 扩展此模型意味着您可以为特定用户指定 acl 条目 5. 用户 acl 条目覆盖角色 acl 条目 6. 所考虑的 Acl 条目适用于到目前为止的整个树 7. 拒绝规则覆盖允许规则

那么,如果父页面显示“销售用户被完全拒绝访问”,然后我们所在的页面显示“当前用户 bob 具有完全访问权限”,会发生什么?

这是基于开发人员/企业对如何处理这种情况做出的选择......

我的想法是:用户在范围内比角色更本地拒绝规则适用于父级,而允许规则适用于我将采用允许规则的页面。

但是,如果 Parent 规则是针对用户的,而角色的 Page 规则是针对当前页面的,我会使用相同的逻辑为角色采用规则。

这些 ACL 的大部分内容都是主观的,我倾向于依赖人们习惯的东西,例如:Windows 中的文件系统权限,这样应用程序就会以用户认为“规范”的方式运行,这会让你保持从将来得到20个问题。

反正大多。

于 2012-07-28T13:07:15.923 回答
0

看到这个网址这是 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
于 2012-07-29T20:11:56.877 回答
0

我从PHPGacl学到了很多东西。现在有点旧项目,但这些概念在今天仍然完全有效。

他们的手册帮助我了解了从基础到更复杂问题的所有内容。

于 2012-07-31T14:44:39.660 回答
0

鉴于上述信息,我们可以编写一些简单的工具,就我而言,我正在使用内容管理系统,因此我的 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);

看起来有很多代码,但是如果您取出格式和注释(以满足编码标准),实际上代码很少,如果您将其复制到 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 爬取。不利的一面……我怀疑如果您的页面深深嵌套在树中,它会特别有效。

我仍然对改进这一点的建议持开放态度,但这感觉是当时实施可管理解决方案的最干净的方式。现在我完全了解了我可以寻求提高效率的问题。

但至少你有一个参考点吧:)

于 2012-07-31T21:55:29.983 回答