Як реалізувати дозволи для бізнес-логіки в PostgreSQL (або SQL взагалі)?


16

Припустимо, у мене є таблиця предметів:

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

Тепер я хочу ввести поняття "дозволи" для кожного елемента (зверніть увагу, я тут не говорю про дозволи доступу до бази даних, а про дозволи на ділову логіку для цього елемента). Кожен елемент має дозволи за замовчуванням, а також дозволи користувача, які можуть змінювати типові дозволи.

Я спробував придумати кілька способів втілити це, і придумав такі рішення, але не знаю, який найкращий і чому:

1) Булеве рішення

Використовуйте булеву колонку для кожного дозволу:

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

Переваги : кожен дозвіл названий.

Недоліки : Є десятки дозволів, які значно збільшують кількість стовпців, і їх потрібно визначити двічі (один раз у кожній таблиці).

2) Рішення цілого числа

Використовуйте ціле число і розглядайте його як бітове поле (тобто біт 0 є для can_change_description, біт 1 призначений для can_change_priceтощо) та використовуйте побітові операції для встановлення чи читання дозволів).

CREATE DOMAIN permissions AS integer;

Переваги : дуже швидко.

Недоліки : Ви повинні відслідковувати, який біт стоїть, для якого дозволу в базі даних та інтерфейсному інтерфейсі.

3) Рішення Бітфілда

Те саме, що 2), але використовувати bit(n). Швидше за все ті ж переваги та недоліки, можливо, трохи повільніше.

4) Рішення Енума

Використовуйте тип enum для дозволів:

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

а потім створити додаткову таблицю для дозволів за замовчуванням:

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

    PRIMARY KEY(item, perm)
);

і змініть таблицю визначення користувача на:

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

Переваги : легко називати окремі дозволи (вам не потрібно обробляти бітові позиції).

Недоліки : Навіть під час отримання дозволів за замовчуванням він вимагає доступу до двох додаткових таблиць: по-перше, таблиця дозволів за замовчуванням та по-друге, системний каталог, що зберігає значення перерахунків.

Тим більше, що дозволи за замовчуванням потрібно отримати для кожного перегляду сторінки цього елемента , вплив останньої альтернативи на ефективність може бути значним.

5) Рішення масиву Enum

Те саме, що і 4), але використовуйте масив, щоб утримувати всі (за замовчуванням) дозволи:

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

CREATE TABLE items
(
    item serial PRIMARY KEY,

    granted_permissions permission ARRAY,
    ...
);

Переваги : легко називати окремі дозволи (вам не потрібно обробляти бітові позиції).

Недоліки : порушує 1-ю нормальну форму і трохи негарна. Займає значну кількість байт підряд, якщо кількість дозволів велика (близько 50).

Чи можете ви придумати інші альтернативи?

Який підхід слід застосовувати і чому?

Зверніть увагу: це модифікована версія питання, розміщеного раніше на Stackoverflow .


2
З десятками різних дозволів я можу вибрати одне (або більше) bigintполів (кожне добре для 64 біт) або біт-рядок. Я написав пару пов'язаних відповідей на ТА, які можуть бути корисними.
Ервін Брандштеттер

Відповіді:


7

Я знаю, що ви не запитуєте про безпеку бази даних як такої , але ви можете робити все, що завгодно, використовуючи безпеку бази даних. Ви можете навіть використовувати це у веб-додатку. Якщо ви не хочете використовувати захист бази даних, схеми все ще застосовуються.

Вам потрібно захистити рівень стовпців, захист на рівні рядків та, ймовірно, ієрархічне управління ролями. Безпекою на основі ролей керувати набагато простіше, ніж безпекою на основі користувачів.

Цей приклад код призначений для PostgreSQL 9.4, який вийде незабаром. Можна це зробити з 9.3, але потрібно більше ручної праці.

Ви хочете, щоб усе було індексованим, якщо ви переймаєтесь ефективністю †, якою ви повинні бути. Це означає, що поля бітової маски та масиву, ймовірно, не будуть хорошою ідеєю.

У цьому прикладі ми зберігаємо основні таблиці даних у dataсхемі та відповідні представлення в 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
);

Поставте тригер на data.thing для вставок та оновлень, що підтверджують, що стовпець власника є current_user. Можливо, дозволити лише власнику видалити власні записи (ще один тригер).

Створіть WITH CHECK OPTIONподання, яким користувачі фактично користуватимуться. Намагайтеся дуже важко зробити його оновленим, інакше вам знадобляться тригери / правила, що більше роботи.

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;

Далі створіть таблицю списку контролю доступу:

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

Змініть свій погляд на обліковий запис 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;

Створіть таблицю привілеїв рядків за замовчуванням:

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

  primary key (table_name, role_name, privilege)
);

Поставте тригер на вставку на data.thing, щоб воно скопіювало привілеї рядків за замовчуванням у security.thing_acl.

  • Налаштуйте безпеку рівня таблиці відповідно (запобігайте вставкам від небажаних користувачів). Ніхто не повинен мати змогу читати дані чи схеми безпеки.
  • Налаштуйте безпеку на рівні стовпців відповідним чином (не дозволяйте деяким користувачам бачити / редагувати деякі стовпці). Ви можете використовувати has_column_privilege (), щоб перевірити, чи може користувач бачити стовпець.
  • Можливо, ви хочете визначити безпечний тег у своєму поданні.
  • Розглянемо додавання grantorтаadmin_option стовпців до таблиць acl, щоб відстежувати, хто надав привілей, та чи може одержувач отримувати привілеї в цьому рядку.
  • Тестові партії

† У цьому випадку pg_has_role, мабуть, не індексується. Вам доведеться отримати список усіх чудових ролей для current_user та порівняти їх із значенням власника / отримувача.


Ви бачили частину "Я не говорю про дозволи доступу до бази даних тут "?
a_horse_with_no_name

@a_horse_with_no_name так, я зробив. Він міг написати власну систему RLS / ACL, або він міг би використовувати вбудовану безпеку бази даних, щоб робити те, що просить.
Ніл МакГуйган

Дякую за детальну відповідь! Однак я не думаю, що використання ролей баз даних є правильною відповіддю, оскільки не лише персонал, а й кожен користувач може мати дозволи. Прикладами можуть бути "can_view_item", "can_bulk_order_item" або "can_review_item". Я думаю, що мій оригінальний вибір імен дозволів змусив вас повірити, що мова йде лише про дозволи на персонал, але всі ці імена були лише прикладами, щоб абстрагувати складності. Як я вже говорив у першому запитанні, мова йде про дозволи користувача , а не про дозволи на персонал .
JohnCand

У будь-якому випадку, наявність окремих ролей баз даних для кожної ряду користувачів у таблиці користувачів здається непосильною і важко керованою. Однак я вважаю, що ваша відповідь цінна для розробників, які застосовують лише дозволи на персонал.
ДжонКанд

1
@JohnCand Я не дуже розумію, як простіше керувати дозволами в іншому місці, але, будь ласка, вкажіть нам на ваше рішення, як тільки ви знайдете його! :)
Ніл МакГуйган

4

Чи обдумали ви використовувати розширення PostgreSQL списку контролю доступу ?

Він містить вбудований тип даних PostgreSQL типу ACE та набір функцій, які дозволяють перевірити, чи має користувач дозвіл на доступ до даних. Він працює або з системою ролей PostgreSQL, або з абстрактними номерами (або UUID), що представляють ідентифікатори користувача / ролі програми.

У вашому випадку ви просто додаєте стовпчик ACL до своїх таблиць даних і використовуєте одну з acl_check_accessфункцій, щоб перевірити користувача на 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'

Використання ACL - це надзвичайно гнучкий спосіб поводження з дозволами ділової логіки. Крім того, це неймовірно швидко - середній накладний обсяг становить лише 25% часу, необхідного для читання запису. Єдине обмеження полягає в тому, що він підтримує максимум 16 спеціальних дозволів на тип об'єкта.


1

Я можу придумати ще одну можливість кодування цього, реляційного

Якщо вам не потрібен permission_per_itemстіл, ви можете пропустити його та підключити Permissionsта Itemsбезпосередньо до item_per_user_permissionsстолу.

введіть тут опис зображення

схема легенди

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.