Once again about Security in Symfony2 user-resource-privilege approach
Not so long ago took up Symfony2. Despite the fact that before that I had a fairly rich experience in communicating with Zend1, the barrier to entry was high for me. Having read enough, something began to turn out for me. The greatest difficulties were caused by the issue of differentiation of access rights. Almost all my searches brought me to the FOSUserBundle or snippets of information on how to extend the functionality of the Security module from the standard delivery of the framework. I did not find any advantages for myself in the bulky FOSUserBundle. Therefore, this article will be about how I finished Symfony2 Security to fit my needs. The goal was the following: symfony2 + security + differentiation of access rights at the object level depending on the role of the user. This article will not be about inheritance of roles and cumulative privileges, information about which you can easily find yourself. Rights scheme in my project: everything that is not allowed is prohibited. One user has strictly one role. A role has access to various resources with a different set of privileges. Different roles may have access to the same resources with different or equal sets of privileges. I will not try to make the code as abstract as possible, but I will simply use fragments from my project related to the functionality of order-orders for equipment maintenance.
So to the point. We have a correctly configured project , a BackendWorkorderBundle was created in it, all routers and firewalls are configured. Those. there is everything except access rights. Including Authentication. For database design, the MySQL Workbench tool was used . Great stuff. There is a version for Linux. The table structure looks like this:
There are two ways to check for privileges:
1. From twig
2. From the controller The
second argument is optional, but necessary for the purposes of my project (it will become clear a bit later in the voter code). I remind you that excluding an object from an html page does not cancel the data validation in the controller.
Voter code. I forgot to mention that there is another BackendCoreBundle bundle in the project that incorporates the most common functions for the entire Backend. Read more about voter'ah here .
The getPrivileges function for user is declared in the doctrine object associated with the backend_user table
Register voter in /app/config/security.yml
You probably noticed that $ object-> getResourceId () is called in the vote function. The method looks as follows
So to the point. We have a correctly configured project , a BackendWorkorderBundle was created in it, all routers and firewalls are configured. Those. there is everything except access rights. Including Authentication. For database design, the MySQL Workbench tool was used . Great stuff. There is a version for Linux. The table structure looks like this:
-- -----------------------------------------------------
-- 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;
There are two ways to check for privileges:
1. From twig
is_granted('[наименование привилегии]', [объект])2. From the controller The
$this->get('security.context')->isGranted('[наименование привилегии]', [объект])second argument is optional, but necessary for the purposes of my project (it will become clear a bit later in the voter code). I remind you that excluding an object from an html page does not cancel the data validation in the controller.
Voter code. I forgot to mention that there is another BackendCoreBundle bundle in the project that incorporates the most common functions for the entire Backend. Read more about voter'ah here .
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;
}
}
The getPrivileges function for user is declared in the doctrine object associated with the backend_user table
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;
}
..
}
Register voter in /app/config/security.yml
services:
security.access.privilege_voter:
class: Backend\CoreBundle\Security\Authorization\Voter\PrivilegeVoter
public: false
tags:
- { name: security.voter }
You probably noticed that $ object-> getResourceId () is called in the vote function. The method looks as follows
That's it! Критика, как обычно, привествуется, если кто-то может указать на недостатвки этого подхода и возможные проблемы при масштабировании — был бы очень рад.