See Release Notes
Long Term Support Release
Differences Between: [Versions 311 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Check the presence of public paths via curl. 19 * 20 * @package core 21 * @category check 22 * @copyright 2020 Brendan Heywood <brendan@catalyst-au.net> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace core\check\environment; 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 use core\check\check; 31 use core\check\result; 32 33 /** 34 * Check the public access of various paths. 35 * 36 * @copyright 2020 Brendan Heywood <brendan@catalyst-au.net> 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class publicpaths extends check { 40 41 /** 42 * Get the short check name 43 * 44 * @return string 45 */ 46 public function get_name(): string { 47 return get_string('check_publicpaths_name', 'report_security'); 48 } 49 50 /** 51 * Returns a list of test urls and metadata. 52 */ 53 public function get_pathsets() { 54 global $CFG; 55 56 // The intention here is that each pattern is a simple regex such that 57 // in future perhaps the various webserver config could be generated as more 58 // pattens are added to these checks. 59 return [ 60 [ 61 'pattern' => '/vendor/', 62 '404' => [ 63 'vendor/', 64 'vendor/bin/behat', 65 ], 66 'details' => get_string('check_vendordir_details', 'report_security', ['path' => $CFG->dirroot.'/vendor']), 67 'summary' => get_string('check_vendordir_info', 'report_security'), 68 ], 69 [ 70 'pattern' => '/node_modules/', 71 '404' => [ 72 'node_modules/', 73 'node_modules/cli/cli.js', 74 ], 75 'summary' => get_string('check_nodemodules_info', 'report_security'), 76 'details' => get_string('check_nodemodules_details', 'report_security', 77 ['path' => $CFG->dirroot . '/node_modules']), 78 ], 79 [ 80 'pattern' => '^\..*', 81 '404' => [ 82 '.git/', 83 '.git/HEAD', 84 '.github/FUNDING.yml', 85 '.stylelintrc', 86 ], 87 ], 88 [ 89 'pattern' => 'composer.json', 90 '404' => [ 91 'composer.json', 92 ], 93 ], 94 [ 95 'pattern' => '.lock', 96 '404' => [ 97 'composer.lock', 98 ], 99 ], 100 [ 101 'pattern' => 'environment.xml', 102 '404' => [ 103 'admin/environment.xml', 104 ], 105 ], 106 [ 107 'pattern' => '', 108 '404' => [ 109 'doesnotexist', // Just to make sure that real 404s are still 404s. 110 ], 111 'summary' => '', 112 ], 113 [ 114 'pattern' => '', 115 '404' => [ 116 'lib/classes/', 117 ], 118 'summary' => get_string('check_dirindex_info', 'report_security'), 119 ], 120 [ 121 'pattern' => 'db/install.xml', 122 '404' => [ 123 'lib/db/install.xml', 124 'mod/assign/db/install.xml', 125 ], 126 ], 127 [ 128 'pattern' => 'readme.txt', 129 '404' => [ 130 'lib/scssphp/readme_moodle.txt', 131 'mod/resource/readme.txt', 132 ], 133 ], 134 [ 135 'pattern' => 'README', 136 '404' => [ 137 'mod/README.txt', 138 'mod/book/README.md', 139 'mod/chat/README.txt', 140 ], 141 ], 142 [ 143 'pattern' => '/upgrade.txt', 144 '404' => [ 145 'auth/manual/upgrade.txt', 146 'lib/upgrade.txt', 147 ], 148 ], 149 [ 150 'pattern' => 'phpunit.xml', 151 '404' => ['phpunit.xml.dist'], 152 ], 153 [ 154 'pattern' => '/fixtures/', 155 '404' => [ 156 'privacy/tests/fixtures/logo.png', 157 'enrol/lti/tests/fixtures/input.xml', 158 ], 159 ], 160 [ 161 'pattern' => '/behat/', 162 '404' => ['blog/tests/behat/delete.feature'], 163 ], 164 ]; 165 } 166 167 /** 168 * Return result 169 * @return result 170 */ 171 public function get_result(): result { 172 global $CFG, $OUTPUT; 173 174 $status = result::OK; 175 $details = ''; 176 $summary = get_string('check_publicpaths_ok', 'report_security'); 177 $errors = []; 178 179 $c = new \curl(); 180 $paths = $this->get_pathsets(); 181 182 $table = new \html_table(); 183 $table->align = ['center', 'right', 'left']; 184 $table->size = ['1%', '1%', '1%', '1%', '1%', '99%']; 185 $table->head = [ 186 get_string('status'), 187 get_string('checkexpected'), 188 get_string('checkactual'), 189 get_string('url'), 190 get_string('category'), 191 get_string('details'), 192 ]; 193 $table->attributes['class'] = 'flexible generaltable generalbox table-sm'; 194 $table->data = []; 195 196 // Used to track duplicated errors. 197 $lastdetail = '-'; 198 199 $curl = new \curl(); 200 $requests = []; 201 202 // Build up a list of all url so we can load them in parallel. 203 foreach ($paths as $path) { 204 foreach (['200', '404'] as $expected) { 205 if (!isset($path[$expected])) { 206 continue; 207 } 208 foreach ($path[$expected] as $test) { 209 $requests[] = [ 210 'nobody' => true, 211 'header' => 1, 212 'url' => $CFG->wwwroot . '/' . $test, 213 'returntransfer' => true, 214 ]; 215 } 216 } 217 } 218 219 $headers = $curl->download($requests); 220 221 foreach ($paths as $path) { 222 foreach (['200', '404'] as $expected) { 223 if (!isset($path[$expected])) { 224 continue; 225 } 226 foreach ($path[$expected] as $test) { 227 $rowsummary = ''; 228 $rowdetail = ''; 229 230 $url = $CFG->wwwroot . '/' . $test; 231 232 // Parse the HTTP header to get the 200 / 404 code. 233 $header = array_shift($headers); 234 $actual = strtok($header, "\n"); 235 $actual = strtok($actual, " "); 236 $actual = strtok(" "); 237 238 if ($actual != $expected) { 239 if (isset($path['summary'])) { 240 $rowsummary = $path['summary']; 241 } else { 242 $rowsummary = get_string('check_publicpaths_generic', 243 'report_security', $path['pattern']); 244 } 245 246 // Special case where a 404 is ideal but a 403 is ok too. 247 if ($actual == 403) { 248 $result = new result(result::INFO, '', ''); 249 $rowsummary .= get_string('check_publicpaths_403', 'report_security'); 250 } else { 251 $result = new result(result::ERROR, '', ''); 252 $status = result::ERROR; 253 $summary = get_string('check_publicpaths_warning', 'report_security'); 254 } 255 256 $rowdetail = isset($path['details']) ? $path['details'] : $rowsummary; 257 258 if (empty($errors[$path['pattern']])) { 259 $summary .= '<li>' . $rowsummary . '</li>'; 260 $errors[$path['pattern']] = 1; 261 } 262 263 } else { 264 $result = new result(result::OK, '', ''); 265 } 266 267 $table->data[] = [ 268 $OUTPUT->check_result($result), 269 $expected, 270 $actual, 271 $OUTPUT->action_link($url, $test, null, ['target' => '_blank']), 272 "<pre>{$path['pattern']}</pre>", 273 ]; 274 275 // Merge duplicate details to display a nicer table. 276 if ($rowdetail == $lastdetail) { 277 $duplicates++; 278 } else { 279 $duplicates = 1; 280 } 281 $detailcell = new \html_table_cell($rowdetail); 282 $detailcell->rowspan = $duplicates; 283 $rows = count($table->data); 284 $table->data[$rows - $duplicates][5] = $detailcell; 285 $lastdetail = $rowdetail; 286 } 287 } 288 } 289 290 $details .= \html_writer::table($table); 291 292 return new result($status, $summary, $details); 293 } 294 295 /** 296 * Link to the dev docs for more info. 297 * 298 * @return action_link|null 299 */ 300 public function get_action_link(): ?\action_link { 301 return new \action_link( 302 new \moodle_url(\get_docs_url('Installing_Moodle#Set_up_your_server')), 303 get_string('moodledocs')); 304 } 305 306 } 307
title
Description
Body
title
Description
Body
title
Description
Body
title
Body