테이블 저장 계층의 계층 적 권한


9

다음 데이터베이스 구조를 가정 (필요한 경우 수정 가능) ...

여기에 이미지 설명을 입력하십시오

페이지와 유효 권한이 포함 된 행을 반환 할 수있는 방식으로 주어진 페이지에서 특정 사용자에 대한 "유효 권한"을 결정하는 좋은 방법을 찾고 있습니다.

이상적인 솔루션에는 CTE를 사용하여 현재 사용자의 주어진 페이지 행에 대한 "유효 권한"을 평가하는 데 필요한 재귀를 수행하는 기능이 포함될 수 있다고 생각합니다.

배경 및 구현 세부 사항

위 스키마는 역할에서 추가 및 제거하여 사용자에게 권한을 부여 할 수있는 컨텐츠 관리 시스템의 시작점을 나타냅니다.

시스템의 리소스 (예 : 페이지)는 해당 역할에 연결된 사용자 그룹에 부여 된 권한을 부여하는 역할과 연결됩니다.

모든 역할을 거부하고 트리의 루트 수준 페이지를 해당 역할에 추가 한 다음 사용자를 해당 역할에 추가하면 사용자를 쉽게 잠글 수 있습니다.

예를 들어 회사에서 근무하는 계약 업체를 장기간 사용할 수없는 경우 권한 구조가 그대로 유지됩니다. 그러면 해당 역할에서 사용자를 간단히 제거하여 원래 권한을 동일하게 부여 할 수 있습니다. .

권한은 이러한 규칙에 따라 파일 시스템에 적용될 수있는 일반적인 ACL 유형 규칙을 기반으로합니다.

CRUD 권한은 널 입력 가능 비트이므로 사용 가능한 값은 true, false이며 다음에 해당하는 경우 정의되지 않습니다.

  • 거짓 + 아무것도 = 거짓
  • 참 + 정의되지 않음 = 참
  • 참 + 참 = 참
  • 미정의 + 미정의 = 미정의
권한 중 하나라도 false 인 경우-> false 
그렇지 않은 경우 그렇지 않으면-> true
그렇지 않으면 (모두 정의되지 않음)-> false

즉, 역할 멤버 자격을 통해 권한이 부여되고 거부 규칙이 허용 규칙을 재정의하지 않는 한 어떤 권한도 부여되지 않습니다.

이것이 적용되는 권한의 "집합"은 현재 페이지를 포함하여 트리에 적용되는 모든 권한입니다. 즉,이 페이지의 트리에있는 모든 페이지에 false가 적용되면 결과는 false입니다. 그러나 여기까지의 전체 트리가 정의되지 않은 경우 현재 페이지에 실제 규칙이 포함되어 있으면 결과는 여기에 해당하지만 상위에 대해서는 false입니다.

가능한 경우 db 구조를 느슨하게 유지하고 싶습니다. 여기서 나의 목표는 다음과 같은 작업을 수행하는 것입니다. select * from pages where effective permissions (read = true) and user = ?따라서 어떤 솔루션이든 효과적인 권한으로 쿼리 가능한 세트를 가질 수 있어야합니다. 어떤 식 으로든 (기준을 지정할 수있는 한 반환하는 것은 선택 사항입니다).

1이 다른 사용자의 자식이고 2 개의 역할이 존재하고 (관리자 역할과 읽기 전용 사용자를위한 역할) 2 개의 페이지가 존재한다고 가정하면 둘 다 예상 출력으로 다음과 같은 내용이 표시 될 것으로 예상되는 루트 수준 페이지에만 연결됩니다.

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

이 모델을 사용 하여 다음과 같은 방식으로 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 + not defined"로직에 따라 페이지가 반환되었습니다. 나머지는 "정의되지 않음"및 "정의되지 않음 + 정의되지 않음"이므로 제외되었습니다.

  • 에 대한 읽기 :

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

    명시 적 진실이 에 대한 설정에서 발견 Page 1하고 대한 Page 1.1. 따라서 전자의 경우 단일 "true"인 반면 후자의 경우 "true + true"입니다. 에 대한 명시적인 읽기 권한이 없으므로 Page 1.2다른 "true + not defined"사례입니다. 따라서 세 페이지가 모두 반환되었습니다.

  • 에 대한 업데이트 :

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

    설정에서에 대해 명시 적 true 가 반환 Page 1되고에 대해 false 가 반환 되었습니다 Page 1.1. 출력으로 만든 페이지의 경우 로직은 Read의 경우와 동일 합니다. 제외 된 행에 대해 falsetrue 가 모두 발견되었으므로 "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
);

이 함수는 지정된 페이지와 사용자에 대한 유효 권한 인 4 개의 열을 반환합니다. 사용 예 :

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.