У меня есть схема таблиц, содержание которых в основном сводится к:
Я хочу создать простое приложение, которое поддерживает управление доступом. Я думаю, что представления были бы хорошим подходом здесь.
Предположим, что у меня есть следующая инициализация базы данных:
/* 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) (здесь, я буду просто использовать пользователя 'боб'). Предположим, что я выполняю это в начале каждой сессии 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 производят значительные издержки, особенно по сравнению с парой простых запросов?
Кроме того, есть ли способ сделать эти представления постоянными в моем определении базы данных, затем связать значение с именем пользователя на сессию? Таким образом, это не должно создавать все эти представления каждый раз пользовательские нагрузки страница.
Несколько проблем с этим подходом:
Одна пользовательская web сессия - это не то же самое, что одна database сессия. Несколько пользователей с подобной настройкой мгновенно выйдут из строя.
Накладные расходы на управление при создании/уничтожении представлений.
Вместо этого я бы рекомендовал что-то вроде следующего представления:
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_)