Saklanan bir hiyerarşideki hiyerarşik izinler


9

Aşağıdaki veritabanı yapısını varsayalım (gerekirse değiştirilebilir) ...

resim açıklamasını buraya girin

Belirli bir sayfada belirli bir kullanıcı için "etkili izinleri" belirlemek için beni Sayfa ve etkin izinleri içeren bir satır döndürmek sağlayacak şekilde belirlemek için güzel bir yol arıyorum.

İdeal çözüm geçerli kullanıcı için belirli bir sayfa satırı için "etkili izinleri" değerlendirmek için gerekli özyineleme gerçekleştirmek için bir CTE kullanan bir işlev içerebilir düşünüyorum.

Arka Plan ve Uygulama ayrıntıları

Yukarıdaki şema, kullanıcılara rol ekleyerek ve rollerinden çıkarılarak izin verilebilecekleri içerik yönetim sistemi için bir başlangıç ​​noktasını temsil eder.

Sistemdeki kaynaklar (örneğin sayfalar), o role bağlı kullanıcı grubuna verdiği izinleri vermek için rollerle ilişkilendirilir.

Fikir, tüm rolü reddetmek ve ağaçtaki kök düzeyi sayfasını bu role ekleyip kullanıcıyı bu role ekleyerek bir kullanıcıyı kolayca kilitleyebilmektir.

Bu, (örneğin) şirket için çalışan bir yüklenici uzun süre boyunca mevcut olmadığında izin yapısının yerinde kalmasına izin verir, bu da kullanıcıyı tek bir rolden kaldırarak orijinal izinlerinin aynı şekilde verilmesine izin verir. .

İzinler, bu kurallara uyarak dosya sistemine uygulanabilecek tipik ACL türü kurallara dayanır.

CRUD izinleri nulllable bitler olmalıdır, böylece mevcut değerler true, false, aşağıdakilerin doğru olduğu yerde tanımlanmamıştır:

  • yanlış + bir şey = yanlış
  • true + tanımlı değil = true
  • true + true = doğru
  • tanımlı değil + tanımlı değil = tanımlı değil
İzinlerden herhangi biri yanlışsa -> yanlış 
Başka varsa doğru -> doğru
Başka (tümü tanımlanmadı) -> yanlış

Diğer bir deyişle, rol üyeliği yoluyla onlara izin verilmedikçe ve bir reddetme kuralı bir izin verme kuralını geçersiz kılmadığı sürece hiçbir şey için izin almazsınız.

Bunun geçerli olduğu izinlerin "kümesi", geçerli sayfaya kadar ve geçerli sayfaya dahil olmak üzere ağaca uygulanan tüm izinlerdir, başka bir deyişle: Bu sayfadaki ağaçtaki herhangi bir sayfaya uygulanan herhangi bir rolde yanlışsa sonuç yanlıştır , ancak buraya kadar tüm ağaç tanımlanmamışsa geçerli sayfa gerçek bir kural içeriyorsa, sonuç burada doğrudur ancak üst öğe için yanlış olur.

Mümkünse db yapısını gevşek bir şekilde tutmak istiyorum, ayrıca buradaki select * from pages where effective permissions (read = true) and user = ?amacımın aşağıdaki gibi bir şey yapabilmek olduğunu unutmayın: bu yüzden herhangi bir çözüm, içinde etkili izinleri olan bir sorgulanabilir sete izin verebilmem gerekir. bir şekilde (kriterler belirtilebildiği sürece bunları iade etmek isteğe bağlıdır).

1 sayfanın diğerinin alt öğesi, 2 yönetici kullanıcı ve 1 salt okunur kullanıcılar için 2 sayfa bulunduğunu varsayarsak, her ikisi de yalnızca beklenen çıktı olarak böyle bir şey görmeyi beklediğim kök düzey sayfasına bağlıdır:

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

Bu soru hakkında daha fazla tartışma buradan başlayarak ana site sohbet odasında bulunabilir .

Yanıtlar:


11

Bu modeli kullanarak, Sayfalar tablosunu aşağıdaki şekilde sorgulamak için bir yol buldum :

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

GetPermissionStatus içi tablo değerli işlevin sonuç boş grubu ya da bir tek sütun satır olabilir. Sonuç kümesi boş olduğunda, belirtilen sayfa / kullanıcı / izin bileşimi için BOŞ olmayan girdiler olmadığı anlamına gelir. İlgili Sayfalar satırı otomatik olarak filtrelenir.

İşlev bir satır döndürürse, tek sütunu ( IsAllowed ) 1 ( true anlamına gelir ) veya 0 ( false anlamına gelir) içerecektir . WHERE filtresi, satırın çıktıya eklenmesi için değerin 1 olması gerektiğini de denetler.

Fonksiyon ne yapar:

  • belirtilen sayfayı ve tüm üst öğelerini tek bir satır kümesinde toplamak için Sayfalar tablosunu hiyerarşi içinde yürür ;

  • izin sütunlarından biri (ancak yalnızca NULL olmayan değerler) - özellikle üçüncü bağımsız değişken olarak belirtilen izne karşılık gelen ile birlikte, belirtilen kullanıcının dahil olduğu tüm rolleri içeren başka bir satır kümesi oluşturur;

  • son olarak, belirtilen sayfayla veya üst öğelerinden herhangi biriyle eşleşen açık izinlerin tamamını bulmak için RolePages tablosu aracılığıyla birinci ve ikinci kümeye katılır .

Sonuçta elde edilen satır kümesi artan izin değerlerine göre sıralanır ve en üstteki değer işlevin sonucu olarak döndürülür. Boş değerler daha erken bir aşamada filtrelendiğinden, liste yalnızca 0 ve 1 içerebilir. Bu nedenle, izinler listesinde en az bir "reddet" (0) varsa, bu işlevin sonucu olacaktır. Aksi takdirde, en üstteki sonuç 1 olur, ancak seçilen sayfalara karşılık gelen rollerin açık "izinleri" yoksa ya da belirtilen sayfa ve kullanıcı için eşleşen herhangi bir giriş yoksa, sonuç boş olur satır kümesi.

Bu işlev:

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

Test durumu

  • 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
    
  • Veri ekler:

    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
    

    Bu nedenle, sadece bir kullanıcı kullanılır, ancak alt nesnelerdeki karıştırma mantığını test etmek için iki rol arasında çeşitli izin değerleri kombinasyonlarıyla iki role atanır.

    Sayfa hiyerarşisi çok basit: bir ebeveyn, iki çocuk. Ebeveyn bir rolle, diğer rolle çocuklardan biri ile ilişkilidir.

  • Test komut dosyası:

    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;
    
  • Temizlemek:

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

Sonuçlar

  • için oluştur :

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

    Açık bir vardı gerçek için Page 1.1sadece. Sayfa "doğru + tanımlanmadı" mantığına göre döndürüldü. Diğerleri "tanımlanmadı" ve "tanımlanmadı + tanımlanmadı" - dolayısıyla hariç tutuldu.

  • için okuyun :

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

    Ve için ayarlarında açık bir true bulundu . Böylece, birincisi için sadece tek bir "doğru" iken, ikincisi için "doğru + doğru" idi. İçin açık okuma izni yoktu , bu yüzden başka bir "doğru + tanımlı değil" vakasıydı. Böylece, üç sayfanın tamamı iade edildi.Page 1Page 1.1Page 1.2

  • için Güncelleme :

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

    Ayarlardan, için açık bir true değeri Page 1ve bir false için döndürüldü Page 1.1. Çıktıya giren sayfalar için mantık Okuma durumunda olduğu gibiydi . Dışlanan satır için hem yalancı ve gerçek bulundu ve böylece "sahte + şey" mantığı çalıştı.

  • için Sil döndü hiçbir satır vardı. Ebeveyn ve çocuklardan birinde ortamlarda açık null'ler vardı ve diğer çocuğun hiçbir şeyi yoktu.

Tüm izinleri alın

Şimdi sadece tüm etkin izinleri döndürmek istiyorsanız, GetPermissionStatus işlevini uyarlayabilirsiniz :

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

İşlev dört sütun döndürür - belirtilen sayfa ve kullanıcı için geçerli izinler. Kullanım örneği:

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

Çıktı:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.