Первый вопрос
Объясните, пожалуйста, как в MVC можно реализовать самый простой ACL.
Вот первый подход к использованию Acl в контроллере ...
<?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 не имеет доступа к модели. Как мы можем это реализовать?
Надеюсь, что мои мысли ясны. Извините за мой английский.
Спасибо.
источник
if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()
(иначе, отобразите «У вас нет доступа к профилю этого пользователя» или что-то в этом роде? Я не понимаю.Ответы:
Первая часть / ответ (реализация ACL)
По моему скромному мнению, лучший способ приблизиться к этому - использовать шаблон декоратора. По сути, это означает, что вы берете свой объект и помещаете его внутри другого объекта, который будет действовать как защитная оболочка. Это НЕ потребует от вас расширения исходного класса. Вот пример:
И вот как вы используете такую структуру:
Как вы могли заметить, это решение имеет несколько преимуществ:
Controller
Но с этим методом также есть одна серьезная проблема - вы не можете изначально проверить, реализует ли защищенный объект и интерфейс (что также применимо для поиска существующих методов) или является частью некоторой цепочки наследования.
Вторая часть / ответ (RBAC для объектов)
В этом случае главное отличие, которое вы должны осознавать, заключается в том, что ваши объекты домена (в примере:)
Profile
сами содержат информацию о владельце. Это означает, что для проверки того, имеет ли (и на каком уровне) пользователь к нему доступ, вам потребуется изменить эту строку:По сути, у вас есть два варианта:
Предоставьте ACL для рассматриваемого объекта. Но вы должны быть осторожны, чтобы не нарушить Закон Деметры :
Запросите все необходимые сведения и предоставьте ACL только с тем, что ему нужно, что также сделает его немного более удобным для модульного тестирования:
Пара видеороликов, которые могут помочь вам придумать собственную реализацию:
Боковые примечания
Кажется, у вас довольно распространенное (и совершенно неправильное) понимание того, что такое модель в MVC. Модель - это не класс . Если у вас есть класс с именем
FooBarModel
или что-то, что наследует,AbstractModel
значит, вы делаете это неправильно.В собственном MVC Модель - это слой, содержащий множество классов. Большую часть занятий можно разделить на две группы, исходя из ответственности:
- Бизнес-логика домена
( подробнее : здесь и здесь ):
Экземпляры из этой группы классов занимаются вычислением значений, проверкой различных условий, реализацией правил продаж и всем остальным, что вы бы назвали «бизнес-логикой». Они понятия не имеют, как хранятся данные, где они хранятся и даже существует ли хранилище.
Домен Бизнес-объект не зависит от базы данных. Когда вы создаете счет, не имеет значения, откуда поступают данные. Это может быть как из SQL, так и из удаленного REST API, или даже скриншот документа MSWord. Бизнес-логика не меняется.
- Доступ к данным и хранение
Экземпляры, созданные из этой группы классов, иногда называют объектами доступа к данным. Обычно структуры, реализующие шаблон Data Mapper (не путайте с ORM с таким же именем .. никакого отношения). Здесь будут находиться ваши операторы SQL (или, может быть, ваш DomDocument, потому что вы храните его в XML).
Помимо двух основных частей, следует упомянуть еще одну группу экземпляров / классов:
- Услуги
Здесь в игру вступают ваши и сторонние компоненты. Например, вы можете думать об «аутентификации» как об услуге, которая может быть предоставлена вашим собственным или каким-то внешним кодом. Также "отправитель почты" может быть службой, которая может связывать воедино некоторый объект домена с PHPMailer или SwiftMailer или вашим собственным компонентом отправителя почты.
Другой источник услуг - это абстракция на уровне домена и доступа к данным. Они созданы для упрощения кода, используемого контроллерами. Например: создание новой учетной записи пользователя может потребовать работы с несколькими объектами домена и мапперами . Но при использовании службы ему потребуется всего одна или две строки в контроллере.
При создании сервисов вы должны помнить, что весь слой должен быть тонким . В сервисах нет бизнес-логики. Они существуют только для того, чтобы манипулировать объектом домена, компонентами и мапперами.
Одна из их общих черт заключается в том, что службы не влияют напрямую на уровень представления и являются настолько автономными, что могут использоваться (и часто прекращают работу) вне самой структуры MVC. Кроме того, такие самодостаточные структуры значительно упрощают переход на другую структуру / архитектуру из-за чрезвычайно низкой связи между сервисом и остальным приложением.
источник
Request
экземпляр (или какой-то его аналог). Контроллер только извлекает данные изRequest
экземпляра и передает большую их часть соответствующим службам (некоторые из них тоже отправляются на просмотр). Службы выполняют операции, которые вы им приказывали. Затем, когда представление формирует ответ, оно запрашивает данные у служб и на основе этой информации генерирует ответ. Указанный ответ может быть либо HTML, созданным из нескольких шаблонов, либо просто заголовком местоположения HTTP. Зависит от состояния, установленного контроллером.ACL и контроллеры
Прежде всего: это чаще всего разные вещи / слои. Когда вы критикуете примерный код контроллера, он объединяет и то, и другое - очевидно, слишком тесно.
Терешко уже наметил способ, как вы можете отделить это больше с помощью шаблона декоратора.
Я бы сначала отступил на шаг назад, чтобы найти исходную проблему, с которой вы столкнулись, и затем немного ее обсудить.
С одной стороны, вы хотите иметь контроллеры, которые просто выполняют ту работу, которую им приказывают (команда или действие, назовем это командой).
С другой стороны, вы хотите иметь возможность добавлять ACL в свое приложение. Область работы этих ACL должна заключаться - если я правильно понял ваш вопрос - для управления доступом к определенным командам ваших приложений.
Таким образом, для такого контроля доступа необходимо что-то еще, что объединяет эти два аспекта. В зависимости от контекста, в котором выполняется команда, срабатывает ACL, и необходимо принимать решения, может ли конкретная команда быть выполнена конкретным субъектом (например, пользователем).
Подведем итог тому, что у нас есть:
Компонент ACL здесь является центральным: он должен знать хотя бы что-то о команде (чтобы идентифицировать команду, чтобы быть точным), и он должен иметь возможность идентифицировать пользователя. Обычно пользователей легко идентифицировать по уникальному идентификатору. Но часто в веб-приложениях есть пользователи, которые вообще не идентифицируются, их часто называют гостями, анонимами, всеми и т. Д. В этом примере мы предполагаем, что ACL может использовать объект пользователя и инкапсулировать эти детали. Пользовательский объект привязан к объекту запроса приложения, и ACL может использовать его.
А как насчет определения команды? Ваша интерпретация шаблона MVC предполагает, что команда состоит из имени класса и имени метода. Если присмотреться, для команды есть даже аргументы (параметры). Итак, уместно спросить, что именно определяет команду? Имя класса, имя метода, количество или имена аргументов, даже данные внутри любого из аргументов или смесь всего этого?
В зависимости от того, какой уровень детализации вам нужен для идентификации команды в вашем ACL, это может сильно различаться. Для примера давайте оставим его просто и укажем, что команда идентифицируется по имени класса и имени метода.
Таким образом, контекст того, как эти три части (ACL, команда и пользователь) принадлежат друг другу, теперь более ясен.
Можно сказать, что с воображаемым компонентом ACL мы уже можем делать следующее:
Просто посмотрите, что здесь происходит: сделав и команду, и пользователя идентифицируемыми, ACL может выполнять свою работу. Работа ACL не связана с работой как пользовательского объекта, так и конкретной команды.
Не хватает только одной части, она не может жить в воздухе. И это не так. Итак, вам нужно найти место, где должен сработать контроль доступа. Давайте посмотрим, что происходит в стандартном веб-приложении:
Чтобы найти это место, мы знаем, что оно должно быть до того, как будет выполнена конкретная команда, поэтому мы можем сократить этот список, и нам нужно только изучить следующие (потенциальные) места:
В какой-то момент в вашем приложении вы знаете, что конкретный пользователь запросил выполнение конкретной команды. Здесь вы уже выполняете своего рода ACL: если пользователь запрашивает команду, которой не существует, вы не разрешаете выполнение этой команды. Поэтому, где бы это ни происходило в вашем приложении, может быть хорошим местом для добавления «настоящих» проверок ACL:
Команда найдена, и мы можем создать ее идентификацию, чтобы ACL мог с ней справиться. В случае, если команда не разрешена для пользователя, команда не будет выполнена (действие). Может быть,
CommandNotAllowedResponse
вместо этогоCommandNotFoundResponse
запрос не может быть преобразован в конкретную команду.Место, где отображение конкретного HTTPRequest отображается на команду, часто называется маршрутизацией . Поскольку у Routing уже есть задание по поиску команды, почему бы не расширить его, чтобы проверить, действительно ли команда разрешена для ACL? Например , посредством расширения
Router
к маршрутизатору осведомлены ACL:RouterACL
. Если ваш маршрутизатор еще не знаетUser
, тоRouter
это не подходящее место, потому что для работы ACL необходимо идентифицировать не только команду, но и пользователя. Таким образом, это место может быть разным, но я уверен, что вы можете легко найти место, которое нужно расширить, потому что это место, которое выполняет требования пользователя и команды:Пользователь доступен с самого начала, сначала команда с
Request(Command)
.Поэтому вместо того, чтобы помещать проверки ACL в конкретную реализацию каждой команды, вы помещаете ее перед ней. Вам не нужны какие-либо сложные шаблоны, магия или что-то еще, ACL выполняет свою работу, пользователь выполняет свою работу, и особенно команда выполняет свою работу: просто команда, ничего больше. Команде неинтересно знать, применимы ли к нему роли, охраняется он где-то или нет.
Так что просто держите отдельно вещи, которые не принадлежат друг другу. Используйте небольшую переформулировку принципа единой ответственности (SRP) : должна быть только одна причина для изменения команды - потому что команда изменилась. Не потому, что вы сейчас вводите ACL в свое приложение. Не потому, что вы переключаете объект User. Не потому, что вы переходите с интерфейса HTTP / HTML на интерфейс SOAP или командной строки.
ACL в вашем случае управляет доступом к команде, а не самой командой.
источник
Одна из возможностей - обернуть все ваши контроллеры в другой класс, который расширяет Controller, и передать ему все вызовы функций обернутому экземпляру после проверки авторизации.
Вы также можете сделать это в диспетчере (если в вашем приложении он действительно есть) и искать разрешения на основе URL-адресов, а не методов управления.
изменить : нужен ли вам доступ к базе данных, серверу LDAP и т. д., это ортогонально вопросу. Я хотел сказать, что вы можете реализовать авторизацию на основе URL-адресов, а не методов контроллера. Это более надежно, потому что вы обычно не будете изменять свои URL-адреса (вид общедоступного интерфейса в области URL-адресов), но вы также можете изменить реализации своих контроллеров.
Как правило, у вас есть один или несколько файлов конфигурации, в которых вы сопоставляете определенные шаблоны URL-адресов с конкретными методами аутентификации и директивами авторизации. Диспетчер перед отправкой запроса на контроллеры определяет, авторизован ли пользователь, и прерывает отправку, если это не так.
источник