表存储的层次结构中的层次结构权限


9

假设以下数据库结构(如有需要,可以修改)...

在此处输入图片说明

我正在寻找一种确定给定页面上给定用户的“有效权限”的好方法,该方法允许我返回包含Page和有效权限的行。

我认为理想的解决方案可能包括使用CTE来执行递归的功能,以评估当前用户给定页面行的“有效权限”。

背景和实施细节

上面的架构表示内容管理系统的起点,在其中可以通过将用户添加到角色或从角色中删除来授予用户权限。

系统中的资源(例如页面)与角色关联,以授予链接到该角色的用户组所授予的权限。

这个想法是能够通过简单地拒绝所有角色并将树中的根级别页面添加到该角色,然后将用户添加到该角色来轻松锁定用户。

当(例如)为公司工作的承包商长期不可用时,这将允许许可结构保留在原位,然后通过简单地从该角色中删除用户,也将允许相同的原始权限授予。

权限基于典型的ACL类型规则,遵循这些规则可能适用于文件系统。

CRUD权限应为可为空的位,因此,在满足以下条件的情况下,可用值是true,false和未定义:

  • 假+任何东西=假
  • 正确+未定义=正确
  • 真+真=真
  • 未定义+未定义=未定义
如果任何权限为假->假 
否则为true-> true
其他(所有未定义)-> false

换句话说,除非您通过角色成员资格授予任何权限,并且拒绝规则将覆盖允许规则,否则您将无任何权限。

应用于此权限的“集合”是指直到当前页面并包括当前页面的所有应用于树的权限,换句话说:如果将false应用于应用于此页面的树中任何页面的任何角色,则结果为false ,但是如果未定义到此处的整个树,则当前页面包含true规则,则此处的结果为true,但对父级而言将为false。

如果可能的话,我想宽松地保留db结构,同时请记住,我的目标是能够执行以下操作:select * from pages where effective permissions (read = true) and user = ?因此,任何解决方案都应能够使我拥有一个具有有效权限的可查询集。以某种方式(只要可以指定条件,返回它们是可选的)。

假设存在2个页面,其中1个是其他角色的子代,并且存在2个角色,一个用于admin用户,一个用于只读用户,这两个页面都仅链接到根级别页面,我希望看到这样的内容作为预期的输出:

Admin user:
Id, Parent, Name, Create, Read, Update, Delete
1,  null,   Root, True  , True, True  , True 
2,  1,      Child,True  , True, True  , True 

Read only user:
Id, Parent, Name, Create, Read, Update, Delete
1,  null,   Root, False , True, False , False 
2,  1,      Child,False , True, False , False

关于这个问题的进一步讨论可以在从这里开始的主站点聊天室中找到

Answers:


11

使用此模型,我想出了一种以以下方式查询Pages表的方法:

SELECT
  p.*
FROM
  dbo.Pages AS p
  CROSS APPLY dbo.GetPermissionStatus(p.Id, @CurrentUserId, @PermissionName) AS ps
WHERE
  ps.IsAllowed = 1
;

所述GetPermissionStatus联表值函数的结果可以是一个空集或者一个单一列的行。当结果集为空时,表示指定的页面/用户/权限组合没有非NULL条目。相应的页面行会自动过滤掉。

如果函数确实返回了一行,则其唯一的列(IsAllowed)将包含1(表示true)或0(表示false)。WHERE筛选器还检查要包含在输出中的行的值必须为1。

函数的作用:

  • Pages表移到层次结构中,以将指定页面及其所有父级收集到一个行集中;

  • 构建另一行集,其中包含指定用户所包含的所有角色,以及一个权限列(但仅限于非NULL值),特别是与指定为第三个参数的权限相对应的一个权限集;

  • 最后,通过RolePages表联接第一组和第二组,以查找与指定页面或其任何父项匹配的完整的显式权限集。

结果行集按权限值的升序排序,并且返回最高值作为函数的结果。由于空值是在较早的阶段过滤掉的,因此列表只能包含0和1。因此,如果权限列表中至少有一个“拒绝”(0),则将是该功能的结果。否则,最上面的结果将是1,除非与所选页面相对应的角色恰好没有明确的“允许”,或者完全没有指定页面和用户的匹配条目,在这种情况下,结果将为空行集。

这是功能:

CREATE FUNCTION dbo.GetPermissionStatus
(
  @PageId int,
  @UserId int,
  @PermissionName varchar(50)
)
RETURNS TABLE
AS
RETURN
(
  WITH
    Hierarchy AS
    (
      SELECT
        p.Id,
        p.ParentId
      FROM
        dbo.Pages AS p
      WHERE
        p.Id = @PageId

      UNION ALL

      SELECT
        p.Id,
        p.ParentId
      FROM
        dbo.Pages AS p
        INNER JOIN hierarchy AS h ON p.Id = h.ParentId
    ),
    Permissions AS
    (
      SELECT
        ur.Role_Id,
        x.IsAllowed
      FROM
        dbo.UserRoles AS ur
        INNER JOIN Roles AS r ON ur.Role_Id = r.Id
        CROSS APPLY
        (
          SELECT
            CASE @PermissionName
              WHEN 'Create' THEN [Create]
              WHEN 'Read'   THEN [Read]
              WHEN 'Update' THEN [Update]
              WHEN 'Delete' THEN [Delete]
            END
        ) AS x (IsAllowed)
      WHERE
        ur.User_Id = @UserId AND
        x.IsAllowed IS NOT NULL
    )
  SELECT TOP (1)
    perm.IsAllowed
  FROM
    Hierarchy AS h
    INNER JOIN dbo.RolePages AS rp ON h.Id = rp.Page_Id
    INNER JOIN Permissions AS perm ON rp.Role_Id = perm.Role_Id
  ORDER BY
    perm.IsAllowed ASC
);

测试用例

  • DDL:

    CREATE TABLE dbo.Users (
      Id       int          PRIMARY KEY,
      Name     varchar(50)  NOT NULL,
      Email    varchar(100)
    );
    
    CREATE TABLE dbo.Roles (
      Id       int          PRIMARY KEY,
      Name     varchar(50)  NOT NULL,
      [Create] bit,
      [Read]   bit,
      [Update] bit,
      [Delete] bit
    );
    
    CREATE TABLE dbo.Pages (
      Id       int          PRIMARY KEY,
      ParentId int          FOREIGN KEY REFERENCES dbo.Pages (Id),
      Name     varchar(50)  NOT NULL
    );
    
    CREATE TABLE dbo.UserRoles (
      User_Id  int          NOT NULL  FOREIGN KEY REFERENCES dbo.Users (Id),
      Role_Id  int          NOT NULL  FOREIGN KEY REFERENCES dbo.Roles (Id),
      PRIMARY KEY (User_Id, Role_Id)
    );
    
    CREATE TABLE dbo.RolePages (
      Role_Id  int          NOT NULL  FOREIGN KEY REFERENCES dbo.Roles (Id),
      Page_Id  int          NOT NULL  FOREIGN KEY REFERENCES dbo.Pages (Id),
      PRIMARY KEY (Role_Id, Page_Id)
    );
    GO
  • 数据插入:

    INSERT INTO
      dbo.Users (ID, Name)
    VALUES
      (1, 'User A')
    ;
    INSERT INTO
      dbo.Roles (ID, Name, [Create], [Read], [Update], [Delete])
    VALUES
      (1, 'Role R', NULL, 1, 1, NULL),
      (2, 'Role S', 1   , 1, 0, NULL)
    ;
    INSERT INTO
      dbo.Pages (Id, ParentId, Name)
    VALUES
      (1, NULL, 'Page 1'),
      (2, 1, 'Page 1.1'),
      (3, 1, 'Page 1.2')
    ;
    INSERT INTO
      dbo.UserRoles (User_Id, Role_Id)
    VALUES
      (1, 1),
      (1, 2)
    ;
    INSERT INTO
      dbo.RolePages (Role_Id, Page_Id)
    VALUES
      (1, 1),
      (2, 3)
    ;
    GO

    因此,仅使用一个用户,但将其分配给两个角色,并使用两个角色之间的权限值的各种组合来测试子对象上的混合逻辑。

    页面层次结构非常简单:一个父级,两个子级。父母与一个角色相关联,孩子与另一个角色相关联。

  • 测试脚本:

    DECLARE @CurrentUserId int = 1;
    SELECT p.* FROM dbo.Pages AS p CROSS APPLY dbo.GetPermissionStatus(p.Id, @CurrentUserId, 'Create') AS perm WHERE perm.IsAllowed = 1;
    SELECT p.* FROM dbo.Pages AS p CROSS APPLY dbo.GetPermissionStatus(p.Id, @CurrentUserId, 'Read'  ) AS perm WHERE perm.IsAllowed = 1;
    SELECT p.* FROM dbo.Pages AS p CROSS APPLY dbo.GetPermissionStatus(p.Id, @CurrentUserId, 'Update') AS perm WHERE perm.IsAllowed = 1;
    SELECT p.* FROM dbo.Pages AS p CROSS APPLY dbo.GetPermissionStatus(p.Id, @CurrentUserId, 'Delete') AS perm WHERE perm.IsAllowed = 1;
  • 清理:

    DROP FUNCTION dbo.GetPermissionStatus;
    GO
    DROP TABLE dbo.UserRoles, dbo.RolePages, dbo.Users, dbo.Roles, dbo.Pages;
    GO

结果

  • 用于创建

    Id  ParentId  Name
    --  --------  --------
    2   1         Page 1.1

    有一个明确的真实Page 1.1唯一。该页面是根据“ true +未定义”逻辑返回的。其他的是“未定义”和“未定义+未定义” –因此被排除在外。

  • 阅读

    Id  ParentId  Name
    --  --------  --------
    1   NULL      Page 1
    2   1         Page 1.1
    3   1         Page 1.2

    一个明确的真实在的设置中发现Page 1Page 1.1。因此,对于前者来说,它只是一个“ true”,而对于后者来说,它是“ true + true”。没有对的明确读取权限Page 1.2,因此是另一种“ true + not defined”情况。因此,所有三个页面都已返回。

  • 用于更新

    Id  ParentId  Name
    --  --------  --------
    1   NULL      Page 1
    3   1         Page 1.2

    从设置,明确真正得到返回的Page 1虚假Page 1.1。对于进入输出的页面,其逻辑与Read相同。对于排除的行,找到了falsetrue,因此“ false + any”逻辑起作用。

  • 对于删除,没有返回任何行。父母和一个孩子在设置中具有明确的空值,而另一个孩子则没有任何内容。

获取所有权限

现在,如果您只想返回所有有效权限,则可以调整GetPermissionStatus函数:

CREATE FUNCTION dbo.GetPermissions(@PageId int, @UserId int)
RETURNS TABLE
AS
RETURN
(
  WITH
    Hierarchy AS
    (
      SELECT
        p.Id,
        p.ParentId
      FROM
        dbo.Pages AS p
      WHERE
        p.Id = @PageId

      UNION ALL

      SELECT
        p.Id,
        p.ParentId
      FROM
        dbo.Pages AS p
        INNER JOIN hierarchy AS h ON p.Id = h.ParentId
    ),
    Permissions AS
    (
      SELECT
        ur.Role_Id,
        r.[Create],
        r.[Read],
        r.[Update],
        r.[Delete]
      FROM
        dbo.UserRoles AS ur
        INNER JOIN Roles AS r ON ur.Role_Id = r.Id
      WHERE
        ur.User_Id = @UserId
    )
  SELECT
    [Create] = ISNULL(CAST(MIN(CAST([Create] AS int)) AS bit), 0),
    [Read]   = ISNULL(CAST(MIN(CAST([Read]   AS int)) AS bit), 0),
    [Update] = ISNULL(CAST(MIN(CAST([Update] AS int)) AS bit), 0),
    [Delete] = ISNULL(CAST(MIN(CAST([Delete] AS int)) AS bit), 0)
  FROM
    Hierarchy AS h
    INNER JOIN dbo.RolePages AS rp ON h.Id = rp.Page_Id
    INNER JOIN Permissions AS perm ON rp.Role_Id = perm.Role_Id
);

该函数返回四列–指定页面和用户的有效权限。用法示例:

DECLARE @CurrentUserId int = 1;
SELECT
  *
FROM
  dbo.Pages AS p
  CROSS APPLY dbo.GetPermissions(p.Id, @CurrentUserId) AS perm
;

输出:

Id  ParentId  Name      Create Read  Update Delete
--  --------  --------  ------ ----- ------ ------
1   NULL      Page 1    0      1     1      0
2   1         Page 1.1  1      1     0      0
3   1         Page 1.2  0      1     1      0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.