Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
ControllerAclTest.php
Go to the documentation of this file.
1 <?php
7 
11 
12 class ControllerAclTest extends \PHPUnit\Framework\TestCase
13 {
17  const ACL_FUNC_NAME = '_isAllowed';
18 
22  const ACL_CONST_NAME = 'ADMIN_RESOURCE';
23 
27  const DEFAULT_BACKEND_RESOURCE = 'Magento_Backend::admin';
28 
34  private $whiteListedBackendControllers = [];
35 
41  private $aclResources;
42 
46  protected function setUp()
47  {
48  $whitelistedClasses = [];
49  $path = sprintf('%s/_files/controller_acl_test_whitelist_*.txt', __DIR__);
50  foreach (glob($path) as $listFile) {
51  $whitelistedClasses = array_merge(
52  $whitelistedClasses,
53  file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)
54  );
55  }
56  foreach ($whitelistedClasses as $item) {
57  if (substr($item, 0, 1) === '#') {
58  continue;
59  }
60  $this->whiteListedBackendControllers[$item] = 1;
61  }
62  }
63 
67  public function testAcl()
68  {
69  $errorMessages = [];
70  $pathMask = sprintf('%s/../../../_files/changed_files*', __DIR__, DIRECTORY_SEPARATOR);
72  foreach ($changedFiles as $line) {
73  $relativeFilePath = $line[0];
74  // we don't have to check tests,
75  if ($this->isItTest($relativeFilePath)) {
76  continue;
77  }
78 
79  $controllerPath = $this->getControllerPath($relativeFilePath);
80  if (!$controllerPath) {
81  continue;
82  }
83 
84  $controllerClass = $this->getClassByFilePath($controllerPath);
85  // skip whitelisted controllers.
86  if (isset($this->whiteListedBackendControllers[$controllerClass->getName()])) {
87  continue;
88  }
89  // we don't have to check abstract classes.
90  if ($controllerClass->isAbstract()) {
91  continue;
92  }
93 
94  $className = $controllerClass->getName();
95 
96  if (!$this->isClassExtendsBackendClass($controllerClass)) {
97  $inheritanceMessage = "Backend controller $className have to inherit " . AbstractAction::class;
98  $errorMessages[] = $inheritanceMessage;
99  continue;
100  };
101 
102  $isAclRedefinedInTheChildClass = $this->isConstantOverwritten($controllerClass)
103  || $this->isMethodOverwritten($controllerClass);
104  if (!$isAclRedefinedInTheChildClass) {
105  $errorMessages[] = "Backend controller $className have to overwrite _isAllowed method or "
106  . 'ADMIN_RESOURCE constant';
107  }
108 
109  $errorMessages = array_merge($errorMessages, $this->collectAclErrorsInTheXml($controllerClass));
110  }
111  sort($errorMessages);
112  $this->assertEmpty($errorMessages, implode("\n", $errorMessages));
113  }
114 
121  private function collectAclErrorsInTheXml(\ReflectionClass $class)
122  {
123  $errorMessages = [];
124  $className = $class->getName();
125  $method = $class->getMethod(self::ACL_FUNC_NAME);
126  $codeLines = file($method->getFileName());
127  $length = $method->getEndLine() - $method->getStartLine();
128  $start = $method->getStartLine();
129  $codeOfTheMethod = implode(' ', array_slice($codeLines, $start, $length));
130  preg_match('~["\']Magento_.*?::.*?["\']~', $codeOfTheMethod, $matches);
131  $aclResources = $this->getAclResources();
132  foreach ($matches as $resource) {
133  $resourceUnquoted = str_replace(['"', "'"], ['', ''], $resource);
134  if (!isset($aclResources[$resourceUnquoted])) {
135  $errorMessages[] = "ACL $resource exists in $className but doesn't exists in the acl.xml file";
136  }
137  }
138  return $errorMessages;
139  }
140 
146  private function getAclResources()
147  {
148  if ($this->aclResources !== null) {
149  return $this->aclResources;
150  }
151  $aclFiles = Files::init()->getConfigFiles('acl.xml', []);
152  $xmlResources = [];
153  array_map(function ($file) use (&$xmlResources) {
154  $config = simplexml_load_file($file[0]);
155  $nodes = $config->xpath('.//resource/@id') ?: [];
156  foreach ($nodes as $node) {
157  $xmlResources[(string)$node] = $node;
158  }
159  }, $aclFiles);
160  $this->aclResources = $xmlResources;
161  return $this->aclResources;
162  }
163 
170  private function isConstantOverwritten(\ReflectionClass $class)
171  {
172  // check that controller overwrites default ACL to some specific
173  if ($class->getConstant(self::ACL_CONST_NAME) !== self::DEFAULT_BACKEND_RESOURCE) {
174  return true;
175  }
176 
177  return false;
178  }
179 
186  private function isMethodOverwritten(\ReflectionClass $class)
187  {
188  // check that controller overwrites default ACL to some specific (at least we check that it was overwritten).
189  $method = $class->getMethod(self::ACL_FUNC_NAME);
190  try {
191  $method->getPrototype();
192  return true;
193  } catch (\ReflectionException $e) {
194  return false;
195  }
196  }
197 
204  private function isClassExtendsBackendClass(\ReflectionClass $class)
205  {
206  while ($parentClass = $class->getParentClass()) {
207  if (AbstractAction::class === $parentClass->getName()) {
208  return true;
209  }
210  $class = $parentClass;
211  }
212  return false;
213  }
214 
221  private function isItTest($relativeFilePath)
222  {
223  $isTest = (preg_match('~.*?(/dev/tests/|/Test/Unit/).*?\.php$~', $relativeFilePath) === 1);
224  return $isTest;
225  }
226 
233  private function getControllerPath($relativeFilePath)
234  {
235  if (preg_match('~(Magento\/[^\/]+\/Controller\/Adminhtml\/.*)\.php~', $relativeFilePath, $matches)) {
236  if (count($matches) === 2) {
237  $partPath = $matches[1];
238  return $partPath;
239  }
240  }
241  return '';
242  }
243 
250  private function getClassByFilePath($controllerPath)
251  {
252  $className = str_replace('/', '\\', $controllerPath);
253  try {
254  $reflectionClass = new \ReflectionClass($className);
255  } catch (\ReflectionException $e) {
256  $reflectionClass = new \ReflectionClass(new \stdClass());
257  }
258  return $reflectionClass;
259  }
260 }
static getPhpFiles($changedFilesList, $fileTypes=0)
$config
Definition: fraud_order.php:17
defined('TESTS_BP')||define('TESTS_BP' __DIR__
Definition: _bootstrap.php:60
$start
Definition: listing.phtml:18
$resource
Definition: bulk.php:12
$_option $_optionId $class
Definition: date.phtml:13
$reflectionClass
Definition: categories.php:25
$method
Definition: info.phtml:13
if($currentSelectedMethod==$_code) $className
Definition: form.phtml:31