Bagaimana cara menerapkan izin logika bisnis di PostgreSQL (atau SQL secara umum)?


16

Anggap saya memiliki daftar item:

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

Sekarang, saya ingin memperkenalkan konsep "izin" untuk setiap item (harap dicatat, saya tidak berbicara tentang izin akses database di sini, tetapi izin logika bisnis untuk item tersebut). Setiap item memiliki izin default dan juga izin per pengguna yang dapat mengesampingkan izin default.

Saya mencoba memikirkan beberapa cara untuk mengimplementasikan ini dan menemukan solusi berikut, tetapi saya tidak yakin yang mana yang terbaik dan mengapa:

1) Solusi Boolean

Gunakan kolom boolean untuk setiap izin:

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

Keuntungan : Setiap izin diberi nama.

Kekurangan : Ada puluhan izin yang meningkatkan jumlah kolom secara signifikan dan Anda harus mendefinisikannya dua kali (satu kali di setiap tabel).

2) Solusi Integer

Gunakan integer dan perlakukan sebagai bitfield (yaitu bit 0 untuk can_change_description, bit 1 untuk can_change_price, dan sebagainya, dan gunakan operasi bitwise untuk mengatur atau membaca izin).

CREATE DOMAIN permissions AS integer;

Keuntungan : sangat cepat.

Kekurangan : Anda harus melacak bit mana yang menunjukkan izin di database dan antarmuka front-end.

3) Solusi Bitfield

Sama seperti 2), tetapi gunakan bit(n). Kemungkinan besar kelebihan dan kekurangan yang sama, mungkin sedikit lebih lambat.

4) Solusi Enum

Gunakan tipe enum untuk izin:

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

lalu buat tabel tambahan untuk izin default:

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

    PRIMARY KEY(item, perm)
);

dan ubah tabel definisi per pengguna ke:

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

Keuntungan : Mudah menyebutkan izin individu (Anda tidak harus menangani posisi bit).

Kekurangan : Bahkan ketika hanya mengambil izin default, itu memerlukan mengakses dua tabel tambahan: pertama, tabel izin default, dan kedua, katalog sistem menyimpan nilai enum.

Terutama karena izin default harus diambil untuk setiap tampilan halaman dari item itu , dampak kinerja dari alternatif terakhir mungkin signifikan.

5) Solusi Enum Array

Sama seperti 4), tetapi gunakan array untuk menampung semua izin (default):

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

CREATE TABLE items
(
    item serial PRIMARY KEY,

    granted_permissions permission ARRAY,
    ...
);

Keuntungan : Mudah menyebutkan izin individu (Anda tidak harus menangani posisi bit).

Kekurangan : Mematahkan bentuk normal pertama dan agak jelek. Mengambil banyak byte dalam satu baris jika jumlah izinnya besar (sekitar 50).

Bisakah Anda memikirkan alternatif lain?

Pendekatan mana yang harus diambil dan mengapa?

Harap dicatat: ini adalah versi modifikasi dari pertanyaan yang diposting sebelumnya di Stackoverflow .


2
Dengan lusinan izin yang berbeda, saya mungkin memilih satu (atau lebih) bigintbidang (masing-masing bagus untuk 64 bit) atau sedikit string. Saya menulis beberapa jawaban terkait pada SO yang mungkin bisa membantu.
Erwin Brandstetter

Jawaban:


7

Saya tahu bahwa Anda tidak bertanya tentang keamanan basis data per se , tetapi Anda dapat melakukan apa yang Anda inginkan menggunakan keamanan basis data. Anda bahkan dapat menggunakan ini di aplikasi web. Jika Anda tidak ingin menggunakan keamanan basis data, maka skema tersebut masih berlaku.

Anda ingin keamanan tingkat kolom, keamanan tingkat baris, dan mungkin manajemen peran hierarkis. Keamanan berbasis peran jauh lebih mudah dikelola daripada keamanan berbasis pengguna.

Contoh kode ini adalah untuk PostgreSQL 9.4, yang segera keluar. Anda dapat melakukannya dengan 9.3, tetapi ada lebih banyak tenaga kerja manual yang dibutuhkan.

Anda ingin semuanya dapat diindeks jika Anda khawatir dengan kinerja †, yang seharusnya Anda lakukan. Ini berarti bidang bit-mask dan array mungkin bukan ide yang bagus.

Dalam contoh ini, kami menyimpan tabel data utama dalam dataskema, dan tampilan yang sesuai di 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
);

Letakkan pemicu pada data. Sesuatu untuk sisipan dan pembaruan yang memaksa kolom pemilik adalah current_user. Mungkin hanya mengizinkan pemilik untuk menghapus catatannya sendiri (pemicu lain).

Buat WITH CHECK OPTIONtampilan, yang sebenarnya akan digunakan pengguna. Berusaha sangat keras untuk membuatnya bisa diupdate, jika tidak Anda akan memerlukan pemicu / aturan, yang lebih banyak bekerja.

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;

Selanjutnya, buat tabel daftar kontrol-akses:

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

Ubah tampilan Anda ke akun untuk ACL:

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;

Buat tabel hak istimewa baris default:

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

  primary key (table_name, role_name, privilege)
);

Letakkan pemicu pada insert pada data.thing sehingga menyalin hak baris default ke security.thing_acl.

  • Sesuaikan keamanan tingkat tabel dengan tepat (cegah penyisipan dari pengguna yang tidak diinginkan). Tidak seorang pun harus dapat membaca data atau skema keamanan.
  • Sesuaikan keamanan tingkat kolom dengan tepat (mencegah beberapa pengguna melihat / mengedit beberapa kolom). Anda dapat menggunakan has_column_privilege () untuk memeriksa apakah pengguna dapat melihat kolom.
  • Mungkin Anda ingin tag penentu keamanan di tampilan Anda.
  • Pertimbangkan menambahkan grantordan admin_optionkolom ke tabel acl untuk melacak siapa yang memberikan hak istimewa, dan apakah penerima dapat mengelola hak istimewa pada baris itu.
  • Tes banyak

† Dalam hal ini pg_has_role mungkin tidak dapat diindeks. Anda harus mendapatkan daftar semua peran superior ke current_user dan membandingkannya dengan nilai pemilik / penerima.


Apakah Anda melihat bagian " Saya tidak berbicara tentang izin akses basis data di sini "?
a_horse_with_no_name

@a_horse_with_no_name ya saya lakukan. Dia bisa menulis sendiri sistem RLS / ACL, atau dia bisa menggunakan keamanan bawaan database untuk melakukan apa yang dia minta.
Neil McGuigan

Terima kasih atas jawaban terinci Anda! Namun, saya tidak berpikir menggunakan peran basis data adalah jawaban yang tepat di sini karena tidak hanya staf, tetapi juga setiap pengguna tunggal dapat memiliki izin. Contohnya adalah 'can_view_item', 'can_bulk_order_item' atau 'can_review_item'. Saya pikir pilihan asli saya untuk nama izin membuat Anda percaya bahwa ini hanya tentang izin staf, tetapi semua nama ini hanyalah contoh untuk menghilangkan kompleksitas. Seperti yang saya katakan di pertanyaan awal, ini tentang per izin pengguna , bukan per izin staf .
JohnCand

Bagaimanapun, harus memiliki peran basis data yang terpisah untuk setiap baris pengguna dalam tabel pengguna tampaknya terlalu banyak dan sulit dikelola. Namun, saya pikir jawaban Anda sangat berharga bagi pengembang yang menerapkan izin staf yang adil.
JohnCand

1
@ JohnCand Saya tidak benar-benar melihat bagaimana lebih mudah untuk mengelola izin di tempat lain, tapi tolong tunjukkan kami ke solusi Anda setelah Anda menemukannya! :)
Neil McGuigan

4

Sudahkah Anda mempertimbangkan untuk menggunakan ekstensi PostgreSQL Daftar Kontrol Akses ?

Ini berisi ACE tipe data PostgreSQL asli dan serangkaian fungsi yang memungkinkan Anda untuk memeriksa apakah pengguna memiliki izin untuk mengakses data. Ini berfungsi baik dengan sistem peran PostgreSQL atau dengan angka abstrak (atau UUID) yang mewakili pengguna aplikasi / ID peran Anda.

Dalam kasus Anda, Anda cukup menambahkan kolom ACL ke tabel data Anda dan menggunakan salah satu acl_check_accessfungsi untuk memeriksa pengguna terhadap ACL.

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'

Menggunakan ACL adalah cara yang sangat fleksibel untuk berurusan dengan izin logika bisnis. Selain itu, ini sangat cepat - biaya overhead rata-rata hanya 25% dari waktu yang diperlukan untuk membaca catatan. Satu-satunya batasan adalah bahwa ia mendukung maksimum 16 izin khusus per jenis objek.


1

Saya bisa memikirkan kemungkinan lain untuk menyandikan ini, yang relasional

Jika Anda tidak membutuhkan permission_per_itemtabel, Anda dapat melewati dan menghubungkan Permissionsdan Itemslangsung ke item_per_user_permissionstabel.

masukkan deskripsi gambar di sini

diagram legenda

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.