Skip to content

Commit 51e76a3

Browse files
Merge branch 'MAGETWO-95945' into 2.3-bugfixes-020119
2 parents 0502433 + 4502caf commit 51e76a3

File tree

3 files changed

+210
-0
lines changed

3 files changed

+210
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\CodeMessDetector\Rule\Design;
10+
11+
use PDepend\Source\AST\ASTClass;
12+
use PHPMD\AbstractNode;
13+
use PHPMD\AbstractRule;
14+
use PHPMD\Node\ClassNode;
15+
use PHPMD\Rule\ClassAware;
16+
17+
/**
18+
* Session and Cookies must be used only in HTML Presentation layer.
19+
*/
20+
class CookieAndSessionMisuse extends AbstractRule implements ClassAware
21+
{
22+
/**
23+
* Is given class a controller?
24+
*
25+
* @param \ReflectionClass $class
26+
* @return bool
27+
*/
28+
private function isController(\ReflectionClass $class): bool
29+
{
30+
return $class->isSubclassOf(\Magento\Framework\App\ActionInterface::class);
31+
}
32+
33+
/**
34+
* Is given class a block?
35+
*
36+
* @param \ReflectionClass $class
37+
* @return bool
38+
*/
39+
private function isBlock(\ReflectionClass $class): bool
40+
{
41+
return $class->isSubclassOf(\Magento\Framework\View\Element\BlockInterface::class);
42+
}
43+
44+
/**
45+
* Is given class an HTML UI data provider?
46+
*
47+
* @param \ReflectionClass $class
48+
* @return bool
49+
*/
50+
private function isUiDataProvider(\ReflectionClass $class): bool
51+
{
52+
return $class->isSubclassOf(
53+
\Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface::class
54+
);
55+
}
56+
57+
/**
58+
* Is given class an HTML UI Document?
59+
*
60+
* @param \ReflectionClass $class
61+
* @return bool
62+
*/
63+
private function isUiDocument(\ReflectionClass $class): bool
64+
{
65+
return $class->isSubclassOf(\Magento\Framework\View\Element\UiComponent\DataProvider\Document::class)
66+
|| $class->getName() === \Magento\Framework\View\Element\UiComponent\DataProvider\Document::class;
67+
}
68+
69+
/**
70+
* Is given class a plugin for controllers?
71+
*
72+
* @param \ReflectionClass $class
73+
* @return bool
74+
*/
75+
private function isControllerPlugin(\ReflectionClass $class): bool
76+
{
77+
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
78+
if (preg_match('/^(after|around|before).+/i', $method->getName())) {
79+
try {
80+
$argument = $method->getParameters()[0]->getClass();
81+
} catch (\ReflectionException $exception) {
82+
//Non-existing class (autogenerated perhaps)
83+
continue;
84+
}
85+
$isAction = $argument->isSubclassOf(\Magento\Framework\App\ActionInterface::class)
86+
|| $argument->getName() === \Magento\Framework\App\ActionInterface::class;
87+
if ($isAction) {
88+
return true;
89+
}
90+
}
91+
}
92+
93+
return false;
94+
}
95+
96+
/**
97+
* Is given class a plugin for blocks?
98+
*
99+
* @param \ReflectionClass $class
100+
* @return bool
101+
*/
102+
private function isBlockPlugin(\ReflectionClass $class): bool
103+
{
104+
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
105+
if (preg_match('/^(after|around|before).+/i', $method->getName())) {
106+
try {
107+
$argument = $method->getParameters()[0]->getClass();
108+
} catch (\ReflectionException $exception) {
109+
//Non-existing class (autogenerated perhaps)
110+
continue;
111+
}
112+
$isBlock = $argument->isSubclassOf(\Magento\Framework\View\Element\BlockInterface::class)
113+
|| $argument->getName() === \Magento\Framework\View\Element\BlockInterface::class;
114+
if ($isBlock) {
115+
return true;
116+
}
117+
}
118+
}
119+
120+
return false;
121+
}
122+
123+
/**
124+
* Whether given class depends on classes to pay attention to.
125+
*
126+
* @param \ReflectionClass $class
127+
* @return bool
128+
*/
129+
private function doesUseRestrictedClasses(\ReflectionClass $class): bool
130+
{
131+
$constructor = $class->getConstructor();
132+
if ($constructor) {
133+
foreach ($constructor->getParameters() as $argument) {
134+
try {
135+
if ($class = $argument->getClass()) {
136+
if ($class->isSubclassOf(\Magento\Framework\Session\SessionManagerInterface::class)
137+
|| $class->getName() === \Magento\Framework\Session\SessionManagerInterface::class
138+
|| $class->isSubclassOf(\Magento\Framework\Stdlib\Cookie\CookieReaderInterface::class)
139+
|| $class->getName() === \Magento\Framework\Stdlib\Cookie\CookieReaderInterface::class
140+
) {
141+
return true;
142+
}
143+
}
144+
} catch (\ReflectionException $exception) {
145+
//Failed to load the argument's class information
146+
continue;
147+
}
148+
}
149+
}
150+
151+
return false;
152+
}
153+
154+
/**
155+
* @inheritdoc
156+
*
157+
* @param ClassNode|ASTClass $node
158+
*/
159+
public function apply(AbstractNode $node)
160+
{
161+
try {
162+
$class = new \ReflectionClass($node->getFullQualifiedName());
163+
} catch (\Throwable $exception) {
164+
//Failed to load class, nothing we can do
165+
return;
166+
}
167+
168+
if ($this->doesUseRestrictedClasses($class)) {
169+
if (!$this->isController($class)
170+
&& !$this->isBlock($class)
171+
&& !$this->isUiDataProvider($class)
172+
&& !$this->isUiDocument($class)
173+
&& !$this->isControllerPlugin($class)
174+
&& !$this->isBlockPlugin($class)
175+
) {
176+
$this->addViolation($node, [$node->getFullQualifiedName()]);
177+
}
178+
}
179+
}
180+
}

dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,35 @@ class PostOrder implements ActionInterface
5454
...
5555
return $response;
5656
}
57+
}
58+
]]>
59+
</example>
60+
</rule>
61+
<rule name="CookieAndSessionMisuse"
62+
class="Magento\CodeMessDetector\Rule\Design\CookieAndSessionMisuse"
63+
message= "The class {0} uses sessions or cookies while not being a part of HTML Presentation layer">
64+
<description>
65+
<![CDATA[
66+
Sessions and cookies must only be used in classes directly responsible for HTML presentation because Web APIs do not
67+
rely on cookies and sessions. If you need to get current user use Magento\Authorization\Model\UserContextInterface
68+
]]>
69+
</description>
70+
<priority>2</priority>
71+
<properties />
72+
<example>
73+
<![CDATA[
74+
class OrderProcessor
75+
{
76+
public function __construct(SessionManagerInterface $session) {
77+
$this->session = $session;
78+
}
79+
80+
public function place(OrderInterface $order)
81+
{
82+
//Will not be present if processing a WebAPI request
83+
$currentOrder = $this->session->get('current_order');
84+
...
85+
}
5786
}
5887
]]>
5988
</example>

dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,6 @@
4949
<rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/FinalImplementation" />
5050
<rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/AllPurposeAction" />
5151
<rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/RequestAwareBlockMethod" />
52+
<rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/CookieAndSessionMisuse" />
5253

5354
</ruleset>

0 commit comments

Comments
 (0)