Схема базы данных для ACL - PullRequest
27 голосов
/ 04 мая 2011

Я хочу создать схему для ACL;однако я разрываюсь между несколькими способами его реализации.

Я почти уверен, что не хочу иметь дело с каскадными разрешениями, так как это приводит к путанице на сервере и для администраторов сайтов..

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

Сначала я собирался нормализовать данные и иметь три таблицы для представления отношений.

ROLES { id, name }
RESOURCES { id, name }
PERMISSIONS { id, role_id, resource_id }

Запрос, чтобы выяснить, было ли разрешено где-то пользователю, будет выглядеть так:

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

Тогда я понял, что у меня будет только около 20 ресурсов, каждый из которых может содержать до 5действия (создание, обновление, просмотр и т. д.) и, возможно, еще 8 ролей.Это означает, что я могу проявить вопиющее пренебрежение к нормализации данных, так как у меня никогда не будет более пары сотен возможных записей.

Так что, возможно, такая схема будет иметь больше смысла.

ROLES { id, name }
PERMISSIONS { id, role_id, resource_name }

что позволило бы мне искать записи в одном запросе

SELECT * FROM permissions WHERE role_id = ? AND permission  = ? ($user_role_id, 'post.update')

Так что из этого является более правильным?Существуют ли другие схемы расположения для ACL?

Ответы [ 3 ]

29 голосов
/ 10 мая 2011

По моему опыту, реальный вопрос в основном сводится к тому, произойдет ли какое-либо ограничение пользовательского ограничения доступа.

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

Один из вариантов - придерживаться флага общего / частного профиля и придерживаться широких преимущественных проверок разрешений: 'users.view' (просматривает общедоступных пользователей) против, скажем, 'users.view_all' (просматривает всех пользователей для модераторов).

Другой вариант включает более тонкие разрешения, вы можете захотеть, чтобы они могли настраивать вещи, чтобы они могли создавать сами себя (а) видны всем, (б) видны их избранным друзьям, (в) полностью сохранены в тайне и, возможно, (г) доступны для просмотра всем, кроме их выбранных бузо.В этом случае вам нужно хранить данные, относящиеся к владельцу / доступу, для отдельных строк, и вам нужно будет тщательно абстрагировать некоторые из этих вещей, чтобы избежать материализации транзитивного замыкания плотного ориентированного графа.

При любом подходе я обнаружил, что дополнительная сложность в редактировании / назначении ролей компенсируется получающейся в результате легкостью / гибкостью при назначении разрешений для отдельных фрагментов данных, и что следующее работает лучше всего:

  1. Пользователи могут иметь несколько ролей
  2. Роли и разрешения, объединенные в одной таблице с флагом, позволяющим различать их (полезно при редактировании ролей / разрешений)
  3. Роли могут назначать другиеролям, а также ролям и разрешениям можно назначать разрешения (но разрешения не могут назначать роли) из одной и той же таблицы.

Полученный ориентированный граф затем можно получить в двух запросах, построенных раз и навсегда вразумное количество времени, используя тот язык, который вы используете, и кешируется в Memcache или Similar для последующего использования.

Оттуда получение прав пользователя - это проверка его ролей и их обработка с использованием графа разрешений для получения окончательных разрешений.Проверьте разрешения, убедившись, что пользователь имеет указанную роль / разрешение или нет.И затем запустите ваш запрос / выдайте ошибку, основанную на этой проверке разрешений.

Вы можете расширить проверку для отдельных узлов (т. Е. check_perms($user, 'users.edit', $node) для "можно редактировать этот узел" против check_perms($user, 'users.edit') для "может редактироватьузел "), если вам нужно, и у вас будет что-то очень гибкое / простое в использовании для конечных пользователей.

Как следует из первого примера, опасайтесь слишком сильно ориентироваться на разрешения на уровне строк.Узкое место в производительности меньше при проверке разрешений отдельного узла, чем при получении списка допустимых узлов (т. Е. Только тех, которые пользователь может просматривать или редактировать).Я бы не советовал ничего, кроме флагов и полей user_id в самих строках, если вы не очень хорошо разбираетесь в оптимизации запросов.

8 голосов
/ 09 мая 2011

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

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

Реальный вопрос, который нужно задать, не «Сколько строк у меня будет?», А «Насколько важно, чтобы база данных всегда давала мне правильные ответы?» Для базы данных, которая будет использоваться для реализации ACL, я бы сказал «Довольно опасно».

Во всяком случае, небольшое количество строк говорит о том, что вам не нужно беспокоиться о производительности, поэтому 5NF должен быть легким выбором. Вам нужно будет набрать 5NF, прежде чем добавлять какие-либо идентификационные номера.

Запрос, чтобы выяснить, был ли пользователь разрешено где-то будет выглядеть это:

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions 
WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

То, что вы написали, что, поскольку два запроса вместо внутреннего соединения, наводит на мысль, что вы, возможно, находитесь над своей головой. (Это наблюдение, а не критика.)

SELECT p.* 
FROM permissions p
INNER JOIN resources r ON (r.id = p.resource_id AND 
                           r.name = ?)
1 голос
/ 04 мая 2011

Вы можете использовать SET для назначения ролей.

CREATE TABLE permission (
  id integer primary key autoincrement
  ,name varchar
  ,perm SET('create', 'edit', 'delete', 'view')
  ,resource_id integer );
...