Итак, к делу. У нас есть правильно настроенный проект, в нем создан BackendWorkorderBundle, настроены все роутеры и фаерволы. Т.е. есть все, за исключением прав доступа. Включая аутификацию. Для проектирование БД использовался инструмент MySQL Workbench. Отличная штука. Есть версия под Linux. Структура таблиц выглядит так:
-- -----------------------------------------------------
-- Table `backend_role`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `backend_role` (
`role_id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NULL,
`description` VARCHAR(45) NULL,
PRIMARY KEY (`role_id`))
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `backend_user`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `backend_user` (
`user_id` INT NOT NULL AUTO_INCREMENT,
`role_id` INT NOT NULL,
`firstname` VARCHAR(45) NULL,
`lastname` VARCHAR(45) NULL,
`printname` VARCHAR(45) NULL,
`username` VARCHAR(45) NULL,
`salt` VARCHAR(255) NULL,
`password` VARCHAR(255) NULL,
`created` DATETIME NULL,
`updated` DATETIME NULL,
`last_login` DATETIME NULL,
`is_active` TINYINT(1) NULL,
PRIMARY KEY (`user_id`),
INDEX `fk_backend_user_backend_role1_idx` (`role_id` ASC),
CONSTRAINT `fk_backend_user_backend_role1`
FOREIGN KEY (`role_id`)
REFERENCES `parts`.`backend_role` (`role_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `backend_rule`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `backend_rule` (
`rule_id` INT NOT NULL AUTO_INCREMENT,
`role_id` INT NOT NULL,
`resource_id` VARCHAR(255) NULL,
`privileges` TEXT NULL,
PRIMARY KEY (`rule_id`),
INDEX `fk_backend_rule_backend_role1_idx` (`role_id` ASC),
CONSTRAINT `fk_backend_rule_backend_role1`
FOREIGN KEY (`role_id`)
REFERENCES `parts`.`backend_role` (`role_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Проверять наличие привилегий можно двумя способами:
1. Из twig
is_granted('[наименование привилегии]', [объект])
2. Из контроллера
$this->get('security.context')->isGranted('[наименование привилегии]', [объект])
Второй аргумент не обязателен, но необходим для целей моего проекта (станет понятно чуть ниже в коде voter'а). Напоминаю, что исключение объекта из html страницы не отменяет проверку данных в контроллере.
Код voter'a. Забыл упомянуть, что в проекте есть есть еще один бандл BackendCoreBundle, которые вбирает в себя наиболее общие функции для всего Backend'a. Подробнее о voter'ах можно почитать здесь.
<?php
// /src/Backend/CoreBundle/Security/Authorization/Voter/PrivilegeVoter.php
namespace Backend\CoreBundle\Security\Authorization\Voter;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class PrivilegeVoter implements VoterInterface
{
public function supportsAttribute($attribute)
{
return true;
}
public function supportsClass($class)
{
return in_array($class, array(
'Backend\WorkorderBundle\Entity\Workorder'
));
}
public function vote(TokenInterface $token, $object, array $attributes)
{
//применим ли voter к объекту определенного класса.
//необходимо так как наш вотер будет опрашиваться во всех случаях контроля привилегий.
if ( !($this->supportsClass(get_class($object))) ) {
return VoterInterface::ACCESS_ABSTAIN;
}
foreach ($attributes as $attribute) { //необходимо адаптировать функцию под ваши нужды
if ( !$this->supportsAttribute($attribute) ) {
return VoterInterface::ACCESS_ABSTAIN;
}
}
//магия творится здесь
$user = $token->getUser();
$privileges = $user->getPrivileges();
$resourceId = $object->getResourceId();
$acess_granted = false;
foreach ($attributes as $attribute) {
if (isset($privileges[$resourceId])) {
$resource_privileges = $privileges[$resourceId];
if (in_array($attribute, $resource_privileges)) {
$acess_granted = true;
} else {
$acess_granted = false;
break;
}
}
}
if ($acess_granted)
return VoterInterface::ACCESS_GRANTED;
return VoterInterface::ACCESS_DENIED;
}
}
Фунция getPrivileges для user объявлена в объекте doctrine, связанном с таблицей backend_user
<?php
///src/Backend/CoreBundle/Entity/BackendUser.php
namespace Backend\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
/**
* BackendUser
*
* @ORM\Table(name="backend_user")
* @ORM\Entity
*/
class BackendUser implements AdvancedUserInterface, \Serializable
{
..
public function getPrivileges()
{
//цепочка выглядит так: backend_user->backend_role->backend_rule
//функция $rule->getPrivileges() возвращает значение поля privileges таблицы backend_rule
//то есть текущая функция возвращает массив ключами которого являеются resource_id,
//а элементами массивы привилений для доступа к этому ресурсу (хранятся через запятую)
$rules = $this->getRole()->getRules();
$result = array();
foreach ($rules as $rule){
$result[$rule->getResourceId()] = explode(",", $rule->getPrivileges());
}
return $result;
}
..
}
Регистрируем voter в /app/config/security.yml
services:
security.access.privilege_voter:
class: Backend\CoreBundle\Security\Authorization\Voter\PrivilegeVoter
public: false
tags:
- { name: security.voter }
Вы, наверное, обратили внимание, что в функции vote вызывается $object->getResourceId(). Выглядит метод следующим образом
<?php
// /src/Backend/WorkorderBundle/Entity/Workorder.php
namespace Backend\WorkorderBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Workorder
*
* @ORM\Table(name="workorder")
* @ORM\Entity
*/
class Workorder
{
..
public function getResourceId()
{
//функция добавлена для гибкости и в текущий момент возвращает имя класса
//В данном случае Backend\WorkorderBundle\Entity\Workorder
return get_class($this);
}
..
}
That's it! Критика, как обычно, привествуется, если кто-то может указать на недостатвки этого подхода и возможные проблемы при масштабировании — был бы очень рад.
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.
Комментариев нет:
Отправить комментарий