+++ /dev/null
-<?php
-
-namespace Drupal\security_review;
-
-use Drupal\Core\DependencyInjection\DependencySerializationTrait;
-use Drupal\Core\Logger\RfcLogLevel;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\user\Entity\User;
-
-/**
- * Defines a security check.
- */
-abstract class Check {
-
- use DependencySerializationTrait;
- use StringTranslationTrait;
-
- /**
- * The configuration storage for this check.
- *
- * @var \Drupal\Core\Config\Config $config
- */
- protected $config;
-
- /**
- * The service container.
- *
- * @var \Symfony\Component\DependencyInjection\ContainerInterface
- */
- protected $container;
-
- /**
- * Settings handler for this check.
- *
- * @var \Drupal\security_review\CheckSettingsInterface $settings
- */
- protected $settings;
-
- /**
- * The State system.
- *
- * @var \Drupal\Core\State\State
- */
- protected $state;
-
- /**
- * The check's prefix in the State system.
- *
- * @var string
- */
- protected $statePrefix;
-
- /**
- * Initializes the configuration storage and the settings handler.
- */
- public function __construct() {
- $this->container = \Drupal::getContainer();
-
- $this->config = $this->configFactory()
- ->getEditable('security_review.check.' . $this->id());
- $this->settings = new CheckSettings($this, $this->config);
- $this->state = $this->container->get('state');
- $this->statePrefix = 'security_review.check.' . $this->id() . '.';
-
- // Set check ID in config.
- if ($this->config->get('id') != $this->id()) {
- $this->config->set('id', $this->id());
- $this->config->save();
- }
- }
-
- /**
- * Returns the namespace of the check.
- *
- * Usually it's the same as the module's name.
- *
- * Naming rules (if overridden):
- * - All characters should be lowerspace.
- * - Use characters only from the english alphabet.
- * - Don't use spaces (use "_" instead).
- *
- * @return string
- * Machine namespace of the check.
- */
- public function getMachineNamespace() {
- $namespace = strtolower($this->getNamespace());
- $namespace = preg_replace("/[^a-z0-9 ]/", '', $namespace);
- $namespace = str_replace(' ', '_', $namespace);
-
- return $namespace;
- }
-
- /**
- * Returns the namespace of the check.
- *
- * Usually it's the same as the module's name.
- *
- * @return string
- * Human-readable namespace of the check.
- */
- public abstract function getNamespace();
-
- /**
- * Returns the machine name of the check.
- *
- * Naming rules (if overridden):
- * - All characters should be lowerspace.
- * - Use characters only from the english alphabet.
- * - Don't use spaces (use "_" instead).
- *
- * @return string
- * ID of check.
- */
- public function getMachineTitle() {
- $title = strtolower($this->getTitle());
- $title = preg_replace("/[^a-z0-9 ]/", '', $title);
- $title = str_replace(' ', '_', $title);
-
- return $title;
- }
-
- /**
- * Returns the human-readable title of the check.
- *
- * @return string
- * Title of check.
- */
- public abstract function getTitle();
-
- /**
- * Returns the identifier constructed using the namespace and title values.
- *
- * @return string
- * Unique identifier of the check.
- */
- public final function id() {
- return $this->getMachineNamespace() . '-' . $this->getMachineTitle();
- }
-
- /**
- * Returns whether the findings should be stored or reproduced when needed.
- *
- * The only case when this function should return false is if the check can
- * generate a lot of findings (like the File permissions check for example).
- * Turning this off for checks that don't generate findings at all or just a
- * few of them actually means more overhead as the check has to be re-run
- * in order to get its last result.
- *
- * @return bool
- * Boolean indicating whether findings will be stored.
- */
- public function storesFindings() {
- return TRUE;
- }
-
- /**
- * Returns the check-specific settings' handler.
- *
- * @return \Drupal\security_review\CheckSettingsInterface
- * The settings interface of the check.
- */
- public function settings() {
- return $this->settings;
- }
-
- /**
- * The actual procedure of carrying out the check.
- *
- * @return \Drupal\security_review\CheckResult
- * The result of running the check.
- */
- public abstract function run();
-
- /**
- * Same as run(), but used in CLI context such as Drush.
- *
- * @return \Drupal\security_review\CheckResult
- * The result of running the check.
- */
- public function runCli() {
- return $this->run();
- }
-
- /**
- * Returns the check-specific help page.
- *
- * @return array
- * The render array of the check's help page.
- */
- public abstract function help();
-
- /**
- * Returns the evaluation page of a result.
- *
- * Usually this is a list of the findings and an explanation.
- *
- * @param \Drupal\security_review\CheckResult $result
- * The check result to evaluate.
- *
- * @return array
- * The render array of the evaluation page.
- */
- public function evaluate(CheckResult $result) {
- return [];
- }
-
- /**
- * Evaluates a CheckResult and returns a plaintext output.
- *
- * @param \Drupal\security_review\CheckResult $result
- * The check result to evaluate.
- *
- * @return string
- * The evaluation string.
- */
- public function evaluatePlain(CheckResult $result) {
- return '';
- }
-
- /**
- * Converts a result integer to a human-readable result message.
- *
- * @param int $result_const
- * The result integer.
- *
- * @return string
- * The human-readable result message.
- */
- public abstract function getMessage($result_const);
-
- /**
- * Returns the last stored result of the check.
- *
- * Returns null if no results have been stored yet.
- *
- * @param bool $get_findings
- * Whether to get the findings too.
- *
- * @return \Drupal\security_review\CheckResult|null
- * The last stored result (or null).
- */
- public function lastResult($get_findings = FALSE) {
- // Get stored data from State system.
- $state_prefix = $this->statePrefix . 'last_result.';
- $result = $this->state->get($state_prefix . 'result');
- if ($get_findings) {
- $findings = $this->state->get($state_prefix . 'findings');
- }
- else {
- $findings = [];
- }
- $time = $this->state->get($state_prefix . 'time');
- // Force boolean value.
- $visible = $this->state->get($state_prefix . 'visible') == TRUE;
-
- // Check validity of stored data.
- $valid_result = is_int($result)
- && $result >= CheckResult::SUCCESS
- && $result <= CheckResult::INFO;
- $valid_findings = is_array($findings);
- $valid_time = is_int($time) && $time > 0;
-
- // If invalid, return NULL.
- if (!$valid_result || !$valid_findings || !$valid_time) {
- return NULL;
- }
-
- // Construct the CheckResult.
- $last_result = new CheckResult($this, $result, $findings, $visible, $time);
-
- // Do a check run for acquiring findings if required.
- if ($get_findings && !$this->storesFindings()) {
- // Run the check to get the findings.
- $fresh_result = $this->run();
-
- // If it malfunctioned return the last known good result.
- if (!($fresh_result instanceof CheckResult)) {
- return $last_result;
- }
-
- if ($fresh_result->result() != $last_result->result()) {
- // If the result is not the same store the new result and return it.
- $this->storeResult($fresh_result);
- $this->securityReview()->logCheckResult($fresh_result);
- return $fresh_result;
- }
- else {
- // Else return the old result with the fresh one's findings.
- return CheckResult::combine($last_result, $fresh_result);
- }
- }
-
- return $last_result;
- }
-
- /**
- * Returns the timestamp the check was last run.
- *
- * Returns 0 if it has not been run yet.
- *
- * @return int
- * The timestamp of the last stored result.
- */
- public function lastRun() {
- $last_result_time = $this->state
- ->get($this->statePrefix . 'last_result.time');
-
- if (!is_int($last_result_time)) {
- return 0;
- }
- return $last_result_time;
- }
-
- /**
- * Returns whether the check is skipped. Checks are not skipped by default.
- *
- * @return bool
- * Boolean indicating whether the check is skipped.
- */
- public function isSkipped() {
- $is_skipped = $this->config->get('skipped');
-
- if (!is_bool($is_skipped)) {
- return FALSE;
- }
- return $is_skipped;
- }
-
- /**
- * Returns the user the check was skipped by.
- *
- * Returns null if it hasn't been skipped yet or the user that skipped the
- * check is not valid anymore.
- *
- * @return \Drupal\user\Entity\User|null
- * The user the check was last skipped by (or null).
- */
- public function skippedBy() {
- $skipped_by = $this->config->get('skipped_by');
-
- if (!is_int($skipped_by)) {
- return NULL;
- }
- return User::load($skipped_by);
- }
-
- /**
- * Returns the timestamp the check was last skipped on.
- *
- * Returns 0 if it hasn't been skipped yet.
- *
- * @return int
- * The UNIX timestamp the check was last skipped on (or 0).
- */
- public function skippedOn() {
- $skipped_on = $this->config->get('skipped_on');
-
- if (!is_int($skipped_on)) {
- return 0;
- }
- return $skipped_on;
- }
-
- /**
- * Enables the check. Has no effect if the check was not skipped.
- */
- public function enable() {
- if ($this->isSkipped()) {
- $this->config->set('skipped', FALSE);
- $this->config->save();
-
- // Log.
- $context = ['@name' => $this->getTitle()];
- $this->securityReview()->log($this, '@name check no longer skipped', $context, RfcLogLevel::NOTICE);
- }
- }
-
- /**
- * Marks the check as skipped.
- *
- * It still can be ran manually, but will remain skipped on the Run & Review
- * page.
- */
- public function skip() {
- if (!$this->isSkipped()) {
- // Store skip data.
- $this->config->set('skipped', TRUE);
- $this->config->set('skipped_by', $this->currentUser()->id());
- $this->config->set('skipped_on', time());
- $this->config->save();
-
- // Log.
- $context = ['@name' => $this->getTitle()];
- $this->securityReview()->log($this, '@name check skipped', $context, RfcLogLevel::NOTICE);
- }
- }
-
- /**
- * Stores a result in the state system.
- *
- * @param \Drupal\security_review\CheckResult $result
- * The result to store.
- */
- public function storeResult(CheckResult $result) {
- if ($result == NULL) {
- $context = [
- '@reviewcheck' => $this->getTitle(),
- '@namespace' => $this->getNamespace(),
- ];
- $this->securityReview()->log($this, 'Unable to store check @reviewcheck for @namespace', $context, RfcLogLevel::CRITICAL);
- return;
- }
-
- $findings = $this->storesFindings() ? $result->findings() : [];
- $this->state->setMultiple([
- $this->statePrefix . 'last_result.result' => $result->result(),
- $this->statePrefix . 'last_result.time' => $result->time(),
- $this->statePrefix . 'last_result.visible' => $result->isVisible(),
- $this->statePrefix . 'last_result.findings' => $findings,
- ]);
- }
-
- /**
- * Creates a new CheckResult for this Check.
- *
- * @param int $result
- * The result integer (see the constants defined in CheckResult).
- * @param array $findings
- * The findings.
- * @param bool $visible
- * The visibility of the result.
- * @param int $time
- * The time the test was run.
- *
- * @return \Drupal\security_review\CheckResult
- * The created CheckResult.
- */
- public function createResult($result, array $findings = [], $visible = TRUE, $time = NULL) {
- return new CheckResult($this, $result, $findings, $visible, $time);
- }
-
- /**
- * Returns the Security Review Checklist service.
- *
- * @return \Drupal\security_review\Checklist
- * Security Review's Checklist service.
- */
- protected function checklist() {
- return $this->container->get('security_review.checklist');
- }
-
- /**
- * Returns the Config factory.
- *
- * @return \Drupal\Core\Config\ConfigFactory
- * Config factory.
- */
- protected function configFactory() {
- return $this->container->get('config.factory');
- }
-
- /**
- * Returns the service container.
- *
- * @return \Symfony\Component\DependencyInjection\ContainerInterface
- * Service container.
- */
- protected function container() {
- return $this->container;
- }
-
- /**
- * Returns the current Drupal user.
- *
- * @return \Drupal\Core\Session\AccountProxy
- * Current Drupal user.
- */
- protected function currentUser() {
- return $this->container->get('current_user');
- }
-
- /**
- * Returns the database connection.
- *
- * @return \Drupal\Core\Database\Connection
- * Database connection.
- */
- protected function database() {
- return $this->container->get('database');
- }
-
- /**
- * Returns the entity manager.
- *
- * @return \Drupal\Core\Entity\EntityManagerInterface
- * Entity manager.
- */
- protected function entityManager() {
- return $this->container->get('entity.manager');
- }
-
- /**
- * Returns the Drupal Kernel.
- *
- * @return \Drupal\Core\DrupalKernel
- * Drupal Kernel.
- */
- protected function kernel() {
- return $this->container->get('kernel');
- }
-
- /**
- * Returns the module handler.
- *
- * @return \Drupal\Core\Extension\ModuleHandler
- * Module handler.
- */
- protected function moduleHandler() {
- return $this->container->get('module_handler');
- }
-
- /**
- * Returns the Security Review Security service.
- *
- * @return \Drupal\security_review\Security
- * Security Review's Security service.
- */
- protected function security() {
- return $this->container->get('security_review.security');
- }
-
- /**
- * Returns the Security Review service.
- *
- * @return \Drupal\security_review\SecurityReview
- * Security Review service.
- */
- protected function securityReview() {
- return $this->container->get('security_review');
- }
-
-}