├── Tests ├── Fixtures │ ├── no_name.yaml │ ├── full_definition.yaml │ └── content.yaml ├── TestRole.php ├── TestUser.php ├── Form │ └── Type │ │ ├── ValidatorExtensionTypeTestCase.php │ │ ├── RoleFormTypeTest.php │ │ └── TypeTestCase.php ├── Util │ └── PermissionsHandlerTest.php ├── BundleTest.php ├── Functional │ └── DependencyInjectionTest.php ├── Model │ ├── RoleTest.php │ └── UserTest.php ├── Doctrine │ └── RoleManagerTest.php ├── DependencyInjection │ └── EloyekunlePermissionsExtensionTest.php └── Security │ └── PermissionVoterTest.php ├── .gitignore ├── Resources └── config │ ├── serializer │ └── Model.User.yml │ ├── permissions.xml │ ├── validator │ └── validation.xml │ ├── role.xml │ ├── doctrine.xml │ ├── doctrine_role.xml │ ├── security.xml │ ├── doctrine-mapping │ ├── Role.orm.xml │ └── User.orm.xml │ ├── storage-validation │ └── orm.xml │ └── schema │ └── doctrine-mapping.xsd ├── phpunit ├── Model ├── RoleManager.php ├── RoleManagerInterface.php ├── RoleInterface.php ├── UserInterface.php ├── User.php └── Role.php ├── phpunit.xml.dist ├── Util ├── PermissionsHandlerInterface.php └── PermissionsHandler.php ├── .php_cs.dist ├── .travis.yml ├── LICENSE ├── DependencyInjection ├── Compiler │ └── ValidationPass.php ├── Configuration.php └── EloyekunlePermissionsExtension.php ├── composer.json ├── Form └── Type │ └── RoleFormType.php ├── EloyekunlePermissionsBundle.php ├── Security └── PermissionsVoter.php ├── Doctrine └── RoleManager.php └── README.md /Tests/Fixtures/no_name.yaml: -------------------------------------------------------------------------------- 1 | permissions: 2 | 'delete users': 3 | title: 'Delete Users' 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock 4 | phpunit.xml 5 | .php_cs 6 | .php_cs.cache 7 | .phpunit -------------------------------------------------------------------------------- /Tests/Fixtures/full_definition.yaml: -------------------------------------------------------------------------------- 1 | title: Users 2 | 3 | permissions: 4 | 'edit users': 5 | title: 'Edit Users' 6 | -------------------------------------------------------------------------------- /Resources/config/serializer/Model.User.yml: -------------------------------------------------------------------------------- 1 | Eloyekunle\PermissionsBundle\Model\User: 2 | properties: 3 | userRoles: 4 | expose: true -------------------------------------------------------------------------------- /phpunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests; 13 | 14 | use Eloyekunle\PermissionsBundle\Model\Role; 15 | 16 | class TestRole extends Role 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /Tests/TestUser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests; 13 | 14 | use Eloyekunle\PermissionsBundle\Model\User; 15 | 16 | class TestUser extends User 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /Resources/config/permissions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | %eloyekunle_permissions.module.definitions_path% 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/validator/validation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Model/RoleManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Model; 13 | 14 | abstract class RoleManager implements RoleManagerInterface 15 | { 16 | /** 17 | * {@inheritdoc} 18 | */ 19 | public function createRole() 20 | { 21 | $class = $this->getClass(); 22 | $role = new $class(); 23 | 24 | return $role; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./Tests 7 | 8 | 9 | 10 | 11 | 12 | ./ 13 | 14 | ./Resources 15 | ./Tests 16 | ./vendor 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/config/role.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | %eloyekunle_permissions.model.role.class% 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/doctrine.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %eloyekunle_permissions.model_manager_name% 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Util/PermissionsHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Util; 13 | 14 | interface PermissionsHandlerInterface 15 | { 16 | /** 17 | * Gets all available permissions. 18 | * 19 | * @return array 20 | * A multi-dimensional array with the permission key as the keys, and other fields in an array as the value 21 | */ 22 | public function getPermissions(); 23 | } 24 | -------------------------------------------------------------------------------- /Resources/config/doctrine_role.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %eloyekunle_permissions.model.role.class% 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Resources/config/security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/Role.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Resources/config/storage-validation/orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | 7 | 8 | For the full copyright and license information, please view the LICENSE 9 | file that was distributed with this source code. 10 | EOF; 11 | 12 | return PhpCsFixer\Config::create() 13 | ->setRules([ 14 | '@Symfony' => true, 15 | 'combine_consecutive_unsets' => true, 16 | 'header_comment' => ['header' => $header], 17 | 'linebreak_after_opening_tag' => true, 18 | 'no_php4_constructor' => true, 19 | 'no_useless_else' => true, 20 | 'ordered_class_elements' => true, 21 | 'ordered_imports' => true, 22 | 'php_unit_construct' => true, 23 | 'php_unit_strict' => true, 24 | 'phpdoc_no_empty_return' => false, 25 | ]) 26 | ->setUsingCache(true) 27 | ->setRiskyAllowed(true) 28 | ->setFinder( 29 | PhpCsFixer\Finder::create() 30 | ->in(__DIR__) 31 | ) 32 | ; 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | cache: 6 | directories: 7 | - .phpunit 8 | - $HOME/.composer/cache/files 9 | 10 | branches: 11 | only: 12 | - master 13 | - /^\d+\.\d+$/ 14 | 15 | matrix: 16 | fast_finish: true 17 | include: 18 | - php: 5.6 19 | # Symfony 3 LTS 20 | - php: 7.0 21 | env: SYMFONY_LTS='^3' 22 | # Symfony 4 23 | - php: 7.1 24 | - php: 7.2 25 | # development dependencies 26 | - php: 7.2 27 | env: DEPENDENCIES='dev' 28 | allow_failures: 29 | - php: 7.2 30 | env: DEPENDENCIES='dev' 31 | 32 | before_install: 33 | - echo "memory_limit=4G" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini 34 | - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then phpenv config-rm xdebug.ini; fi 35 | - if [ "$DEPENDENCIES" != "" ]; then composer config minimum-stability $DEPENDENCIES; fi 36 | - if [ "$SYMFONY_LTS" != "" ]; then composer require --dev --no-update symfony/lts=$SYMFONY_LTS; fi 37 | 38 | install: 39 | - composer update $COMPOSER_FLAGS --prefer-dist 40 | - ./phpunit install 41 | 42 | script: ./phpunit 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Elijah Oyekunle (eloyekunle@gmail.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/User.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ValidationPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\DependencyInjection\Compiler; 13 | 14 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | 17 | /** 18 | * Registers the additional validators according to the storage. 19 | */ 20 | class ValidationPass implements CompilerPassInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function process(ContainerBuilder $container) 26 | { 27 | $storage = $container->getParameter('eloyekunle_permissions.storage'); 28 | 29 | $configDir = __DIR__.'/../../Resources/config'; 30 | $validationFile = $configDir.'/storage-validation/'.$storage.'.xml'; 31 | 32 | $validatorBuilder = $container->getDefinition('validator.builder'); 33 | $validatorBuilder->addMethodCall('addXmlMappings', [[$validationFile, $configDir.'/validator/validation.xml']]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/Form/Type/ValidatorExtensionTypeTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Form\Type; 13 | 14 | use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension; 15 | use Symfony\Component\Validator\ConstraintViolationList; 16 | 17 | /** 18 | * Class ValidatorExtensionTypeTestCase 19 | * FormTypeValidatorExtension added as default. Useful for form types with `constraints` option. 20 | * 21 | * @author Sullivan Senechal 22 | */ 23 | class ValidatorExtensionTypeTestCase extends TypeTestCase 24 | { 25 | /** 26 | * @return array 27 | */ 28 | protected function getTypeExtensions() 29 | { 30 | $validator = $this->getMockBuilder('Symfony\Component\Validator\Validator\ValidatorInterface')->getMock(); 31 | $validator->method('validate')->will($this->returnValue(new ConstraintViolationList())); 32 | 33 | return array( 34 | new FormTypeValidatorExtension($validator), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/Form/Type/RoleFormTypeTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Form\Type; 13 | 14 | use Eloyekunle\PermissionsBundle\Form\Type\RoleFormType; 15 | use Eloyekunle\PermissionsBundle\Tests\TestRole; 16 | 17 | class RoleFormTypeTest extends ValidatorExtensionTypeTestCase 18 | { 19 | public function testSubmit() 20 | { 21 | $role = new TestRole(); 22 | 23 | $form = $this->factory->create(RoleFormType::class, $role); 24 | $formData = [ 25 | 'role' => 'Content Admin', 26 | 'permissions' => ['view content', 'delete content'], 27 | ]; 28 | $form->submit($formData); 29 | 30 | $this->assertTrue($form->isSynchronized()); 31 | $this->assertSame($role, $form->getData()); 32 | $this->assertSame('Content Admin', $role->getRole()); 33 | $this->assertSame($formData['permissions'], $role->getPermissions()); 34 | } 35 | 36 | /** 37 | * @return array 38 | */ 39 | protected function getTypes() 40 | { 41 | return array_merge(parent::getTypes(), array( 42 | new RoleFormType('Eloyekunle\PermissionsBundle\Tests\TestRole'), 43 | )); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Util/PermissionsHandlerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Util; 13 | 14 | use Eloyekunle\PermissionsBundle\Util\PermissionsHandler; 15 | use Eloyekunle\PermissionsBundle\Util\PermissionsHandlerInterface; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | class PermissionsHandlerTest extends TestCase 19 | { 20 | /** @var PermissionsHandlerInterface */ 21 | protected $permissionsHandler; 22 | 23 | public function setUp() 24 | { 25 | $this->permissionsHandler = new PermissionsHandler(__DIR__.'/../Fixtures'); 26 | } 27 | 28 | public function testHandlerReadsDefinitions() 29 | { 30 | $permissions = $this->permissionsHandler->getPermissions(); 31 | $this->assertArrayHasKey('edit users', $permissions); 32 | $this->assertSame('full_definition', $permissions['edit users']['provider']); 33 | $this->assertSame('Edit Users', $permissions['edit users']['title']); 34 | 35 | $this->assertArrayHasKey('delete users', $permissions); 36 | $this->assertSame('no_name', $permissions['delete users']['provider']); 37 | $this->assertSame('Delete Users', $permissions['delete users']['title']); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/Form/Type/TypeTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Form\Type; 13 | 14 | use Symfony\Component\Form\FormBuilder; 15 | use Symfony\Component\Form\Forms; 16 | use Symfony\Component\Form\Test\TypeTestCase as BaseTypeTestCase; 17 | 18 | /** 19 | * Class TypeTestCase. 20 | * 21 | * @author Sullivan Senechal 22 | * 23 | * Could be removed for using directly base class since PR: https://github.com/symfony/symfony/pull/14506 24 | */ 25 | abstract class TypeTestCase extends BaseTypeTestCase 26 | { 27 | protected function setUp() 28 | { 29 | parent::setUp(); 30 | 31 | $this->factory = Forms::createFormFactoryBuilder() 32 | ->addTypes($this->getTypes()) 33 | ->addExtensions($this->getExtensions()) 34 | ->addTypeExtensions($this->getTypeExtensions()) 35 | ->getFormFactory(); 36 | 37 | $this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory); 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | protected function getTypeExtensions() 44 | { 45 | return array(); 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | protected function getTypes() 52 | { 53 | return array(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/BundleTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests; 13 | 14 | use Eloyekunle\PermissionsBundle\DependencyInjection\Compiler\DoctrineMappingPass; 15 | use Eloyekunle\PermissionsBundle\DependencyInjection\Compiler\ValidationPass; 16 | use Eloyekunle\PermissionsBundle\EloyekunlePermissionsBundle; 17 | use PHPUnit\Framework\TestCase; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | 20 | class BundleTest extends TestCase 21 | { 22 | public function testBuildCompilerPasses() 23 | { 24 | $container = new ContainerBuilder(); 25 | $bundle = new EloyekunlePermissionsBundle(); 26 | $bundle->build($container); 27 | 28 | $config = $container->getCompilerPassConfig(); 29 | $passes = $config->getBeforeOptimizationPasses(); 30 | 31 | $foundMappingPass = false; 32 | $foundValidation = false; 33 | 34 | foreach ($passes as $pass) { 35 | if ($pass instanceof ValidationPass) { 36 | $foundValidation = true; 37 | } elseif ($pass instanceof DoctrineMappingPass) { 38 | $foundMappingPass = true; 39 | } 40 | } 41 | 42 | $this->assertTrue($foundMappingPass, 'DoctrineMappingPass was not found'); 43 | $this->assertTrue($foundValidation, 'ValidationPass was not found'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eloyekunle/permissions-bundle", 3 | "description": "A Simple Permission Management Bundle for Symfony", 4 | "type": "symfony-bundle", 5 | "keywords": ["User management"], 6 | "require": { 7 | "php": "^5.5.9 || ^7.0", 8 | "doctrine/doctrine-bundle": "^1.9", 9 | "doctrine/orm": "^2.5 || ^2.6", 10 | "symfony/framework-bundle": "^2.7 || ^3.0 || ^4.0", 11 | "symfony/form": "^2.7 || ^3.0 || ^4.0", 12 | "symfony/yaml": "^2.7 || ^3.0 || ^4.0", 13 | "symfony/validator": "^2.8 || ^3.0 || ^4.0", 14 | "symfony/security-bundle": "^2.8 || ^3.0 || ^4.0", 15 | "symfony/twig-bundle": "^2.8 || ^3.0 || ^4.0", 16 | "symfony/templating": "^2.8 || ^3.0 || ^4.0", 17 | "twig/twig": "^1.28 || ^2.0", 18 | "paragonie/random_compat": "^1 || ^2" 19 | }, 20 | "require-dev": { 21 | "friendsofphp/php-cs-fixer": "^2.2", 22 | "symfony/phpunit-bridge": "^2.8 || ^3.0 || ^4.0" 23 | }, 24 | "license": "MIT", 25 | "authors": [ 26 | { 27 | "name": "Elijah Oyekunle", 28 | "email": "eloyekunle@gmail.com" 29 | } 30 | ], 31 | "autoload": { 32 | "psr-4": { 33 | "Eloyekunle\\PermissionsBundle\\": "" 34 | }, 35 | "exclude-from-classmap": [ 36 | "/Tests" 37 | ] 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Eloyekunle\\PermissionsBundle\\Tests\\": "/Tests" 42 | } 43 | }, 44 | "extra": { 45 | "branch-alias": { 46 | "dev-master": "master" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Form/Type/RoleFormType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Form\Type; 13 | 14 | use Symfony\Component\Form\AbstractType; 15 | use Symfony\Component\Form\Extension\Core\Type\CollectionType; 16 | use Symfony\Component\Form\Extension\Core\Type\TextType; 17 | use Symfony\Component\Form\FormBuilderInterface; 18 | use Symfony\Component\OptionsResolver\OptionsResolver; 19 | 20 | class RoleFormType extends AbstractType 21 | { 22 | /** @var string */ 23 | private $class; 24 | 25 | public function __construct($class) 26 | { 27 | $this->class = $class; 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function buildForm(FormBuilderInterface $builder, array $options) 34 | { 35 | $builder 36 | ->add('role', TextType::class) 37 | ->add( 38 | 'permissions', 39 | CollectionType::class, 40 | [ 41 | 'entry_type' => TextType::class, 42 | 'allow_add' => true, 43 | ] 44 | ); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function configureOptions(OptionsResolver $resolver) 51 | { 52 | $resolver->setDefaults( 53 | [ 54 | 'data_class' => $this->class, 55 | 'csrf_protection' => false, 56 | ] 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Model/RoleManagerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Model; 13 | 14 | interface RoleManagerInterface 15 | { 16 | public function findRoles(); 17 | 18 | /** 19 | * Creates and empty role. 20 | * 21 | * @return RoleInterface 22 | */ 23 | public function createRole(); 24 | 25 | /** 26 | * Returns the role's fully qualified class name. 27 | * 28 | * @return string 29 | */ 30 | public function getClass(); 31 | 32 | /** 33 | * Updates a role. 34 | * 35 | * @param \Eloyekunle\PermissionsBundle\Model\RoleInterface $role 36 | * @param bool $andFlush 37 | * 38 | * @return mixed 39 | */ 40 | public function updateRole(RoleInterface $role, $andFlush = true); 41 | 42 | /** 43 | * Finds one role by criteria. 44 | * 45 | * @param array $criteria 46 | * 47 | * @return RoleInterface|null 48 | */ 49 | public function findRoleBy(array $criteria); 50 | 51 | /** 52 | * Find one role by ID. 53 | * 54 | * @param int $id 55 | * 56 | * @return RoleInterface|null 57 | */ 58 | public function findRole($id); 59 | 60 | /** 61 | * Deletes a role. 62 | * 63 | * @param \Eloyekunle\PermissionsBundle\Model\RoleInterface $role 64 | * 65 | * @return void 66 | */ 67 | public function deleteRole(RoleInterface $role); 68 | } 69 | -------------------------------------------------------------------------------- /EloyekunlePermissionsBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass; 15 | use Eloyekunle\PermissionsBundle\DependencyInjection\Compiler\ValidationPass; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | use Symfony\Component\HttpKernel\Bundle\Bundle; 18 | 19 | class EloyekunlePermissionsBundle extends Bundle 20 | { 21 | /** 22 | * @param ContainerBuilder $container 23 | */ 24 | public function build(ContainerBuilder $container) 25 | { 26 | parent::build($container); 27 | $container->addCompilerPass(new ValidationPass()); 28 | $this->addRegisterMappingsPass($container); 29 | } 30 | 31 | /** 32 | * @param ContainerBuilder $container 33 | */ 34 | private function addRegisterMappingsPass(ContainerBuilder $container) 35 | { 36 | $mappings = [ 37 | realpath( 38 | __DIR__.'/Resources/config/doctrine-mapping' 39 | ) => 'Eloyekunle\PermissionsBundle\Model', 40 | ]; 41 | if (class_exists( 42 | 'Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass' 43 | )) { 44 | $container->addCompilerPass( 45 | DoctrineOrmMappingsPass::createXmlMappingDriver( 46 | $mappings, 47 | ['eloyekunle_permissions.model_manager_name'], 48 | 'eloyekunle_permissions.backend_type_orm' 49 | ) 50 | ); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Util/PermissionsHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Util; 13 | 14 | use Symfony\Component\Finder\Finder; 15 | use Symfony\Component\Yaml\Parser; 16 | 17 | class PermissionsHandler implements PermissionsHandlerInterface 18 | { 19 | /** @var string */ 20 | protected $definitionsPath; 21 | 22 | /** @var array */ 23 | protected $modules; 24 | 25 | /** @var Finder */ 26 | protected $finder; 27 | 28 | /** @var Parser */ 29 | protected $parser; 30 | 31 | /** @var array */ 32 | protected $permissions; 33 | 34 | public function __construct($definitionsPath) 35 | { 36 | $this->definitionsPath = $definitionsPath; 37 | $this->finder = Finder::create()->files()->name('*.yaml')->in($this->definitionsPath); 38 | $this->parser = new Parser(); 39 | 40 | $this->buildPermissionsYaml(); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getPermissions() 47 | { 48 | return $this->permissions; 49 | } 50 | 51 | /** 52 | * Builds the list of currently active modules with their permissions. 53 | */ 54 | protected function buildPermissionsYaml() 55 | { 56 | $permissions = []; 57 | 58 | foreach ($this->finder as $definitionsFile) { 59 | $module = $this->parser->parseFile($definitionsFile->getPathname()); 60 | $moduleKey = $definitionsFile->getBasename('.yaml'); 61 | 62 | foreach ($module['permissions'] as $key => $permission) { 63 | $permissions[$key] = $permission + ['provider' => $moduleKey]; 64 | } 65 | } 66 | 67 | $this->permissions = $permissions; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\DependencyInjection; 13 | 14 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 15 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 16 | use Symfony\Component\Config\Definition\ConfigurationInterface; 17 | 18 | class Configuration implements ConfigurationInterface 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function getConfigTreeBuilder() 24 | { 25 | $treeBuilder = new TreeBuilder('eloyekunle_permissions'); 26 | $rootNode = method_exists(TreeBuilder::class, 'getRootNode') ? $treeBuilder->getRootNode() : $treeBuilder->root('eloyekunle_permissions'); 27 | 28 | $supportedDrivers = ['orm']; 29 | 30 | $rootNode 31 | ->addDefaultsIfNotSet() 32 | ->children() 33 | ->scalarNode('db_driver') 34 | ->validate() 35 | ->ifNotInArray($supportedDrivers) 36 | ->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($supportedDrivers)) 37 | ->end() 38 | ->cannotBeOverwritten() 39 | ->defaultValue('orm') 40 | ->end() 41 | ->scalarNode('role_class')->isRequired()->cannotBeEmpty()->end() 42 | ->scalarNode('model_manager_name')->defaultNull()->end() 43 | ->end(); 44 | 45 | $this->addModuleSection($rootNode); 46 | 47 | return $treeBuilder; 48 | } 49 | 50 | private function addModuleSection(ArrayNodeDefinition $node) 51 | { 52 | $node 53 | ->children() 54 | ->arrayNode('module') 55 | ->addDefaultsIfNotSet() 56 | ->canBeUnset() 57 | ->children() 58 | ->scalarNode('definitions_path')->cannotBeEmpty()->isRequired()->end() 59 | ->end(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Security/PermissionsVoter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Security; 13 | 14 | use Eloyekunle\PermissionsBundle\Model\RoleInterface; 15 | use Eloyekunle\PermissionsBundle\Model\UserInterface; 16 | use Eloyekunle\PermissionsBundle\Util\PermissionsHandlerInterface; 17 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 18 | use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; 19 | use Symfony\Component\Security\Core\Authorization\Voter\Voter; 20 | 21 | class PermissionsVoter extends Voter 22 | { 23 | /** 24 | * @var AccessDecisionManagerInterface 25 | */ 26 | protected $decisionManager; 27 | 28 | /** 29 | * @var PermissionsHandlerInterface 30 | */ 31 | protected $moduleHandler; 32 | 33 | public function __construct(AccessDecisionManagerInterface $accessDecisionManager, PermissionsHandlerInterface $permissionsHandler) 34 | { 35 | $this->decisionManager = $accessDecisionManager; 36 | $this->moduleHandler = $permissionsHandler; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | protected function supports($attribute, $subject) 43 | { 44 | // Check if attribute is declared as permission. 45 | if (!in_array($attribute, array_keys($this->moduleHandler->getPermissions()))) { 46 | return false; 47 | } 48 | 49 | return true; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token) 56 | { 57 | // Super Admin Pass 58 | if ($this->decisionManager->decide($token, [RoleInterface::ROLE_SUPER_ADMIN])) { 59 | return true; 60 | } 61 | 62 | // Deny access if user is not logged in. 63 | $user = $token->getUser(); 64 | if (!$user instanceof UserInterface) { 65 | return false; 66 | } 67 | 68 | return $user->hasPermission($attribute); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Doctrine/RoleManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Doctrine; 13 | 14 | use Doctrine\Common\Persistence\ObjectManager; 15 | use Doctrine\Common\Persistence\ObjectRepository; 16 | use Eloyekunle\PermissionsBundle\Model\RoleInterface; 17 | use Eloyekunle\PermissionsBundle\Model\RoleManager as BaseRoleManager; 18 | 19 | class RoleManager extends BaseRoleManager 20 | { 21 | /** @var ObjectManager */ 22 | protected $objectManager; 23 | 24 | /** @var string */ 25 | protected $class; 26 | 27 | public function __construct(ObjectManager $om, $class) 28 | { 29 | $this->objectManager = $om; 30 | $this->class = $class; 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function getClass() 37 | { 38 | return $this->class; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function findRoles() 45 | { 46 | return $this->getRepository()->findAll(); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function findRoleBy(array $criteria) 53 | { 54 | return $this->getRepository()->findOneBy($criteria); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function findRole($id) 61 | { 62 | return $this->getRepository()->find($id); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function updateRole(RoleInterface $role, $andFlush = true) 69 | { 70 | $this->objectManager->persist($role); 71 | 72 | if ($andFlush) { 73 | $this->objectManager->flush(); 74 | } 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function deleteRole(RoleInterface $role) 81 | { 82 | $this->objectManager->remove($role); 83 | $this->objectManager->flush(); 84 | } 85 | 86 | /** 87 | * @return ObjectRepository 88 | */ 89 | protected function getRepository() 90 | { 91 | return $this->objectManager->getRepository($this->getClass()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Model/RoleInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Model; 13 | 14 | interface RoleInterface 15 | { 16 | /** 17 | * Role ID for anonymous users; should match what's in the "role" table. 18 | */ 19 | const ROLE_DEFAULT = 'ROLE_USER'; 20 | 21 | /** 22 | * Role ID for super admin users; should match what's in the "role" table. 23 | */ 24 | const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN'; 25 | 26 | /** 27 | * @return int 28 | */ 29 | public function getId(); 30 | 31 | /** 32 | * @return null|string 33 | */ 34 | public function getRole(); 35 | 36 | /** 37 | * @param null|string $name 38 | */ 39 | public function setRole($name); 40 | 41 | /** 42 | * Returns a list of permissions assigned to the role. 43 | * 44 | * @return array 45 | * The permissions assigned to the role 46 | */ 47 | public function getPermissions(); 48 | 49 | /** 50 | * Sets the permissions of a role. 51 | * This overwrites all previous roles. 52 | * 53 | * @param array $permissions 54 | */ 55 | public function setPermissions(array $permissions); 56 | 57 | /** 58 | * Checks if the role has a permission. 59 | * 60 | * @param string $permission 61 | * The permission to check for 62 | * 63 | * @return bool 64 | * TRUE if the role has the permission, FALSE if not 65 | */ 66 | public function hasPermission($permission); 67 | 68 | /** 69 | * Grant permissions to the role. 70 | * 71 | * @param string $permission 72 | * The permission to grant 73 | * 74 | * @return $this 75 | */ 76 | public function grantPermission($permission); 77 | 78 | /** 79 | * Revokes a permissions from the user role. 80 | * 81 | * @param string $permission 82 | * The permission to revoke 83 | * 84 | * @return $this 85 | */ 86 | public function revokePermission($permission); 87 | 88 | /** 89 | * Indicates that a role has all available permissions. 90 | * 91 | * @return bool 92 | * TRUE if the role has all permissions 93 | */ 94 | public function isSuperAdmin(); 95 | } 96 | -------------------------------------------------------------------------------- /Model/UserInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Model; 13 | 14 | interface UserInterface 15 | { 16 | /** 17 | * Returns the roles granted to the user. 18 | * 19 | * 20 | * public function getRoles() 21 | * { 22 | * return array('ROLE_USER'); 23 | * } 24 | * 25 | * 26 | * This returns an array of the role names because Symfony expects a User::getRoles() method that returns a string 27 | * array of user roles. User::getUserRoles() currently returns RoleInterfaces[]. 28 | * 29 | * @return string[] The user roles 30 | */ 31 | public function getRoles(); 32 | 33 | /** 34 | * Tells if the the given user has the super admin role. 35 | * 36 | * @return bool 37 | */ 38 | public function isSuperAdmin(); 39 | 40 | /** 41 | * Checks whether a user has a certain permission. 42 | * 43 | * @param string $permission 44 | * The permission string to check 45 | * 46 | * @return bool 47 | * TRUE if the user has the permission, FALSE otherwise 48 | */ 49 | public function hasPermission($permission); 50 | 51 | /** 52 | * Whether a user has a certain role. 53 | * 54 | * @param string $role 55 | * The role ID to check 56 | * 57 | * @return bool 58 | * Returns TRUE if the user has the role, otherwise FALSE 59 | */ 60 | public function hasRole(RoleInterface $role); 61 | 62 | /** 63 | * Sets the roles of the user. 64 | * 65 | * This overwrites any previous roles. 66 | * 67 | * @param \Eloyekunle\PermissionsBundle\Model\RoleInterface[] $roles 68 | * 69 | * @return static 70 | */ 71 | public function setUserRoles(array $roles); 72 | 73 | /** 74 | * @return \Eloyekunle\PermissionsBundle\Model\RoleInterface[]|null 75 | */ 76 | public function getUserRoles(); 77 | 78 | /** 79 | * Add a role to a user. 80 | * 81 | * @param \Eloyekunle\PermissionsBundle\Model\RoleInterface $role 82 | * The role to add 83 | */ 84 | public function addRole(RoleInterface $role); 85 | 86 | /** 87 | * Remove a role from a user. 88 | * 89 | * @param \Eloyekunle\PermissionsBundle\Model\RoleInterface $role 90 | * The role to remove 91 | */ 92 | public function removeRole(RoleInterface $role); 93 | } 94 | -------------------------------------------------------------------------------- /Tests/Functional/DependencyInjectionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Functional; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; 15 | use Eloyekunle\PermissionsBundle\Doctrine\RoleManager; 16 | use Eloyekunle\PermissionsBundle\EloyekunlePermissionsBundle; 17 | use Symfony\Bundle\FrameworkBundle\FrameworkBundle; 18 | use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; 19 | use Symfony\Component\Config\Loader\LoaderInterface; 20 | use Symfony\Component\DependencyInjection\ContainerBuilder; 21 | use Symfony\Component\HttpKernel\Kernel; 22 | 23 | class DependencyInjectionTest extends KernelTestCase 24 | { 25 | public function testBundleLoaded() 26 | { 27 | self::bootKernel(); 28 | $container = self::$kernel->getContainer(); 29 | 30 | $this->assertInstanceOf(RoleManager::class, $container->get('eloyekunle_permissions.role_manager')); 31 | } 32 | 33 | protected static function getKernelClass() 34 | { 35 | return TestKernel::class; 36 | } 37 | } 38 | 39 | class TestKernel extends Kernel 40 | { 41 | public function registerBundles() 42 | { 43 | return [ 44 | new FrameworkBundle(), 45 | new DoctrineBundle(), 46 | new EloyekunlePermissionsBundle(), 47 | ]; 48 | } 49 | 50 | public function registerContainerConfiguration(LoaderInterface $loader) 51 | { 52 | $loader->load(function (ContainerBuilder $container) { 53 | $container->loadFromExtension('framework', [ 54 | 'secret' => 'test', 55 | 'router' => array( 56 | 'resource' => '%kernel.root_dir%/config/routing.yml', 57 | ), 58 | ]); 59 | $container->loadFromExtension('eloyekunle_permissions', [ 60 | 'db_driver' => 'orm', 61 | 'role_class' => 'Eloyekunle\PermissionsBundle\Tests\TestRole', 62 | ]); 63 | $container->loadFromExtension('doctrine', [ 64 | 'dbal' => [ 65 | 'driver' => 'pdo_sqlite', 66 | 'path' => $this->getCacheDir().'/testing.db', 67 | ], 68 | 'orm' => [], 69 | ]); 70 | }); 71 | } 72 | 73 | public function getCacheDir() 74 | { 75 | return sys_get_temp_dir().'/'.str_replace('\\', '-', get_class($this)).'/cache/'.$this->environment; 76 | } 77 | 78 | public function getLogDir() 79 | { 80 | return sys_get_temp_dir().'/'.str_replace('\\', '-', get_class($this)).'/log/'.$this->environment; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/Model/RoleTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Model; 13 | 14 | use Eloyekunle\PermissionsBundle\Model\RoleInterface; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class RoleTest extends TestCase 18 | { 19 | public function testName() 20 | { 21 | $role = $this->getRole(); 22 | $this->assertNull($role->getRole()); 23 | 24 | $role->setRole('System Administrator'); 25 | $this->assertSame('System Administrator', $role->getRole()); 26 | } 27 | 28 | public function testGrantPermission() 29 | { 30 | $role = $this->getRole(); 31 | $this->assertFalse($role->hasPermission('Administer Systems')); 32 | 33 | $role->grantPermission('Administer Systems'); 34 | $this->assertTrue($role->hasPermission('Administer Systems')); 35 | } 36 | 37 | public function testRevokePermission() 38 | { 39 | $role = $this->getRole(); 40 | $role->grantPermission('Administer Systems'); 41 | $this->assertTrue($role->hasPermission('Administer Systems')); 42 | 43 | $role->revokePermission('Administer Systems'); 44 | $this->assertFalse($role->hasPermission('Administer Systems')); 45 | } 46 | 47 | public function testSuperAdminPermissions() 48 | { 49 | $role = $this->getRole(); 50 | $this->assertFalse($role->hasPermission('Administer Systems')); 51 | 52 | $role->setRole(RoleInterface::ROLE_SUPER_ADMIN); 53 | $this->assertTrue($role->hasPermission('Administer Systems')); 54 | $role->grantPermission('Play with Cats'); 55 | $this->assertSame([], $role->getPermissions()); 56 | $role->revokePermission('Play with Cats'); 57 | $this->assertSame([], $role->getPermissions()); 58 | 59 | $role->setRole('Not Admin'); 60 | $this->assertFalse($role->hasPermission('Administer Systems')); 61 | } 62 | 63 | public function testSetPermissions() 64 | { 65 | $role = $this->getRole(); 66 | $this->assertFalse($role->hasPermission('Administer Systems')); 67 | $this->assertFalse($role->hasPermission('View Reports')); 68 | 69 | $permissions = ['Administer Systems', 'View Reports', '']; 70 | $role->setPermissions($permissions); 71 | $this->assertTrue($role->hasPermission('Administer Systems')); 72 | $this->assertTrue($role->hasPermission('View Reports')); 73 | $this->assertSame(2, count($role->getPermissions())); 74 | } 75 | 76 | /** 77 | * @return RoleInterface 78 | * 79 | * @throws \ReflectionException 80 | */ 81 | protected function getRole() 82 | { 83 | return $this->getMockForAbstractClass('Eloyekunle\PermissionsBundle\Model\Role'); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Model/User.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Model; 13 | 14 | use Doctrine\Common\Collections\ArrayCollection; 15 | use Doctrine\Common\Collections\Collection; 16 | 17 | /** 18 | * Storage agnostic user object. 19 | */ 20 | abstract class User implements UserInterface 21 | { 22 | /** 23 | * @var RoleInterface[]|Collection 24 | */ 25 | protected $userRoles; 26 | 27 | /** 28 | * User constructor. 29 | */ 30 | public function __construct() 31 | { 32 | $this->userRoles = new ArrayCollection(); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getRoles() 39 | { 40 | $roleNames = []; 41 | $roles = $this->getUserRoles(); 42 | 43 | foreach ($roles as $role) { 44 | $roleNames[] = $role->getRole(); 45 | } 46 | 47 | return array_unique($roleNames); 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function hasRole(RoleInterface $role) 54 | { 55 | return $this->userRoles->contains($role); 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function isSuperAdmin() 62 | { 63 | $isSuperAdmin = false; 64 | foreach ($this->getUserRoles() as $role) { 65 | if ($role->isSuperAdmin()) { 66 | return true; 67 | } 68 | } 69 | 70 | return $isSuperAdmin; 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function removeRole(RoleInterface $role) 77 | { 78 | if (!$this->userRoles->contains($role)) { 79 | return; 80 | } 81 | 82 | $this->userRoles->removeElement($role); 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function addRole(RoleInterface $role) 89 | { 90 | if ($this->userRoles->contains($role)) { 91 | return; 92 | } 93 | 94 | $this->userRoles->add($role); 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function setUserRoles(array $userRoles) 101 | { 102 | foreach ($userRoles as $role) { 103 | $this->addRole($role); 104 | } 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function hasPermission($permission) 111 | { 112 | $hasPermission = false; 113 | 114 | foreach ($this->getUserRoles() as $role) { 115 | if ($role->isSuperAdmin() || $role->hasPermission($permission)) { 116 | return true; 117 | } 118 | } 119 | 120 | return $hasPermission; 121 | } 122 | 123 | /** 124 | * @return RoleInterface[] 125 | */ 126 | public function getUserRoles() 127 | { 128 | return $this->userRoles; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Model/Role.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Model; 13 | 14 | abstract class Role implements RoleInterface 15 | { 16 | /** 17 | * The machine name of this role. 18 | * 19 | * @var mixed 20 | */ 21 | protected $id; 22 | 23 | /** 24 | * The human-readable name of this role. 25 | * 26 | * @var string 27 | */ 28 | protected $role; 29 | 30 | /** 31 | * The permissions belonging to this role. 32 | * 33 | * @var array 34 | */ 35 | protected $permissions; 36 | 37 | public function __construct($role = null) 38 | { 39 | $this->role = $role; 40 | $this->permissions = []; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getId() 47 | { 48 | return $this->id; 49 | } 50 | 51 | public function getRole() 52 | { 53 | return $this->role; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function setRole($role) 60 | { 61 | $this->role = $role; 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function getPermissions() 70 | { 71 | if ($this->isSuperAdmin()) { 72 | return []; 73 | } 74 | 75 | return $this->permissions; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function setPermissions(array $permissions) 82 | { 83 | $this->permissions = []; 84 | 85 | foreach ($permissions as $permission) { 86 | if (!$permission) { 87 | break; 88 | } 89 | $this->grantPermission($permission); 90 | } 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function hasPermission($permission) 99 | { 100 | if ($this->isSuperAdmin()) { 101 | return true; 102 | } 103 | 104 | return in_array($permission, $this->getPermissions(), true); 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function grantPermission($permission) 111 | { 112 | if ($this->isSuperAdmin()) { 113 | return $this; 114 | } 115 | if (!$this->hasPermission($permission)) { 116 | $this->permissions[] = $permission; 117 | } 118 | 119 | return $this; 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function revokePermission($permission) 126 | { 127 | if ($this->isSuperAdmin()) { 128 | return $this; 129 | } 130 | $this->permissions = array_diff($this->permissions, [$permission]); 131 | 132 | return $this; 133 | } 134 | 135 | /** 136 | * {@inheritdoc} 137 | */ 138 | public function isSuperAdmin() 139 | { 140 | return $this->getRole() === static::ROLE_SUPER_ADMIN; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Tests/Model/UserTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Model; 13 | 14 | use Eloyekunle\PermissionsBundle\Model\RoleInterface; 15 | use Eloyekunle\PermissionsBundle\Model\UserInterface; 16 | use Eloyekunle\PermissionsBundle\Tests\TestRole; 17 | use PHPUnit\Framework\TestCase; 18 | 19 | class UserTest extends TestCase 20 | { 21 | public function testTrueHasRole() 22 | { 23 | $user = $this->getUser(); 24 | $newrole = $this->createMockRole('ROLE_X'); 25 | 26 | $user->addRole($newrole); 27 | $this->assertTrue($user->hasRole($newrole)); 28 | 29 | // Role already exists (added above), so addRole should return without doing anything 30 | $user->addRole($newrole); 31 | $this->assertTrue($user->hasRole($newrole)); 32 | 33 | $user->removeRole($newrole); 34 | $this->assertFalse($user->hasRole($newrole)); 35 | 36 | // Role already removed (removed above), so removeRole should return without doing anything 37 | $user->removeRole($newrole); 38 | $this->assertFalse($user->hasRole($newrole)); 39 | } 40 | 41 | public function testFalseHasRole() 42 | { 43 | $user = $this->getUser(); 44 | $newrole = $this->createMockRole('ROLE_X'); 45 | 46 | $this->assertFalse($user->hasRole($newrole)); 47 | $user->addRole($newrole); 48 | $this->assertTrue($user->hasRole($newrole)); 49 | } 50 | 51 | public function testIsSuperAdmin() 52 | { 53 | $user = $this->getUser(); 54 | $permission = 'do stuff'; 55 | 56 | $this->assertFalse($user->isSuperAdmin()); 57 | $this->assertFalse($user->hasPermission($permission)); 58 | 59 | $superAdminRole = $this->createMockRole(RoleInterface::ROLE_SUPER_ADMIN); 60 | $user->addRole($superAdminRole); 61 | 62 | $this->assertTrue($user->isSuperAdmin()); 63 | $this->assertTrue($user->hasPermission($permission)); 64 | } 65 | 66 | public function testSetUserRoles() 67 | { 68 | $roleNames = ['Content Admin', 'Service Admin']; 69 | $roles = []; 70 | foreach ($roleNames as $roleName) { 71 | $roles[] = $this->createMockRole($roleName); 72 | } 73 | $user1 = $this->getUser(); 74 | $user2 = $this->getUser(); 75 | $user2->setUserRoles($roles); 76 | 77 | foreach ($roles as $role) { 78 | $this->assertFalse($user1->hasRole($role)); 79 | $this->assertTrue($user2->hasRole($role)); 80 | } 81 | 82 | $this->assertSame($roleNames, $user2->getRoles()); 83 | } 84 | 85 | /** 86 | * @param $roleName 87 | * 88 | * @return RoleInterface 89 | */ 90 | protected function createMockRole($roleName) 91 | { 92 | return new TestRole($roleName); 93 | } 94 | 95 | /** 96 | * @return UserInterface 97 | * 98 | * @throws \ReflectionException 99 | */ 100 | protected function getUser() 101 | { 102 | return $this->getMockForAbstractClass('Eloyekunle\PermissionsBundle\Model\User'); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Tests/Doctrine/RoleManagerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Doctrine; 13 | 14 | use Eloyekunle\PermissionsBundle\Doctrine\RoleManager; 15 | use Eloyekunle\PermissionsBundle\Model\RoleManagerInterface; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | class RoleManagerTest extends TestCase 19 | { 20 | const ROLE_CLASS = 'Eloyekunle\PermissionsBundle\Tests\TestRole'; 21 | 22 | /** @var \PHPUnit_Framework_MockObject_MockObject */ 23 | protected $repository; 24 | 25 | /** @var \PHPUnit_Framework_MockObject_MockObject */ 26 | protected $om; 27 | 28 | /** @var RoleManagerInterface */ 29 | protected $roleManager; 30 | 31 | public function setUp() 32 | { 33 | if (!interface_exists('Doctrine\Common\Persistence\ObjectManager')) { 34 | $this->markTestSkipped('Doctrine Common has to be installed for this test to run.'); 35 | } 36 | 37 | $class = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock(); 38 | $this->om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock(); 39 | $this->repository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')->getMock(); 40 | 41 | $this->om->expects($this->any()) 42 | ->method('getRepository') 43 | ->with($this->equalTo(static::ROLE_CLASS)) 44 | ->will($this->returnValue($this->repository)); 45 | $this->om->expects($this->any()) 46 | ->method('getClassMetadata') 47 | ->with($this->equalTo(static::ROLE_CLASS)) 48 | ->will($this->returnValue($class)); 49 | $class->expects($this->any()) 50 | ->method('getName') 51 | ->will($this->returnValue(static::ROLE_CLASS)); 52 | 53 | $this->roleManager = new RoleManager($this->om, static::ROLE_CLASS); 54 | } 55 | 56 | public function testGetClass() 57 | { 58 | $this->assertSame(static::ROLE_CLASS, $this->roleManager->getClass()); 59 | } 60 | 61 | public function testFindRoles() 62 | { 63 | $this->repository->expects($this->once())->method('findAll'); 64 | 65 | $this->roleManager->findRoles(); 66 | } 67 | 68 | public function testFindRoleBy() 69 | { 70 | $crit = ['name' => 'finestRole']; 71 | $this->repository->expects($this->once())->method('findOneBy')->with($crit); 72 | 73 | $this->roleManager->findRoleBy($crit); 74 | } 75 | 76 | public function testFindRole() 77 | { 78 | $id = 10; 79 | $this->repository->expects($this->once())->method('find')->with($id); 80 | 81 | $this->roleManager->findRole($id); 82 | } 83 | 84 | public function testUpdateRole() 85 | { 86 | $role = $this->getRole(); 87 | $this->om->expects($this->once())->method('persist')->with($role); 88 | $this->om->expects($this->once())->method('flush'); 89 | 90 | $this->roleManager->updateRole($role); 91 | } 92 | 93 | public function testDeleteRole() 94 | { 95 | $role = $this->getRole(); 96 | $this->om->expects($this->once())->method('remove')->with($role); 97 | $this->om->expects($this->once())->method('flush'); 98 | 99 | $this->roleManager->deleteRole($role); 100 | } 101 | 102 | protected function getRole() 103 | { 104 | return $this->roleManager->createRole(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/EloyekunlePermissionsExtensionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\DependencyInjection; 13 | 14 | use Eloyekunle\PermissionsBundle\DependencyInjection\EloyekunlePermissionsExtension; 15 | use PHPUnit\Framework\TestCase; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | use Symfony\Component\Yaml\Parser; 18 | 19 | /** 20 | * EloyekunlePermissionsExtension Test. 21 | * 22 | * @author Elijah Oyekunle 23 | */ 24 | class EloyekunlePermissionsExtensionTest extends TestCase 25 | { 26 | /** @var ContainerBuilder */ 27 | private $container; 28 | 29 | /** @var EloyekunlePermissionsExtension */ 30 | private $extension; 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function setUp() 36 | { 37 | $this->container = new ContainerBuilder(); 38 | $this->extension = new EloyekunlePermissionsExtension(); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function tearDown() 45 | { 46 | unset($this->container, $this->extension); 47 | } 48 | 49 | /** 50 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 51 | */ 52 | public function testLoadThrowsExceptionUnlessDatabaseDriverIsValid() 53 | { 54 | $config = $this->getEmptyConfig(); 55 | $config['db_driver'] = 'bar'; 56 | $this->extension->load([$config], $this->container); 57 | } 58 | 59 | public function testLoadManagerClassWithDefaults() 60 | { 61 | $this->createEmptyConfig(); 62 | 63 | $this->assertParameter('orm', 'eloyekunle_permissions.storage'); 64 | $this->assertAlias('eloyekunle_permissions.role_manager.default', 'eloyekunle_permissions.role_manager'); 65 | } 66 | 67 | public function testModelClassWithDefaults() 68 | { 69 | $this->createEmptyConfig(); 70 | 71 | $this->assertParameter('Acme\MyBundle\Document\Role', 'eloyekunle_permissions.model.role.class'); 72 | } 73 | 74 | protected function createEmptyConfig() 75 | { 76 | $config = $this->getEmptyConfig(); 77 | $this->extension->load(array($config), $this->container); 78 | $this->assertTrue($this->container instanceof ContainerBuilder); 79 | } 80 | 81 | protected function createFullConfig() 82 | { 83 | $config = $this->getFullConfig(); 84 | $this->extension->load(array($config), $this->container); 85 | $this->assertTrue($this->container instanceof ContainerBuilder); 86 | } 87 | 88 | /** 89 | * getEmptyConfig. 90 | * 91 | * @return array 92 | */ 93 | protected function getEmptyConfig() 94 | { 95 | $yaml = <<parse($yaml); 102 | } 103 | 104 | /** 105 | * getFullConfig. 106 | * 107 | * @return array 108 | */ 109 | protected function getFullConfig() 110 | { 111 | $yaml = <<parse($yaml); 120 | } 121 | 122 | /** 123 | * @param string $value 124 | * @param string $key 125 | */ 126 | private function assertAlias($value, $key) 127 | { 128 | $this->assertSame($value, (string) $this->container->getAlias($key), sprintf('%s alias is correct', $key)); 129 | } 130 | 131 | /** 132 | * @param mixed $value 133 | * @param string $key 134 | */ 135 | private function assertParameter($value, $key) 136 | { 137 | $this->assertSame($value, $this->container->getParameter($key), sprintf('%s parameter is correct', $key)); 138 | } 139 | 140 | /** 141 | * @param string $id 142 | */ 143 | private function assertHasDefinition($id) 144 | { 145 | $this->assertTrue($this->container->hasDefinition($id) ?: $this->container->hasParameter($id)); 146 | } 147 | 148 | /** 149 | * @param string $id 150 | */ 151 | private function assertNotHasDefinition($id) 152 | { 153 | $this->assertTrue($this->container->hasDefinition($id) ?: $this->container->hasParameter($id)); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /DependencyInjection/EloyekunlePermissionsExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\DependencyInjection; 13 | 14 | use Symfony\Component\Config\Definition\Processor; 15 | use Symfony\Component\Config\FileLocator; 16 | use Symfony\Component\DependencyInjection\Alias; 17 | use Symfony\Component\DependencyInjection\ContainerBuilder; 18 | use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; 19 | use Symfony\Component\DependencyInjection\Reference; 20 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 21 | 22 | class EloyekunlePermissionsExtension extends Extension 23 | { 24 | /** 25 | * @var array 26 | */ 27 | private static $doctrineDrivers = array( 28 | 'orm' => array( 29 | 'registry' => 'doctrine', 30 | 'tag' => 'doctrine.event_subscriber', 31 | ), 32 | ); 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function load(array $configs, ContainerBuilder $container) 38 | { 39 | $processor = new Processor(); 40 | $configuration = new Configuration(); 41 | 42 | $config = $processor->processConfiguration($configuration, $configs); 43 | 44 | $loader = new XmlFileLoader( 45 | $container, 46 | new FileLocator(__DIR__.'/../Resources/config') 47 | ); 48 | 49 | $loader->load('doctrine.xml'); 50 | $container->setAlias( 51 | 'eloyekunle_permissions.doctrine_registry', 52 | new Alias(self::$doctrineDrivers[$config['db_driver']]['registry'], false) 53 | ); 54 | $container->setParameter($this->getAlias().'.backend_type_'.$config['db_driver'], true); 55 | 56 | if (isset(self::$doctrineDrivers[$config['db_driver']])) { 57 | $definition = $container->getDefinition('eloyekunle_permissions.object_manager'); 58 | $definition->setFactory([new Reference('eloyekunle_permissions.doctrine_registry'), 'getManager']); 59 | } 60 | 61 | $this->remapParametersNamespaces($config, $container, [ 62 | '' => [ 63 | 'db_driver' => 'eloyekunle_permissions.storage', 64 | 'role_class' => 'eloyekunle_permissions.model.role.class', 65 | 'model_manager_name' => 'eloyekunle_permissions.model_manager_name', 66 | ], 67 | ] 68 | ); 69 | 70 | $this->loadRole($config, $container, $loader); 71 | 72 | if (!empty($config['module'])) { 73 | $this->loadPermissions($config['module'], $container, $loader); 74 | } 75 | } 76 | 77 | /** 78 | * @param array $config 79 | * @param ContainerBuilder $container 80 | * @param array $map 81 | */ 82 | protected function remapParameters(array $config, ContainerBuilder $container, array $map) 83 | { 84 | foreach ($map as $name => $paramName) { 85 | if (array_key_exists($name, $config)) { 86 | $container->setParameter($paramName, $config[$name]); 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * @param array $config 93 | * @param ContainerBuilder $container 94 | * @param array $namespaces 95 | */ 96 | protected function remapParametersNamespaces(array $config, ContainerBuilder $container, array $namespaces) 97 | { 98 | foreach ($namespaces as $ns => $map) { 99 | if ($ns) { 100 | if (!array_key_exists($ns, $config)) { 101 | continue; 102 | } 103 | $namespaceConfig = $config[$ns]; 104 | } else { 105 | $namespaceConfig = $config; 106 | } 107 | if (is_array($map)) { 108 | $this->remapParameters($namespaceConfig, $container, $map); 109 | } else { 110 | foreach ($namespaceConfig as $name => $value) { 111 | $container->setParameter(sprintf($map, $name), $value); 112 | } 113 | } 114 | } 115 | } 116 | 117 | private function loadRole(array $config, ContainerBuilder $container, XmlFileLoader $loader) 118 | { 119 | $loader->load('role.xml'); 120 | $loader->load('doctrine_role.xml'); 121 | 122 | $container->setAlias('eloyekunle_permissions.role_manager', new Alias('eloyekunle_permissions.role_manager.default', true) 123 | ); 124 | } 125 | 126 | private function loadPermissions(array $config, ContainerBuilder $container, XmlFileLoader $loader) 127 | { 128 | $loader->load('permissions.xml'); 129 | 130 | $this->remapParametersNamespaces( 131 | $config, 132 | $container, 133 | [ 134 | '' => [ 135 | 'definitions_path' => 'eloyekunle_permissions.module.definitions_path', 136 | ], 137 | ] 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Tests/Security/PermissionVoterTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Eloyekunle\PermissionsBundle\Tests\Security; 13 | 14 | use Eloyekunle\PermissionsBundle\Model\RoleInterface; 15 | use Eloyekunle\PermissionsBundle\Security\PermissionsVoter; 16 | use Eloyekunle\PermissionsBundle\Tests\TestRole; 17 | use Eloyekunle\PermissionsBundle\Tests\TestUser; 18 | use Eloyekunle\PermissionsBundle\Util\PermissionsHandler; 19 | use Eloyekunle\PermissionsBundle\Util\PermissionsHandlerInterface; 20 | use PHPUnit\Framework\TestCase; 21 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 22 | use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; 23 | use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; 24 | use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; 25 | use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; 26 | 27 | class PermissionVoterTest extends TestCase 28 | { 29 | /** @var TokenInterface */ 30 | protected $token; 31 | 32 | /** @var PermissionsVoter */ 33 | protected $voter; 34 | 35 | /** @var AccessDecisionManagerInterface */ 36 | protected $accessDecisionManager; 37 | 38 | /** @var PermissionsHandlerInterface */ 39 | protected $permissionsHandler; 40 | 41 | protected function setUp() 42 | { 43 | $this->accessDecisionManager = new AccessDecisionManager([new RoleVoter()]); 44 | $this->token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); 45 | $this->permissionsHandler = new PermissionsHandler(__DIR__.'/../Fixtures'); 46 | 47 | $this->voter = $this->createPermissionsVoter(); 48 | } 49 | 50 | public function getTests() 51 | { 52 | return [ 53 | [['delete users'], VoterInterface::ACCESS_DENIED, new \StdClass(), ''], 54 | [['review articles'], VoterInterface::ACCESS_ABSTAIN, new \StdClass(), 'Access Abstain if permission is not declared in modules.'], 55 | ]; 56 | } 57 | 58 | public function getTestsNew() 59 | { 60 | return [ 61 | [['delete users'], new \StdClass()], 62 | [['review articles'], new \StdClass(), VoterInterface::ACCESS_ABSTAIN], 63 | ]; 64 | } 65 | 66 | /** 67 | * @dataProvider getTests 68 | * 69 | * @param array $attributes 70 | * @param $expectedVote 71 | * @param $subject 72 | * @param $message 73 | */ 74 | public function testVote(array $attributes, $expectedVote, $subject, $message) 75 | { 76 | $voter = new PermissionsVoter(new AccessDecisionManager(), $this->permissionsHandler); 77 | $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); 78 | 79 | $this->assertSame($expectedVote, $voter->vote($token, $subject, $attributes), $message); 80 | 81 | if (VoterInterface::ACCESS_ABSTAIN !== $expectedVote) { 82 | $role = $this->createMockRole(); 83 | foreach ($attributes as $attribute) { 84 | $role->grantPermission($attribute); 85 | } 86 | $user = $this->createMockUser(); 87 | $user->addRole($role); 88 | $token->expects($this->atLeastOnce()) 89 | ->method('getUser') 90 | ->willReturn($user); 91 | 92 | $this->assertSame(VoterInterface::ACCESS_GRANTED, $voter->vote($token, $subject, $attributes)); 93 | 94 | foreach ($attributes as $attribute) { 95 | $role->revokePermission($attribute); 96 | } 97 | 98 | $this->assertSame(VoterInterface::ACCESS_DENIED, $voter->vote($token, $subject, $attributes)); 99 | } 100 | } 101 | 102 | /** 103 | * @dataProvider getTestsNew 104 | * 105 | * @param array $attributes 106 | * @param $subject 107 | * @param $expectedVote 108 | */ 109 | public function testSuperAdminPass(array $attributes, $subject, $expectedVote = VoterInterface::ACCESS_GRANTED) 110 | { 111 | $this->assertSame($expectedVote, $this->voter->vote($this->getToken([RoleInterface::ROLE_SUPER_ADMIN]), $subject, $attributes)); 112 | } 113 | 114 | protected function createPermissionsVoter() 115 | { 116 | return new PermissionsVoter($this->accessDecisionManager, $this->permissionsHandler); 117 | } 118 | 119 | protected function getToken(array $roles) 120 | { 121 | foreach ($roles as $i => $role) { 122 | $roles[$i] = $this->createMockRole()->setRole($role); 123 | } 124 | $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); 125 | $token->expects($this->any()) 126 | ->method('getRoles') 127 | ->will($this->returnValue($roles)); 128 | 129 | return $token; 130 | } 131 | 132 | protected function createMockRole() 133 | { 134 | return new TestRole(); 135 | } 136 | 137 | protected function createMockUser() 138 | { 139 | return new TestUser(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EloyekunlePermissionsBundle 2 | =========================== 3 | 4 | [![Build Status](https://travis-ci.org/eloyekunle/PermissionsBundle.svg?branch=master)](https://travis-ci.org/eloyekunle/PermissionsBundle) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/eloyekunle/PermissionsBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/eloyekunle/PermissionsBundle/?branch=master) 6 | [![Code Coverage](https://scrutinizer-ci.com/g/eloyekunle/PermissionsBundle/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/eloyekunle/PermissionsBundle/?branch=master) 7 | 8 | The EloyekunlePermissionsBundle provides support for a database-backed permissions system in Symfony2. 9 | It provides a flexible framework for permissions management that aims to handle common tasks such as flexible 10 | Permissions Definitions, Roles Creation and Authorization Checking (using Symfony Voters). 11 | 12 | Features include: 13 | 14 | - Roles can be stored via Doctrine ORM. 15 | - Flexible & Modular permissions definitions in YAML files. From few permissions to hundreds, this bundle has your back. 16 | - Symfony Voter for Authorization Checking. 17 | - Unit tested. 18 | 19 | CONTENTS 20 | -------- 21 | 22 | * [Installation](#installation) 23 | * [Usage](#usage) 24 | * [Contributions](#contributions) 25 | * [Support](#support) 26 | * [Credits](#credits) 27 | 28 | ## INSTALLATION 29 | Installation is a quick (I promise!) 5 step process: 30 | 31 | 1. [Download EloyekunlePermissionsBundle using composer](#step-1-download-the-bundle) 32 | 2. [Enable the Bundle](#step-2-enable-the-bundle) 33 | 3. [Create your Role class](#step-3-create-role-class) 34 | 4. [Configure your User class](#step-4-configure-your-user-class) 35 | 5. [Configure the bundle](#step-5-configure-the-bundle) 36 | 6. [Update your database schema](#step-5-update-your-database-schema) 37 | 38 | ### Step 1: Download the bundle 39 | 40 | Open a command console, enter your project directory and execute the 41 | following command to download the latest stable version of this bundle: 42 | 43 | ```bash 44 | $ composer require "eloyekunle/permissions-bundle" 45 | ``` 46 | 47 | This command requires you to have Composer installed globally, as explained 48 | in the [installation chapter](https://getcomposer.org/doc/00-intro.md) of the Composer documentation. 49 | 50 | ### Step 2: Enable the bundle 51 | 52 | Then, enable the bundle by adding the following line in the ``config/bundles.php`` 53 | file of your project, e.g. (Symfony >=4): 54 | 55 | ```php 56 | // config/bundles.php 57 | return [ 58 | // ... 59 | Eloyekunle\PermissionsBundle\EloyekunlePermissionsBundle::class => ['all' => true], 60 | ]; 61 | ``` 62 | 63 | ### Step 3: Create Role class 64 | 65 | The goal of this bundle is to persist some ``Role`` class to a database. 66 | Your first job, then, is to create the ``Role`` class 67 | for your application. This class can look and act however you want: add any 68 | properties or methods you find useful. This is *your* ``Role`` class. 69 | 70 | The bundle provides base classes which are already mapped for most fields 71 | to make it easier to create your entity. Here is how you use it: 72 | 73 | 1. Extend the base ``Role`` class (from the ``Model`` folder if you are using 74 | any of the doctrine variants) 75 | 2. Map the ``id`` field. It must be protected as it is inherited from the parent class. 76 | 3. When you extend from the mapped superclass provided by the bundle, don't 77 | redefine the mapping for the other fields as it is provided by the bundle. 78 | 4. If you override the __construct() method in your Role class, be sure 79 | to call parent::__construct(), as the base Role class depends on this to initialize some fields. 80 | 81 | #### Doctrine ORM Role class 82 | 83 | If you're persisting your roles via the Doctrine ORM, then your ``Role`` class 84 | should live in the ``Entity`` namespace of your bundle and look like this to 85 | start: 86 | 87 | ##### PHP 88 | 89 | ```php 90 | 234 | * array ( 235 | * 'title' => 'Edit Content', 236 | * 'description' => 'Grants permission to edit content.', 237 | * 'dependencies' => 238 | * array ( 239 | * 0 => 'view content', 240 | * ), 241 | * 'provider' => 'content', 242 | * ), 243 | * 'view content' => 244 | * array ( 245 | * 'title' => 'View Content', 246 | * 'description' => 'Grants permission to view content.', 247 | * 'provider' => 'content', 248 | * ), 249 | * ) 250 | */ 251 | $permissions = $permissionsHandler->getPermissions(); 252 | // ........................ 253 | } 254 | } 255 | ``` 256 | 257 | ### 2. Set up a Role 258 | 259 | The bundle ships with a [`RoleManager`](https://github.com/eloyekunle/PermissionsBundle/blob/master/Model/RoleManagerInterface.php) 260 | which is available as a service and can be injected into your Controllers/Services. It contains useful utility methods 261 | to manager roles. You can also `get` it from the container as `eloyekunle_permissions.role_manager`. 262 | 263 | ```php 264 | createRole(); 275 | $role->setRole('Content Admin'); 276 | 277 | $roleManager->updateRole($role); 278 | // ...... 279 | } 280 | } 281 | ``` 282 | 283 | This creates and persists a `Role` entity to your database. 284 | 285 | ### 3. Check Permissions in your Controllers 286 | 287 | The bundle ships with a [voter](https://symfony.com/doc/current/security/voters.html), [`PermissionsVoter`](https://github.com/eloyekunle/PermissionsBundle/blob/master/Security/PermissionsVoter.php). 288 | You can check for user permissions by using the `isGranted()` method on Symfony's authorization checker or call 289 | `denyAccessUnlessGranted()` in a controller. 290 | 291 | ```php 292 | denyAccessUnlessGranted('edit content'); 310 | // Get a Content object - e.g. query for it. 311 | // $content = ......; 312 | } 313 | 314 | /** 315 | * @Route("/content/{id}", name="content_show") 316 | */ 317 | public function show($id) 318 | { 319 | $this->denyAccessUnlessGranted('view content'); 320 | // Get a Content object - e.g. query for it. 321 | // $content = ......; 322 | } 323 | } 324 | ``` 325 | 326 | ## CONTRIBUTIONS 327 | 328 | Contributions of any kind are welcome: code, documentation, ideas etc. 329 | Issues and feature requests are tracked in the [Github issue tracker](https://github.com/eloyekunle/PermissionsBundle/issues). 330 | 331 | ## SUPPORT 332 | If you need any support related to this bundle, you can contact me on the [Symfony Slack group](http://symfony-devs.slack.com) 333 | (eloyekunle), or send me an email (eloyekunle@gmail.com). 334 | 335 | ## CREDITS 336 | - Bundle inspired by the [Drupal Permissions System](https://api.drupal.org/api/drupal/core!core.api.php/group/user_api/8.5.x)! 337 | - Implementation inspired by some excellent Symfony bundles, especially [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle). 338 | - [Elijah Oyekunle](https://elijahoyekunle.com) - [LinkedIn](https://www.linkedin.com/in/elijahoyekunle) - [Twitter](https://twitter.com/elijahoyekunle) - [Github](https://github.com/eloyekunle) -------------------------------------------------------------------------------- /Resources/config/schema/doctrine-mapping.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | --------------------------------------------------------------------------------