Як я можу застосувати список контролю доступу в моїй веб-програмі MVC?


96

Перше запитання

Будь ласка, не могли б ви пояснити мені, як найпростіший ACL може бути впроваджений в MVC.

Ось перший підхід використання Acl в Controller ...

<?php
class MyController extends Controller {

  public function myMethod() {        
    //It is just abstract code
    $acl = new Acl();
    $acl->setController('MyController');
    $acl->setMethod('myMethod');
    $acl->getRole();
    if (!$acl->allowed()) die("You're not allowed to do it!");
    ...    
  }

}
?>

Це дуже поганий підхід, і мінусом є те, що нам потрібно додати код Acl до методу кожного контролера, але додаткові залежності нам не потрібні!

Наступним підходом є створення всіх методів контролера privateта додавання ACL-коду в __callметод контролера .

<?php
class MyController extends Controller {

  private function myMethod() {
    ...
  }

  public function __call($name, $params) {
    //It is just abstract code
    $acl = new Acl();
    $acl->setController(__CLASS__);
    $acl->setMethod($name);
    $acl->getRole();
    if (!$acl->allowed()) die("You're not allowed to do it!");
    ...   
  }

}
?>

Це краще, ніж попередній код, але головні мінуси - це ...

  • Усі методи контролера повинні бути приватними
  • Ми повинні додати код ACL у метод __call кожного контролера.

Наступним підходом є введення коду Acl у батьківський контролер, але нам все-таки потрібно зберегти приватні всі дочірні методи контролера.

Яке рішення? А яка найкраща практика? Куди слід закликати функції Acl, щоб вирішити, чи дозволяти або заборонити метод виконання.

Друге питання

Друге питання - про отримання ролі за допомогою Acl. Давайте уявимо, що у нас є гості, користувачі та друзі користувачів. Користувач обмежив доступ до перегляду його профілю, щоб тільки його друзі могли його переглядати. Усі гості не можуть переглядати профіль цього користувача. Отже, ось логіка ..

  • ми повинні забезпечити, щоб метод, який викликається, був профільним
  • ми повинні виявити власника цього профілю
  • ми повинні виявити, переглядач є власником цього профілю чи ні
  • ми повинні читати правила обмеження щодо цього профілю
  • ми маємо вирішити виконувати чи не виконувати метод профілю

Основне питання стосується виявлення власника профілю. Ми можемо виявити, хто є власником профілю, що виконує тільки метод моделі $ model-> getOwner (), але Acl не має доступу до моделі. Як ми можемо це реалізувати?

Я сподіваюся, що мої думки зрозумілі. Вибачте за мою англійську.

Дякую.


1
Я навіть не розумію, навіщо вам потрібні "Списки контролю доступу" для взаємодії користувачів. Ви б просто не сказали щось подібне if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()(інакше відобразити "Ви не маєте доступу до профілю цього користувача" чи щось подібне? Я цього не
зрозумію

2
Можливо, тому, що Kirzilla хоче керувати всіма умовами доступу в одному місці - переважно в конфігурації. Отже, будь-яку зміну дозволів можна вносити в Admin замість зміни коду.
Маріо

Відповіді:


185

Перша частина / відповідь (впровадження ACL)

На мою скромну думку, найкращим способом підійти до цього буде використання декоративного малюнка. В основному це означає, що ви берете свій предмет і розміщуєте його всередині іншого предмета, який буде діяти як захисна оболонка. Це НЕ вимагає від вас продовження початкового класу. Ось приклад:

class SecureContainer
{

    protected $target = null;
    protected $acl = null;

    public function __construct( $target, $acl )
    {
        $this->target = $target;
        $this->acl = $acl;
    }

    public function __call( $method, $arguments )
    {
        if ( 
             method_exists( $this->target, $method )
          && $this->acl->isAllowed( get_class($this->target), $method )
        ){
            return call_user_func_array( 
                array( $this->target, $method ),
                $arguments
            );
        }
    }

}

І ось як ви використовуєте такий тип структури:

// assuming that you have two objects already: $currentUser and $controller
$acl = new AccessControlList( $currentUser );

$controller = new SecureContainer( $controller, $acl );
// you can execute all the methods you had in previous controller 
// only now they will be checked against ACL
$controller->actionIndex();

Як ви могли помітити, це рішення має ряд переваг:

  1. стримування може використовуватися на будь-якому об'єкті, а не лише на примірники Controller
  2. перевірка авторизації відбувається поза цільовим об'єктом, що означає:
    • оригінальний об'єкт не несе відповідальності за контроль доступу, дотримується SRP
    • коли ви отримаєте "дозвіл відмовлено", ви не заблоковані всередині контролера, більше варіантів
  3. Ви можете ввести цей захищений екземпляр у будь-який інший об'єкт, він збереже захист
  4. оберніть його і забудьте .. Ви можете зробити вигляд, що це оригінальний об'єкт, він буде реагувати так само

Але у цього методу також є одна основна проблема - ви не можете власноруч перевірити, чи реалізовані захищені об’єкти та інтерфейс (що також застосовується для пошуку існуючих методів) чи є частиною якогось ланцюжка успадкування.

Друга частина / відповідь (RBAC для об'єктів)

У цьому випадку основною відмінністю, яку ви повинні визнати, є те, що ви, об’єкти домену (наприклад Profile:), містять деталі про власника. Це означає, що для того, щоб ви перевірили, чи має користувач (і на якому рівні) доступ до нього, вам потрібно буде змінити цей рядок:

$this->acl->isAllowed( get_class($this->target), $method )

По суті у вас є два варіанти:

  • Забезпечте ACL відповідним об'єктом. Але потрібно бути обережним, щоб не порушити закон Деметри :

    $this->acl->isAllowed( get_class($this->target), $method )
  • Запросіть усі відповідні деталі та надайте ACL лише те, що йому потрібно, що також зробить його дещо зручнішим для модульного тестування:

    $command = array( get_class($this->target), $method );
    /* -- snip -- */
    $this->acl->isAllowed( $this->target->getPermissions(), $command )

Кілька відео, які можуть допомогти вам придумати власну реалізацію:

Бічні нотатки

Ви, здається, маєте досить поширене (і зовсім неправильне) розуміння того, що таке Модель у MVC. Модель - це не клас . Якщо у вас є клас з іменем FooBarModelабо щось, що успадковує, AbstractModelто ви робите це неправильно.

У правильному MVC Модель являє собою шар, який містить безліч класів. Значну частину класів можна розділити на дві групи, виходячи з відповідальності:

- Логіка ділового бізнесу

( читайте більше : тут і тут ):

Приклади з цієї групи класів займаються обчисленням значень, перевіряють різні умови, впроваджують правила продажу та роблять все інше, що ви б назвали "бізнес-логіка". Вони не мають уявлення про те, як зберігаються дані, де вони зберігаються або навіть якщо зберігання існує в першу чергу.

Об'єкт доменного бізнесу не залежить від бази даних. Коли ви створюєте рахунок-фактуру, не має значення, звідки надходять дані. Це може бути як SQL, так і віддалений REST API, або навіть скріншот документа MSWord. Логіка бізнесу не змінюється.

- Доступ та зберігання даних

Екземпляри, створені з цієї групи класів, іноді називають об'єктами доступу до даних. Зазвичай структури, що реалізують шаблон відображення даних (не плутайте з ORM з однойменною назвою .. ніякого відношення). Тут будуть ваші оператори SQL (або, можливо, ваш DomDocument, оскільки ви зберігаєте їх у XML).

Окрім двох основних частин, є ще одна група екземплярів / класів, про які слід згадати:

- Послуги

Тут входять у гру ваш та сторонні компоненти. Наприклад, ви можете думати про "автентифікацію" як про послугу, яка може надаватися вашим власним або якимсь зовнішнім кодом. Також "відправник пошти" буде послугою, яка може поєднати якийсь об'єкт домену з PHPMailer або SwiftMailer або вашим власним компонентом відправника пошти.

Іншим джерелом послуг є абстракція на рівні домену та доступу до даних. Вони створені для спрощення коду, що використовується контролерами. Наприклад: для створення нового облікового запису користувача може знадобитися робота з кількома об’єктами домену та картографами . Але, користуючись послугою, йому знадобиться лише один або два рядки в контролері.

Під час надання послуг потрібно пам’ятати, що весь шар повинен бути тонким . У послугах немає бізнес-логіки. Вони лише там, щоб жонглювати об’єктом домену, компонентами та картографами.

Одне з усіх спільних рис полягає в тому, що послуги не впливають на рівень View якимось прямим чином і є автономними настільки, що їх можна (і часто виходити - використовувати) поза самою структурою MVC. Також такі самозабезпечені структури значно полегшують перехід на інший фреймворк / архітектуру через надзвичайно низьку зв'язок між сервісом та рештою програми.


34
Щойно за 5 хвилин перечитання я дізнався більше, ніж за місяці. Чи погоджуєтесь ви з: тонкі контролери відправляються до служб, які збирають дані перегляду? Крім того, якщо ви коли-небудь приймаєте запитання безпосередньо, надішліть мені повідомлення.
Стефан

2
Я частково згоден. Збір даних із зору відбувається поза MVC-тріадою, коли ви ініціалізуєте Requestекземпляр (або його аналог). Контролер лише витягує дані з Requestекземпляра та передає більшість із них належним службам (частина з них також переглядається). Служби виконують операції, які ви їм наказали. Потім, коли подання генерує відповідь, воно запитує дані у служб і на основі цієї інформації генерує відповідь. Зазначена відповідь може бути як HTML, виготовленим із декількох шаблонів, так і просто заголовком розташування HTTP. Залежить від стану, встановленого контролером.
tereško

4
Для використання спрощеного пояснення: контролер "пише" в модель і переглядає, перегляд "читає" з моделі. Модельний рівень - це пасивна структура у всіх шаблонах, пов'язаних з Інтернетом, натхненних MVC.
tereško

@Stephane, що стосується прямого запитання, ти завжди можеш надіслати мені повідомлення в твіттері. Або ви ставили питання про якусь "довгу форму", яку не можна набити 140 символами?
tereško

Читання з моделі: чи це означає якусь активну роль для моделі? Я ніколи цього не чув. Я завжди можу надіслати вам посилання через твіттер, якщо це ваша перевага. Як бачите, ці відповіді швидко перетворюються на розмови, і я намагався з повагою ставитись до цього сайту та ваших послідовників у Twitter.
Стефан

16

ACL та контролери

Перш за все: це найчастіше різні речі / шари. Коли ви критикуєте зразковий код контролера, він поєднує і те, і інше - очевидно, занадто жорстке.

tereško вже окреслив спосіб, як можна більше роз’єднати це за допомогою декоративного малюнка.

Я б спершу сходив крок назад, щоб шукати оригінальну проблему, з якою ви стикаєтесь, і трохи потім обговорити її.

З одного боку, ви хочете мати контролери, які просто виконують роботу, яку їм наказано (команда або дія, назвемо це командою).

З іншого боку, ви хочете мати змогу помістити ACL у вашу заявку. Сфера роботи цих ACL повинна бути - якщо я правильно зрозумів ваше запитання - керувати доступом до певних команд ваших програм.

Отже, такий вид контролю доступу потребує чогось іншого, що об’єднує цих двох. Виходячи з контексту, в якому виконується команда, починається ACL і потрібно робити рішення про те, чи може конкретна команда виконуватись конкретним суб'єктом (наприклад, користувачем).

Давайте підсумуємо до цього моменту те, що ми маємо:

  • Командування
  • ACL
  • Користувач

Компонент ACL тут є центральним: він повинен знати хоча б щось про команду (щоб ідентифікація команди була точною), і вона повинна мати можливість ідентифікувати користувача. Користувачів, як правило, легко ідентифікувати за допомогою унікального ідентифікатора. Але часто у веб-додатках є користувачі, які взагалі не ідентифікуються, їх часто називають гостем, анонімними, усіма тощо. Для цього прикладу ми припускаємо, що ACL може споживати об’єкт користувача та інкапсулювати ці дані. Об'єкт користувача прив'язаний до об'єкта запиту програми, і ACL може споживати його.

Що з ідентифікацією команди? Ваша інтерпретація шаблону MVC передбачає, що команда складається з імені класу та імені методу. Якщо ми уважніше розглянемо, для команди існують навіть аргументи (параметри). Тож правильно запитати, що саме ідентифікує команду? Ім'я класу, ім'я методу, кількість або імена аргументів, навіть дані всередині будь-якого з аргументів або суміш всього цього?

Залежно від того, на якому рівні деталізації вам потрібно визначити команду у вашому ACL'ing, це може сильно відрізнятися. Для прикладу давайте збережемо його просто і вкажемо, що команда ідентифікується за назвою класу та методом.

Тож контекст того, як ці три частини (ACL, Command та User) належать одна одній, тепер є більш зрозумілим.

Можна сказати, що з уявним вмістом ACL ми вже можемо зробити наступне:

$acl->commandAllowedForUser($command, $user);

Просто подивіться, що тут відбувається: зробивши і команду, і користувача ідентифікованими, ACL може зробити це ефективно. Робота ACL не пов'язана з роботою як об'єкта користувача, так і конкретної команди.

Не вистачає лише однієї частини, вона не може жити в повітрі. І це не так. Отже, вам потрібно знайти місце, де повинен входити контроль доступу. Давайте подивимося, що відбувається у стандартному веб-застосуванні:

User -> Browser -> Request (HTTP)
   -> Request (Command) -> Action (Command) -> Response (Command) 
   -> Response(HTTP) -> Browser -> User

Щоб знайти це місце, ми знаємо, що це повинно бути перед виконанням конкретної команди, тому ми можемо зменшити цей список і потрібно лише розглянути наступні (потенційні) місця:

User -> Browser -> Request (HTTP)
   -> Request (Command)

У певний момент у вашій програмі ви знаєте, що конкретний користувач просив виконати конкретну команду. Ви вже робите тут певний ACL: якщо користувач запитує команду, яка не існує, ви не дозволяєте виконувати цю команду. Тож де б це не траплялося у вашій програмі, можливо, було б гарним місцем для додавання "справжніх" перевірок ACL:

Команда була знайдена, і ми можемо створити її ідентифікацію, щоб ACL впорався з нею. Якщо команда не дозволена користувачеві, команда не буде виконана (дія). Можливо, CommandNotAllowedResponseзамість CommandNotFoundResponseзапиту для справи запит не може бути вирішений за допомогою конкретної команди.

Місце, де відображення конкретного HTTPRequest відображається на команді, часто називається маршрутизацією . Оскільки маршрутизація вже має завдання знайти команду, чому б не розширити її, щоб перевірити, чи дійсно команда дозволена для ACL? Наприклад , за допомогою розширення Router до маршрутизатора обізнані ACL: RouterACL. Якщо ваш маршрутизатор ще не знає User, то Routerце не те місце, тому що для роботи ACL'а повинна бути визначена не тільки команда, але й користувач. Отже, це місце може відрізнятися, але я впевнений, що ви можете легко знайти місце, яке вам потрібно розширити, оскільки саме це місце заповнює вимоги користувача та команди:

User -> Browser -> Request (HTTP)
   -> Request (Command)

Користувач доступний з самого початку, Команда спочатку з Request(Command).

Отже, замість того, щоб поміщати перевірки ACL у конкретну реалізацію кожної команди, ви розміщуєте її перед нею. Вам не потрібні важкі шаблони, магія чи що завгодно, ACL робить свою роботу, користувач виконує свою роботу, і особливо команда робить свою роботу: Просто команда, нічого іншого. Команда не зацікавлена ​​знати, застосовуються до неї ролі чи ні, якщо вона десь охороняється чи ні.

Тож просто тримайте речі, які не належать одне одному. Використовуйте трохи переформулювання принципу єдиної відповідальності (SRP) : Для зміни команди має бути лише одна причина - тому що команда змінилася. Не тому, що тепер ви вводите ACL'ing у свою заявку. Не тому, що ви перемикаєте об'єкт User. Не тому, що ви переходите з інтерфейсу HTTP / HTML на SOAP або інтерфейс командного рядка.

ACL у вашому випадку контролює доступ до команди, а не саму команду.


Два запитання: CommandNotFoundResponse & CommandNotAllowedResponse: чи могли б ви передати їх із класу ACL на маршрутизатор або контролер і очікувати загальної відповіді? 2: Якби ви хотіли включити метод + атрибути, як би ви це впоралися?
Стефан

1: Відповідь - це відповідь, тут не від ACL, а від маршрутизатора, ACL допомагає маршрутизатору з’ясувати тип відповіді (не знайдено, особливо: заборонено). 2: Залежить. Якщо ви маєте на увазі атрибути як параметри від дій, і вам потрібно ACL'ing з параметрами, поставте їх під ACL.
hakre

13

Одна з можливостей - перенести всі ваші контролери в інший клас, який розширює Controller, і запропонувати йому делегувати всі виклики функції обгорнутому екземпляру після перевірки на авторизацію.

Ви також можете зробити це вище за течією, у диспетчері (якщо ваш додаток насправді його має) і шукати дозволи на основі URL-адрес, замість методів управління.

редагувати : чи потрібен вам доступ до бази даних, сервера LDAP тощо, є ортогональним питанню. Моя думка полягала в тому, що ви можете реалізувати авторизацію на основі URL-адрес, а не методів контролера. Вони надійніші, оскільки ти зазвичай не змінюєш свої URL-адреси (тип загальнодоступного інтерфейсу URL-адрес), але ти можеш також змінити реалізацію своїх контролерів.

Як правило, у вас є один або кілька файлів конфігурації, де ви позначаєте конкретні шаблони URL-адрес на конкретні методи аутентифікації та директиви авторизації. Диспетчер, перш ніж відправляти запит до контролерів, визначає, чи є користувач авторизованим, і припиняє диспетчеризацію, якщо він цього не робить.


Будь ласка, можете оновити свою відповідь та додати більше інформації про диспетчера. У мене диспетчер - він виявляє, який метод контролера я повинен викликати за URL-адресою. Але я не можу зрозуміти, як я можу отримати роль (мені потрібно отримати доступ до БД, щоб це зробити) у Dispatcher. Сподіваємось почути вас найближчим часом.
Кірзілла

Ага, зрозуміла ваша ідея. Я повинен вирішити, дозволити виконання чи ні без доступу до методу! Пальці вгору! Останнє невирішене питання - як отримати доступ до моделі від Acl. Якісь ідеї?
Кірзілла

@Kirzilla У мене такі ж проблеми з контролерами. Здається, залежності повинні бути десь там. Навіть якщо ACL не є, як щодо рівня моделі? Як ви можете запобігти тому, щоб це не було залежністю?
Стефан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.