PostgreSQL'de (veya genel olarak SQL'de) iş mantığı izinleri nasıl uygulanır?


16

Diyelim ki bir tablom var:

CREATE TABLE items
(
    item serial PRIMARY KEY,
    ...
);

Şimdi, (not ben değilim lütfen her öğe için "izinler" kavramını tanıtmak istiyorum değil o öğenin burada veritabanı erişim izinleri hakkında konuşurken, ancak iş mantığı izinleri). Her öğenin varsayılan izinleri ve varsayılan izinleri geçersiz kılabilecek kullanıcı başına izinleri vardır.

Bunu uygulamanın birkaç yolunu düşünmeye çalıştım ve aşağıdaki çözümleri buldum, ancak hangisinin en iyi ve neden olduğu konusunda emin değilim:

1) Boole Çözümü

Her izin için bir boole sütunu kullanın:

CREATE TABLE items
(
    item serial PRIMARY KEY,

    can_change_description boolean NOT NULL,
    can_change_price boolean NOT NULL,
    can_delete_item_from_store boolean NOT NULL,
    ...
);

CREATE TABLE item_per_user_permissions
(
    item int NOT NULL REFERENCES items(item),
    user int NOT NULL REFERENCES users(user),

    PRIMARY KEY(item, user),

    can_change_description boolean NOT NULL,
    can_change_price boolean NOT NULL,
    can_delete_item_from_store boolean NOT NULL,
    ...
);

Avantajları : Her izin adlandırılır.

Dezavantajları : Sütun sayısını önemli ölçüde artıran düzinelerce izin vardır ve bunları iki kez tanımlamanız gerekir (her tabloda bir kez).

2) Tamsayı Çözümü

Bir tam sayı kullanın ve bunu bir bit alanı olarak ele alın (yani bit 0 içindir can_change_description, bit 1 içindir can_change_price, vb. Ve izinleri ayarlamak veya okumak için bitsel işlemleri kullanın).

CREATE DOMAIN permissions AS integer;

Avantajları : çok hızlı.

Dezavantajları : Hem veritabanında hem de ön uç arabiriminde hangi bitin hangi izin anlamına geldiğini takip etmeniz gerekir.

3) Bitfield Çözümü

2 ile aynı), ancak kullanın bit(n). Büyük olasılıkla aynı avantaj ve dezavantajlar, belki biraz daha yavaş.

4) Enum Çözümü

İzinler için bir numaralandırma türü kullanın:

CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);

ve ardından varsayılan izinler için fazladan bir tablo oluşturun:

CREATE TABLE item_default_permissions
(
    item int NOT NULL REFERENCES items(item),
    perm permission NOT NULL,

    PRIMARY KEY(item, perm)
);

ve kullanıcı başına tanım tablosunu şu şekilde değiştirin:

CREATE TABLE item_per_user_permissions
(
    item int NOT NULL REFERENCES items(item),
    user int NOT NULL REFERENCES users(user),
    perm permission NOT NULL,

    PRIMARY KEY(item, user, perm)    
);

Avantajları : Bireysel izinleri adlandırması kolaydır (bit konumlarını işlemeniz gerekmez).

Dezavantajları : Yalnızca varsayılan izinleri alırken bile, iki ek tabloya erişmeyi gerektirir: birincisi, varsayılan izinler tablosu ve ikincisi, enum değerlerini depolayan sistem kataloğu.

Özellikle bu öğenin her bir sayfa görünümü için varsayılan izinlerin alınması gerektiğinden , son alternatifin performans etkisi önemli olabilir.

5) Enum Dizi Çözümü

4 ile aynı), ancak tüm (varsayılan) izinleri tutmak için bir dizi kullanın:

CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);

CREATE TABLE items
(
    item serial PRIMARY KEY,

    granted_permissions permission ARRAY,
    ...
);

Avantajları : Bireysel izinleri adlandırması kolaydır (bit konumlarını işlemeniz gerekmez).

Dezavantajları : 1. normal formu kırar ve biraz çirkin. İzin sayısı büyükse (yaklaşık 50) arka arkaya önemli sayıda bayt alır.

Başka alternatifler düşünebilir misiniz?

Hangi yaklaşım izlenmeli ve neden?

Lütfen dikkat: Bu, daha önce Stackoverflow'da yayınlanan bir sorunun değiştirilmiş sürümüdür .


2
Düzinelerce farklı izinle bir (veya daha fazla) bigintalan (her biri 64 bit için iyi) veya bir bit dizesi seçebilirim . SO'ya yardımcı olabilecek birkaç ilgili cevap
Erwin Brandstetter

Yanıtlar:


7

Ben kendi başına veritabanı güvenliği hakkında sormadığını biliyorum , ama veritabanı güvenliğini kullanarak istediğinizi yapabilirsiniz. Bunu bir web uygulamasında bile kullanabilirsiniz. Veritabanı güvenliğini kullanmak istemiyorsanız, şemalar hala geçerlidir.

Sütun düzeyinde güvenlik, satır düzeyinde güvenlik ve muhtemelen hiyerarşik rol yönetimi istiyorsunuz. Rol Tabanlı güvenliğin yönetilmesi, Kullanıcı Tabanlı güvenliğe göre çok daha kolaydır.

Bu örnek kod, yakında çıkacak olan PostgreSQL 9.4 içindir. 9.3 ile yapabilirsiniz, ancak daha fazla el emeği gerekir.

Olmanız gereken performans † ile ilgileniyorsanız, her şeyin dizine eklenebilir olmasını istersiniz. Bu, bit maskesi ve dizi alanlarının muhtemelen iyi bir fikir olmayacağı anlamına gelir.

Bu örnekte, ana veri tablolarını dataşemada ve karşılık gelen görünümleri şemada tutuyoruz public.

create schema data; --main data tables
create schema security; --acls, security triggers, default privileges

create table data.thing (
  thing_id int primary key,
  subject text not null, --or whatever
  owner name not null
);

Sahip sütununun geçerli_kullanıcı olmasını zorunlu kılan ekler ve güncellemeler için data.thing öğesine bir tetikleyici koyun. Belki de sadece sahibinin kendi kayıtlarını silmesine izin verin (başka bir tetikleyici).

WITH CHECK OPTIONKullanıcıların gerçekte kullanacağı bir görünüm oluşturun . Güncellenebilir hale getirmek için gerçekten çok çalışın, aksi takdirde daha fazla çalışma olan tetikleyicilere / kurallara ihtiyacınız olacaktır.

create view public.thing with(security_barrier) as 
select
thing_id,
subject,
owner,
from data.thing
where
pg_has_role(owner, 'member') --only owner or roles "above" him can view his rows. 
WITH CHECK OPTION;

Ardından, bir erişim kontrol listesi tablosu oluşturun:

--privileges r=read, w=write

create table security.thing_acl (
  thing_id int,
  grantee name, --the role to whom your are granting the privilege
  privilege char(1) check (privilege in ('r','w') ),

  primary key (thing_id, grantee, privilege),

  foreign key (thing_id) references data.thing(thing_id) on delete cascade
);

EKL'lerin hesabını değiştirmek için görünümünüzü değiştirin:

drop view public.thing;

create view public.thing with(security_barrier) as 
select
thing_id,
subject,
owner
from data.thing a
where
pg_has_role(owner, 'member')
or exists (select 1 from security.thing_acl b where b.thing_id = a.thing_id and pg_has_role(grantee, 'member') and privilege='r')
with check option;

Varsayılan satır ayrıcalıkları tablosu oluşturun:

create table security.default_row_privileges (
  table_name name,
  role_name name,
  privilege char(1),

  primary key (table_name, role_name, privilege)
);

Data.thing üzerindeki insert üzerine, varsayılan satır ayrıcalıklarını security.thing_acl dosyasına kopyalaması için bir tetikleyici koyun.

  • Tablo düzeyinde güvenliği uygun şekilde ayarlayın (istenmeyen kullanıcılardan ekleme yapılmasını önleyin). Hiç kimse veri veya güvenlik şemalarını okuyamamalı.
  • Sütun düzeyinde güvenliği uygun şekilde ayarlayın (bazı kullanıcıların bazı sütunları görmesini / düzenlemesini önleyin). Bir kullanıcının bir sütun görüp görmediğini kontrol etmek için has_column_privilege () kullanabilirsiniz.
  • Muhtemelen görünümünüzde güvenlik tanımlayıcı etiketi olmasını istersiniz.
  • Eklemeyi düşünün grantorve admin_optionimtiyaz sahibi bu satırın üzerinde ayrıcalıkları yönetebilir edip acl'ler tablolara sütunlar yetki verilmiş kişileri izlemek, ve.
  • Test lotları

† Bu durumda pg_has_role muhtemelen dizine eklenemez. Current_user için tüm üst düzey rollerin bir listesini almanız ve bunun yerine sahip / yetkilendiren değeriyle karşılaştırmanız gerekir.


" Burada veritabanı erişim izinlerinden bahsetmiyorum " bölümünü gördünüz mü ?
a_horse_with_no_name

@a_horse_with_no_name evet yaptım. Kendi RLS / ACL sistemini yazabilir veya istediği şeyi yapmak için bir veritabanının yerleşik güvenliğini kullanabilir.
Neil McGuigan

Ayrıntılı cevabınız için teşekkürler! Ancak, sadece personel değil, aynı zamanda her kullanıcının da izinleri olabileceğinden, veritabanı rollerini kullanmanın burada doğru cevap olduğunu düşünmüyorum. Örnekler 'can_view_item', 'can_bulk_order_item' veya 'can_review_item' olabilir. Sanırım orijinal izin adları seçimim, bunun sadece personel izinleri ile ilgili olduğuna inanmanıza neden oldu, ancak tüm bu isimler sadece karmaşıklıkları soyutlamak için örnek oldu. Orijinal soruda söylediğim gibi, personel izinleri başına değil, kullanıcı izinleri hakkındadır .
JohnCand

Her neyse, kullanıcı tablosundaki her kullanıcı satırı için ayrı veritabanı rollerine sahip olmak zor ve yönetilebilir görünmüyor. Ancak, yanıtınızın yalnızca personel izinlerini uygulayan geliştiriciler için değerli olduğunu düşünüyorum.
JohnCand

1
@JohnCand Başka bir yerde izinleri yönetmenin ne kadar kolay olduğunu görmüyorum, ancak bulduğunuzda lütfen çözümünüze yönlendirin! :)
Neil McGuigan

4

Access Control List PostgreSQL uzantısını kullanmayı düşündünüz mü ?

Yerel PostgreSQL veri türü ACE ve bir kullanıcının verilere erişim iznine sahip olup olmadığını kontrol etmenizi sağlayan bir dizi işlev içerir. PostgreSQL rol sistemiyle veya uygulama kullanıcı / rol kimliklerinizi temsil eden soyut numaralarla (veya UUID'lerle) çalışır.

Sizin durumunuzda, veri tablolarınıza bir ACL sütunu eklersiniz ve acl_check_accessbir kullanıcıyı ACL'ye karşı kontrol etmek için işlevlerden birini kullanırsınız .

CREATE TABLE items
(
    item serial PRIMARY KEY,
    acl ace[],
    ...
);

INSERT INTO items(acl, ...) VALUES ('{a//<user id>=r, a//<role id>=rwd, ...}');

SELECT * FROM items where acl_check_access(acl, 'r', <roles of the user>, false) = 'r'

ACL'leri kullanmak, iş mantığı izinleriyle baş etmenin son derece esnek bir yoludur. Buna ek olarak, inanılmaz derecede hızlı - ortalama ek yük, bir kaydı okumak için gereken sürenin sadece% 25'i. Tek sınırlama, nesne türü başına maksimum 16 özel izni desteklemesidir.


1

Bunu kodlamak için başka bir olasılık düşünebilirim, ilişkisel olanı

permission_per_itemTableye ihtiyacınız yoksa, atlayabilir Permissionsve Itemsdoğrudan item_per_user_permissionsmasaya bağlanabilirsiniz .

resim açıklamasını buraya girin

gösterge
şeması

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.