Including security review as a submodule - with patched for Yaffs.
[yaffs-website] / web / modules / contrib / security_review / src / Check.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\security_review\Check.
6  */
7
8 namespace Drupal\security_review;
9
10 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
11 use Drupal\Core\Logger\RfcLogLevel;
12 use Drupal\Core\Routing\LinkGeneratorTrait;
13 use Drupal\Core\Routing\UrlGeneratorTrait;
14 use Drupal\Core\StringTranslation\StringTranslationTrait;
15 use Drupal\user\Entity\User;
16
17 /**
18  * Defines a security check.
19  */
20 abstract class Check {
21
22   use DependencySerializationTrait;
23   use LinkGeneratorTrait;
24   use UrlGeneratorTrait;
25   use StringTranslationTrait;
26
27   /**
28    * The configuration storage for this check.
29    *
30    * @var \Drupal\Core\Config\Config $config
31    */
32   protected $config;
33
34   /**
35    * The service container.
36    *
37    * @var \Symfony\Component\DependencyInjection\ContainerInterface
38    */
39   protected $container;
40
41   /**
42    * Settings handler for this check.
43    *
44    * @var \Drupal\security_review\CheckSettingsInterface $settings
45    */
46   protected $settings;
47
48   /**
49    * The State system.
50    *
51    * @var \Drupal\Core\State\State
52    */
53   protected $state;
54
55   /**
56    * The check's prefix in the State system.
57    *
58    * @var string
59    */
60   protected $statePrefix;
61
62   /**
63    * Initializes the configuration storage and the settings handler.
64    */
65   public function __construct() {
66     $this->container = \Drupal::getContainer();
67
68     $this->config = $this->configFactory()
69       ->getEditable('security_review.check.' . $this->id());
70     $this->settings = new CheckSettings($this, $this->config);
71     $this->state = $this->container->get('state');
72     $this->statePrefix = 'security_review.check.' . $this->id() . '.';
73
74     // Set check ID in config.
75     if ($this->config->get('id') != $this->id()) {
76       $this->config->set('id', $this->id());
77       $this->config->save();
78     }
79   }
80
81   /**
82    * Returns the namespace of the check.
83    *
84    * Usually it's the same as the module's name.
85    *
86    * Naming rules (if overridden):
87    *   - All characters should be lowerspace.
88    *   - Use characters only from the english alphabet.
89    *   - Don't use spaces (use "_" instead).
90    *
91    * @return string
92    *   Machine namespace of the check.
93    */
94   public function getMachineNamespace() {
95     $namespace = strtolower($this->getNamespace());
96     $namespace = preg_replace("/[^a-z0-9 ]/", '', $namespace);
97     $namespace = str_replace(' ', '_', $namespace);
98
99     return $namespace;
100   }
101
102   /**
103    * Returns the namespace of the check.
104    *
105    * Usually it's the same as the module's name.
106    *
107    * @return string
108    *   Human-readable namespace of the check.
109    */
110   public abstract function getNamespace();
111
112   /**
113    * Returns the machine name of the check.
114    *
115    * Naming rules (if overridden):
116    *   - All characters should be lowerspace.
117    *   - Use characters only from the english alphabet.
118    *   - Don't use spaces (use "_" instead).
119    *
120    * @return string
121    *   ID of check.
122    */
123   public function getMachineTitle() {
124     $title = strtolower($this->getTitle());
125     $title = preg_replace("/[^a-z0-9 ]/", '', $title);
126     $title = str_replace(' ', '_', $title);
127
128     return $title;
129   }
130
131   /**
132    * Returns the human-readable title of the check.
133    *
134    * @return string
135    *   Title of check.
136    */
137   public abstract function getTitle();
138
139   /**
140    * Returns the identifier constructed using the namespace and title values.
141    *
142    * @return string
143    *   Unique identifier of the check.
144    */
145   public final function id() {
146     return $this->getMachineNamespace() . '-' . $this->getMachineTitle();
147   }
148
149   /**
150    * Returns whether the findings should be stored or reproduced when needed.
151    *
152    * The only case when this function should return false is if the check can
153    * generate a lot of findings (like the File permissions check for example).
154    * Turning this off for checks that don't generate findings at all or just a
155    * few of them actually means more overhead as the check has to be re-run
156    * in order to get its last result.
157    *
158    * @return bool
159    *   Boolean indicating whether findings will be stored.
160    */
161   public function storesFindings() {
162     return TRUE;
163   }
164
165   /**
166    * Returns the check-specific settings' handler.
167    *
168    * @return \Drupal\security_review\CheckSettingsInterface
169    *   The settings interface of the check.
170    */
171   public function settings() {
172     return $this->settings;
173   }
174
175   /**
176    * The actual procedure of carrying out the check.
177    *
178    * @return \Drupal\security_review\CheckResult
179    *   The result of running the check.
180    */
181   public abstract function run();
182
183   /**
184    * Same as run(), but used in CLI context such as Drush.
185    *
186    * @return \Drupal\security_review\CheckResult
187    *   The result of running the check.
188    */
189   public function runCli() {
190     return $this->run();
191   }
192
193   /**
194    * Returns the check-specific help page.
195    *
196    * @return array
197    *   The render array of the check's help page.
198    */
199   public abstract function help();
200
201   /**
202    * Returns the evaluation page of a result.
203    *
204    * Usually this is a list of the findings and an explanation.
205    *
206    * @param \Drupal\security_review\CheckResult $result
207    *   The check result to evaluate.
208    *
209    * @return array
210    *   The render array of the evaluation page.
211    */
212   public function evaluate(CheckResult $result) {
213     return [];
214   }
215
216   /**
217    * Evaluates a CheckResult and returns a plaintext output.
218    *
219    * @param \Drupal\security_review\CheckResult $result
220    *   The check result to evaluate.
221    *
222    * @return string
223    *   The evaluation string.
224    */
225   public function evaluatePlain(CheckResult $result) {
226     return '';
227   }
228
229   /**
230    * Converts a result integer to a human-readable result message.
231    *
232    * @param int $result_const
233    *   The result integer.
234    *
235    * @return string
236    *   The human-readable result message.
237    */
238   public abstract function getMessage($result_const);
239
240   /**
241    * Returns the last stored result of the check.
242    *
243    * Returns null if no results have been stored yet.
244    *
245    * @param bool $get_findings
246    *   Whether to get the findings too.
247    *
248    * @return \Drupal\security_review\CheckResult|null
249    *   The last stored result (or null).
250    */
251   public function lastResult($get_findings = FALSE) {
252     // Get stored data from State system.
253     $state_prefix = $this->statePrefix . 'last_result.';
254     $result = $this->state->get($state_prefix . 'result');
255     if ($get_findings) {
256       $findings = $this->state->get($state_prefix . 'findings');
257     }
258     else {
259       $findings = [];
260     }
261     $time = $this->state->get($state_prefix . 'time');
262     // Force boolean value.
263     $visible = $this->state->get($state_prefix . 'visible') == TRUE;
264
265     // Check validity of stored data.
266     $valid_result = is_int($result)
267       && $result >= CheckResult::SUCCESS
268       && $result <= CheckResult::INFO;
269     $valid_findings = is_array($findings);
270     $valid_time = is_int($time) && $time > 0;
271
272     // If invalid, return NULL.
273     if (!$valid_result || !$valid_findings || !$valid_time) {
274       return NULL;
275     }
276
277     // Construct the CheckResult.
278     $last_result = new CheckResult($this, $result, $findings, $visible, $time);
279
280     // Do a check run for acquiring findings if required.
281     if ($get_findings && !$this->storesFindings()) {
282       // Run the check to get the findings.
283       $fresh_result = $this->run();
284
285       // If it malfunctioned return the last known good result.
286       if (!($fresh_result instanceof CheckResult)) {
287         return $last_result;
288       }
289
290       if ($fresh_result->result() != $last_result->result()) {
291         // If the result is not the same store the new result and return it.
292         $this->storeResult($fresh_result);
293         $this->securityReview()->logCheckResult($fresh_result);
294         return $fresh_result;
295       }
296       else {
297         // Else return the old result with the fresh one's findings.
298         return CheckResult::combine($last_result, $fresh_result);
299       }
300     }
301
302     return $last_result;
303   }
304
305   /**
306    * Returns the timestamp the check was last run.
307    *
308    * Returns 0 if it has not been run yet.
309    *
310    * @return int
311    *   The timestamp of the last stored result.
312    */
313   public function lastRun() {
314     $last_result_time = $this->state
315       ->get($this->statePrefix . 'last_result.time');
316
317     if (!is_int($last_result_time)) {
318       return 0;
319     }
320     return $last_result_time;
321   }
322
323   /**
324    * Returns whether the check is skipped. Checks are not skipped by default.
325    *
326    * @return bool
327    *   Boolean indicating whether the check is skipped.
328    */
329   public function isSkipped() {
330     $is_skipped = $this->config->get('skipped');
331
332     if (!is_bool($is_skipped)) {
333       return FALSE;
334     }
335     return $is_skipped;
336   }
337
338   /**
339    * Returns the user the check was skipped by.
340    *
341    * Returns null if it hasn't been skipped yet or the user that skipped the
342    * check is not valid anymore.
343    *
344    * @return \Drupal\user\Entity\User|null
345    *   The user the check was last skipped by (or null).
346    */
347   public function skippedBy() {
348     $skipped_by = $this->config->get('skipped_by');
349
350     if (!is_int($skipped_by)) {
351       return NULL;
352     }
353     return User::load($skipped_by);
354   }
355
356   /**
357    * Returns the timestamp the check was last skipped on.
358    *
359    * Returns 0 if it hasn't been skipped yet.
360    *
361    * @return int
362    *   The UNIX timestamp the check was last skipped on (or 0).
363    */
364   public function skippedOn() {
365     $skipped_on = $this->config->get('skipped_on');
366
367     if (!is_int($skipped_on)) {
368       return 0;
369     }
370     return $skipped_on;
371   }
372
373   /**
374    * Enables the check. Has no effect if the check was not skipped.
375    */
376   public function enable() {
377     if ($this->isSkipped()) {
378       $this->config->set('skipped', FALSE);
379       $this->config->save();
380
381       // Log.
382       $context = ['@name' => $this->getTitle()];
383       $this->securityReview()->log($this, '@name check no longer skipped', $context, RfcLogLevel::NOTICE);
384     }
385   }
386
387   /**
388    * Marks the check as skipped.
389    *
390    * It still can be ran manually, but will remain skipped on the Run & Review
391    * page.
392    */
393   public function skip() {
394     if (!$this->isSkipped()) {
395       // Store skip data.
396       $this->config->set('skipped', TRUE);
397       $this->config->set('skipped_by', $this->currentUser()->id());
398       $this->config->set('skipped_on', time());
399       $this->config->save();
400
401       // Log.
402       $context = ['@name' => $this->getTitle()];
403       $this->securityReview()->log($this, '@name check skipped', $context, RfcLogLevel::NOTICE);
404     }
405   }
406
407   /**
408    * Stores a result in the state system.
409    *
410    * @param \Drupal\security_review\CheckResult $result
411    *   The result to store.
412    */
413   public function storeResult(CheckResult $result) {
414     if ($result == NULL) {
415       $context = [
416         '@reviewcheck' => $this->getTitle(),
417         '@namespace' => $this->getNamespace(),
418       ];
419       $this->securityReview()->log($this, 'Unable to store check @reviewcheck for @namespace', $context, RfcLogLevel::CRITICAL);
420       return;
421     }
422
423     $findings = $this->storesFindings() ? $result->findings() : [];
424     $this->state->setMultiple([
425       $this->statePrefix . 'last_result.result' => $result->result(),
426       $this->statePrefix . 'last_result.time' => $result->time(),
427       $this->statePrefix . 'last_result.visible' => $result->isVisible(),
428       $this->statePrefix . 'last_result.findings' => $findings,
429     ]);
430   }
431
432   /**
433    * Creates a new CheckResult for this Check.
434    *
435    * @param int $result
436    *   The result integer (see the constants defined in CheckResult).
437    * @param array $findings
438    *   The findings.
439    * @param bool $visible
440    *   The visibility of the result.
441    * @param int $time
442    *   The time the test was run.
443    *
444    * @return \Drupal\security_review\CheckResult
445    *   The created CheckResult.
446    */
447   public function createResult($result, array $findings = [], $visible = TRUE, $time = NULL) {
448     return new CheckResult($this, $result, $findings, $visible, $time);
449   }
450
451   /**
452    * Returns the Security Review Checklist service.
453    *
454    * @return \Drupal\security_review\Checklist
455    *   Security Review's Checklist service.
456    */
457   protected function checklist() {
458     return $this->container->get('security_review.checklist');
459   }
460
461   /**
462    * Returns the Config factory.
463    *
464    * @return \Drupal\Core\Config\ConfigFactory
465    *   Config factory.
466    */
467   protected function configFactory() {
468     return $this->container->get('config.factory');
469   }
470
471   /**
472    * Returns the service container.
473    *
474    * @return \Symfony\Component\DependencyInjection\ContainerInterface
475    *   Service container.
476    */
477   protected function container() {
478     return $this->container;
479   }
480
481   /**
482    * Returns the current Drupal user.
483    *
484    * @return \Drupal\Core\Session\AccountProxy
485    *   Current Drupal user.
486    */
487   protected function currentUser() {
488     return $this->container->get('current_user');
489   }
490
491   /**
492    * Returns the database connection.
493    *
494    * @return \Drupal\Core\Database\Connection
495    *   Database connection.
496    */
497   protected function database() {
498     return $this->container->get('database');
499   }
500
501   /**
502    * Returns the entity manager.
503    *
504    * @return \Drupal\Core\Entity\EntityManagerInterface
505    *   Entity manager.
506    */
507   protected function entityManager() {
508     return $this->container->get('entity.manager');
509   }
510
511   /**
512    * Returns the Drupal Kernel.
513    *
514    * @return \Drupal\Core\DrupalKernel
515    *   Drupal Kernel.
516    */
517   protected function kernel() {
518     return $this->container->get('kernel');
519   }
520
521   /**
522    * Returns the module handler.
523    *
524    * @return \Drupal\Core\Extension\ModuleHandler
525    *   Module handler.
526    */
527   protected function moduleHandler() {
528     return $this->container->get('module_handler');
529   }
530
531   /**
532    * Returns the Security Review Security service.
533    *
534    * @return \Drupal\security_review\Security
535    *   Security Review's Security service.
536    */
537   protected function security() {
538     return $this->container->get('security_review.security');
539   }
540
541   /**
542    * Returns the Security Review service.
543    *
544    * @return \Drupal\security_review\SecurityReview
545    *   Security Review service.
546    */
547   protected function securityReview() {
548     return $this->container->get('security_review');
549   }
550
551 }