Skip to content

Commit

Permalink
Restrict mappers from configured role
Browse files Browse the repository at this point in the history
  • Loading branch information
phansys authored and greg0ire committed Jun 19, 2019
1 parent 7619f20 commit 1cd1d01
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 24 deletions.
37 changes: 29 additions & 8 deletions docs/reference/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,21 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ
'class' => User::class
])

// "privateNotes" field will be rendered only if the authenticated
// user is granted with the "ROLE_ADMIN_MODERATOR" role
->add('privateNotes', null, [], [
'role' => 'ROLE_ADMIN_MODERATOR'
])

// if no type is specified, SonataAdminBundle tries to guess it
->add('body')

// conditionally add "status" field if the subject already exists
// `ifFalse()` is also available to build this kind of condition
->ifTrue($this->hasSubject())
->add('status')
->ifEnd()

// ...
;
}
Expand All @@ -209,6 +221,9 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ
$datagridMapper
->add('title')
->add('author')
->add('privateNotes', null, [], null, null, [
'role' => 'ROLE_ADMIN_MODERATOR'
])
;
}

Expand All @@ -219,6 +234,9 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ
->addIdentifier('title')
->add('slug')
->add('author')
->add('privateNotes', null, [
'role' => 'ROLE_ADMIN_MODERATOR'
])
;
}

Expand All @@ -230,21 +248,24 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ
->add('title')
->add('slug')
->add('author')
->add('privateNotes', null, [
'role' => 'ROLE_ADMIN_MODERATOR'
])
;
}
}

Internally, the provided ``Admin`` class will use these three functions to create three
``FieldDescriptionCollection`` instances:

* ``$formFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances
for title, author and body
* ``$filterFieldDescriptions``, containing two ``FieldDescriptionInterface`` instances
for title and author
* ``$listFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances
for title, slug and author
* ``$showFieldDescriptions``, containing four ``FieldDescriptionInterface`` instances
for id, title, slug and author
* ``$formFieldDescriptions``, containing four (and conditionally five) ``FieldDescriptionInterface``
instances for title, author, body and privateNotes (and status, if the condition is met)
* ``$filterFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances
for title, author and privateNotes
* ``$listFieldDescriptions``, containing four ``FieldDescriptionInterface`` instances
for title, slug, author and privateNotes
* ``$showFieldDescriptions``, containing five ``FieldDescriptionInterface`` instances
for id, title, slug, author and privateNotes

The actual ``FieldDescription`` implementation is provided by the storage abstraction
bundle that you choose during the installation process, based on the
Expand Down
6 changes: 4 additions & 2 deletions src/Datagrid/DatagridMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ public function add(
);
}

// add the field with the DatagridBuilder
$this->builder->addFilter($this->datagrid, $type, $fieldDescription, $this->admin);
if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) {
// add the field with the DatagridBuilder
$this->builder->addFilter($this->datagrid, $type, $fieldDescription, $this->admin);
}

return $this;
}
Expand Down
6 changes: 4 additions & 2 deletions src/Datagrid/ListMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ public function add($name, $type = null, array $fieldDescriptionOptions = [])
);
}

// add the field with the FormBuilder
$this->builder->addField($this->list, $type, $fieldDescription, $this->admin);
if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) {
// add the field with the FormBuilder
$this->builder->addField($this->list, $type, $fieldDescription, $this->admin);
}

return $this;
}
Expand Down
17 changes: 11 additions & 6 deletions src/Form/FormMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,13 @@ public function add($name, $type = null, array $options = [], array $fieldDescri
$this->admin->addFormFieldDescription($fieldName, $fieldDescription);

if ($name instanceof FormBuilderInterface) {
$this->formBuilder->add($name);
$type = null;
$options = [];
} else {
$name = $fieldDescription->getName();

// Note that the builder var is actually the formContractor:
$options = array_replace_recursive($this->builder->getDefaultOptions($type, $fieldDescription), $options);
$options = array_replace_recursive($this->builder->getDefaultOptions($type, $fieldDescription) ?? [], $options);

// be compatible with mopa if not installed, avoid generating an exception for invalid option
// force the default to false ...
Expand All @@ -125,7 +128,7 @@ public function add($name, $type = null, array $options = [], array $fieldDescri
}

if (!isset($options['label'])) {
$options['label'] = $this->admin->getLabelTranslatorStrategy()->getLabel($fieldDescription->getName(), 'form', 'label');
$options['label'] = $this->admin->getLabelTranslatorStrategy()->getLabel($name, 'form', 'label');
}

$help = null;
Expand All @@ -134,13 +137,15 @@ public function add($name, $type = null, array $options = [], array $fieldDescri
unset($options['help']);
}

$this->formBuilder->add($fieldDescription->getName(), $type, $options);

if (null !== $help) {
$this->admin->getFormFieldDescription($fieldDescription->getName())->setHelp($help);
$this->admin->getFormFieldDescription($name)->setHelp($help);
}
}

if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) {
$this->formBuilder->add($name, $type, $options);
}

return $this;
}

Expand Down
6 changes: 4 additions & 2 deletions src/Show/ShowMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ public function add($name, $type = null, array $fieldDescriptionOptions = [])

$fieldDescription->setOption('safe', $fieldDescription->getOption('safe', false));

// add the field with the FormBuilder
$this->builder->addField($this->list, $type, $fieldDescription, $this->admin);
if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) {
// add the field with the FormBuilder
$this->builder->addField($this->list, $type, $fieldDescription, $this->admin);
}

return $this;
}
Expand Down
31 changes: 30 additions & 1 deletion tests/Datagrid/DatagridMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
*/
class DatagridMapperTest extends TestCase
{
private const DEFAULT_GRANTED_ROLE = 'ROLE_ADMIN_BAZ';

/**
* @var DatagridMapper
*/
Expand All @@ -43,7 +45,7 @@ class DatagridMapperTest extends TestCase
*/
private $datagrid;

public function setUp(): void
protected function setUp(): void
{
$datagridBuilder = $this->createMock(DatagridBuilderInterface::class);

Expand Down Expand Up @@ -89,6 +91,12 @@ public function setUp(): void
->method('getModelManager')
->willReturn($modelManager);

$admin->expects($this->any())
->method('isGranted')
->willReturnCallback(static function (string $name, object $object = null): bool {
return self::DEFAULT_GRANTED_ROLE === $name;
});

$this->datagridMapper = new DatagridMapper($datagridBuilder, $this->datagrid, $admin);
}

Expand Down Expand Up @@ -263,6 +271,27 @@ public function testReorder(): void
], array_keys($this->datagrid->getFilters()));
}

public function testAddOptionRole(): void
{
$this->datagridMapper->add('bar', 'bar');

$this->assertTrue($this->datagridMapper->has('bar'));

$this->datagridMapper->add('quux', 'bar', [], null, null, ['role' => 'ROLE_QUX']);

$this->assertTrue($this->datagridMapper->has('bar'));
$this->assertFalse($this->datagridMapper->has('quux'));

$this->datagridMapper
->add('foobar', 'bar', [], null, null, ['role' => self::DEFAULT_GRANTED_ROLE])
->add('foo', 'bar', [], null, null, ['role' => 'ROLE_QUX'])
->add('baz', 'bar');

$this->assertTrue($this->datagridMapper->has('foobar'));
$this->assertFalse($this->datagridMapper->has('foo'));
$this->assertTrue($this->datagridMapper->has('baz'));
}

private function getFieldDescriptionMock(?string $name = null, ?string $label = null): BaseFieldDescription
{
$fieldDescription = $this->getMockForAbstractClass(BaseFieldDescription::class);
Expand Down
31 changes: 30 additions & 1 deletion tests/Datagrid/ListMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
*/
class ListMapperTest extends TestCase
{
private const DEFAULT_GRANTED_ROLE = 'ROLE_ADMIN_BAZ';

/**
* @var ListMapper
*/
Expand All @@ -44,7 +46,7 @@ class ListMapperTest extends TestCase
*/
private $admin;

public function setUp(): void
protected function setUp(): void
{
$listBuilder = $this->createMock(ListBuilderInterface::class);
$this->fieldDescriptionCollection = new FieldDescriptionCollection();
Expand Down Expand Up @@ -78,6 +80,12 @@ public function setUp(): void
->method('getLabelTranslatorStrategy')
->willReturn($labelTranslatorStrategy);

$this->admin->expects($this->any())
->method('isGranted')
->willReturnCallback(static function (string $name, object $object = null): bool {
return self::DEFAULT_GRANTED_ROLE === $name;
});

$this->listMapper = new ListMapper($listBuilder, $this->fieldDescriptionCollection, $this->admin);
}

Expand Down Expand Up @@ -257,6 +265,27 @@ public function testReorder(): void
], true), print_r($this->fieldDescriptionCollection->getElements(), true));
}

public function testAddOptionRole(): void
{
$this->listMapper->add('bar', 'bar');

$this->assertTrue($this->listMapper->has('bar'));

$this->listMapper->add('quux', 'bar', ['role' => 'ROLE_QUX']);

$this->assertTrue($this->listMapper->has('bar'));
$this->assertFalse($this->listMapper->has('quux'));

$this->listMapper
->add('foobar', 'bar', ['role' => self::DEFAULT_GRANTED_ROLE])
->add('foo', 'bar', ['role' => 'ROLE_QUX'])
->add('baz', 'bar');

$this->assertTrue($this->listMapper->has('foobar'));
$this->assertFalse($this->listMapper->has('foo'));
$this->assertTrue($this->listMapper->has('baz'));
}

private function getFieldDescriptionMock(?string $name = null, ?string $label = null): BaseFieldDescription
{
$fieldDescription = $this->getMockForAbstractClass(BaseFieldDescription::class);
Expand Down
40 changes: 39 additions & 1 deletion tests/Form/FormMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Sonata\AdminBundle\Builder\FormContractorInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
use Sonata\AdminBundle\Tests\Fixtures\Admin\CleanAdmin;
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Expand All @@ -29,6 +30,8 @@

class FormMapperTest extends TestCase
{
private const DEFAULT_GRANTED_ROLE = 'ROLE_ADMIN_BAZ';

/**
* @var FormContractorInterface
*/
Expand All @@ -49,7 +52,7 @@ class FormMapperTest extends TestCase
*/
protected $formMapper;

public function setUp(): void
protected function setUp(): void
{
$this->contractor = $this->createMock(FormContractorInterface::class);

Expand All @@ -59,6 +62,15 @@ public function setUp(): void
$formBuilder = new FormBuilder('test', 'stdClass', $eventDispatcher, $formFactory);

$this->admin = new CleanAdmin('code', 'class', 'controller');
$securityHandler = $this->createMock(SecurityHandlerInterface::class);
$securityHandler
->expects($this->any())
->method('isGranted')
->willReturnCallback(static function (AdminInterface $admin, $attributes, $object = null): bool {
return self::DEFAULT_GRANTED_ROLE === $attributes;
});

$this->admin->setSecurityHandler($securityHandler);

$this->modelManager = $this->getMockForAbstractClass(ModelManagerInterface::class);

Expand Down Expand Up @@ -117,6 +129,7 @@ public function testWithOptions(): void
{
$this->formMapper->with('foobar', [
'translation_domain' => 'Foobar',
'role' => self::DEFAULT_GRANTED_ROLE,
]);

$this->assertSame(['foobar' => [
Expand All @@ -128,6 +141,7 @@ public function testWithOptions(): void
'name' => 'foobar',
'box_class' => 'box box-primary',
'fields' => [],
'role' => self::DEFAULT_GRANTED_ROLE,
]], $this->admin->getFormGroups());

$this->assertSame(['default' => [
Expand Down Expand Up @@ -454,6 +468,30 @@ public function testFieldNameIsSanitized(): void
$this->assertSame(['fo__o', 'ba____z'], $this->formMapper->keys());
}

public function testAddOptionRole(): void
{
$this->formMapper->add('bar', 'bar');

$this->assertTrue($this->formMapper->has('bar'));

$this->formMapper->add('quux', 'bar', [], ['role' => 'ROLE_QUX']);

$this->assertTrue($this->formMapper->has('bar'));
$this->assertFalse($this->formMapper->has('quux'));

$this->formMapper
->with('qux')
->add('foobar', 'bar', [], ['role' => self::DEFAULT_GRANTED_ROLE])
->add('foo', 'bar', [], ['role' => 'ROLE_QUX'])
->add('baz', 'bar')
->end();

$this->assertArrayHasKey('qux', $this->admin->getFormGroups());
$this->assertTrue($this->formMapper->has('foobar'));
$this->assertFalse($this->formMapper->has('foo'));
$this->assertTrue($this->formMapper->has('baz'));
}

private function getFieldDescriptionMock(
?string $name = null,
?string $label = null,
Expand Down

0 comments on commit 1cd1d01

Please sign in to comment.