ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç repository.min.js000064400000000662152267245240010120 0ustar00define("core/check/repository",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getCheckResult=void 0;_exports.getCheckResult=(adminTreeId,settingName,includeDetails)=>(0,_ajax.call)([{methodname:"core_check_get_result_admintree",args:{admintreeid:adminTreeId,settingname:settingName,includedetails:includeDetails}}])[0]})); //# sourceMappingURL=repository.min.js.mapcheck_result.min.js000064400000004023152267245240010347 0ustar00define("core/check/check_result",["exports","./repository","core/str","core/templates"],(function(_exports,_repository,_str,Templates){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getAndRender= /** * Check API result functions * * @module core/check * @author Matthew Hilton * @copyright Catalyst IT, 2023 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ async function(domSelector,adminTreeId,settingName,includeDetails){const element=document.querySelector(domSelector);if(!element)return void window.console.error("Check selector not found");try{const result=await(0,_repository.getCheckResult)(adminTreeId,settingName,includeDetails),decoded=(new DOMParser).parseFromString(result.html,"text/html").documentElement.textContent;element.innerHTML=decoded}catch(e){window.console.error(e),element.innerHTML=await Templates.render("core/notification",{iserror:!0,closebutton:!1,announce:0,extraclasses:"",message:await(0,_str.getString)("checkerror","core",adminTreeId)})}},Templates=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Templates)})); //# sourceMappingURL=check_result.min.js.maprepository.min.js.map000064400000003653152267245240010677 0ustar00{"version":3,"file":"repository.min.js","sources":["../../src/check/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Check API webservice repository\n *\n * @module core/check\n * @author Matthew Hilton \n * @copyright Catalyst IT, 2023\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\n\n/**\n * Call check_get_result webservice function\n *\n * @param {String} adminTreeId Id of the admin_setting that called this webservice. Used to retrieve the check registered to it.\n * @param {String} settingName Setting name (used to find it's parent)\n * @param {Boolean} includeDetails If details should be included in the response\n */\nexport const getCheckResult = (adminTreeId, settingName, includeDetails) => fetchMany([{\n methodname: 'core_check_get_result_admintree',\n args: {\n admintreeid: adminTreeId,\n settingname: settingName,\n includedetails: includeDetails,\n },\n}])[0];\n\n"],"names":["adminTreeId","settingName","includeDetails","methodname","args","admintreeid","settingname","includedetails"],"mappings":"gMAiC8B,CAACA,YAAaC,YAAaC,kBAAmB,cAAU,CAAC,CACnFC,WAAY,kCACZC,KAAM,CACFC,YAAaL,YACbM,YAAaL,YACbM,eAAgBL,mBAEpB"}check_result.min.js.map000064400000006412152267245240011127 0ustar00{"version":3,"file":"check_result.min.js","sources":["../../src/check/check_result.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Check API result functions\n *\n * @module core/check\n * @author Matthew Hilton \n * @copyright Catalyst IT, 2023\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getCheckResult} from './repository';\nimport {getString} from 'core/str';\nimport * as Templates from 'core/templates';\n\n/**\n * Get the result of a check and replace a given DOM element with the result.\n *\n * @method getAndRender\n * @param {String} domSelector A CSS selector for a dom element to replace the the HTML for.\n * @param {String} adminTreeId Id of the admin_setting that called this webservice. Used to retrieve the check registered to it.\n * @param {String} settingName Name of setting (used to find the parent node in the admin tree)\n * @param {Boolean} includeDetails If true, details will be included in the check.\n * By default only the status and the summary is returned.\n */\nexport async function getAndRender(domSelector, adminTreeId, settingName, includeDetails) {\n const element = document.querySelector(domSelector);\n\n if (!element) {\n window.console.error('Check selector not found');\n return;\n }\n\n try {\n const result = await getCheckResult(adminTreeId, settingName, includeDetails);\n const decoded = new DOMParser().parseFromString(result.html, \"text/html\").documentElement.textContent;\n element.innerHTML = decoded;\n } catch (e) {\n window.console.error(e);\n\n // Render error as a red notification.\n element.innerHTML = await Templates.render('core/notification', {\n iserror: true,\n closebutton: false,\n announce: 0,\n extraclasses: '',\n message: await getString('checkerror', 'core', adminTreeId)\n });\n }\n}\n"],"names":["domSelector","adminTreeId","settingName","includeDetails","element","document","querySelector","window","console","error","result","decoded","DOMParser","parseFromString","html","documentElement","textContent","innerHTML","e","Templates","render","iserror","closebutton","announce","extraclasses","message"],"mappings":";;;;;;;;;eAsCmCA,YAAaC,YAAaC,YAAaC,sBAChEC,QAAUC,SAASC,cAAcN,iBAElCI,oBACDG,OAAOC,QAAQC,MAAM,sCAKfC,aAAe,8BAAeT,YAAaC,YAAaC,gBACxDQ,SAAU,IAAIC,WAAYC,gBAAgBH,OAAOI,KAAM,aAAaC,gBAAgBC,YAC1FZ,QAAQa,UAAYN,QACtB,MAAOO,GACLX,OAAOC,QAAQC,MAAMS,GAGrBd,QAAQa,gBAAkBE,UAAUC,OAAO,oBAAqB,CAC5DC,SAAS,EACTC,aAAa,EACbC,SAAU,EACVC,aAAc,GACdC,cAAe,kBAAU,aAAc,OAAQxB"}external/get_result_admintree.php000064400000013514152267671550013330 0ustar00. namespace core\check\external; use admin_root; use admin_setting_check; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_value; use context_system; use invalid_parameter_exception; /** * Webservice to get result of a given check. * * @package core * @category check * @copyright 2023 Matthew Hilton * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class get_result_admintree extends external_api { /** * Defines parameters * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters([ 'admintreeid' => new external_value(PARAM_TEXT, 'ID of node in admintree'), 'settingname' => new external_value(PARAM_TEXT, 'Name of setting'), 'includedetails' => new external_value(PARAM_BOOL, 'If the details should be included in the response. Depending on the check, details could be slower to return.', VALUE_DEFAULT, false), ]); } /** * Gets the result of the check and returns it. * @param string $admintreeid ID of admin_setting to find check object from * @param string $settingname Name of admin_setting to find check object from * @param bool $includedetails If the details should be included in the response. * @param admin_root|null $admintree Root of admin tree to use (for unit testing) * @return array returned data */ public static function execute(string $admintreeid, string $settingname, bool $includedetails, ?admin_root $admintree = null): array { global $OUTPUT, $CFG; // Validate parameters. self::validate_parameters(self::execute_parameters(), [ 'admintreeid' => $admintreeid, 'settingname' => $settingname, 'includedetails' => $includedetails, ]); // Context and capability checks. $context = context_system::instance(); self::validate_context($context); require_admin(); require_once($CFG->libdir . '/adminlib.php'); // Find admin node so we can load the check object. $check = self::get_check_from_setting($admintreeid, $settingname, $admintree); if (empty($check)) { throw new invalid_parameter_exception("Could not find check object using admin tree."); } // Execute the check and get the result. $result = $check->get_result(); // Build the response. $data = [ 'status' => s($result->get_status()), 'summary' => s($result->get_summary()), 'html' => s($OUTPUT->check_full_result($check, $result, $includedetails)), ]; // Since details might be slower to obtain, we allow this to be optionally returned. if ($includedetails) { $data['details'] = s($result->get_details()); } return $data; } /** * Finds the check from the admin tree. * * @param string $settingid ID of the adming_setting * @param string $settingname Name of the admin_setting * @param admin_root|null $tree Admin tree to use (for unit testing). Null will default to the admin_get_root() */ private static function get_check_from_setting(string $settingid, string $settingname, ?admin_root $tree = null) { // Since settings do not know exactly who their parents are in the tree, we must search for the setting. if (empty($tree)) { $tree = \admin_get_root(); } // Search for the setting name. // To do this, we must search in each category. $categories = $tree->search($settingname); $allsettings = array_map(function($c) { return $c->settings; }, array_values($categories)); // Flatten the array. $allsettings = array_merge(...$allsettings); // Find the one that matches the unique id exactly and are check settings. $matchingsettings = array_filter($allsettings, function($s) use ($settingid) { return $s->get_id() == $settingid && $s instanceof admin_setting_check; }); // There was either none found or more than one found. // In this case, we cannot determine which to use so just return null. if (count($matchingsettings) != 1) { return null; } $setting = current($matchingsettings); return $setting->get_check(); } /** * Defines return structure. * @return external_single_structure */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'status' => new external_value(PARAM_TEXT, 'Result status constant'), 'summary' => new external_value(PARAM_TEXT, 'Summary of result'), 'html' => new external_value(PARAM_TEXT, 'Rendered full html result', VALUE_OPTIONAL), 'details' => new external_value(PARAM_TEXT, 'Details of result (if includedetails was enabled)', VALUE_OPTIONAL), ]); } } table.php000064400000010442152267671550006365 0ustar00. /** * A table of check results * * @package core * @category check * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check; defined('MOODLE_INTERNAL') || die(); /** * A table of check results * * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class table implements \renderable { /** * @var \moodle_url $url */ protected $url = ''; /** * @var string $type What type of checks */ protected $type = ''; /** * @var check $detail a specific check to focus on */ public $detail = ''; /** * @var array $checks shown in this table */ public $checks = []; /** * Constructor * * @param string $type of check * @param string $url of report * @param string $detail check to focus on */ public function __construct($type, $url, $detail = '') { // We may need a bit more memory and this may take a long time to process. \raise_memory_limit(MEMORY_EXTRA); \core_php_time_limit::raise(); $this->type = $type; $this->url = $url; $this->checks = \core\check\manager::get_checks($type); if ($detail) { $this->checks = array_filter($this->checks, function($check) use ($detail) { return $detail == $check->get_ref(); }); if (!empty($this->checks)) { $this->detail = reset($this->checks); } } } /** * Render a table of checks * * @param renderer $output to use * @return string html output */ public function render($output) { $table = new \html_table(); $table->data = []; $table->head = [ get_string('status'), get_string('check'), get_string('summary'), get_string('action'), ]; $table->colclasses = [ 'rightalign status', 'leftalign check', 'leftalign summary', 'leftalign action', ]; $table->id = $this->type . 'reporttable'; $table->attributes = ['class' => 'admintable ' . $this->type . 'report generaltable']; foreach ($this->checks as $check) { $ref = $check->get_ref(); $result = $check->get_result(); $component = $check->get_component(); $actionlink = $check->get_action_link(); $link = new \moodle_url($this->url, ['detail' => $ref]); $row = []; $row[] = $output->check_result($result); $row[] = $output->action_link($link, $check->get_name()); $row[] = $result->get_summary() . '
' . \html_writer::start_tag('small') . $output->action_link($link, get_string('moreinfo')) . \html_writer::end_tag('small'); if ($actionlink) { $row[] = $output->render($actionlink); } else { $row[] = ''; } $table->data[] = $row; } $html = \html_writer::table($table); if ($this->detail && $result) { $details = $result->get_details(); if (!empty($details)) { $html .= $output->heading(get_string('details'), 3); $html .= $output->box($details, 'generalbox boxwidthnormal boxaligncenter'); } $html .= $output->continue_button($this->url); } return $html; } } environment/environment.php000064400000004367152267671550012217 0ustar00. /** * Environment check * * @package core * @category check * @copyright 2020 Brendan Heywood (brendan@catalyst-au.net) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\environment; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Environment check * * @package core * @copyright 2020 Brendan Heywood (brendan@catalyst-au.net) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class environment extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('environment', 'admin'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/environment.php'), get_string('environment', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; require_once($CFG->libdir.'/environmentlib.php'); list($status, $details) = check_moodle_environment($CFG->release, ENV_SELECT_NEWER); if ($status) { $summary = get_string('environmentok', 'admin'); $status = result::OK; } else { $summary = get_string('environmenterrortodo', 'admin'); $status = result::ERROR; } return new result($status, $summary, ''); } } environment/publicpaths.php000064400000024663152267671550012172 0ustar00. /** * Check the presence of public paths via curl. * * @package core * @category check * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\environment; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Check the public access of various paths. * * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class publicpaths extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_publicpaths_name', 'report_security'); } /** * Returns a list of test urls and metadata. */ public function get_pathsets() { global $CFG; // The intention here is that each pattern is a simple regex such that // in future perhaps the various webserver config could be generated as more // pattens are added to these checks. return [ [ 'pattern' => '/vendor/', '404' => [ 'vendor/', 'vendor/bin/behat', ], 'details' => get_string('check_vendordir_details', 'report_security', ['path' => $CFG->dirroot.'/vendor']), 'summary' => get_string('check_vendordir_info', 'report_security'), ], [ 'pattern' => '/node_modules/', '404' => [ 'node_modules/', 'node_modules/cli/cli.js', ], 'summary' => get_string('check_nodemodules_info', 'report_security'), 'details' => get_string('check_nodemodules_details', 'report_security', ['path' => $CFG->dirroot . '/node_modules']), ], [ 'pattern' => '^\..*', '404' => [ '.git/', '.git/HEAD', '.github/FUNDING.yml', '.stylelintrc', '.upgradenotes/', ], ], [ 'pattern' => 'composer.json', '404' => [ 'composer.json', ], ], [ 'pattern' => '.lock', '404' => [ 'composer.lock', ], ], [ 'pattern' => 'environment.xml', '404' => [ 'admin/environment.xml', ], ], [ 'pattern' => '', '404' => [ 'doesnotexist', // Just to make sure that real 404s are still 404s. ], 'summary' => '', ], [ 'pattern' => '', '404' => [ 'lib/classes/', ], 'summary' => get_string('check_dirindex_info', 'report_security'), ], [ 'pattern' => 'db/install.xml', '404' => [ 'lib/db/install.xml', 'mod/assign/db/install.xml', ], ], [ 'pattern' => 'readme.txt', '404' => [ 'lib/scssphp/readme_moodle.txt', 'mod/resource/readme.txt', ], ], [ 'pattern' => 'README', '404' => [ 'mod/README.txt', 'mod/book/README.md', ], ], [ 'pattern' => '\/(upgrade\.txt|UPGRADING\.md|UPGRADING\-CURRENT\.md)', '404' => [ 'auth/manual/upgrade.txt', 'lib/upgrade.txt', 'UPGRADING.md', 'UPGRADING-CURRENT.md', 'reportbuilder/UPGRADING.md', ], 'summary' => get_string('check_upgradefile_info', 'report_security'), ], [ 'pattern' => 'phpunit.xml', '404' => ['phpunit.xml.dist'], ], [ 'pattern' => '/fixtures/', '404' => [ 'privacy/tests/fixtures/logo.png', 'enrol/lti/tests/fixtures/input.xml', ], ], [ 'pattern' => '/behat/', '404' => ['blog/tests/behat/delete.feature'], ], ]; } /** * Return result * @return result */ public function get_result(): result { global $CFG, $OUTPUT; $status = result::OK; $details = ''; $summary = get_string('check_publicpaths_ok', 'report_security'); $errors = []; $c = new \curl(); $paths = $this->get_pathsets(); $table = new \html_table(); $table->align = ['center', 'right', 'left']; $table->size = ['1%', '1%', '1%', '1%', '1%', '99%']; $table->head = [ get_string('status'), get_string('checkexpected'), get_string('checkactual'), get_string('url'), get_string('category'), get_string('details'), ]; $table->attributes['class'] = 'flexible generaltable generalbox table-sm table table-hover'; $table->data = []; // Used to track duplicated errors. $lastdetail = '-'; $curl = new \curl(); $requests = []; // Build up a list of all url so we can load them in parallel. foreach ($paths as $path) { foreach (['200', '404'] as $expected) { if (!isset($path[$expected])) { continue; } foreach ($path[$expected] as $test) { $requests[] = [ 'nobody' => true, 'header' => 1, 'url' => $CFG->wwwroot . '/' . $test, 'returntransfer' => true, ]; } } } $headers = $curl->download($requests); foreach ($paths as $path) { foreach (['200', '404'] as $expected) { if (!isset($path[$expected])) { continue; } foreach ($path[$expected] as $test) { $rowsummary = ''; $rowdetail = ''; $url = $CFG->wwwroot . '/' . $test; // Parse the HTTP header to get the 200 / 404 code. $header = array_shift($headers); $actual = strtok($header, "\n"); $actual = strtok($actual, " "); $actual = strtok(" "); if ($actual != $expected) { if (isset($path['summary'])) { $rowsummary = $path['summary']; } else { $rowsummary = get_string('check_publicpaths_generic', 'report_security', $path['pattern']); } // Special case where a 404 is ideal but a 403 is ok too. if ($actual == 403) { $result = new result(result::INFO, '', ''); $rowsummary .= get_string('check_publicpaths_403', 'report_security'); } else { $result = new result(result::ERROR, '', ''); $status = result::ERROR; $summary = get_string('check_publicpaths_warning', 'report_security'); } $rowdetail = isset($path['details']) ? $path['details'] : $rowsummary; if (empty($errors[$path['pattern']])) { $summary .= '
  • ' . $rowsummary . '
  • '; $errors[$path['pattern']] = 1; } } else { $result = new result(result::OK, '', ''); } $table->data[] = [ $OUTPUT->check_result($result), $expected, $actual, $OUTPUT->action_link($url, $test, null, ['target' => '_blank']), "
    {$path['pattern']}
    ", ]; // Merge duplicate details to display a nicer table. if ($rowdetail == $lastdetail) { $duplicates++; } else { $duplicates = 1; } $detailcell = new \html_table_cell($rowdetail); $detailcell->rowspan = $duplicates; $rows = count($table->data); $table->data[$rows - $duplicates][5] = $detailcell; $lastdetail = $rowdetail; } } } $details .= \html_writer::table($table); return new result($status, $summary, $details); } /** * Link to the dev docs for more info. * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url(\get_docs_url('Installing_Moodle#Set_up_your_server')), get_string('moodledocs')); } } environment/unsecuredataroot.php000064400000004650152267671550013235 0ustar00. /** * Verifies fatal misconfiguration of dataroot * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\environment; defined('MOODLE_INTERNAL') || die(); use core\check\result; /** * Verifies fatal misconfiguration of dataroot * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class unsecuredataroot extends \core\check\check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_unsecuredataroot_name', 'report_security'); } /** * Return result * @return result */ public function get_result(): result { global $CFG; require_once($CFG->libdir.'/adminlib.php'); $details = get_string('check_unsecuredataroot_details', 'report_security'); $insecuredataroot = is_dataroot_insecure(true); if ($insecuredataroot == INSECURE_DATAROOT_WARNING) { $status = result::ERROR; $summary = get_string('check_unsecuredataroot_warning', 'report_security', $CFG->dataroot); } else if ($insecuredataroot == INSECURE_DATAROOT_ERROR) { $status = result::CRITICAL; $summary = get_string('check_unsecuredataroot_error', 'report_security', $CFG->dataroot); } else { $status = result::OK; $summary = get_string('check_unsecuredataroot_ok', 'report_security'); } return new result($status, $summary, $details); } } environment/upgradecheck.php000064400000004560152267671550012273 0ustar00. /** * Upgrade check * * @package core * @category check * @copyright 2020 Brendan Heywood (brendan@catalyst-au.net) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\environment; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Upgrade check * * @package core * @copyright 2020 Brendan Heywood (brendan@catalyst-au.net) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class upgradecheck extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('checkupgradepending', 'admin'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/index.php?cache=1'), get_string('notifications', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; require("$CFG->dirroot/version.php"); $newversion = "$release ($version)"; if ($version < $CFG->version) { $status = result::ERROR; $summary = get_string('downgradedcore', 'error'); } else if (moodle_needs_upgrading()) { $status = result::ERROR; $summary = get_string('cliupgradepending', 'admin'); } else { $status = result::OK; $summary = get_string('cliupgradenoneed', 'core_admin', $newversion); } return new result($status, $summary); } } environment/antivirus.php000064400000010656152267671550011675 0ustar00. namespace core\check\environment; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Checks status of antivirus scanners by looking back at any recent scans. * * @package core * @category check * @author Kevin Pham * @copyright Catalyst IT, 2021 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class antivirus extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_antivirus_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php', ['section' => 'manageantiviruses']), get_string('antivirussettings', 'antivirus')); } /** * Return result * @return result */ public function get_result(): result { global $CFG, $DB; $details = \html_writer::tag('p', get_string('check_antivirus_details', 'report_security')); // If no scanners are enabled, then return an NA status since the results do not matter. if (empty($CFG->antiviruses)) { $status = result::NA; $summary = get_string('check_antivirus_info', 'report_security'); return new result($status, $summary, $details); } $logmanager = get_log_manager(); $readers = $logmanager->get_readers('\core\log\sql_internal_table_reader'); // If reader is not a sql_internal_table_reader return UNKNOWN since we // aren't able to fetch the required information. Legacy logs are not // supported here. They do not hold enough adequate information to be // used for these checks. if (empty($readers)) { $status = result::UNKNOWN; $summary = get_string('check_antivirus_logstore_not_supported', 'report_security'); return new result($status, $summary, $details); } $reader = reset($readers); // If there has been a recent timestamp within threshold period, then // set the status to ERROR and describe the problem, e.g. X issues in // the last N period. $threshold = get_config('antivirus', 'threshold'); $params = []; $params['lookback'] = time() - $threshold; // Type of "targets" to include. list($targetsqlin, $inparams) = $DB->get_in_or_equal([ 'antivirus_scan_file', 'antivirus_scan_data', ], SQL_PARAMS_NAMED); $params = array_merge($inparams, $params); // Specify criteria for search. $selectwhere = "timecreated > :lookback AND target $targetsqlin AND action = 'error'"; $totalerrors = $reader->get_events_select_count($selectwhere, $params); if (!empty($totalerrors)) { $status = result::ERROR; $summary = get_string('check_antivirus_error', 'report_security', [ 'errors' => $totalerrors, 'lookback' => format_time($threshold) ]); } else if (!empty($CFG->antiviruses)) { $status = result::OK; // Fetch count of enabled antiviruses (we don't care about which ones). $totalantiviruses = !empty($CFG->antiviruses) ? count(explode(',', $CFG->antiviruses)) : 0; $summary = get_string('check_antivirus_ok', 'report_security', [ 'scanners' => $totalantiviruses, 'lookback' => format_time($threshold) ]); } return new result($status, $summary, $details); } } environment/configrw.php000064400000004133152267671550011460 0ustar00. /** * Verifies config.php is not writable anymore after installation * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\environment; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies config.php is not writable anymore after installation * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class configrw extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_configrw_name', 'report_security'); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $details = get_string('check_configrw_details', 'report_security'); if (is_writable($CFG->dirroot . '/config.php')) { $status = result::WARNING; $summary = get_string('check_configrw_warning', 'report_security'); } else { $status = result::OK; $summary = get_string('check_configrw_ok', 'report_security'); } return new result($status, $summary, $details); } } environment/preventexecpath.php000064400000004077152267671550013056 0ustar00. /** * Verifies the status of preventexecpath * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\environment; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies the status of preventexecpath * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class preventexecpath extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_preventexecpath_name', 'report_security'); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $details = get_string('check_preventexecpath_details', 'report_security'); if (empty($CFG->preventexecpath)) { $status = result::WARNING; $summary = get_string('check_preventexecpath_warning', 'report_security'); } else { $status = result::OK; $summary = get_string('check_preventexecpath_ok', 'report_security'); } return new result($status, $summary, $details); } } environment/displayerrors.php000064400000004451152267671550012547 0ustar00. /** * Verifies displaying of errors * * Problem for lib files and 3rd party code because we can not disable debugging * in these scripts (they do not include config.php) * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\environment; defined('MOODLE_INTERNAL') || die(); use core\check\result; use core\check\check; /** * Verifies displaying of errors * * Problem for lib files and 3rd party code because we can not disable debugging * in these scripts (they do not include config.php) * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class displayerrors extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_displayerrors_name', 'report_security'); } /** * Return result * @return result */ public function get_result(): result { $details = get_string('check_displayerrors_details', 'report_security'); if (defined('WARN_DISPLAY_ERRORS_ENABLED')) { $status = result::WARNING; $summary = get_string('check_displayerrors_error', 'report_security'); } else { $status = result::OK; $summary = get_string('check_displayerrors_ok', 'report_security'); } return new result($status, $summary, $details); } } manager.php000064400000012255152267671550006714 0ustar00. /** * Check API manager * * @package core * @category check * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check; defined('MOODLE_INTERNAL') || die(); /** * Check API manager * * @package core * @category check * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class manager { /** * The list of valid check types */ public const TYPES = ['status', 'security', 'performance']; /** * Return all status checks * * @param string $type of checks to fetch * @return array of check objects */ public static function get_checks(string $type): array { if (!in_array($type, self::TYPES)) { throw new \moodle_exception("Invalid check type '$type'"); } $method = 'get_' . $type . '_checks'; $checks = self::$method(); return $checks; } /** * Return all performance checks * * @return array of check objects */ public static function get_performance_checks(): array { $checks = [ new performance\designermode(), new performance\cachejs(), new performance\debugging(), new performance\backups(), new performance\stats(), new performance\dbschema(), ]; // Any plugin can add status checks to this report by implementing a callback // _status_checks() which returns a check object. $morechecks = get_plugins_with_function('performance_checks', 'lib.php'); foreach ($morechecks as $plugintype => $plugins) { foreach ($plugins as $plugin => $pluginfunction) { $result = $pluginfunction(); foreach ($result as $check) { $check->set_component($plugintype . '_' . $plugin); $checks[] = $check; } } } return $checks; } /** * Return all status checks * * @return array of check objects */ public static function get_status_checks(): array { $checks = [ new environment\environment(), new environment\upgradecheck(), new environment\antivirus(), ]; // Any plugin can add status checks to this report by implementing a callback // _status_checks() which returns a check object. $morechecks = get_plugins_with_function('status_checks', 'lib.php'); foreach ($morechecks as $plugintype => $plugins) { foreach ($plugins as $plugin => $pluginfunction) { $result = $pluginfunction(); foreach ($result as $check) { $check->set_component($plugintype . '_' . $plugin); $checks[] = $check; } } } return $checks; } /** * Return all security checks * * @return array of check objects */ public static function get_security_checks(): array { $checks = [ new environment\displayerrors(), new environment\unsecuredataroot(), new environment\publicpaths(), new environment\configrw(), new environment\preventexecpath(), new security\embed(), new security\openprofiles(), new security\crawlers(), new security\passwordpolicy(), new security\emailchangeconfirmation(), new security\webcron(), new http\cookiesecure(), new access\riskadmin(), new access\riskxss(), new access\riskbackup(), new access\defaultuserrole(), new access\guestrole(), new access\frontpagerole(), ]; // Any plugin can add security checks to this report by implementing a callback // _security_checks() which returns a check object. $morechecks = get_plugins_with_function('security_checks', 'lib.php'); foreach ($morechecks as $plugintype => $plugins) { foreach ($plugins as $plugin => $pluginfunction) { $result = $pluginfunction(); foreach ($result as $check) { $check->set_component($plugintype . '_' . $plugin); $checks[] = $check; } } } return $checks; } } access/riskbackup.php000064400000003662152267671550010703 0ustar00. /** * Lists all roles that have the ability to backup user data, as well as users * * @package core * @category check * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\access; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Lists all roles that have the ability to backup user data, as well as users * * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class riskbackup extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_riskbackup_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/roles/manage.php'), get_string('manageroles', 'role')); } /** * Return result * @return result */ public function get_result(): result { return new riskbackup_result(); } } access/defaultuserrole.php000064400000007755152267671550011761 0ustar00. namespace core\check\access; use core\check\check; use core\check\result; /** * Verifies sanity of default user role. * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class defaultuserrole extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_defaultuserrole_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { global $CFG, $DB; $defaultrole = $DB->get_record('role', ['id' => $CFG->defaultuserroleid]); return new \action_link( new \moodle_url('/admin/roles/define.php', ['action' => 'view', 'roleid' => $defaultrole->id]), get_string('definitionofrolex', 'core_role', role_get_name($defaultrole)) ); } /** * Return result * @return result */ public function get_result(): result { global $DB, $CFG; $details = ''; if (!$defaultrole = $DB->get_record('role', ['id' => $CFG->defaultuserroleid])) { $status = result::WARNING; $summary = get_string('check_defaultuserrole_notset', 'report_security'); return new result($status, $summary, $details); } // Risky caps - usually very dangerous. $sql = "SELECT rc.id, rc.contextid, rc.capability FROM {role_capabilities} rc JOIN {capabilities} cap ON cap.name = rc.capability WHERE " . $DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS)) . " <> 0 AND rc.permission = :capallow AND rc.roleid = :roleid"; $riskyresults = $DB->get_records_sql($sql, [ 'capallow' => CAP_ALLOW, 'roleid' => $defaultrole->id, ]); // If automatic approval is disabled, then the requestdelete capability is not risky. if (!get_config('tool_dataprivacy', 'automaticdatadeletionapproval')) { $riskyresults = array_filter($riskyresults, function ($object) { return $object->capability !== 'tool/dataprivacy:requestdelete'; }); } // Count the number of unique contexts that have risky caps. $riskycount = count(array_unique(array_column($riskyresults, 'contextid'))); // It may have either none or 'user' archetype - nothing else, or else it would break during upgrades badly. if ($defaultrole->archetype === '' or $defaultrole->archetype === 'user') { $legacyok = true; } else { $legacyok = false; } if ($riskycount or !$legacyok) { $status = result::CRITICAL; $summary = get_string('check_defaultuserrole_error', 'report_security', role_get_name($defaultrole)); } else { $status = result::OK; $summary = get_string('check_defaultuserrole_ok', 'report_security'); } $details = get_string('check_defaultuserrole_details', 'report_security'); return new result($status, $summary, $details); } } access/riskadmin.php000064400000005455152267671550010530 0ustar00. /** * Lists all admins. * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\access; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Lists all admins. * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class riskadmin extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_riskadmin_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/roles/admins.php'), get_string('siteadministrators', 'role')); } /** * Return result * @return result */ public function get_result(): result { global $DB, $CFG; $userfieldsapi = \core_user\fields::for_userpic(); $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $sql = "SELECT $userfields FROM {user} u WHERE u.id IN ($CFG->siteadmins)"; $admins = $DB->get_records_sql($sql); $admincount = count($admins); foreach ($admins as $uid => $user) { $url = "$CFG->wwwroot/user/view.php?id=$user->id"; $link = \html_writer::link($url, fullname($user, true) . ' (' . s($user->email) . ')'); $admins[$uid] = \html_writer::tag('li' , $link); } $admins = \html_writer::tag('ul', implode('', $admins)); $status = result::INFO; $summary = get_string('check_riskadmin_ok', 'report_security', $admincount); $details = get_string('check_riskadmin_detailsok', 'report_security', $admins); return new result($status, $summary, $details); } } access/riskxss.php000064400000004205152267671550010245 0ustar00. /** * Lists all users with XSS risk * * It would be great to combine this with risk trusts in user table, * unfortunately nobody implemented user trust UI yet :-( * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\access; defined('MOODLE_INTERNAL') || die(); use core\check\result; /** * Lists all users with XSS risk * * It would be great to combine this with risk trusts in user table, * unfortunately nobody implemented user trust UI yet :-( * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class riskxss extends \core\check\check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_riskxss_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/roles/manage.php'), get_string('manageroles', 'role')); } /** * Return result * @return result */ public function get_result(): result { return new riskxss_result(); } } access/frontpagerole.php000064400000007370152267671550011414 0ustar00. /** * Verifies sanity of frontpage role * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\access; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies sanity of frontpage role * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class frontpagerole extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_frontpagerole_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=frontpagesettings#admin-defaultfrontpageroleid'), get_string('frontpagesettings', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $DB, $CFG; if (!$frontpagerole = $DB->get_record('role', array('id' => $CFG->defaultfrontpageroleid))) { $status = result::INFO; $summary = get_string('check_frontpagerole_notset', 'report_security'); $details = get_string('check_frontpagerole_details', 'report_security'); return new result($status, $summary, $details); } // Risky caps - usually very dangerous. $sql = "SELECT COUNT(DISTINCT rc.contextid) FROM {role_capabilities} rc JOIN {capabilities} cap ON cap.name = rc.capability WHERE " . $DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS)) . " <> 0 AND rc.permission = :capallow AND rc.roleid = :roleid"; $riskycount = $DB->count_records_sql($sql, [ 'capallow' => CAP_ALLOW, 'roleid' => $frontpagerole->id, ]); // There is no legacy role type for frontpage yet - anyway we can not allow teachers or admins there! if ($frontpagerole->archetype === 'teacher' or $frontpagerole->archetype === 'editingteacher' or $frontpagerole->archetype === 'coursecreator' or $frontpagerole->archetype === 'manager') { $legacyok = false; } else { $legacyok = true; } if ($riskycount or !$legacyok) { $status = result::CRITICAL; $summary = get_string('check_frontpagerole_error', 'report_security', role_get_name($frontpagerole)); } else { $status = result::OK; $summary = get_string('check_frontpagerole_ok', 'report_security'); } $details = get_string('check_frontpagerole_details', 'report_security'); return new result($status, $summary, $details); } } access/riskbackup_result.php000064400000020227152267671550012275 0ustar00. /** * Lists all roles that have the ability to backup user data, as well as users * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\access; use context; use stdClass; use core\check\result; /** * Lists all roles that have the ability to backup user data, as well as users * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class riskbackup_result extends \core\check\result { /** @var stdClass[] $systemroles */ private $systemroles; /** @var stdClass[] $overriddenroles */ private $overriddenroles; /** @var string $sqluserinfo */ private $sqluserinfo; /** * Constructor */ public function __construct() { global $DB; $syscontext = \context_system::instance(); $params = array('capability' => 'moodle/backup:userinfo', 'permission' => CAP_ALLOW, 'contextid' => $syscontext->id); $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, r.archetype FROM {role} r JOIN {role_capabilities} rc ON rc.roleid = r.id WHERE rc.capability = :capability AND rc.contextid = :contextid AND rc.permission = :permission"; $this->systemroles = $DB->get_records_sql($sql, $params); // Ensure first field is unique (role.id + role_capabilities.contextid). $roleidcontextfield = $DB->sql_concat_join("','", ['r.id', 'rc.contextid']); $params = array('capability' => 'moodle/backup:userinfo', 'permission' => CAP_ALLOW, 'contextid' => $syscontext->id); $sql = "SELECT DISTINCT {$roleidcontextfield} AS rolecontext, r.id, r.name, r.shortname, r.sortorder, r.archetype, rc.contextid FROM {role} r JOIN {role_capabilities} rc ON rc.roleid = r.id WHERE rc.capability = :capability AND rc.contextid <> :contextid AND rc.permission = :permission"; $this->overriddenroles = $DB->get_records_sql($sql, $params); // List of users that are able to backup personal info // note: // "sc" is context where is role assigned, // "c" is context where is role overridden or system context if in role definition. $params = [ 'capability' => 'moodle/backup:userinfo', 'permission' => CAP_ALLOW, 'context1' => CONTEXT_COURSE, 'context2' => CONTEXT_COURSE, ]; $this->sqluserinfo = " FROM (SELECT DISTINCT rcx.contextid, rcx.roleid FROM {role_capabilities} rcx WHERE rcx.permission = :permission AND rcx.capability = :capability) rc JOIN {context} c ON c.id = rc.contextid JOIN {context} sc ON sc.contextlevel <= :context1 JOIN {role_assignments} ra ON ra.contextid = sc.id AND ra.roleid = rc.roleid JOIN {user} u ON u.id = ra.userid AND u.deleted = 0 WHERE (sc.path = c.path OR sc.path LIKE " . $DB->sql_concat('c.path', "'/%'") . " OR c.path LIKE " . $DB->sql_concat('sc.path', "'/%'") . ") AND c.contextlevel <= :context2"; $usercount = $DB->count_records_sql("SELECT COUNT('x') FROM (SELECT DISTINCT u.id $this->sqluserinfo) userinfo", $params); $systemrolecount = empty($this->systemroles) ? 0 : count($this->systemroles); $overriddenrolecount = empty($this->overriddenroles) ? 0 : count($this->overriddenroles); if (max($usercount, $systemrolecount, $overriddenrolecount) > 0) { $this->status = result::WARNING; } else { $this->status = result::OK; } $a = (object)array( 'rolecount' => $systemrolecount, 'overridecount' => $overriddenrolecount, 'usercount' => $usercount, ); $this->summary = get_string('check_riskbackup_warning', 'report_security', $a); } /** * Showing the full list of roles may be slow so defer it * * @return string */ public function get_details(): string { global $CFG, $DB; $details = ''; // Make a list of roles. if ($this->systemroles) { $links = array(); foreach ($this->systemroles as $role) { $role->name = role_get_name($role); $role->url = (new \moodle_url('/admin/roles/manage.php', ['action' => 'edit', 'roleid' => $role->id]))->out(); $links[] = \html_writer::tag('li', get_string('check_riskbackup_editrole', 'report_security', $role)); } $links = \html_writer::tag('ul', implode('', $links)); $details .= get_string('check_riskbackup_details_systemroles', 'report_security', $links); } // Make a list of overrides to roles. if ($this->overriddenroles) { $links = array(); foreach ($this->overriddenroles as $role) { $context = context::instance_by_id($role->contextid); $role->name = role_get_name($role, $context, ROLENAME_BOTH); $role->contextname = $context->get_context_name(); $role->url = (new \moodle_url('/admin/roles/override.php', ['contextid' => $role->contextid, 'roleid' => $role->id]))->out(); $links[] = \html_writer::tag('li', get_string('check_riskbackup_editoverride', 'report_security', $role)); } $links = \html_writer::tag('ul', implode('', $links)); $details .= get_string('check_riskbackup_details_overriddenroles', 'report_security', $links); } // Get a list of affected users as well. $users = array(); list($sort, $sortparams) = users_order_by_sql('u'); $params = [ 'capability' => 'moodle/backup:userinfo', 'permission' => CAP_ALLOW, 'context1' => CONTEXT_COURSE, 'context2' => CONTEXT_COURSE, ]; $userfieldsapi = \core_user\fields::for_userpic(); $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $rs = $DB->get_recordset_sql(" SELECT DISTINCT $userfields, ra.contextid, ra.roleid $this->sqluserinfo ORDER BY $sort", array_merge($params, $sortparams)); foreach ($rs as $user) { $context = \context::instance_by_id($user->contextid); $url = new \moodle_url('/admin/roles/assign.php', ['contextid' => $user->contextid, 'roleid' => $user->roleid]); $a = (object)array( 'fullname' => fullname($user), 'url' => $url->out(), 'email' => s($user->email), 'contextname' => $context->get_context_name(), ); $users[] = \html_writer::tag('li', get_string('check_riskbackup_unassign', 'report_security', $a)); } $rs->close(); if (!empty($users)) { $users = \html_writer::tag('ul', implode('', $users)); $details .= get_string('check_riskbackup_details_users', 'report_security', $users); } return $details; } } access/riskxss_result.php000064400000007617152267671550011655 0ustar00. /** * Lists all users with XSS risk * * It would be great to combine this with risk trusts in user table, * unfortunately nobody implemented user trust UI yet :-( * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\access; defined('MOODLE_INTERNAL') || die(); use core\check\result; /** * Lists all users with XSS risk * * It would be great to combine this with risk trusts in user table, * unfortunately nobody implemented user trust UI yet :-( * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class riskxss_result extends \core\check\result { /** @var array SQL parameters. */ protected $params = []; /** @var string SQL statement. */ protected $sqlfrom; /** * Constructor */ public function __construct() { global $DB; $this->params = array('capallow' => CAP_ALLOW); $this->sqlfrom = "FROM (SELECT DISTINCT rcx.contextid, rcx.roleid FROM {role_capabilities} rcx JOIN {capabilities} cap ON (cap.name = rcx.capability AND " . $DB->sql_bitand('cap.riskbitmask', RISK_XSS) . " <> 0) WHERE rcx.permission = :capallow) rc, {context} c, {context} sc, {role_assignments} ra, {user} u WHERE c.id = rc.contextid AND (sc.path = c.path OR sc.path LIKE " . $DB->sql_concat('c.path', "'/%'") . " OR c.path LIKE " . $DB->sql_concat('sc.path', "'/%'") . ") AND u.id = ra.userid AND u.deleted = 0 AND ra.contextid = sc.id AND ra.roleid = rc.roleid"; $count = $DB->count_records_sql("SELECT COUNT(DISTINCT u.id) $this->sqlfrom", $this->params); if ($count == 0) { $this->status = result::OK; } else { $this->status = result::WARNING; } $this->summary = get_string('check_riskxss_warning', 'report_security', $count); } /** * Showing the full list of user may be slow so defer it * * @return string */ public function get_details(): string { global $CFG, $DB; $userfieldsapi = \core_user\fields::for_userpic(); $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; $users = $DB->get_records_sql("SELECT DISTINCT $userfields $this->sqlfrom", $this->params); foreach ($users as $uid => $user) { $url = "$CFG->wwwroot/user/view.php?id=$user->id"; $link = \html_writer::link($url, fullname($user, true) . ' (' . s($user->email) . ')'); $users[$uid] = \html_writer::tag('li' , $link); } $users = \html_writer::tag('ul', implode('', $users)); return get_string('check_riskxss_details', 'report_security', $users); } } access/guestrole.php000064400000006707152267671550010561 0ustar00. /** * Verifies sanity of guest role * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\access; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies sanity of guest role * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class guestrole extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_guestrole_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=userpolicies'), get_string('userpolicies', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $DB, $CFG; if (!$guestrole = $DB->get_record('role', ['id' => $CFG->guestroleid])) { $status = result::WARNING; $summary = get_string('check_guestrole_notset', 'report_security'); return new result($status, $summary); } // Risky caps - usually very dangerous. $sql = "SELECT COUNT(DISTINCT rc.contextid) FROM {role_capabilities} rc JOIN {capabilities} cap ON cap.name = rc.capability WHERE " . $DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS)) . " <> 0 AND rc.permission = :capallow AND rc.roleid = :roleid"; $riskycount = $DB->count_records_sql($sql, [ 'capallow' => CAP_ALLOW, 'roleid' => $guestrole->id, ]); // It may have either no or 'guest' archetype - nothing else, or else it would break during upgrades badly. if ($guestrole->archetype === '' or $guestrole->archetype === 'guest') { $legacyok = true; } else { $legacyok = false; } if ($riskycount or !$legacyok) { $status = result::CRITICAL; $summary = get_string('check_guestrole_error', 'report_security', format_string($guestrole->name)); } else { $status = result::OK; $summary = get_string('check_guestrole_ok', 'report_security'); } $details = get_string('check_guestrole_details', 'report_security'); return new result($status, $summary, $details); } } http/cookiesecure.php000064400000005374152267671550010745 0ustar00. /** * Verifies if https enabled only secure cookies allowed * * This prevents redirections and sending of cookies to unsecure port. * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\http; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies if https enabled only secure cookies allowed * * This prevents redirections and sending of cookies to unsecure port. * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cookiesecure extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_cookiesecure_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=httpsecurity#admin-cookiesecure'), get_string('httpsecurity', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $details = get_string('check_cookiesecure_details', 'report_security'); if (!is_https()) { $status = result::WARNING; $summary = get_string('check_cookiesecure_http', 'report_security'); return new result($status, $summary, $details); } if (!is_moodle_cookie_secure()) { $status = result::ERROR; $summary = get_string('check_cookiesecure_error', 'report_security'); } else { $status = result::OK; $summary = get_string('check_cookiesecure_ok', 'report_security'); } return new result($status, $summary, $details); } } performance/stats.php000064400000004077152267671550010744 0ustar00. namespace core\check\performance; use core\check\check; use core\check\result; /** * Stats check * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class stats extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('stats'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/search.php', ['query' => 'enablestats']), get_string('enablestats', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; if (!empty($CFG->enablestats)) { $status = result::WARNING; $summary = get_string('check_enablestats_comment_enable', 'report_performance'); } else { $status = result::OK; $summary = get_string('check_enablestats_comment_disable', 'report_performance'); } $details = get_string('check_enablestats_details', 'report_performance'); return new result($status, $summary, $details); } } performance/backups.php000064400000004741152267671550011234 0ustar00. /** * Backups check * * @package core * @category check * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\performance; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Backups check * * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class backups extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_backup', 'report_performance'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php', ['section' => 'automated']), get_string('automatedsetup', 'backup')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php'); $automatedbackupsenabled = get_config('backup', 'backup_auto_active'); if ($automatedbackupsenabled == \backup_cron_automated_helper::AUTO_BACKUP_ENABLED) { $status = result::WARNING; $summary = get_string('check_backup_comment_enable', 'report_performance'); } else { $status = result::OK; $summary = get_string('check_backup_comment_disable', 'report_performance'); } $details = get_string('check_backup_details', 'report_performance'); return new result($status, $summary, $details); } } performance/designermode.php000064400000004520152267671550012244 0ustar00. /** * Designer mode * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\performance; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Designer mode * * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class designermode extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('themedesignermode', 'admin'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/search.php', ['query' => 'themedesignermode']), get_string('themedesignermode', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $DB, $CFG; if (empty($CFG->themedesignermode)) { $status = result::OK; $summary = get_string('check_themedesignermode_comment_disable', 'report_performance'); } else { $status = result::CRITICAL; $summary = get_string('check_themedesignermode_comment_enable', 'report_performance'); } $details = get_string('check_themedesignermode_details', 'report_performance'); return new result($status, $summary, $details); } } performance/dbschema.php000064400000004724152267671550011353 0ustar00. /** * DB schema performance check * * @package core * @category check * @copyright 2021 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\performance; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * DB schema performance check * * @copyright 2021 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class dbschema extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_dbschema_name', 'report_performance'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url(\get_docs_url('Verify_Database_Schema')), get_string('moodledocs')); } /** * Return result * @return result */ public function get_result(): result { global $DB; $dbmanager = $DB->get_manager(); $schema = $dbmanager->get_install_xml_schema(); if (!$errors = $dbmanager->check_database_schema($schema)) { return new result(result::OK, get_string('check_dbschema_ok', 'report_performance'), ''); } $details = ''; foreach ($errors as $tablename => $items) { $details .= \html_writer::tag('h4', $tablename); foreach ($items as $item) { $details .= \html_writer::tag('pre', $item); } } return new result(result::ERROR, get_string('check_dbschema_errors', 'report_performance'), $details); } } performance/debugging.php000064400000004375152267671550011542 0ustar00. /** * Debugging check * * @package core * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\performance; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Debugging check * * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class debugging extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('debug', 'admin'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php', ['section' => 'debugging']), get_string('debug', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; if (!$CFG->debugdeveloper) { $status = result::OK; $summary = get_string('check_debugmsg_comment_nodeveloper', 'report_performance'); } else { $status = result::WARNING; $summary = get_string('check_debugmsg_comment_developer', 'report_performance'); } $details = get_string('check_debugmsg_details', 'report_performance'); return new result($status, $summary, $details); } } performance/cachejs.php000064400000004377152267671550011211 0ustar00. /** * CacheJS check * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\performance; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * CacheJS check * * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cachejs extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('cachejs', 'admin'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/search.php', ['query' => 'cachejs']), get_string('cachejs', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; if (empty($CFG->cachejs)) { $status = result::CRITICAL; $summary = get_string('check_cachejs_comment_disable', 'report_performance'); } else { $status = result::OK; $summary = get_string('check_cachejs_comment_enable', 'report_performance'); } $details = get_string('check_cachejs_details', 'report_performance'); return new result($status, $summary, $details); } } check.php000064400000006661152267671550006363 0ustar00. /** * Base class for checks * * @package core * @category check * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check; use coding_exception; /** * Base class for checks * * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class check { /** * @var string $component - The component / plugin this task belongs to. * * This can be autopopulated by the check manager. * Otherwise, it is dynamically determined by get_component(). */ protected $component = ''; /** * Get the frankenstyle component name * * @return string */ public function get_component(): string { // Return component if has been set by the manager. if (!empty($this->component)) { return $this->component; } // Else work it out based on the classname. // Because the first part of the classname is always the component. $parts = explode("\\", get_called_class()); if (empty($parts)) { throw new coding_exception("Unable to determine component for check"); } return $parts[0]; } /** * Get the frankenstyle component name * * @param string $component name */ public function set_component(string $component) { $this->component = $component; } /** * Get the check's id * * This defaults to the base name of the class which is ok in the most * cases but if you have a check which can have multiple instances then * you should override this to be unique. * * @return string must be unique within a component */ public function get_id(): string { $class = get_class($this); $id = explode("\\", $class); return end($id); } /** * Get the check reference * * @return string must be globally unique */ public function get_ref(): string { $ref = $this->get_component(); if (!empty($ref)) { $ref .= '_'; } $ref .= $this->get_id(); return $ref; } /** * Get the short check name * * @return string */ public function get_name(): string { $id = $this->get_id(); return get_string("check{$id}", $this->get_component()); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return null; } /** * Return the result * * @return result object */ abstract public function get_result(): result; } security/emailchangeconfirmation.php000064400000005350152267671550014015 0ustar00. /** * Verifies email confirmation - spammers were changing mails very often * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\security; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies email confirmation - spammers were changing mails very often * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class emailchangeconfirmation extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_emailchangeconfirmation_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=sitepolicies#admin-emailchangeconfirmation'), get_string('sitepolicies', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $details = get_string('check_emailchangeconfirmation_details', 'report_security'); if (empty($CFG->emailchangeconfirmation)) { if (empty($CFG->allowemailaddresses)) { $status = result::WARNING; $summary = get_string('check_emailchangeconfirmation_error', 'report_security'); } else { $status = result::INFO; $summary = get_string('check_emailchangeconfirmation_info', 'report_security'); } } else { $status = result::OK; $summary = get_string('check_emailchangeconfirmation_ok', 'report_security'); } return new result($status, $summary, $details); } } security/passwordpolicy.php000064400000004565152267671550012240 0ustar00. /** * Verifies if password policy set * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\security; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies if password policy set * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class passwordpolicy extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_passwordpolicy_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=sitepolicies#admin-passwordpolicy'), get_string('sitepolicies', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $details = get_string('check_passwordpolicy_details', 'report_security'); if (empty($CFG->passwordpolicy)) { $status = result::WARNING; $summary = get_string('check_passwordpolicy_error', 'report_security'); } else { $status = result::OK; $summary = get_string('check_passwordpolicy_ok', 'report_security'); } return new result($status, $summary, $details); } } security/crawlers.php000064400000005417152267671550010775 0ustar00. /** * Verifies web crawler (search engine) access * * Not combined with disabled guest access because attackers might gain guest * access by modifying browser signature. * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\security; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies web crawler (search engine) access * * Not combined with disabled guest access because attackers might gain guest * access by modifying browser signature. * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class crawlers extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_crawlers_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=sitepolicies#admin-opentowebcrawlers'), get_string('sitepolicies', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $details = get_string('check_crawlers_details', 'report_security'); if (empty($CFG->opentowebcrawlers)) { $status = result::OK; $summary = get_string('check_crawlers_ok', 'report_security'); } else if (!empty($CFG->guestloginbutton)) { $status = result::INFO; $summary = get_string('check_crawlers_info', 'report_security'); } else { $status = result::ERROR; $summary = get_string('check_crawlers_error', 'report_security'); } return new result($status, $summary, $details); } } security/openprofiles.php000064400000005014152267671550011651 0ustar00. /** * Verifies open profiles - originally open by default, not anymore because spammer abused it a lot * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\security; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies open profiles - originally open by default, not anymore because spammer abused it a lot * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class openprofiles extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_openprofiles_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=sitepolicies#admin-forcelogin'), get_string('sitepolicies', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $details = get_string('check_openprofiles_details', 'report_security'); if (empty($CFG->forcelogin) and empty($CFG->forceloginforprofiles)) { $status = result::WARNING; $summary = get_string('check_openprofiles_error', 'report_security'); } else { $status = result::OK; $summary = get_string('check_openprofiles_ok', 'report_security'); } return new result($status, $summary, $details); } } security/webcron.php000064400000004701152267671550010605 0ustar00. /** * Verifies the status of web cron * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\security; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies the status of web cron * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class webcron extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_webcron_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=sitepolicies#admin-cronclionly'), get_string('sitepolicies', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $croncli = $CFG->cronclionly; $cronremotepassword = $CFG->cronremotepassword; if (empty($croncli) && empty($cronremotepassword)) { $status = result::WARNING; $summary = get_string('check_webcron_warning', 'report_security'); } else { $status = result::OK; $summary = get_string('check_webcron_ok', 'report_security'); } $details = get_string('check_webcron_details', 'report_security'); return new result($status, $summary, $details); } } security/embed.php000064400000004625152267671550010227 0ustar00. /** * Verifies sloppy embedding - this should have been removed long ago!! * * @package core * @category check * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\check\security; defined('MOODLE_INTERNAL') || die(); use core\check\check; use core\check\result; /** * Verifies sloppy embedding - this should have been removed long ago!! * * @copyright 2020 Brendan Heywood * @copyright 2008 petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class embed extends check { /** * Get the short check name * * @return string */ public function get_name(): string { return get_string('check_embed_name', 'report_security'); } /** * A link to a place to action this * * @return \action_link|null */ public function get_action_link(): ?\action_link { return new \action_link( new \moodle_url('/admin/settings.php?section=sitepolicies#admin-allowobjectembed'), get_string('sitepolicies', 'admin')); } /** * Return result * @return result */ public function get_result(): result { global $CFG; $details = get_string('check_embed_details', 'report_security'); if (!empty($CFG->allowobjectembed)) { $status = result::ERROR; $summary = get_string('check_embed_error', 'report_security'); } else { $status = result::OK; $summary = get_string('check_embed_ok', 'report_security'); } return new result($status, $summary, $details); } } result.php000064400000012101152267671550006606 0ustar00. namespace core\check; /** * A check object returns a result object * * Most checks can use this an instance of this directly but if you have a * 'details' which is computationally expensive then extend this and overide * the get_details() method so that it is only called when it will be needed. * * @package core * @category check * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class result implements \renderable { /** * This is used to notify if a check does not apply. * * In most cases if a check doesn't apply a check object shouldn't be made. * This state exists for when you always want visibilty of the check itself. * Can be useful for a check which depends on another check and it helps * focus on the other check which matters more. */ const NA = 'na'; /** * Ideally all checks should be ok. */ const OK = 'ok'; /** * This is used to show info for a check. * * This is equivalent to OK but could be used for alerting to potential * future warnings such as a deprecation in a service. */ const INFO = 'info'; /** * This means we could not determine the state. * * An example might be an expensive check done via cron, and it has never run. * It would be prudent to consider an unknown check as a warning or error. */ const UNKNOWN = 'unknown'; /** * Warnings * * Something is not ideal and should be addressed, eg usability or the * speed of the site may be affected, but it may self heal (eg a load spike) */ const WARNING = 'warning'; /** * This is used to notify if a check failed. * * Something is wrong with a component and a feature is not working. */ const ERROR = 'error'; /** * This is used to notify if a check is a major critical issue. * * An error which is affecting everyone in a major way. */ const CRITICAL = 'critical'; /** * @var string $status - status */ protected $status = self::UNKNOWN; /** * @var string summary - should be roughly 1 line of plain text and may change depending on the state. */ protected $summary = ''; /** * @var string details about check. * * This may be a large amount of preformatted html text, possibly describing all the * different states and actions to address them. */ protected $details = ''; /** * Constructor * * @param string $status code * @param string $summary a 1 liner summary * @param string $details as a html chunk */ public function __construct($status, $summary, $details = '') { $this->status = $status; $this->summary = $summary; $this->details = $details; } /** * Get the check status * * @return string one of the consts eg result::OK */ public function get_status(): string { return $this->status; } /** * Summary of the check * @return string formatted html */ public function get_summary(): string { return $this->summary; } /** * Get the check detailed info * @return string formatted html */ public function get_details(): string { return $this->details; } /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output typically, the renderer that's calling this function * @return array data context for a mustache template */ public function export_for_template(\renderer_base $output) { return array( 'status' => clean_text(get_string('status' . $this->status)), 'isna' => $this->status === self::NA, 'isok' => $this->status === self::OK, 'isinfo' => $this->status === self::INFO, 'isunknown' => $this->status === self::UNKNOWN, 'iswarning' => $this->status === self::WARNING, 'iserror' => $this->status === self::ERROR, 'iscritical' => $this->status === self::CRITICAL, ); } /** * Which mustache template? * * @return string path to mustache template */ public function get_template_name(): string { return 'core/check/result'; } } check_result.js000064400000004662152270016320007563 0ustar00// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Check API result functions * * @module core/check * @author Matthew Hilton * @copyright Catalyst IT, 2023 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {getCheckResult} from './repository'; import {getString} from 'core/str'; import * as Templates from 'core/templates'; /** * Get the result of a check and replace a given DOM element with the result. * * @method getAndRender * @param {String} domSelector A CSS selector for a dom element to replace the the HTML for. * @param {String} adminTreeId Id of the admin_setting that called this webservice. Used to retrieve the check registered to it. * @param {String} settingName Name of setting (used to find the parent node in the admin tree) * @param {Boolean} includeDetails If true, details will be included in the check. * By default only the status and the summary is returned. */ export async function getAndRender(domSelector, adminTreeId, settingName, includeDetails) { const element = document.querySelector(domSelector); if (!element) { window.console.error('Check selector not found'); return; } try { const result = await getCheckResult(adminTreeId, settingName, includeDetails); const decoded = new DOMParser().parseFromString(result.html, "text/html").documentElement.textContent; element.innerHTML = decoded; } catch (e) { window.console.error(e); // Render error as a red notification. element.innerHTML = await Templates.render('core/notification', { iserror: true, closebutton: false, announce: 0, extraclasses: '', message: await getString('checkerror', 'core', adminTreeId) }); } } repository.js000064400000003043152270016320007317 0ustar00// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Check API webservice repository * * @module core/check * @author Matthew Hilton * @copyright Catalyst IT, 2023 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {call as fetchMany} from 'core/ajax'; /** * Call check_get_result webservice function * * @param {String} adminTreeId Id of the admin_setting that called this webservice. Used to retrieve the check registered to it. * @param {String} settingName Setting name (used to find it's parent) * @param {Boolean} includeDetails If details should be included in the response */ export const getCheckResult = (adminTreeId, settingName, includeDetails) => fetchMany([{ methodname: 'core_check_get_result_admintree', args: { admintreeid: adminTreeId, settingname: settingName, includedetails: includeDetails, }, }])[0];