ExternalCode::checkExternal()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 15
c 2
b 0
f 1
dl 0
loc 23
rs 9.4555
cc 5
nc 5
nop 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Drupal\qa\Plugin\QaCheck\System;
6
7
use Drupal\Core\Extension\ModuleExtensionList;
8
use Drupal\Core\Extension\ThemeExtensionList;
9
use Drupal\qa\Data;
10
use Drupal\qa\Pass;
11
use Drupal\qa\Plugin\QaCheckBase;
12
use Drupal\qa\Plugin\QaCheckInterface;
13
use Drupal\qa\Plugin\QaCheckManager;
14
use Drupal\qa\Result;
15
use ReflectionFunction;
16
use Symfony\Component\DependencyInjection\ContainerInterface;
17
18
/**
19
 * ExternalCode identifies code loaded from outside the project root and vendor.
20
 *
21
 * @QaCheck(
22
 *   id = "system.external",
23
 *   label = @Translation("System: external code"),
24
 *   details = @Translation("External code may be the result of an exploit."),
25
 *   usesBatch = false,
26
 *   steps = 1,
27
 * )
28
 */
29
class ExternalCode extends QaCheckBase implements QaCheckInterface {
30
31
  const NAME = 'system.external';
32
33
  /**
34
   * The element_list.module service.
35
   *
36
   * @var \Drupal\Core\Extension\ModuleExtensionList
37
   */
38
  protected $elm;
39
40
  /**
41
   * The extension.list.theme service.
42
   *
43
   * @var \Drupal\Core\Extension\ThemeExtensionList
44
   */
45
  protected $elt;
46
47
  /**
48
   * The list of internal functions.
49
   *
50
   * @var array
51
   */
52
  protected $internalFunctions;
53
54
  /**
55
   * The plugin_manager.qa_check service.
56
   *
57
   * @var \Drupal\qa\Plugin\QaCheckManager
58
   */
59
  protected $qam;
60
61
  /**
62
   * ExternalCode constructor.
63
   *
64
   * @param array $configuration
65
   *   The plugin configuration.
66
   * @param string $id
67
   *   The plugin ID.
68
   * @param array $definition
69
   *   The plugin definition.
70
   * @param \Drupal\Core\Extension\ModuleExtensionList $elm
71
   *   The extension.list.module service.
72
   * @param \Drupal\Core\Extension\ThemeExtensionList $elt
73
   *   The extension.list.theme service.
74
   * @param \Drupal\qa\Plugin\QaCheckManager $qam
75
   *   The plugin_manager.qa_check service.
76
   */
77
  public function __construct(
78
    array $configuration,
79
    string $id,
80
    array $definition,
81
    ModuleExtensionList $elm,
82
    ThemeExtensionList $elt,
83
    QaCheckManager $qam
84
  ) {
85
    parent::__construct($configuration, $id, $definition);
86
    $this->elm = $elm;
87
    $this->elt = $elt;
88
    $this->qam = $qam;
89
90
    $this->qam->initInternalFunctions();
91
  }
92
93
  /**
94
   * {@inheritdoc}
95
   */
96
  public static function create(
97
    ContainerInterface $container,
98
    array $configuration,
99
    $id,
100
    $definition
101
  ) {
102
    $elm = $container->get('extension.list.module');
103
    assert($elm instanceof ModuleExtensionList);
104
    $elt = $container->get('extension.list.theme');
105
    assert($elt instanceof ThemeExtensionList);
106
    $qam = $container->get(Data::MANAGER);
107
    assert($qam instanceof QaCheckManager);
108
    return new static($configuration, $id, $definition, $elm, $elt, $qam);
109
  }
110
111
  /**
112
   * Identify code loaded from outside the web root and vendor directory.
113
   *
114
   * This is not necessarily an issue, but warrants a manual verification.
115
   *
116
   * @return \Drupal\qa\Result
117
   *   The check result.
118
   */
119
  protected function checkExternal(): Result {
120
    $external = [];
121
    $funcs = array_flip(get_defined_functions()['user']);
122
    foreach ($funcs as $func => $_) {
123
      if (isset($this->qam->internalFunctions[$func])) {
124
        continue;
125
      }
126
      try {
127
        $rf = new ReflectionFunction($func);
128
      }
129
      catch (\ReflectionException $e) {
130
        // XXX probably cannot happen since the function is loaded.
131
        $external[$func] = ('function not found');
132
        continue;
133
      }
134
      $file = realpath($rf->getFileName());
135
      // TODO improve resilience, probably at the cost of computing time.
136
      if (strpos($file, $this->qam->root) === FALSE) {
137
        $external[$func] = $file;
138
      }
139
    }
140
    $res = new Result('external', empty($external), $external);
141
    return $res;
142
143
  }
144
145
  /**
146
   * {@inheritdoc}
147
   */
148
  public function run(): Pass {
149
    $pass = parent::run();
150
    $pass->record($this->checkExternal());
151
    $pass->life->end();
152
    return $pass;
153
  }
154
155
}
156