Как я могу осуществлять контроль доступа через таблицу SQL?
-
03-07-2019 - |
Вопрос
Я пытаюсь создать систему контроля доступа.
Вот урезанный пример того, как выглядит таблица, доступ к которой я пытаюсь контролировать:
things table:
id group_id name
1 1 thing 1
2 1 thing 2
3 1 thing 3
4 1 thing 4
5 2 thing 5
А таблица контроля доступа выглядит так:
access table:
user_id type object_id access
1 group 1 50
1 thing 1 10
1 thing 2 100
Доступ может быть предоставлен либо путем непосредственного указания идентификатора «вещи», либо предоставлен для всей группы вещей путем указания идентификатора группы.В приведенном выше примере пользователю 1 предоставлен уровень доступа 50 к группе 1, который должен применяться, если не существует каких-либо других правил, предоставляющих более конкретный доступ к отдельной вещи.
Мне нужен запрос, который возвращает список вещей (только идентификаторы подходят) вместе с уровнем доступа для конкретного пользователя.Итак, используя приведенный выше пример, я бы хотел что-то вроде этого для идентификатора пользователя 1:
desired result:
thing_id access
1 10
2 100
3 50 (things 3 and 4 have no specific access rule,
4 50 so this '50' is from the group rule)
5 (thing 5 has no rules at all, so although I
still want it in the output, there's no access
level for it)
Самое близкое, что я могу придумать, это:
SELECT *
FROM things
LEFT JOIN access ON
user_id = 1
AND (
(access.type = 'group' AND access.object_id = things.group_id)
OR (access.type = 'thing' AND access.object_id = things.id)
)
Но это возвращает несколько строк, тогда как мне нужна только одна для каждой строки в таблице «вещи».Я не уверен, как получить одну строку для каждой «вещи» или как определить приоритет правил «вещи» над правилами «группы».
Если это поможет, я использую базу данных PostgreSQL.
Пожалуйста, не стесняйтесь оставлять комментарии, если есть какая-то информация, которую я пропустил.
Заранее спасибо!
Решение
Я не знаю диалекта Postgres SQL, но, возможно, что-то вроде:
select thing.*, coalesce ( ( select access
from access
where userid = 1
and type = 'thing'
and object_id = thing.id
),
( select access
from access
where userid = 1
and type = 'group'
and object_id = thing.group_id
)
)
from things
Кстати, дизайн мне не нравится.Я бы предпочел, чтобы таблица доступа была разделена на две части:
thing_access (user_id, thing_id, access)
group_access (user_id, group_id, access)
Тогда мой запрос становится:
select thing.*, coalesce ( ( select access
from thing_access
where userid = 1
and thing_id = thing.id
),
( select access
from group_access
where userid = 1
and group_id = thing.group_id
)
)
from things
Я предпочитаю это, потому что теперь в таблицах доступа можно использовать внешние ключи.
Другие советы
Я только вчера вечером прочитал статью об этом.У него есть некоторые идеи о том, как это сделать.Если вы не можете использовать ссылку в заголовке, попробуйте использовать Академию Google на Ограничение раскрытия информации в базах данных Гиппократа.
Хотя есть несколько хороших ответов, наиболее эффективным, вероятно, будет что-то вроде этого:
SELECT things.id, things.group_id, things.name, max(access)
FROM things
LEFT JOIN access ON
user_id = 1
AND (
(access.type = 'group' AND access.object_id = things.group_id)
OR (access.type = 'thing' AND access.object_id = things.id)
)
group by things.id, things.group_id, things.name
Который просто использует суммирование, добавленное к вашему запросу, чтобы получить то, что вы ищете.
Тони:
Неплохое решение, мне нравится, вроде работает.Вот ваш запрос после незначительной настройки:
SELECT
things.*,
coalesce (
( SELECT access
FROM access
WHERE user_id = 1
AND type = 'thing'
AND object_id = things.id
),
( SELECT access
FROM access
WHERE user_id = 1
AND type = 'group'
AND object_id = things.group_id
)
) AS access
FROM things;
И результаты выглядят правильно:
id | group_id | name | access
----+----------+---------+--------
1 | 1 | thing 1 | 10
2 | 1 | thing 2 | 100
3 | 1 | thing 3 | 50
4 | 1 | thing 4 | 50
5 | 2 | thing 5 |
Я делаю полностью обратите внимание на то, что это не идеальная схема.Тем не менее, я в некоторой степени застрял в этом.
Йозеф:
Ваше решение очень похоже на то, с чем я играл, и мои инстинкты (какие они есть) подсказывают мне, что это можно сделать именно так.К сожалению, это не дает полностью правильных результатов:
id | group_id | name | max
----+----------+---------+-----
1 | 1 | thing 1 | 50
2 | 1 | thing 2 | 100
3 | 1 | thing 3 | 50
4 | 1 | thing 4 | 50
5 | 2 | thing 5 |
Уровень доступа для «вещи 1» принял более высокое значение доступа «группы», а не более конкретное значение доступа «вещи», равное 10, что мне и нужно.Я не думаю, что есть способ исправить это в течение короткого времени. GROUP BY
, но если у кого-то есть какие-либо предложения, я более чем рад оказаться неправым в этом вопросе.