สิทธิ์ลำดับชั้นในตารางที่จัดเก็บลำดับชั้น


9

สมมติว่าโครงสร้างฐานข้อมูลต่อไปนี้ (แก้ไขได้ถ้าต้องการ) ...

ป้อนคำอธิบายรูปภาพที่นี่

ฉันกำลังมองหาวิธีที่ดีในการพิจารณา "สิทธิ์ที่มีประสิทธิภาพ" สำหรับผู้ใช้ที่ได้รับในหน้าเว็บที่กำหนดในวิธีที่ช่วยให้ฉันกลับแถวที่มีหน้าและสิทธิ์ที่มีประสิทธิภาพ

ฉันคิดว่าทางออกที่ดีที่สุดอาจรวมถึงฟังก์ชันที่ใช้ CTE เพื่อดำเนินการเรียกซ้ำที่จำเป็นในการประเมิน "การอนุญาตที่มีประสิทธิภาพ" สำหรับแถวหน้าที่กำหนดสำหรับผู้ใช้ปัจจุบัน

ความเป็นมาและรายละเอียดการใช้งาน

สคีมาด้านบนแสดงถึงจุดเริ่มต้นสำหรับระบบการจัดการเนื้อหาที่ผู้ใช้สามารถได้รับอนุญาตโดยการเพิ่มและลบออกจากบทบาท

ทรัพยากรในระบบ (เช่นหน้า) เกี่ยวข้องกับบทบาทเพื่อให้กลุ่มผู้ใช้ที่เชื่อมโยงกับบทบาทนั้นได้รับอนุญาต

แนวคิดคือเพื่อให้สามารถล็อกผู้ใช้ได้ง่ายโดยเพียงแค่ปฏิเสธบทบาททั้งหมดและเพิ่มเพจระดับรูทในทรีให้กับบทบาทนั้นจากนั้นเพิ่มผู้ใช้เข้ากับบทบาทนั้น

สิ่งนี้จะช่วยให้โครงสร้างการอนุญาตยังคงอยู่เมื่อ (ตัวอย่าง) ผู้รับเหมาที่ทำงานให้ บริษัท ไม่พร้อมใช้งานเป็นเวลานานจากนั้นจะอนุญาตให้มีการอนุญาตแบบเดิมโดยเพียงแค่ลบผู้ใช้ออกจากบทบาทนั้น .

การอนุญาตขึ้นอยู่กับกฎชนิด ACL ทั่วไปที่อาจนำไปใช้กับระบบไฟล์โดยทำตามกฎเหล่านี้

สิทธิ์ CRUD จะเป็นบิตที่ไม่สามารถใช้ได้ดังนั้นค่าที่มีอยู่จะเป็นจริง, เท็จ, ไม่ได้กำหนดไว้โดยที่ข้อมูลต่อไปนี้เป็นจริง:

  • false + Anything = false
  • จริง + ไม่ได้กำหนด = จริง
  • จริง + จริง = จริง
  • ไม่ได้กำหนด + ไม่ได้กำหนด = ไม่ได้กำหนดไว้
หากการอนุญาตใด ๆ เป็นเท็จ -> เท็จ 
ถ้ามีจริง -> จริง
อื่น (ไม่ได้กำหนดทั้งหมด) -> false

กล่าวอีกนัยหนึ่งคุณจะไม่ได้รับอนุญาตใด ๆ ยกเว้นคุณได้รับสิทธิ์ผ่านการเป็นสมาชิกบทบาทและกฎปฏิเสธจะแทนที่กฎการอนุญาต

"การตั้งค่า" ของการอนุญาตที่ใช้กับนี้คือการอนุญาตทั้งหมดที่นำมาใช้กับต้นไม้จนถึงและรวมถึงหน้าปัจจุบันในคำอื่น ๆ : หากเท็จอยู่ในบทบาทใด ๆ ที่ใช้กับหน้าใด ๆ ในต้นไม้ที่หน้านี้ผลที่ได้คือเท็จ แต่ถ้าไม่ได้กำหนดทรีทั้งหมดถึงที่นี่หน้าปัจจุบันจะมีกฎที่แท้จริงผลลัพธ์จะเป็นจริงที่นี่ แต่อาจเป็นเท็จสำหรับผู้ปกครอง

ฉันต้องการเก็บโครงสร้าง db ไว้อย่างหลวม ๆ หากเป็นไปได้โปรดจำไว้ว่าเป้าหมายของฉันที่นี่คือสามารถทำสิ่งต่าง ๆ เช่นselect * from pages where effective permissions (read = true) and user = ?ดังนั้นวิธีแก้ไขปัญหาใด ๆ ควรจะอนุญาตให้ฉันมีชุดคำถามที่น่าสงสัยด้วยสิทธิ์ที่มีประสิทธิภาพในนั้น ด้วยวิธีใดวิธีหนึ่ง (การส่งคืนเป็นตัวเลือกตราบใดที่เกณฑ์สามารถระบุได้)

สมมติว่ามี 2 หน้าโดยที่ 1 เป็นลูกของอีกบทบาทและ 2 มีอยู่หนึ่งสำหรับผู้ใช้ผู้ดูแลระบบและ 1 สำหรับผู้ใช้อ่านอย่างเดียวทั้งสองเชื่อมโยงกับหน้าระดับรากเท่านั้นที่ฉันคาดว่าจะเห็นสิ่งนี้เป็นผลลัพธ์ที่คาดหวัง:

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

การอภิปรายต่อไปรอบ ๆ คำถามนี้สามารถพบได้ในห้องแชทหลักเริ่มต้นที่นี่

คำตอบ:


11

เมื่อใช้แบบจำลองนี้ฉันจะพบวิธีสืบค้นตารางหน้าในลักษณะต่อไปนี้:

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 (หมายถึงจริง ) หรือ 0 (หมายถึงเท็จ ) ตัวกรอง WHERE จะตรวจสอบเพิ่มเติมว่าค่าต้องเป็น 1 สำหรับแถวที่จะรวมในเอาต์พุต

ฟังก์ชั่นทำอะไร:

  • เดินตารางPagesขึ้นตามลำดับชั้นเพื่อรวบรวมเพจที่ระบุและพาเรนต์ทั้งหมดลงในชุดแถวเดียว

  • สร้างชุดแถวอื่นที่มีบทบาททั้งหมดที่ผู้ใช้ที่ระบุรวมอยู่ในหนึ่งคอลัมน์พร้อมด้วยหนึ่งในคอลัมน์การอนุญาต (แต่เฉพาะค่าที่ไม่ใช่ค่า NULL) - เฉพาะที่สอดคล้องกับการอนุญาตที่ระบุเป็นอาร์กิวเมนต์ที่สาม

  • ในที่สุดให้รวมชุดแรกและชุดที่สองผ่านตารางRolePagesเพื่อค้นหาชุดสิทธิ์ที่ชัดเจนที่ตรงกับหน้าเว็บที่ระบุหรือชุดการปกครองใด ๆ

ชุดแถวผลลัพธ์ถูกเรียงในลำดับจากน้อยไปหามากของค่าอนุญาตและค่าสูงสุดจะถูกส่งกลับเป็นผลลัพธ์ของฟังก์ชัน เนื่องจาก nulls จะถูกกรองออกในระยะก่อนหน้ารายการสามารถมีเพียง 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เท่านั้น เพจถูกส่งคืนตามตรรกะ "จริง + ไม่ได้กำหนด" คนอื่น ๆ คือ "ไม่ได้กำหนด" และ "ไม่ได้กำหนด + ไม่ได้กำหนด" - ซึ่งได้รับการยกเว้น

  • สำหรับอ่าน :

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

    ชัดเจนจริงที่พบในการตั้งค่าสำหรับและPage 1 Page 1.1ดังนั้นสำหรับอดีตมันเป็นเพียง "จริง" ในขณะที่สำหรับหลัง "จริง + จริง" ไม่มีสิทธิ์การอ่านที่ชัดเจนสำหรับPage 1.2กรณีนี้เป็นกรณี "จริง + ไม่ได้กำหนด" ดังนั้นทั้งสามหน้าจึงถูกส่งกลับ

  • สำหรับการอัปเดต :

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

    จากการตั้งค่าที่ชัดเจนที่แท้จริงคือการกลับมาPage 1และเท็จPage 1.1สำหรับ สำหรับหน้าเว็บที่ทำให้มันเป็นเอาท์พุทตรรกะเป็นเช่นเดียวกับในกรณีของการอ่าน สำหรับแถวที่ถูกแยกออกจะพบทั้งfalseและtrueดังนั้นตรรกะ "false + Anything" จึงทำงานได้

  • สำหรับการลบไม่มีการส่งคืนแถว ผู้ปกครองและเด็กคนหนึ่งมีค่า Null ชัดเจนในการตั้งค่าและเด็กคนอื่น ๆ ไม่มีอะไรเลย

รับสิทธิ์ทั้งหมด

ตอนนี้ถ้าคุณเพียงต้องการคืนสิทธิ์ที่มีประสิทธิภาพทั้งหมดคุณสามารถปรับฟังก์ชั่น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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.