Skip to content

Commit 82d08d6

Browse files
authored
Merge pull request #378 from brambaud/issue/315
Report missing explicit access check on entity queries
2 parents 479217b + 693c356 commit 82d08d6

14 files changed

+270
-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\EntityQueryAccessCheckDynamicReturnTypeExtension
292+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
289293
-
290294
class: mglaman\PHPStanDrupal\Type\UrlToStringDynamicReturnTypeExtension
291295
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery;
6+
7+
use mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryExecuteWithoutAccessCheckCountType;
8+
use mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryExecuteWithoutAccessCheckType;
9+
use PhpParser\Node;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
14+
final class EntityQueryHasAccessCheckRule implements Rule
15+
{
16+
public function getNodeType(): string
17+
{
18+
return Node\Expr\MethodCall::class;
19+
}
20+
21+
public function processNode(Node $node, Scope $scope): array
22+
{
23+
if (!$node instanceof Node\Expr\MethodCall) {
24+
return [];
25+
}
26+
27+
$name = $node->name;
28+
if (!$name instanceof Node\Identifier) {
29+
return [];
30+
}
31+
if ($name->toString() !== 'execute') {
32+
return [];
33+
}
34+
35+
$type = $scope->getType($node);
36+
37+
if (!$type instanceof EntityQueryExecuteWithoutAccessCheckCountType && !$type instanceof EntityQueryExecuteWithoutAccessCheckType) {
38+
return [];
39+
}
40+
41+
return [
42+
RuleErrorBuilder::message(
43+
'Missing explicit access check on entity query.'
44+
)->tip('See https://www.drupal.org/node/3201242')->build(),
45+
];
46+
}
47+
}

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
}
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 EntityQueryAccessCheckDynamicReturnTypeExtension 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+
}

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 EntityQueryExecuteWithoutAccessCheckCountType();
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 EntityQueryExecuteWithoutAccessCheckType(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 EntityQueryExecuteWithoutAccessCheckType(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 EntityQueryExecuteWithoutAccessCheckCountType 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 EntityQueryExecuteWithoutAccessCheckType extends ArrayType
12+
{
13+
14+
}
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)