Следующее, что мы обсудим, это набор функций для создания контейнера Rbac
, целью которого
является загрузка иерархии ролей из базы данных и кэширование данных в кэше файловой системы.
Кэш позволяет хранить часто используемые данные в быстродействующей памяти. Например, извлечение ролей и привилегий из базы данных может быть довольно медленным, в то время как хранение заранее вычисленной иерархии ролей может быть быстрее.
Для начала давайте настроим кэширование. Для этого вам нужно установить компоненты Zend\Cache и
Zend\Serializer` с
помощью следующих команд:
php composer.phar require zendframework/zend-cache
php composer.phar require zendframework/zend-serializer
Затем измените файл config/autoload/global.php, добавив следующие строки:
use Zend\Cache\Storage\Adapter\Filesystem;
return [
//...
// Настройка кэша.
'caches' => [
'FilesystemCache' => [
'adapter' => [
'name' => Filesystem::class,
'options' => [
// Store cached data in this directory.
'cache_dir' => './data/cache',
// Store cached data for 1 hour.
'ttl' => 60*60*1
],
],
'plugins' => [
[
'name' => 'serializer',
'options' => [
],
],
],
],
],
//...
];
Это позволит вам использовать кэш Filesystem
и хранить кэшированные
данные в каталоге APP_DIR/data/cache.
Если вы хотите больше узнать о кэшировании, пожалуйста, обратитесь к документации компонента ZF3
Zend\Cache
.
Функциональным назначением сервиса RbacManager
будет создание контейнера Rbac
и загрузка
ролей и привилегий из базы данных. Если нужная информация уже была сохранена в кэш, сервис
загрузит ее оттуда, а не из БД.
Другими задачами сервиса RbacManager
будут использование менеджера утверждений, написанного нами ранее,
и проверка на динамические утверждения.
Класс RbacManager
будет иметь два метода:
init()
будет использоваться для загрузки иерархии ролей из базы данных и ее сохранения в кэш;isGranted()
будет использоваться для запроса у контейнера Rbac
информации о том, есть ли
у заданного пользователя заданная привилегия (и проверки менеджера(-ов) утверждений на динамические утверждения).Класс RbacManager
будет считывать конфигурацию и искать ключ rbac_manager
.
Этот ключ должен содержать подключ assertions
, в котором вы можете зарегистрировать все имеющиеся у вас менеджеры утверждений.
return [
//...
// Этот ключ хранит конфигурацию для менеджера RBAC.
'rbac_manager' => [
'assertions' => [Service\RbacAssertionManager::class],
],
];
Код класса RbacManager
, "живущего" в пространстве имен User\Service
, представлен ниже.
<?php
namespace User\Service;
use Zend\Permissions\Rbac\Rbac;
use Zend\Permissions\Rbac\Role as RbacRole;
use User\Entity\User;
use User\Entity\Role;
use User\Entity\Permission;
/**
* Этот сервис отвечает за инициализацию RBAC (Role-Based Access Control – контроль доступа на основе ролей).
*/
class RbacManager
{
/**
* Менеджер сущностей Doctrine.
* @var Doctrine\ORM\EntityManager
*/
private $entityManager;
/**
* Сервис RBAC.
* @var Zend\Permissions\Rbac\Rbac
*/
private $rbac;
/**
* Сервис аутентификации.
* @var Zend\Authentication\AuthenticationService
*/
private $authService;
/**
* Кэш файловой системы.
* @var Zend\Cache\Storage\StorageInterface
*/
private $cache;
/**
* Менеджеры утверждений.
* @var array
*/
private $assertionManagers = [];
/**
* Конструирует сервис.
*/
public function __construct($entityManager, $authService, $cache, $assertionManagers)
{
$this->entityManager = $entityManager;
$this->authService = $authService;
$this->cache = $cache;
$this->assertionManagers = $assertionManagers;
}
/**
* Инициализирует контейнер RBAC.
*/
public function init($forceCreate = false)
{
if ($this->rbac!=null && !$forceCreate) {
// Уже инициализирован; ничего не делаем.
return;
}
// Если пользователь хочет, чтобы мы заново инициализировали контейнер RBAC, очищаем кэш.
if ($forceCreate) {
$this->cache->removeItem('rbac_container');
}
// Пробуем загрузить контейнер Rbac из кэша.
$this->rbac = $this->cache->getItem('rbac_container', $result);
if (!$result)
{
// Создаем контейнер Rbac.
$rbac = new Rbac();
$this->rbac = $rbac;
// Конструируем иерархию ролей, загружая роли и привилегии из базы данных.
$rbac->setCreateMissingRoles(true);
$roles = $this->entityManager->getRepository(Role::class)
->findBy([], ['id'=>'ASC']);
foreach ($roles as $role) {
$roleName = $role->getName();
$parentRoleNames = [];
foreach ($role->getParentRoles() as $parentRole) {
$parentRoleNames[] = $parentRole->getName();
}
$rbac->addRole($roleName, $parentRoleNames);
foreach ($role->getPermissions() as $permission) {
$rbac->getRole($roleName)->addPermission($permission->getName());
}
}
// Сохраняем контейнер Rbac в кэш.
$this->cache->setItem('rbac_container', $rbac);
}
}
/**
* Проверяет, есть ли привилегия у данного пользователя.
* @param User|null $user
* @param string $permission
* @param array|null $params
*/
public function isGranted($user, $permission, $params = null)
{
if ($this->rbac==null) {
$this->init();
}
if ($user==null) {
$identity = $this->authService->getIdentity();
if ($identity==null) {
return false;
}
$user = $this->entityManager->getRepository(User::class)
->findOneByEmail($identity);
if ($user==null) {
// Упс... Эта личность есть в сессии, но в базе данных такого пользователя не существует.
// Мы генерируем исключение, так как, возможно, это является проблемой безопасности.
throw new \Exception('There is no user with such identity');
}
}
$roles = $user->getRoles();
foreach ($roles as $role) {
if ($this->rbac->isGranted($role->getName(), $permission)) {
if ($params==null)
return true;
foreach ($this->assertionManagers as $assertionManager) {
if ($assertionManager->assert($this->rbac, $permission, $params))
return true;
}
}
$parentRoles = $role->getParentRoles();
foreach ($parentRoles as $parentRole) {
if ($this->rbac->isGranted($parentRole->getName(), $permission)) {
return true;
}
}
}
return false;
}
}
Фабрика для класса RbacManager
будет выглядеть следующим образом:
<?php
namespace User\Service\Factory;
use Interop\Container\ContainerInterface;
use User\Service\RbacManager;
use Zend\Authentication\AuthenticationService;
/**
* Это фабричный класс для сервиса RbacManager. Целями данной фабрики являются
* инстанцирование сервиса и передача ему зависимостей (внедрение зависимостей).
*/
class RbacManagerFactory
{
/**
* Этот метод создает сервис RbacManager и возвращает его экземпляр.
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$entityManager = $container->get('doctrine.entitymanager.orm_default');
$authService = $container->get(\Zend\Authentication\AuthenticationService::class);
$cache = $container->get('FilesystemCache');
$assertionManagers = [];
$config = $container->get('Config');
if (isset($config['rbac_manager']['assertions'])) {
foreach ($config['rbac_manager']['assertions'] as $serviceName) {
$assertionManagers[$serviceName] = $container->get($serviceName);
}
}
return new RbacManager($entityManager, $authService, $cache, $assertionManagers);
}
}