Quyền phân cấp trong bảng phân cấp được lưu trữ


9

Giả sử cấu trúc cơ sở dữ liệu sau (có thể sửa đổi nếu cần) ...

nhập mô tả hình ảnh ở đây

Tôi đang tìm kiếm một cách hay để xác định "quyền hiệu quả" cho một người dùng nhất định trên một trang nhất định theo cách cho phép tôi trả lại một hàng có chứa Trang và các quyền hiệu quả.

Tôi nghĩ rằng giải pháp lý tưởng có thể bao gồm chức năng sử dụng CTE để thực hiện đệ quy cần thiết để đánh giá "quyền hiệu quả" cho một hàng trang nhất định cho người dùng hiện tại.

Bối cảnh và chi tiết thực hiện

Lược đồ trên biểu thị điểm bắt đầu cho một hệ thống quản lý nội dung, trong đó người dùng có thể được cấp quyền bằng cách thêm và xóa khỏi vai trò.

Các tài nguyên trong hệ thống (ví dụ: các trang) được liên kết với các vai trò để cấp cho nhóm người dùng được liên kết với vai trò đó các quyền mà nó cấp.

Ý tưởng là có thể dễ dàng khóa người dùng bằng cách từ chối tất cả vai trò và thêm trang cấp gốc trong cây vào vai trò đó và sau đó thêm người dùng vào vai trò đó.

Điều này sẽ cho phép cấu trúc quyền vẫn được giữ nguyên khi (ví dụ) một nhà thầu làm việc cho công ty không có sẵn trong thời gian dài, điều này sau đó cũng sẽ cho phép cấp quyền tương tự bằng cách loại bỏ người dùng khỏi một vai trò đó .

Quyền dựa trên các quy tắc loại ACL điển hình có thể áp dụng cho hệ thống tệp bằng cách tuân theo các quy tắc này.

Các quyền CRUD là các bit nullable để các giá trị khả dụng là true, false, không được xác định trong trường hợp sau đây là true:

  • sai + bất cứ điều gì = sai
  • đúng + không xác định = đúng
  • đúng + đúng = đúng
  • không xác định + không xác định = không xác định
Nếu bất kỳ quyền nào là sai -> sai 
Khác nếu có là đúng -> đúng
Khác (tất cả không được xác định) -> sai

Nói cách khác, bạn không nhận được quyền đối với bất cứ điều gì trừ khi bạn được cấp chúng thông qua vai trò thành viên và quy tắc từ chối ghi đè quy tắc cho phép.

"Tập hợp" các quyền này áp dụng cho tất cả các quyền được áp dụng cho cây lên đến và bao gồm trang hiện tại, nói cách khác: Nếu sai trong bất kỳ vai trò nào được áp dụng cho bất kỳ trang nào trong cây cho trang này thì kết quả là sai , nhưng nếu toàn bộ cây cho đến đây không được xác định thì trang hiện tại chứa quy tắc đúng thì kết quả là đúng ở đây nhưng sẽ là sai đối với cha mẹ.

Tôi muốn giữ lỏng cấu trúc db nếu có thể, hãy nhớ rằng mục tiêu của tôi ở đây là có thể làm một cái gì đó như: select * from pages where effective permissions (read = true) and user = ?vì vậy mọi giải pháp sẽ có thể cho phép tôi có một bộ truy vấn có quyền truy cập hiệu quả trong chúng một cách nào đó (trả lại chúng là tùy chọn miễn là tiêu chí có thể được chỉ định).

Giả sử tồn tại 2 trang trong đó 1 là con của hai vai trò khác và 2 vai trò tồn tại, một cho người dùng quản trị viên và 1 cho người dùng chỉ đọc, cả hai đều được liên kết đến trang cấp gốc mà tôi mong đợi để xem thứ gì đó như đầu ra dự kiến:

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

Thảo luận thêm về câu hỏi này có thể được tìm thấy trong phòng trò chuyện trang web chính bắt đầu từ đây .

Câu trả lời:


11

Sử dụng mô hình này, tôi đã đưa ra một cách để truy vấn bảng Pages theo cách sau:

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

Các GetPermissionStatus kết quả chức năng inline bảng giá trị của có thể là một tập rỗng hoặc một hàng duy nhất cột. Khi tập kết quả trống, điều đó có nghĩa là không có mục không phải NULL cho kết hợp trang / người dùng / quyền được chỉ định. Hàng Trang tương ứng được lọc tự động.

Nếu hàm trả về một hàng, thì cột duy nhất của nó ( Is ALLowed ) sẽ chứa 1 (có nghĩa là đúng ) hoặc 0 (có nghĩa là sai ). Bộ lọc WHERE kiểm tra bổ sung rằng giá trị phải là 1 cho hàng được đưa vào đầu ra.

Chức năng làm gì:

  • đưa bảng Pages lên cấu trúc phân cấp để thu thập trang được chỉ định và tất cả các cha mẹ của nó thành một tập hợp hàng;

  • xây dựng một tập hợp hàng khác chứa tất cả các vai trò mà người dùng đã chỉ định được đưa vào, cùng với một trong các cột quyền (nhưng chỉ các giá trị không phải NULL) - cụ thể là một vai trò tương ứng với quyền được chỉ định làm đối số thứ ba;

  • cuối cùng, tham gia bộ thứ nhất và thứ hai thông qua bảng RolePages để tìm bộ hoàn chỉnh các quyền rõ ràng phù hợp với trang được chỉ định hoặc bất kỳ cha mẹ nào của nó.

Tập hợp hàng kết quả được sắp xếp theo thứ tự tăng dần của các giá trị quyền và giá trị trên cùng được trả về là kết quả của hàm. Vì null được lọc ở giai đoạn trước, danh sách có thể chỉ chứa 0 và 1. Do đó, nếu có ít nhất một "từ chối" (0) trong danh sách các quyền, đó sẽ là kết quả của hàm. Mặt khác, kết quả trên cùng sẽ là 1, trừ khi các vai trò tương ứng với các trang được chọn xảy ra không có "cho phép" rõ ràng hoặc hoàn toàn không có mục nào phù hợp cho trang và người dùng được chỉ định, trong trường hợp đó, kết quả sẽ trống bộ hàng.

Đây là chức năng:

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
);

Trường hợp thử nghiệm

  • 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
  • Chèn dữ liệu:

    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

    Vì vậy, chỉ một người dùng được sử dụng nhưng nó được gán cho hai vai trò, với nhiều kết hợp giá trị quyền khác nhau giữa hai vai trò để kiểm tra logic trộn trên các đối tượng con.

    Hệ thống phân cấp trang rất đơn giản: một phụ huynh, hai đứa trẻ. Cha mẹ được liên kết với một vai trò, một trong những đứa trẻ với vai trò khác.

  • Kịch bản thử nghiệm:

    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;
  • Dọn dẹp:

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

Các kết quả

  • cho Tạo :

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

    Có một sự thật rõ ràng Page 1.1chỉ cho . Trang được trả về theo logic "đúng + không xác định". Những cái khác là "không được xác định" và "không được xác định + không được xác định" - do đó bị loại trừ.

  • để đọc :

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

    Một sự thật rõ ràng đã được tìm thấy trong các cài đặt cho Page 1và cho Page 1.1. Do đó, đối với cái trước nó chỉ là một "true" trong khi đối với cái sau "true + true". Không có quyền đọc rõ ràng cho Page 1.2, vì vậy đây là một trường hợp "đúng + không xác định" khác. Vì vậy, tất cả ba trang đã được trả lại.

  • để cập nhật :

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

    Từ các cài đặt, một sự thật rõ ràng đã được trả về Page 1và một sai cho Page 1.1. Đối với các trang đưa nó vào đầu ra, logic giống như trong trường hợp Đọc . Đối với hàng bị loại trừ cả saiđúng đã được tìm thấy và do đó logic "false + anything" hoạt động.

  • cho Xóa không có hàng trả lại. Cha mẹ và một trong những đứa trẻ có null rõ ràng trong cài đặt và đứa trẻ khác không có gì.

Nhận tất cả các quyền

Bây giờ nếu bạn muốn trả về tất cả các quyền hiệu quả, bạn có thể điều chỉnh chức năng 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
);

Hàm trả về bốn cột - các quyền hiệu quả cho trang và người dùng được chỉ định. Ví dụ sử dụng:

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

Đầu ra:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.