Использование представлений для контроля доступа в PostgreSQL

StackOverflow https://stackoverflow.com/questions/2309147

Вопрос

У меня есть схема таблиц, содержимое которых в основном сводится к:

  • Набор пользователей
  • Набор групп объектов
  • Список контроля доступа (acl), указывающий, какие пользователи имеют доступ к каким группам.
  • Набор объектов, каждый из которых принадлежит ровно одной группе.

Я хочу создать простое приложение, поддерживающее контроль доступа.Я думаю, что просмотры здесь будут хорошим подходом.

Предположим, у меня есть следующая инициализация базы данных:

/* Database definition */

BEGIN;

CREATE SCHEMA foo;

CREATE TABLE foo.users (
    id SERIAL PRIMARY KEY,
    name TEXT
);

CREATE TABLE foo.groups (
    id SERIAL PRIMARY KEY,
    name TEXT
);

CREATE TABLE foo.acl (
    user_ INT REFERENCES foo.users,
    group_ INT REFERENCES foo.groups
);

CREATE TABLE foo.objects (
    id SERIAL PRIMARY KEY,
    group_ INT REFERENCES foo.groups,
    name TEXT,
    data TEXT
);

/* Sample data */

-- Create groups A and B
INSERT INTO foo.groups VALUES (1, 'A');
INSERT INTO foo.groups VALUES (2, 'B');

-- Create objects belonging to group A
INSERT INTO foo.objects VALUES (1, 1, 'object in A', 'apples');
INSERT INTO foo.objects VALUES (2, 1, 'another object in A', 'asparagus');

-- Create objects belonging to group B
INSERT INTO foo.objects VALUES (3, 2, 'object in B', 'bananas');
INSERT INTO foo.objects VALUES (4, 2, 'object in B', 'blueberries');

-- Create users
INSERT INTO foo.users VALUES (1, 'alice');
INSERT INTO foo.users VALUES (2, 'amy');
INSERT INTO foo.users VALUES (3, 'billy');
INSERT INTO foo.users VALUES (4, 'bob');
INSERT INTO foo.users VALUES (5, 'caitlin');
INSERT INTO foo.users VALUES (6, 'charlie');

-- alice and amy can access group A
INSERT INTO foo.acl VALUES (1, 1);
INSERT INTO foo.acl VALUES (2, 1);

-- billy and bob can access group B
INSERT INTO foo.acl VALUES (3, 2);
INSERT INTO foo.acl VALUES (4, 2);

-- caitlin and charlie can access groups A and B
INSERT INTO foo.acl VALUES (5, 1);
INSERT INTO foo.acl VALUES (5, 2);
INSERT INTO foo.acl VALUES (6, 1);
INSERT INTO foo.acl VALUES (6, 2);

COMMIT;

Моя идея состоит в том, чтобы использовать представления, которые отражают базу данных, но ограничивают контент только тем, к которому может получить доступ текущий пользователь (определенный моим PHP-скриптом) (здесь я просто буду использовать пользователя «bob»).Предположим, я запускаю это в начале каждого сеанса PostgreSQL (то есть каждый раз, когда кто-то обращается к странице моего сайта):

BEGIN;

CREATE TEMPORARY VIEW users AS
SELECT * FROM foo.users
WHERE name='bob';

CREATE TEMPORARY VIEW acl AS
SELECT acl.* FROM foo.acl, users
WHERE acl.user_=users.id;

CREATE TEMPORARY VIEW groups AS
SELECT groups.* FROM foo.groups, acl
WHERE groups.id=acl.group_;

CREATE TEMPORARY VIEW objects AS
SELECT objects.* FROM foo.objects, groups
WHERE objects.group_=groups.id;

COMMIT;

Мой вопрос: хороший ли это подход?Производят ли эти операторы CREATE TEMPORARY VIEW значительные накладные расходы, особенно по сравнению с парой простых запросов?

Кроме того, есть ли способ сделать эти представления постоянными в определении моей базы данных, а затем привязать значение к имени пользователя для каждого сеанса?Таким образом, ему не нужно создавать все эти представления каждый раз, когда пользователь загружает страницу.

Это было полезно?

Решение

Несколько проблем с этим подходом:

  1. Один пользователь сеть сессия - это не то же самое, что одна база данных сессия.Несколько пользователей с такой настройкой мгновенно потерпят неудачу.

  2. Накладные расходы на управление созданием/уничтожением представлений.

Вместо этого я бы рекомендовал что-то вроде следующего представления:

CREATE VIEW AllowedObjects
SELECT objects.*, users.name AS alloweduser
FROM objects
   INNER JOIN groups ON groups.id = objects.group_
   INNER JOIN acl ON acl.group_ = groups.id
   INNER JOIN users ON users.id = acl.user_

Затем везде вы выбираете объекты:

SELECT * FROM AllowedObjects
WHERE alloweduser='Bob'

Это предполагает, что у Боба может быть только один ACL, присоединяющий его к определенной группе, в противном случае потребуется DISTINCT.

Это можно абстрагировать до немного менее сложного представления, которое можно использовать для упрощения проверки разрешений для UPDATE и DELETE:

CREATE VIEW AllowedUserGroup
SELECT groups.id AS allowedgroup, users.name AS alloweduser
FROM groups
   INNER JOIN acl ON acl.group_ = groups.id
   INNER JOIN users ON users.id = acl.user_

Это обеспечивает упрощенное представление о том, какие пользователи в каких группах, и которое вы можете проверить по таблице объектов во время UPDATE/DELETE:

UPDATE objects SET foo='bar' WHERE id=42 AND EXISTS
(SELECT NULL FROM AllowedUserGroup 
 WHERE alloweduser='Bob' AND allowedgroup = objects.group_)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top