Skip to content

Commit 456b20a

Browse files
authored
[code-quality] Add SplitAndSecurityAttributeToIsGrantedRector (#715)
1 parent 24414a6 commit 456b20a

File tree

7 files changed

+205
-1
lines changed

7 files changed

+205
-1
lines changed

config/sets/symfony/symfony-code-quality.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
use Rector\Symfony\CodeQuality\Rector\Class_\EventListenerToEventSubscriberRector;
1010
use Rector\Symfony\CodeQuality\Rector\Class_\InlineClassRoutePrefixRector;
1111
use Rector\Symfony\CodeQuality\Rector\Class_\LoadValidatorMetadataToAnnotationRector;
12+
use Rector\Symfony\CodeQuality\Rector\Class_\SplitAndSecurityAttributeToIsGrantedRector;
1213
use Rector\Symfony\CodeQuality\Rector\ClassMethod\ActionSuffixRemoverRector;
1314
use Rector\Symfony\CodeQuality\Rector\ClassMethod\ParamTypeFromRouteRequiredRegexRector;
1415
use Rector\Symfony\CodeQuality\Rector\ClassMethod\RemoveUnusedRequestParamRector;
1516
use Rector\Symfony\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector;
1617
use Rector\Symfony\CodeQuality\Rector\MethodCall\AssertSameResponseCodeWithDebugContentsRector;
1718
use Rector\Symfony\CodeQuality\Rector\MethodCall\LiteralGetToRequestClassConstantRector;
1819
use Rector\Symfony\Symfony26\Rector\MethodCall\RedirectToRouteRector;
19-
use Rector\Symfony\Symfony62\Rector\Class_\SecurityAttributeToIsGrantedAttributeRector;
2020

2121
return static function (RectorConfig $rectorConfig): void {
2222
$rectorConfig->rules([
@@ -43,5 +43,6 @@
4343

4444
// narrow attributes
4545
SingleConditionSecurityAttributeToIsGrantedRector::class,
46+
SplitAndSecurityAttributeToIsGrantedRector::class,
4647
]);
4748
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Rector\Symfony\Tests\CodeQuality\Rector\Class_\SplitAndSecurityAttributeToIsGrantedRector\Fixture;
4+
5+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
6+
7+
#[Security("(is_granted('ROLE_USER') and is_granted('ROLE_ADMIN')) or is_granted('ROLE_SUDO')")]
8+
final class TwoSecurityAttributes
9+
{
10+
11+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Rector\Symfony\Tests\CodeQuality\Rector\Class_\SplitAndSecurityAttributeToIsGrantedRector\Fixture;
4+
5+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
6+
7+
#[Security("is_granted('ROLE_USER') and is_granted('ROLE_ADMIN')")]
8+
final class TwoSecurityAttributes
9+
{
10+
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\Symfony\Tests\CodeQuality\Rector\Class_\SplitAndSecurityAttributeToIsGrantedRector\Fixture;
18+
19+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
20+
21+
#[\Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted('ROLE_USER')]
22+
#[\Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted('ROLE_ADMIN')]
23+
final class TwoSecurityAttributes
24+
{
25+
}
26+
27+
?>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Tests\CodeQuality\Rector\Class_\SplitAndSecurityAttributeToIsGrantedRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class SplitAndSecurityAttributeToIsGrantedRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Symfony\CodeQuality\Rector\Class_\SplitAndSecurityAttributeToIsGrantedRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(SplitAndSecurityAttributeToIsGrantedRector::class);
10+
};

rules/CodeQuality/Rector/AttributeGroup/SingleConditionSecurityAttributeToIsGrantedRector.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
/**
2020
* @see https://github.com/symfony/symfony/pull/27305/
2121
* @see https://stackoverflow.com/a/65439590/1348344
22+
*
23+
* @see \Rector\Symfony\Tests\CodeQuality\Rector\AttributeGroup\SingleConditionSecurityAttributeToIsGrantedRector\SingleConditionSecurityAttributeToIsGrantedRectorTest
2224
*/
2325
final class SingleConditionSecurityAttributeToIsGrantedRector extends AbstractRector
2426
{
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\CodeQuality\Rector\Class_;
6+
7+
use PhpParser\Node\Name\FullyQualified;
8+
use Nette\Utils\Strings;
9+
use PhpParser\Node;
10+
use PhpParser\Node\Arg;
11+
use PhpParser\Node\Attribute;
12+
use PhpParser\Node\AttributeGroup;
13+
use PhpParser\Node\Scalar\String_;
14+
use PhpParser\Node\Stmt\Class_;
15+
use PhpParser\Node\Stmt\ClassMethod;
16+
use Rector\Rector\AbstractRector;
17+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
18+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
19+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
20+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
21+
22+
final class SplitAndSecurityAttributeToIsGrantedRector extends AbstractRector
23+
{
24+
public function getRuleDefinition(): RuleDefinition
25+
{
26+
return new RuleDefinition(
27+
'Split #[Security] attribute with "and" condition string to multiple #[IsGranted] attributes with sole values',
28+
[
29+
new CodeSample(
30+
<<<'CODE_SAMPLE'
31+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
32+
33+
#[Security("is_granted('ROLE_USER') and has_role('ROLE_ADMIN')")]
34+
class SomeClass
35+
{
36+
}
37+
CODE_SAMPLE
38+
,
39+
<<<'CODE_SAMPLE'
40+
use Symfony\Component\Security\Http\Attribute\IsGranted;
41+
42+
#[IsGranted('ROLE_USER')]
43+
#[IsGranted('ROLE_ADMIN')]
44+
class SomeClass
45+
{
46+
}
47+
CODE_SAMPLE
48+
),
49+
50+
]
51+
);
52+
}
53+
54+
public function getNodeTypes(): array
55+
{
56+
return [Class_::class, ClassMethod::class];
57+
}
58+
59+
/**
60+
* @param Class_|ClassMethod $node
61+
*/
62+
public function refactor(Node $node): ?Node
63+
{
64+
$hasChanged = false;
65+
66+
foreach ($node->attrGroups as $key => $attrGroup) {
67+
foreach ($attrGroup->attrs as $attr) {
68+
if (! $this->isName($attr->name, Security::class)) {
69+
continue;
70+
}
71+
72+
$firstArgValue = $attr->args[0]->value;
73+
if (! $firstArgValue instanceof String_) {
74+
continue;
75+
}
76+
77+
$content = $firstArgValue->value;
78+
79+
// unable to resolve with pure attributes
80+
if (str_contains($content, ' or ')) {
81+
continue;
82+
}
83+
84+
// we look for "and"s
85+
if (! str_contains($content, ' and')) {
86+
continue;
87+
}
88+
89+
$andItems = explode(' and ', $content);
90+
91+
$accessRights = [];
92+
93+
foreach ($andItems as $andItem) {
94+
$matches = Strings::match($andItem, '#^(is_granted|has_role)\(\'(?<access_right>[A-Za-z_]+)\'\)$#');
95+
if (! isset($matches['access_right'])) {
96+
// all or nothing
97+
return null;
98+
}
99+
100+
$accessRights[] = $matches['access_right'];
101+
}
102+
103+
unset($node->attrGroups[$key]);
104+
105+
$hasChanged = true;
106+
107+
foreach ($accessRights as $accessRight) {
108+
$attributeGroup = new AttributeGroup([
109+
new Attribute(new FullyQualified(IsGranted::class), [
110+
new Arg(new String_($accessRight)),
111+
]),
112+
]);
113+
114+
$node->attrGroups[] = $attributeGroup;
115+
}
116+
}
117+
}
118+
119+
if ($hasChanged) {
120+
return $node;
121+
}
122+
123+
return null;
124+
}
125+
}

0 commit comments

Comments
 (0)