Хранение пунктов меню с разрешениями пользователя

11

Я создаю систему меню на PHP и MySQL. У меня будет несколько разных меню, и каждое меню будет иметь набор пунктов меню, связанных с ним.

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

То, что у меня пока есть, примерно так:

-------------------
|Menus
-------------------
|id| |display name|
-------------------

----------------------------------------------------------
|Menu items
----------------------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort| |permission|
----------------------------------------------------------

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

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

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

Спасибо.

пролет
источник
У пользователей есть что-то общее? Обычно вы группируете пункты меню в функциональные группы и назначаете пользователей в эти группы (например, пользователи-администраторы, пользователи БД, трейдеры и т. Д.). Затем вы просто управляете группировками, в зависимости от выбора технологии - этим можно управлять, используя что-то вроде Active Directory.
Майкл
1
Здесь вы получите много хороших ответов, но вы можете прочитать о ACL. en.wikipedia.org/wiki/Access_control_list
Reactgular

Ответы:

17

Я смоделирую это, используя диаграмму ER.

  • A PERMISSION- это доступ, предоставленный для ROLEданного объекта MENU_ITEM.
  • ROLE - это набор предопределенных разрешений, которым присваивается имя
  • A USERможет иметь много ролей.
  • Наличие разрешений, назначаемых ролям вместо пользователей, значительно упрощает администрирование разрешений.

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

Затем вы можете создать представление, чтобы вам не приходилось каждый раз записывать объединения:

create or replace view v_user_permissions
select
    distinct mi.id, mi.menu_id. mi.label. mi.link, mi.parent, mi.sort, u.user_id
from
    user u
    join user_role ur on (u.user_id = ur.user_id)
    join role ro on (ur.role_id = role.role_id)
    join permission per on (ro.role_id = per.role_id)
    join menu_item mi on (per.metu_item_id = mi.metu_item_id)

Затем каждый раз, когда вы хотите узнать, к каким пунктам меню пользователь имеет доступ, вы можете запросить его:

select * from v_user_permissions up where up.user_id = 12736 order by up.sort

РЕДАКТИРОВАТЬ:

Поскольку пользователю может быть предоставлено несколько ролей, разрешения ролей могут перекрываться, то есть две разные роли могут иметь доступ к одному и тому же пункту меню. Когда вы определяете роль, вы заранее не знаете, будет ли она иметь какие-то общие права с другими ролями. Но поскольку речь идет об объединении множеств, имеет значение только то, является ли данное разрешение частью набора, а не сколько раз оно появляется, отсюда и distinctусловие в представлении.

Тулаинс Кордова
источник
Здорово, спасибо. Не могу понять, почему я просто не мог составить это сам. Угадайте , я был заблокирован и не опытный :)
поверочный
Я думал, что понял, но, очевидно, не понял. Как я могу иметь несколько разрешений для одного пункта меню?
продолжительность
1
@span Поскольку пользователю может быть предоставлено несколько ролей, а разрешения ролей могут перекрываться, то есть две разные роли могут иметь доступ к одному и тому же пункту меню. Когда вы определяете роль, вы заранее не знаете, будет ли эта роль предоставлена ​​вместе с другой, у которой есть некоторые общие с ней разрешения. Но поскольку эта проблема связана с объединением множеств, важно только, является ли данное разрешение частью набора или нет, а не сколько раз оно появляется.
Тулаинс Кордова
Спасибо, я буду читать твой ответ, пока не получу его;). Я думаю, что моя ошибка заключалась в том, что я мог использовать одно разрешение вместе с ролью для разделения пунктов меню. Кажется, мне нужно разрешение для каждого «типа» пункта меню. Еще раз спасибо за вашу помощь! Я нарисую несколько диаграмм Венна и посмотрю, смогу ли я обойти это правильно \ o /
span
5

Наличие списка, разделенного запятыми, означает сравнение подстрок каждый раз, когда вы выполняете запрос к меню. Это не идеально.

Вам нужно нормализовать таблицу:

---------------------------------------------
|Menu items
---------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort|
---------------------------------------------

+---------------------------+
| menu_permission           |
|---------------------------|
| |menu_permission_id| (pk) |
| |menu_id| (fk)            |
| |permission|              |
+---------------------------+

Если вы все еще хотите , разделенный запятыми список (по некоторым причинам), вы можете вытащить его с вещами , такими , как group_concatв MySQL wm_concat в оракула или аналогичные функции на других языках.

Преимущество для этого многократное.

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

Во-вторых, запрос, который вы пишете, становится намного проще. Чтобы определить, существует ли разрешение «foo» в списке, разделенном запятыми, необходимо проверить «foo».

... permission like "%foo%" ...

Однако это даст ложный положительный результат, если у вас также есть разрешение «foobar». Так что теперь вам нужно пройти тест как

... permission like "%,foo,%" ...

но это даст ложный минус, если 'foo' находится в начале или конце строки. Это приводит к чему-то вроде

... (permission like "foo,%" 
  or permission like "%,foo,%" 
  or permission like "%,foo" ) ...

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

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

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


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

Этот классический подход к этому User -> UserGroupи затем ассоциируется Menu -> MenuItem -> UserGroup. Использование целочисленного значения для взвешивания уровня разрешения.

-------------------
|Menu
-------------------
|id| |display name|
-------------------

-------------------------------------------------------------
|Menu Item
-------------------------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort| | min_option1
-------------------------------------------------------------

-------------------------------------------
| User
-------------------------------------------
|id| | username | password | user_group_id
-------------------------------------------

-------------------------------------------
| User Group
-------------------------------------------
|id| option1 | option2 | option2
-------------------------------------------

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

SELECT * FROM `MenuItem`
    LEFT JOIN `UserGroup` ON (`MenuItem`.`user_group_id` = `UserGroup`.`id`)
    LEFT JOIN `User` ON (`UserGroup`.`id` = `User`.`user_group_id` AND `User`.`id` = $current_user_id)
        WHERE `MenuItem`.`menu_id` = $menu_id AND `UserGroup`.`option1` >= `MenuItem`.`min_option1`;

Это будет выбирать только те меню, которые видны текущему пользователю на основе условия для option1.

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

SELECT * FROM `MenuItem` WHERE `MenuItem`.`menu_id` = $menu_id AND `MenuItem`.`min_option1` >= $user_group_option1;

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

Reactgular
источник
2
Такой дизайн позволял бы привязывать каждый MenuItem к одной группе пользователей, что означает либо ограниченное меню, либо дублирование данных. В действительности вы хотите таблицу ссылок, в идеале. Кроме того, ваш выбор имен таблиц БД в качестве множественного числа меня огорчает;)
Эд Джеймс
@ EdWoodcock, о, очень хорошая мысль. Я должен был пойти с уровнем разрешений (int), а затем сравнить его с уровнем группы пользователей. Я изменю это. Обратите внимание, привычка имен во множественном числе вызвана моим использованием CakePHP. Что странно, потому что фреймворк использует псевдонимы для таблиц в запросах.
Reactgular
@ MatthewFoscarini Не беспокойтесь, я не очень беспокоюсь, если кодовая база последовательна;)
Эд Джеймс
1
Потрясающий ответ. Я буду помнить об этом в следующий раз, когда сделаю что-нибудь подобное. Сейчас я думаю, что решение user61852 подойдет лучше всего, так как оно не требует столько изменений в существующем коде. Спасибо!
продолжительность