Итак, к делу. У нас есть правильно настроенный проект, в нем создан 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
<?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! Критика, как обычно, привествуется, если кто-то может указать на недостатвки этого подхода и возможные проблемы при масштабировании — был бы очень рад.
ссылка на оригинал статьи http://habrahabr.ru/post/203358/
Добавить комментарий