Контроль доступу (авторизація)
Авторизація визначає, чи володіє користувач достатніми привілеями, наприклад, для доступу до певного ресурсу або виконання будь-якої дії. Авторизація передбачає успішну аутентифікацію, тобто що користувач увійшов у систему.
→ Встановлення та вимоги
У прикладах ми будемо використовувати об'єкт класу Nette\Security\User, який представляє
поточного користувача і який ви отримуєте, передаючи його за допомогою
ін'єкції залежностей. У презентаторах
просто викликайте $user = $this->getUser()
.
Для дуже простих сайтів з адмініструванням, де права користувачів не
різняться, можна використовувати як критерій авторизації вже відомий
метод isLoggedIn()
. Інакше кажучи: щойно користувач увійшов у систему,
він має права на всі дії і навпаки.
Ролі
Мета ролей – запропонувати більш точне управління правами і
залишатися незалежними від імені користувача. Щойно користувач
входить у систему, йому призначається одна або кілька ролей. Самі ролі
можуть бути простими рядками, наприклад, admin
, member
,
guest
тощо. Вони вказуються в другому аргументі конструктора
SimpleIdentity
, або як рядок, або як масив.
Як критерій авторизації ми будемо використовувати метод
isInRole()
, який перевіряє, чи входить користувач у задану роль:
Як ви вже знаєте, вихід користувача із системи не стирає його
особистість. Таким чином, метод getIdentity()
, як і раніше, повертає
об'єкт SimpleIdentity
, включаючи всі надані ролі. Nette Framework дотримується
принципу „менше коду, більше безпеки“, тому під час перевірки ролей не
потрібно перевіряти, чи увійшов користувач у систему. Метод
isInRole()
працює з ефективними ролями, тобто якщо користувач
увійшов у систему, то використовуються ролі, призначені особистості,
якщо він не увійшов, то замість них використовується автоматична
спеціальна роль guest
.
Авторизатор
На додаток до ролей ми введемо терміни ресурс і операція:
- роль – це атрибут користувача – наприклад, модератор, редактор, відвідувач, зареєстрований користувач, адміністратор, …
- ресурс – це логічна одиниця додатка – стаття, сторінка, користувач, пункт меню, опитування, ведучий, …
- операція – це конкретна дія, яку користувач може або не може виконувати з ресурсом – перегляд, редагування, видалення, голосування, …
Авторизатор – це об'єкт, який вирішує, чи має дана роль дозвіл на
виконання певної операції з певним ресурсом. Це об'єкт, що
реалізує інтерфейс Nette\Security\Authorizator з одним
методом isAllowed()
:
Ми додаємо авторизатор у конфігурацію як сервіс контейнера DI:
І нижче наведено приклад використання. Зверніть увагу, що цього разу
ми викликаємо метод Nette\Security\User::isAllowed()
, а не метод авторизатора,
тому немає першого параметра $role
. Цей метод викликає
MyAuthorizator::isAllowed()
послідовно для всіх ролей користувачів і
повертає true, якщо хоча б один із них має дозвіл.
Обидва аргументи є необов'язковими, і їхнє значення за замовчуванням означає все.
Дозвіл ACL
Nette поставляється з вбудованою реалізацією авторизатора, класом Nette\Security\Permission, який пропонує легкий і гнучкий рівень ACL (Access Control List) для дозволу та контролю доступу. Коли ми працюємо з цим класом, ми визначаємо ролі, ресурси та окремі дозволи. При цьому ролі та ресурси можуть утворювати ієрархії. Щоб пояснити це, ми покажемо приклад веб-додатка:
guest
: відвідувач, який не ввійшов у систему, якому дозволено читати і переглядати публічну частину сайту, тобто читати статті, коментувати і голосувати в опитуваннях.registered
: користувач, що увійшов у систему, який, крім цього, може залишати коментарі.admin
: може керувати статтями, коментарями й опитуваннями
Отже, ми визначили певні ролі (guest
, registered
і admin
) і
згадали ресурси (article
, comments
, poll
), до яких
користувачі можуть отримати доступ або вчинити дії (view
,
vote
, add
, edit
).
Ми створюємо екземпляр класу Permission і визначаємо ролі. Можна
використовувати успадкування ролей, що гарантує, що, наприклад,
користувач із роллю admin
може робити те, що може робити звичайний
відвідувач сайту (і, звісно, більше).
Тепер ми визначимо список ресурсів, до яких користувачі можуть отримати доступ:
Ресурси також можуть використовувати успадкування, наприклад, ми
можемо додати $acl->addResource('perex', 'article')
.
А тепер найголовніше. Ми визначимо між ними правила, які визначають, хто що може робити:
Що якщо ми хочемо перешкодити комусь отримати доступ до ресурсу?
Тепер, коли ми створили набір правил, ми можемо просто задавати запити на авторизацію:
Те ж саме стосується і зареєстрованого користувача, але він також може коментувати:
Адміністратор може редагувати все, крім опитувань:
Дозволи також можуть оцінюватися динамічно, і ми можемо залишити рішення за нашим власним зворотним викликом, якому передаються всі параметри:
Але як вирішити ситуацію, коли імен ролей і ресурсів недостатньо,
тобто ми хочемо визначити, що, наприклад, роль registered
може
редагувати ресурс article
тільки якщо вона є його автором? Ми
будемо використовувати об'єкти замість рядків, роль буде об'єктом Nette\Security\Role і джерелом Nette\Security\Resource. Їхні методи
getRoleId()
і getResourceId()
повертатимуть вихідні рядки:
А тепер давайте створимо правило:
ACL запитується шляхом передачі об'єктів:
Роль може успадковуватися від однієї або кількох інших ролей. Але що станеться, якщо в одного предка певна дія дозволена, а в іншого – заборонена? Тоді в гру вступає вага ролі – остання роль у масиві успадкованих ролей має найбільшу вагу, перша – найменшу:
Ролі та ресурси також можуть бути видалені (removeRole()
,
removeResource()
), правила також можуть бути скасовані (removeAllow()
,
removeDeny()
). Масив усіх ролей прямих батьків повертає
getRoleParents()
. Чи успадковуються дві сутності одна від одної,
повертається roleInheritsFrom()
і resourceInheritsFrom()
.
Додати як службу
Нам потрібно додати створений нами ACL у конфігурацію як сервіс, щоб
його міг використовувати об'єкт $user
, тобто щоб ми могли
використовувати в коді, наприклад, $user->isAllowed('article', 'view')
. Для
цього ми напишемо для нього фабрику:
І додамо її в конфігурацію:
У провідних ви можете потім перевірити дозволи в методі startup()
,
наприклад: