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'); } }