Skip to content
This repository has been archived by the owner on Nov 16, 2020. It is now read-only.

Commit

Permalink
allow access annotation without admin annotation present, update read…
Browse files Browse the repository at this point in the history
…me and tests
  • Loading branch information
kunicmarko20 committed Mar 25, 2018
1 parent 78763f5 commit b3de700
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 65 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ class Category
If you are using role handler as described [here](https://sonata-project.org/bundles/admin/3-x/doc/reference/security.html#role-handler)
you can add permission per role with this annotation.

>This annotation can be used without Admin annotation present. If you have an admin class for your entity
you can still use this annotation.

```php
<?php

Expand Down Expand Up @@ -298,6 +301,12 @@ class Category
* fieldDescriptionOptions={}
* )
*
* @Sonata\ShowAssociationField(
* field="email",
* type="",
* fieldDescriptionOptions={}
* )
*
* @ORM\ManyToOne(targetEntity="Owner")
* @ORM\JoinColumn(name="owner_id", referencedColumnName="id")
*/
Expand Down Expand Up @@ -378,6 +387,13 @@ class Category
* fieldDescriptionOptions={},
* identifier=false
* )
*
* @Sonata\ListAssociationField(
* field="email",
* type="",
* fieldDescriptionOptions={},
* identifier=false
* )
*
* @ORM\ManyToOne(targetEntity="Owner")
* @ORM\JoinColumn(name="owner_id", referencedColumnName="id")
Expand Down Expand Up @@ -448,6 +464,15 @@ class Category
* fieldOptions={}
* )
*
* @Sonata\DatagridAssociationField(
* field="email",
* type="",
* fieldDescriptionOptions={},
* filterOptions={},
* fieldType="",
* fieldOptions={}
* )
*
* @ORM\ManyToOne(targetEntity="Owner")
* @ORM\JoinColumn(name="owner_id", referencedColumnName="id")
*/
Expand Down Expand Up @@ -524,6 +549,11 @@ class Category
* label="Owner"
* )
*
* @Sonata\ExportAssociationField(
* field="email",
* label="Email"
* )
*
* @ORM\ManyToOne(targetEntity="Owner")
* @ORM\JoinColumn(name="owner_id", referencedColumnName="id")
*/
Expand Down
92 changes: 92 additions & 0 deletions src/DependencyInjection/Compiler/AccessCompilerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace KunicMarko\SonataAnnotationBundle\DependencyInjection\Compiler;

use Doctrine\Common\Annotations\Reader;
use KunicMarko\SonataAnnotationBundle\Annotation\Access;
use KunicMarko\SonataAnnotationBundle\Annotation\Admin;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

/**
* @author Marko Kunic <[email protected]>
*/
final class AccessCompilerPass implements CompilerPassInterface
{
private const ENTITY_ARGUMENT_IN_SERVICE_DEFINITION = 1;

/**
* @var Reader
*/
private $annotationReader;

public function process(ContainerBuilder $container): void
{
$this->annotationReader = $container->get('annotation_reader');
$roles = $container->getParameter('security.role_hierarchy.roles');

foreach ($container->findTaggedServiceIds('sonata.admin') as $id => $tag) {
if (!($class = $this->getClass($container, $id))) {
continue;
}

if ($permissions = $this->getRoles(new \ReflectionClass($class), $this->getRolePrefix($id))) {
$roles = array_merge_recursive($roles, $permissions);
}
}

$container->setParameter('security.role_hierarchy.roles', $roles);
}

private function getClass(ContainerBuilder $container, string $id): ?string
{
$definition = $container->getDefinition($id);

//Entity can be a class or a parameter
$class = $definition->getArgument(self::ENTITY_ARGUMENT_IN_SERVICE_DEFINITION);

if ($class[0] !== '%') {
return $class;
}

if ($container->hasParameter($class = trim($class, '%'))) {
return $container->getParameter($class);
}

throw new \LogicException(sprintf(
'Service "%s" has a parameter "%s" as an argument but it is not found.',
$id,
$class
));
}

private function getRolePrefix(string $serviceId): string
{
return 'ROLE_' . str_replace('.', '_', strtoupper($serviceId)) . '_';
}

private function getRoles(\ReflectionClass $class, string $prefix): array
{
$roles = [];

foreach ($this->annotationReader->getClassAnnotations($class) as $annotation) {
if (!$annotation instanceof Access) {
continue;
}

$roles[$annotation->getRole()] = array_map(
function (string $permission) use ($prefix) {
return $prefix . strtoupper($permission);
},
$annotation->permissions
);
}

return $roles;
}
}
32 changes: 0 additions & 32 deletions src/DependencyInjection/Compiler/AutoRegisterCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ final class AutoRegisterCompilerPass implements CompilerPassInterface
public function process(ContainerBuilder $container): void
{
$this->annotationReader = $container->get('annotation_reader');
$roles = $container->getParameter('security.role_hierarchy.roles');

foreach ($this->findFiles($container->getParameter('sonata_annotation.directory')) as $file) {
$className = $this->getFullyQualifiedClassName($file);
Expand All @@ -48,13 +47,7 @@ public function process(ContainerBuilder $container): void
$serviceId = ($annotation->serviceId ?? $this->getServiceId($file)),
$definition
);

if ($permissions = $this->getRoles($reflection, $this->getRolePrefix($serviceId))) {
$roles = array_merge_recursive($roles, $permissions);
}
}

$container->setParameter('security.role_hierarchy.roles', $roles);
}

private function findFiles(string $directory): \IteratorAggregate
Expand Down Expand Up @@ -94,29 +87,4 @@ private function getServiceId(SplFileInfo $file): string
{
return self::DEFAULT_SERVICE_PREFIX . $this->getClassName($file->getFilename());
}

private function getRolePrefix(string $serviceId): string
{
return 'ROLE_' . str_replace('.', '_', strtoupper($serviceId)) . '_';
}

private function getRoles(\ReflectionClass $class, string $prefix): array
{
$roles = [];

foreach ($this->annotationReader->getClassAnnotations($class) as $annotation) {
if (!$annotation instanceof Access) {
continue;
}

$roles[$annotation->getRole()] = array_map(
function (string $permission) use ($prefix) {
return $prefix . strtoupper($permission);
},
$annotation->permissions
);
}

return $roles;
}
}
5 changes: 3 additions & 2 deletions src/SonataAnnotationBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace KunicMarko\SonataAnnotationBundle;

use KunicMarko\SonataAnnotationBundle\DependencyInjection\Compiler\AccessCompilerPass;
use KunicMarko\SonataAnnotationBundle\DependencyInjection\Compiler\AutoRegisterCompilerPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand All @@ -16,7 +17,7 @@ final class SonataAnnotationBundle extends Bundle
{
public function build(ContainerBuilder $container): void
{
$container
->addCompilerPass(new AutoRegisterCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1);
$container->addCompilerPass(new AutoRegisterCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1);
$container->addCompilerPass(new AccessCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1);
}
}
89 changes: 89 additions & 0 deletions tests/DependencyInjection/Compiler/AccessCompilerPassTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

namespace KunicMarko\SonataAnnotationBundle\Tests\DependencyInjection\Compiler;

use Doctrine\Common\Annotations\AnnotationReader;
use KunicMarko\SonataAnnotationBundle\DependencyInjection\Compiler\AccessCompilerPass;
use KunicMarko\SonataAnnotationBundle\DependencyInjection\Compiler\AutoRegisterCompilerPass;
use KunicMarko\SonataAnnotationBundle\Tests\Fixtures\AccessExceptionClass;
use KunicMarko\SonataAnnotationBundle\Tests\Fixtures\AnnotationClass;
use KunicMarko\SonataAnnotationBundle\Tests\Fixtures\AnnotationExceptionClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

/**
* @author Marko Kunic <[email protected]>
*/
final class AccessCompilerPassTest extends TestCase
{
/**
* @var ContainerBuilder
*/
private $container;

protected function setUp(): void
{
$this->container = new ContainerBuilder();
$this->container->set('annotation_reader', new AnnotationReader());
$this->container->setParameter('security.role_hierarchy.roles', []);
}

public function testProcess(): void
{
$this->initAdminClasses();

$accessCompilerPass = new AccessCompilerPass();
$accessCompilerPass->process($this->container);

$this->assertArrayHasKey('ROLE_VENDOR', $roles = $this->container->getParameter('security.role_hierarchy.roles'));
$this->assertContains('ROLE_APP_ADMIN_ANNOTATIONCLASS_LIST', $roles['ROLE_VENDOR']);
$this->assertContains('ROLE_APP_ADMIN_ANNOTATIONEXCEPTIONCLASS_ALL', $roles['ROLE_VENDOR']);
}

private function initAdminClasses(
$classes = [AnnotationClass::class, AnnotationExceptionClass::class, null]
) {
foreach ($classes as $key => $class) {
$definition = new Definition(
'test',
[null, $class, null]
);

$definition->addTag('sonata.admin', []);

$className = explode("\\", $class ?? '');

$this->container->setDefinition(
'app.admin.' . end($className),
$definition
);
}
}

/**
* @expectedException \LogicException
* @expectedExceptionMessage Service "app.admin.%class%" has a parameter "class" as an argument but it is not found.
*/
public function testProcessExceptionParamNotFound(): void
{
$this->initAdminClasses(['%class%']);

$accessCompilerPass = new AccessCompilerPass();
$accessCompilerPass->process($this->container);
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Argument "role" is mandatory in "KunicMarko\SonataAnnotationBundle\Annotation\Access" annotation.
*/
public function testProcessExceptionAnnotation(): void
{
$this->initAdminClasses([AccessExceptionClass::class]);

$accessCompilerPass = new AccessCompilerPass();
$accessCompilerPass->process($this->container);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ protected function setUp(): void
{
$this->container = new ContainerBuilder();
$this->container->set('annotation_reader', new AnnotationReader());
$this->container->setParameter('security.role_hierarchy.roles', []);
$this->container->setParameter('sonata_annotation.directory', __DIR__ . '/../../Fixtures');
}

/**
* @dataProvider processData
*/
public function testProcess(string $serviceId, string $class, ?string $label, string $role): void
public function testProcess(string $serviceId, string $class, ?string $label): void
{
$autoRegisterCompilerPass = new AutoRegisterCompilerPass();

Expand All @@ -51,9 +50,6 @@ public function testProcess(string $serviceId, string $class, ?string $label, st
$this->assertSame('orm', $attributes[0]['manager_type']);
$this->assertSame($label, $attributes[0]['label']);

$this->assertArrayHasKey('ROLE_VENDOR', $roles = $this->container->getParameter('security.role_hierarchy.roles'));
$this->assertContains($role, $roles['ROLE_VENDOR']);

$this->assertCount(2, $this->container->findTaggedServiceIds('sonata.admin'));
}

Expand All @@ -64,37 +60,12 @@ public function processData(): array
'app.admin.AnnotationClass',
AnnotationClass::class,
'Test',
'ROLE_APP_ADMIN_ANNOTATIONCLASS_LIST',
],
[
'test.the.id',
AnnotationExceptionClass::class,
null,
'ROLE_TEST_THE_ID_ALL',
],
];
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Argument "role" is mandatory in "KunicMarko\SonataAnnotationBundle\Annotation\Access" annotation.
*/
public function testProcessException(): void
{
copy(
($directory = __DIR__ . '/../../Fixtures/'). 'AccessExceptionClass.php.dist',
$directory . 'AccessExceptionClass.php'
);

$autoRegisterCompilerPass = new AutoRegisterCompilerPass();

$autoRegisterCompilerPass->process($this->container);
}

protected function tearDown(): void
{
if (file_exists($file = __DIR__ . '/../../Fixtures/AccessExceptionClass.php')) {
unlink($file);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use KunicMarko\SonataAnnotationBundle\Annotation as Sonata;

/**
* @Sonata\Admin("Test")
* @Sonata\Access()
*
* @author Marko Kunic <[email protected]>
Expand Down
Loading

0 comments on commit b3de700

Please sign in to comment.