From 16e1e45caca3d0a1b1e2eb361d36e2f45932a7a1 Mon Sep 17 00:00:00 2001 From: Colan Schwartz Date: Mon, 29 Feb 2016 18:47:44 -0500 Subject: [PATCH] Issue #2066371 by FluxSauce, codi, colan: Added Drush support without installation & integrated with Site Audit. --- hacked.drush.inc | 185 +++++++++++++++++++++++++---------- hacked.module | 10 +- hacked.report.inc | 12 ++- src/SiteAuditCheckSecurityHacked.php | 114 +++++++++++++++++++++ src/hackedFileHasher.php | 21 +++- 5 files changed, 287 insertions(+), 55 deletions(-) create mode 100644 src/SiteAuditCheckSecurityHacked.php diff --git a/hacked.drush.inc b/hacked.drush.inc index ece0d4c..caa7b23 100644 --- a/hacked.drush.inc +++ b/hacked.drush.inc @@ -32,39 +32,35 @@ function hacked_drush_help($section) { */ function hacked_drush_command() { $items['hacked-list-projects'] = [ - 'description' => "List all projects that can be analysed by Hacked! ", - 'drupal dependencies' => ['hacked'], - 'options' => [ - 'force-rebuild' => 'Rebuild the Hacked! report instead of getting a cached version.', + 'description' => dt('List all projects that can be analysed by Hacked!'), + 'options' => [ + 'force-rebuild' => dt('Rebuild the Hacked! report instead of getting a cached version.'), ], - 'aliases' => ['hlp'], + 'aliases' => ['hlp'], ]; $items['hacked-lock-modified'] = [ - 'description' => "Lock all projects that Hacked! detects are modified, so that drush pm-updatecode will not touch them. (drush-4.x+ only)", - 'drupal dependencies' => ['hacked'], + 'description' => dt('Lock all projects that Hacked! detects are modified, so that drush pm-updatecode will not touch them. (drush-4.x+ only)'), ]; $items['hacked-details'] = [ - 'description' => "Show the Hacked! report about a specific project.", - 'drupal dependencies' => ['hacked'], - 'arguments' => [ - 'project' => 'The machine name of the project to report on.', + 'description' => dt('Show the Hacked! report about a specific project.'), + 'arguments' => [ + 'project' => dt('The machine name of the project to report on.'), ], - 'options' => [ - 'include-unchanged' => 'Show the files that are unchanged too.', + 'options' => [ + 'include-unchanged' => dt('Show the files that are unchanged too.'), ], - 'aliases' => ['hd'], + 'aliases' => ['hd'], ]; $items['hacked-diff'] = [ - 'description' => "Output a unified diff of the project specified.", - 'drupal dependencies' => ['hacked'], - 'arguments' => [ - 'project' => 'The machine name of the project to report on.', + 'description' => dt('Output a unified diff of the project specified.'), + 'arguments' => [ + 'project' => dt('The machine name of the project to report on.'), ], - 'options' => [ - 'diff-options' => 'Command line options to pass through to the diff command.', + 'options' => [ + 'diff-options' => dt('Command line options to pass through to the diff command.'), ], ]; @@ -83,10 +79,10 @@ function hacked_drush_command() { * If TRUE, force rebuild of project data. */ function hacked_calculate_project_data_drush($projects, $force = FALSE) { - include_once DRUPAL_ROOT . '/core/includes/batch.inc'; + hacked_load_drush_dependencies(); // Try to get the report form cache if we can. - $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get('hacked:drush:full-report'); + $cache = drush_cache_get('hacked:drush:full-report', HACKED_CACHE_TABLE); if (!empty($cache->data) && !$force) { return $cache->data; } @@ -95,7 +91,7 @@ function hacked_calculate_project_data_drush($projects, $force = FALSE) { $operations = []; foreach ($projects as $project) { $operations[] = [ - 'hacked_build_report_batch', + 'hacked_build_report_batch_drush', [$project['name']], ]; } @@ -103,8 +99,13 @@ function hacked_calculate_project_data_drush($projects, $force = FALSE) { $batch = [ 'operations' => $operations, 'finished' => 'hacked_build_report_batch_finished_drush', - 'file' => drupal_get_path('module', 'hacked') . '/hacked.report.inc', - 'title' => t('Building report'), + // As this module isn't guaranteed to be installed, we need to override the + // assumption that we're relative to base_path(). So traverse up the + // directory tree until we get to the root, and then provide the absolute + // file system path to the include file. This will only work up to a + // certain depth, however. + 'file' => '../../../../../../../../../../../../..' . __DIR__ . '/hacked.report.inc', + 'title' => dt('Building report'), ]; drush_print('Rebuilding Hacked! report'); @@ -115,12 +116,33 @@ function hacked_calculate_project_data_drush($projects, $force = FALSE) { drush_print('Done.'); // Now we can get the data from the cache. - $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get('hacked:drush:full-report'); + $cache = drush_cache_get('hacked:drush:full-report', HACKED_CACHE_TABLE); if (!empty($cache->data)) { return $cache->data; } } + /** + * Batch callback to build the hacked report. + * + * @param string $project_name + * The name of the project. + * @param array $context + * The current context (modified to include the results on return). + */ +function hacked_build_report_batch_drush($project_name, &$context) { + hacked_load_drush_dependencies(); + if (!isset($context['results']['report'])) { + $context['results']['report'] = array(); + } + + $project = new hackedProject($project_name); + $context['results']['report'][$project_name] = $project->compute_report(); + $context['message'] = dt('Finished processing: @name', array( + '@name' => $project->title(), + )); +} + /** * Completion callback for the report batch. * @@ -134,22 +156,34 @@ function hacked_build_report_batch_finished_drush($success, $results) { // Sort the results. usort($results['report'], '_hacked_project_report_sort_by_status'); // Store them. - \Drupal::cache(HACKED_CACHE_TABLE) - ->set('hacked:drush:full-report', $results['report'], strtotime('+1 day')); + drush_cache_set('hacked:drush:full-report', $results['report'], HACKED_CACHE_TABLE, strtotime('+1 day')); + } +} + + /** + * Validation callback for drush hacked_list_projects. + */ +function drush_hacked_list_projects_validate() { + if (!function_exists('update_get_available')) { + $message = dt('Cannot list projects without the Drupal core update module enabled.'); + return drush_set_error('HACKED_UPDATE_DISABLED', $message); } } /** - * Drush command callback that shows the listing of changed/unchanged projects. + * Command callback for drush hacked_list_projects. */ function drush_hacked_list_projects() { - // Go get the data: + hacked_load_drush_dependencies(); module_load_include('inc', 'update', 'update.report'); + if ($available = update_get_available(TRUE)) { + // Get the data. module_load_include('inc', 'update', 'update.compare'); $data = update_calculate_project_data($available); $force_rebuild = drush_get_option('force-rebuild', FALSE); $projects = hacked_calculate_project_data_drush($data, $force_rebuild); + // Now print the data using drush: $rows[] = [ dt('Title'), @@ -173,12 +207,12 @@ function drush_hacked_list_projects() { break; case HACKED_STATUS_HACKED: - $row[] = t('Changed'); + $row[] = dt('Changed'); break; case HACKED_STATUS_UNCHECKED: default: - $row[] = t('Unchecked'); + $row[] = dt('Unchecked'); break; } @@ -218,11 +252,15 @@ function drush_hacked_lock_modified() { $project_ob = new hackedProject($project['project_name']); $lockfile = $project_ob->file_get_location('local', '.drush-lock-update'); if (!file_exists($lockfile)) { - drush_op('file_put_contents', $lockfile, dt("Locked: modified.")); - drush_print(dt("Locked: @project", ['@project' => $project['name']])); + drush_op('file_put_contents', $lockfile, dt('Locked: modified.')); + drush_print(dt("Locked: @project", [ + '@project' => $project['name'], + ])); } else { - drush_print(dt("@project is modified and already locked", ['@project' => $project['name']])); + drush_print(dt('@project is modified and already locked', [ + '@project' => $project['name'], + ])); } break; @@ -253,7 +291,7 @@ function drush_hacked_pre_pm_updatecode() { */ function hacked_drush_help_alter(&$command) { if (($command['command'] == 'pm-updatecode') || ($command['command'] == 'pm-update')) { - $command['sub-options']['--lock']['--lock-modified'] = "Lock any project that Hacked! determines is modified."; + $command['sub-options']['--lock']['--lock-modified'] = dt('Lock any project that Hacked! determines is modified.'); } } @@ -268,29 +306,36 @@ function drush_hacked_details_validate($short_name = '') { } /** - * Validate hook for the hacked drush commands that need a project. + * Generic validator for hacked drush commands. * * @param string $short_name * The project short name. + * + * @return null|boolean + * Return FALSE if there's an error. */ function drush_hacked_drush_command_validate($short_name = '') { + hacked_load_drush_dependencies(); + if (empty($short_name)) { - return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('A valid project must be specified', ['@project' => $short_name])); + return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('You must specify the name of a project.')); } $project = new hackedProject($short_name); $project->identify_project(); if (!$project->project_identified) { - return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('Could not find project: @project', ['@project' => $short_name])); + return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('Unable to find "@project".', [ + '@project' => $short_name, + ])); } - $project = NULL; } /** - * Drush callback that shows the list of changes/unchanged files in a project. + * Command callback for drush hacked_details. * - * You may specify the --include-unchanged option to show unchanged files too, - * otherwise just the changed and deleted files are shown. + * Shows the list of changes/unchanged files in a project. You may specify the + * --include-unchanged option to show unchanged files too, otherwise just the + * changed and deleted files are shown. * * @param string $short_name * The project short name. @@ -329,16 +374,16 @@ function drush_hacked_details($short_name) { break; case HACKED_STATUS_HACKED: - $row[] = t('Changed'); + $row[] = dt('Changed'); break; case HACKED_STATUS_DELETED: - $row[] = t('Deleted'); + $row[] = dt('Deleted'); break; case HACKED_STATUS_UNCHECKED: default: - $row[] = t('Unchecked'); + $row[] = dt('Unchecked'); break; } @@ -350,7 +395,7 @@ function drush_hacked_details($short_name) { } /** - * Validate hook for the hacked_diff drush command. + * Validation callback for drush hacked_diff. * * @param string $short_name * The project short name. @@ -360,10 +405,11 @@ function drush_hacked_diff_validate($short_name = '') { } /** - * Drush callback that shows the list of changes/unchanged files in a project. + * Command callback for drush hacked_diff. * - * You may specify the --include-unchanged option to show unchanged files too, - * otherwise just the changed and deleted files are shown. + * Shows the list of changes/unchanged files in a project. You may specify the + * --include-unchanged option to show unchanged files too, otherwise just the + * changed and deleted files are shown. * * @param string $short_name * The project short name. @@ -405,3 +451,42 @@ function drush_hacked_diff($short_name) { drush_print($line); } } + +/** + * Add missing dependencies for Drush (if module not installed). + * + * These inclusions would normally be available automatically when the module + * is installed, but they must be explicitly included if it isn't. + */ +function hacked_load_drush_dependencies() { + if (!\Drupal::moduleHandler()->moduleExists('hacked')) { + drush_log(dt('Loading Hacked! dependencies.')); + + // Get the command path. + $path = __DIR__; + + // Use it to include missing dependencies. + require_once $path . '/hacked.module'; + require_once $path . '/src/hackedFileGroup.php'; + require_once $path . '/src/hackedFileHasher.php'; + require_once $path . '/src/hackedFileIgnoreEndingsHasher.php'; + require_once $path . '/src/hackedFileIncludeEndingsHasher.php'; + require_once $path . '/src/hackedProject.php'; + require_once $path . '/src/hackedProjectWebDownloader.php'; + require_once $path . '/src/hackedProjectWebDevDownloader.php'; + require_once $path . '/src/hackedProjectWebFilesDownloader.php'; + require_once $path . '/hacked.report.inc'; + } +} + +/** + * Implements hook_drush_command_alter(). + */ +function hacked_drush_command_alter(&$command) { + if ($command['command'] == 'audit_security') { + $command['checks'][] = array( + 'name' => 'Hacked', + 'location' => __DIR__ . '/hacked.site_audit.inc', + ); + } + } \ No newline at end of file diff --git a/hacked.module b/hacked.module index 274f7bd..4a49602 100644 --- a/hacked.module +++ b/hacked.module @@ -138,8 +138,16 @@ function hacked_file_scan_directory($dir, $mask, $nomask = ['.', '..', 'CVS'], $ function hacked_get_file_hasher($name = NULL) { if (is_null($name)) { $name = \Drupal::config('hacked.settings')->get('selected_file_hasher'); + $name = $name ? $name : HACKED_DEFAULT_FILE_HASHER; } - $hashers = hacked_get_file_hashers(); + + if (!\Drupal::moduleHandler()->moduleExists('hacked')) { + $hashers = hacked_hacked_file_hashers_info(); + } + else { + $hashers = hacked_get_file_hashers(); + } + $class_name = $hashers[$name]['class']; return new $class_name; } diff --git a/hacked.report.inc b/hacked.report.inc index 862e7ca..c3c613d 100644 --- a/hacked.report.inc +++ b/hacked.report.inc @@ -1,4 +1,8 @@ loadInclude('hacked', 'inc', 'includes/hacked_project'); + $project = new hackedProject($project_name); $context['results']['report'][$project_name] = $project->compute_report(); $context['message'] = t('Finished processing: @name', array('@name' => $project->title())); diff --git a/src/SiteAuditCheckSecurityHacked.php b/src/SiteAuditCheckSecurityHacked.php new file mode 100644 index 0000000..340a216 --- /dev/null +++ b/src/SiteAuditCheckSecurityHacked.php @@ -0,0 +1,114 @@ +' . $ret_val . '

'; + $ret_val .= ''; + $ret_val .= ''; + $ret_val .= ''; + foreach ($this->registry['hacked'] as $row) { + $ret_val .= ''; + } + $ret_val .= ''; + $ret_val .= '
NameTitleVersionChanged
' . implode('', $row) . '
'; + } + else { + foreach ($this->registry['hacked'] as $row) { + $ret_val .= PHP_EOL . str_repeat(' ', 6); + $ret_val .= $row['project_name'] . ' v' . $row['project_version']; + $ret_val .= ': ' . $row['lines_different'] . ' line(s)'; + } + } + return $ret_val; + } + + /** + * Implements \SiteAudit\Check\Abstract\getAction(). + */ + public function getAction() {} + + /** + * Implements \SiteAudit\Check\Abstract\calculateScore(). + */ + public function calculateScore() { + $result = drush_invoke_process('@self', 'hacked-list-projects', array(), array('--include-unchanged=0', '--strict=0'), FALSE); + if ($result === FALSE) { + return SiteAuditCheckAbstract::AUDIT_CHECK_SCORE_INFO; + } + + $rows = array(); + foreach ($result['object'] as $info) { + if ($info['counts']['different'] != 0) { + $rows[] = array( + 'project_name' => $info['project_name'], + 'project_title' => $info['title'], + 'project_version' => $info['existing_version'], + 'lines_different' => $info['counts']['different'], + ); + } + } + + $this->registry['hacked'] = $result; + if (empty($result['object'])) { + return SiteAuditCheckAbstract::AUDIT_CHECK_SCORE_FAIL; + } + else { + if (!empty($rows)) { + $this->registry['hacked'] = $rows; + return SiteAuditCheckAbstract::AUDIT_CHECK_SCORE_WARN; + } + else { + return SiteAuditCheckAbstract::AUDIT_CHECK_SCORE_PASS; + } + } + } +} \ No newline at end of file diff --git a/src/hackedFileHasher.php b/src/hackedFileHasher.php index 3fc6600..c318418 100644 --- a/src/hackedFileHasher.php +++ b/src/hackedFileHasher.php @@ -30,11 +30,28 @@ abstract class hackedFileHasher { } function cache_set($filename, $hash) { - \Drupal::cache(HACKED_CACHE_TABLE)->set($this->cache_key($filename), $hash, strtotime('+7 days')); + // Check if we're running as a Drush command. + if (function_exists('drush_verify_cli') && drush_verify_cli()) { + // Use the Drush cache. + drush_cache_set($this->cache_key($filename), $hash, HACKED_CACHE_TABLE, strtotime('+7 days')); + } + else { + // Use the Drupal cache. + \Drupal::cache(HACKED_CACHE_TABLE)->set($this->cache_key($filename), $hash, strtotime('+7 days')); + } } function cache_get($filename) { - $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get($this->cache_key($filename)); + // Check if we're running as a Drush command. + if (function_exists('drush_verify_cli') && drush_verify_cli()) { + // Use the Drush cache. + $cache = drush_cache_get($this->cache_key($filename), HACKED_CACHE_TABLE); + } + else { + // Use the Drupal cache. + $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get($this->cache_key($filename)); + } + if (!empty($cache->data)) { return $cache->data; } -- 2.5.0