Skip to content

Commit 8522a3c

Browse files
committed
Report missing explicit access check on entity queries
1 parent 479217b commit 8522a3c

13 files changed

+236
-6
lines changed

extension.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ rules:
259259
- mglaman\PHPStanDrupal\Rules\Deprecations\PluginAnnotationContextDefinitionsRule
260260
- mglaman\PHPStanDrupal\Rules\Drupal\ModuleLoadInclude
261261
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
262+
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
262263
services:
263264
-
264265
class: mglaman\PHPStanDrupal\Drupal\ServiceMap
@@ -286,6 +287,9 @@ services:
286287
-
287288
class: mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryDynamicReturnTypeExtension
288289
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
290+
-
291+
class: mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryHasAccessCheckDynamicReturnTypeExtension
292+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
289293
-
290294
class: mglaman\PHPStanDrupal\Type\UrlToStringDynamicReturnTypeExtension
291295
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery;
6+
7+
use mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryExecuteDoesNotHaveAccessCheckCountType;
8+
use mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryExecuteDoesNotHaveAccessCheckType;
9+
use mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryType;
10+
use PhpParser\Node;
11+
use PHPStan\Analyser\Scope;
12+
use PHPStan\Rules\Rule;
13+
use PHPStan\Rules\RuleErrorBuilder;
14+
use function PHPStan\dumpType;
15+
16+
final class EntityQueryHasAccessCheckRule implements Rule
17+
{
18+
public function getNodeType(): string
19+
{
20+
return \PhpParser\Node\Expr\MethodCall::class;
21+
}
22+
23+
public function processNode(Node $node, Scope $scope): array
24+
{
25+
if (!$node instanceof Node\Expr\MethodCall) {
26+
return [];
27+
}
28+
29+
$name = $node->name;
30+
if (!$name instanceof Node\Identifier) {
31+
return [];
32+
}
33+
if ($name->toString() !== 'execute') {
34+
return [];
35+
}
36+
37+
$type = $scope->getType($node);
38+
39+
if (!$type instanceof EntityQueryExecuteDoesNotHaveAccessCheckCountType && !$type instanceof EntityQueryExecuteDoesNotHaveAccessCheckType) {
40+
return [];
41+
}
42+
43+
return [
44+
RuleErrorBuilder::message(
45+
'Missing explicit access check on entity query.'
46+
)->tip('See https://www.drupal.org/node/3201242')->build(),
47+
];
48+
}
49+
}

src/Type/EntityQuery/ConfigEntityQueryType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* Type used to represent an entity query instance for config entity query.
99
*/
10-
final class ConfigEntityQueryType extends ObjectType
10+
final class ConfigEntityQueryType extends EntityQueryType
1111
{
1212

1313
}

src/Type/EntityQuery/ContentEntityQueryType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* Type used to represent an entity query instance for content entity query.
99
*/
10-
final class ContentEntityQueryType extends ObjectType
10+
final class ContentEntityQueryType extends EntityQueryType
1111
{
1212

1313
}

src/Type/EntityQuery/EntityQueryCountType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* Type used to represent an entity query instance as count query.
99
*/
10-
final class EntityQueryCountType extends ObjectType
10+
final class EntityQueryCountType extends EntityQueryType
1111
{
1212

1313
}

src/Type/EntityQuery/EntityQueryDynamicReturnTypeExtension.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,19 @@ public function getTypeFromMethodCall(
5252

5353
if ($methodName === 'execute') {
5454
if ($varType instanceof EntityQueryCountType) {
55-
return new IntegerType();
55+
return $varType->hasAccessCheck()
56+
? new IntegerType()
57+
: new EntityQueryExecuteDoesNotHaveAccessCheckCountType();
5658
}
5759
if ($varType instanceof ConfigEntityQueryType) {
58-
return new ArrayType(new StringType(), new StringType());
60+
return $varType->hasAccessCheck()
61+
? new ArrayType(new StringType(), new StringType())
62+
: new EntityQueryExecuteDoesNotHaveAccessCheckType(new StringType(), new StringType());
5963
}
6064
if ($varType instanceof ContentEntityQueryType) {
61-
return new ArrayType(new IntegerType(), new StringType());
65+
return $varType->hasAccessCheck()
66+
? new ArrayType(new IntegerType(), new StringType())
67+
: new EntityQueryExecuteDoesNotHaveAccessCheckType(new IntegerType(), new StringType());
6268
}
6369
return $defaultReturnType;
6470
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Type\EntityQuery;
6+
7+
use PHPStan\Type\IntegerType;
8+
9+
final class EntityQueryExecuteDoesNotHaveAccessCheckCountType extends IntegerType
10+
{
11+
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Type\EntityQuery;
6+
7+
use PHPStan\Type\ArrayType;
8+
9+
use PHPStan\Type\StringType;
10+
11+
final class EntityQueryExecuteDoesNotHaveAccessCheckType extends ArrayType
12+
{
13+
14+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Type\EntityQuery;
6+
7+
use Drupal\Core\Entity\Query\QueryInterface;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Reflection\MethodReflection;
11+
use PHPStan\Reflection\ParametersAcceptorSelector;
12+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
13+
use PHPStan\Type\Type;
14+
15+
class EntityQueryHasAccessCheckDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
16+
{
17+
public function getClass(): string
18+
{
19+
return QueryInterface::class;
20+
}
21+
22+
public function isMethodSupported(MethodReflection $methodReflection): bool
23+
{
24+
return 'accessCheck' === $methodReflection->getName();
25+
}
26+
27+
public function getTypeFromMethodCall(
28+
MethodReflection $methodReflection,
29+
MethodCall $methodCall,
30+
Scope $scope
31+
): Type {
32+
$varType = $scope->getType($methodCall->var);
33+
34+
if (!$varType instanceof EntityQueryType) {
35+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
36+
}
37+
38+
return $varType->withAccessCheck();
39+
}
40+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Type\EntityQuery;
6+
7+
use PHPStan\Type\ObjectType;
8+
9+
class EntityQueryType extends ObjectType
10+
{
11+
private bool $hasAccessCheck = false;
12+
13+
public function hasAccessCheck(): bool
14+
{
15+
return $this->hasAccessCheck;
16+
}
17+
18+
public function withAccessCheck(): self
19+
{
20+
$type = clone $this;
21+
$type->hasAccessCheck = true;
22+
23+
return $type;
24+
}
25+
}

0 commit comments

Comments
 (0)