MDL-61133 core_output: add fontawesome mapping for tags icon
[moodle.git] / report / security / locallib.php
blob65fba97e4de9a6635464c709a80b4f1aec0136e9
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.
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/>.
17 /**
18 * Lib functions
20 * @package report
21 * @subpackage security
22 * @copyright 2008 petr Skoda
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die;
29 define('REPORT_SECURITY_OK', 'ok');
30 define('REPORT_SECURITY_INFO', 'info');
31 define('REPORT_SECURITY_WARNING', 'warning');
32 define('REPORT_SECURITY_SERIOUS', 'serious');
33 define('REPORT_SECURITY_CRITICAL', 'critical');
35 function report_security_hide_timearning() {
36 global $PAGE;
37 $PAGE->requires->js_init_code("Y.one('#timewarning').addClass('timewarninghidden')");
40 function report_security_get_issue_list() {
41 return array(
42 'report_security_check_unsecuredataroot',
43 'report_security_check_displayerrors',
44 'report_security_check_vendordir',
45 'report_security_check_nodemodules',
46 'report_security_check_noauth',
47 'report_security_check_embed',
48 'report_security_check_mediafilterswf',
49 'report_security_check_openprofiles',
50 'report_security_check_google',
51 'report_security_check_passwordpolicy',
52 'report_security_check_emailchangeconfirmation',
53 'report_security_check_cookiesecure',
54 'report_security_check_configrw',
55 'report_security_check_riskxss',
56 'report_security_check_riskadmin',
57 'report_security_check_riskbackup',
58 'report_security_check_defaultuserrole',
59 'report_security_check_guestrole',
60 'report_security_check_frontpagerole',
61 'report_security_check_webcron',
62 'report_security_check_preventexecpath',
67 function report_security_doc_link($issue, $name) {
68 global $CFG, $OUTPUT;
70 if (empty($CFG->docroot)) {
71 return $name;
74 return $OUTPUT->doc_link('report/security/'.$issue, $name);
77 ///=============================================
78 /// Issue checks
79 ///=============================================
82 /**
83 * Verifies unsupported noauth setting
84 * @param bool $detailed
85 * @return object result
87 function report_security_check_noauth($detailed=false) {
88 global $CFG;
90 $result = new stdClass();
91 $result->issue = 'report_security_check_noauth';
92 $result->name = get_string('check_noauth_name', 'report_security');
93 $result->info = null;
94 $result->details = null;
95 $result->status = null;
96 $result->link = null;
97 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=manageauths\">".get_string('authsettings', 'admin').'</a>';
99 if (is_enabled_auth('none')) {
100 $result->status = REPORT_SECURITY_CRITICAL;
101 $result->info = get_string('check_noauth_error', 'report_security');
102 } else {
103 $result->status = REPORT_SECURITY_OK;
104 $result->info = get_string('check_noauth_ok', 'report_security');
107 if ($detailed) {
108 $result->details = get_string('check_noauth_details', 'report_security');
111 return $result;
115 * Verifies if password policy set
116 * @param bool $detailed
117 * @return object result
119 function report_security_check_passwordpolicy($detailed=false) {
120 global $CFG;
122 $result = new stdClass();
123 $result->issue = 'report_security_check_passwordpolicy';
124 $result->name = get_string('check_passwordpolicy_name', 'report_security');
125 $result->info = null;
126 $result->details = null;
127 $result->status = null;
128 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
130 if (empty($CFG->passwordpolicy)) {
131 $result->status = REPORT_SECURITY_WARNING;
132 $result->info = get_string('check_passwordpolicy_error', 'report_security');
133 } else {
134 $result->status = REPORT_SECURITY_OK;
135 $result->info = get_string('check_passwordpolicy_ok', 'report_security');
138 if ($detailed) {
139 $result->details = get_string('check_passwordpolicy_details', 'report_security');
142 return $result;
146 * Verifies sloppy embedding - this should have been removed long ago!!
147 * @param bool $detailed
148 * @return object result
150 function report_security_check_embed($detailed=false) {
151 global $CFG;
153 $result = new stdClass();
154 $result->issue = 'report_security_check_embed';
155 $result->name = get_string('check_embed_name', 'report_security');
156 $result->info = null;
157 $result->details = null;
158 $result->status = null;
159 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
161 if (!empty($CFG->allowobjectembed)) {
162 $result->status = REPORT_SECURITY_CRITICAL;
163 $result->info = get_string('check_embed_error', 'report_security');
164 } else {
165 $result->status = REPORT_SECURITY_OK;
166 $result->info = get_string('check_embed_ok', 'report_security');
169 if ($detailed) {
170 $result->details = get_string('check_embed_details', 'report_security');
173 return $result;
177 * Verifies sloppy swf embedding - this should have been removed long ago!!
178 * @param bool $detailed
179 * @return object result
181 function report_security_check_mediafilterswf($detailed=false) {
182 global $CFG;
184 $result = new stdClass();
185 $result->issue = 'report_security_check_mediafilterswf';
186 $result->name = get_string('check_mediafilterswf_name', 'report_security');
187 $result->info = null;
188 $result->details = null;
189 $result->status = null;
190 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=managemediaplayers\">" .
191 get_string('managemediaplayers', 'media') . '</a>';
193 $activefilters = filter_get_globally_enabled();
195 $enabledmediaplayers = \core\plugininfo\media::get_enabled_plugins();
196 if (array_search('mediaplugin', $activefilters) !== false and array_key_exists('swf', $enabledmediaplayers)) {
197 $result->status = REPORT_SECURITY_CRITICAL;
198 $result->info = get_string('check_mediafilterswf_error', 'report_security');
199 } else {
200 $result->status = REPORT_SECURITY_OK;
201 $result->info = get_string('check_mediafilterswf_ok', 'report_security');
204 if ($detailed) {
205 $result->details = get_string('check_mediafilterswf_details', 'report_security');
208 return $result;
212 * Verifies fatal misconfiguration of dataroot
213 * @param bool $detailed
214 * @return object result
216 function report_security_check_unsecuredataroot($detailed=false) {
217 global $CFG;
219 $result = new stdClass();
220 $result->issue = 'report_security_check_unsecuredataroot';
221 $result->name = get_string('check_unsecuredataroot_name', 'report_security');
222 $result->info = null;
223 $result->details = null;
224 $result->status = null;
225 $result->link = null;
227 $insecuredataroot = is_dataroot_insecure(true);
229 if ($insecuredataroot == INSECURE_DATAROOT_WARNING) {
230 $result->status = REPORT_SECURITY_SERIOUS;
231 $result->info = get_string('check_unsecuredataroot_warning', 'report_security', $CFG->dataroot);
233 } else if ($insecuredataroot == INSECURE_DATAROOT_ERROR) {
234 $result->status = REPORT_SECURITY_CRITICAL;
235 $result->info = get_string('check_unsecuredataroot_error', 'report_security', $CFG->dataroot);
237 } else {
238 $result->status = REPORT_SECURITY_OK;
239 $result->info = get_string('check_unsecuredataroot_ok', 'report_security');
242 if ($detailed) {
243 $result->details = get_string('check_unsecuredataroot_details', 'report_security');
246 return $result;
250 * Verifies displaying of errors - problem for lib files and 3rd party code
251 * because we can not disable debugging in these scripts (they do not include config.php)
252 * @param bool $detailed
253 * @return object result
255 function report_security_check_displayerrors($detailed=false) {
256 $result = new stdClass();
257 $result->issue = 'report_security_check_displayerrors';
258 $result->name = get_string('check_displayerrors_name', 'report_security');
259 $result->info = null;
260 $result->details = null;
261 $result->status = null;
262 $result->link = null;
264 if (defined('WARN_DISPLAY_ERRORS_ENABLED')) {
265 $result->status = REPORT_SECURITY_WARNING;
266 $result->info = get_string('check_displayerrors_error', 'report_security');
267 } else {
268 $result->status = REPORT_SECURITY_OK;
269 $result->info = get_string('check_displayerrors_ok', 'report_security');
272 if ($detailed) {
273 $result->details = get_string('check_displayerrors_details', 'report_security');
276 return $result;
280 * Verifies open profiles - originally open by default, not anymore because spammer abused it a lot
281 * @param bool $detailed
282 * @return object result
284 function report_security_check_openprofiles($detailed=false) {
285 global $CFG;
287 $result = new stdClass();
288 $result->issue = 'report_security_check_openprofiles';
289 $result->name = get_string('check_openprofiles_name', 'report_security');
290 $result->info = null;
291 $result->details = null;
292 $result->status = null;
293 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
295 if (empty($CFG->forcelogin) and empty($CFG->forceloginforprofiles)) {
296 $result->status = REPORT_SECURITY_WARNING;
297 $result->info = get_string('check_openprofiles_error', 'report_security');
298 } else {
299 $result->status = REPORT_SECURITY_OK;
300 $result->info = get_string('check_openprofiles_ok', 'report_security');
303 if ($detailed) {
304 $result->details = get_string('check_openprofiles_details', 'report_security');
307 return $result;
311 * Verifies google access not combined with disabled guest access
312 * because attackers might gain guest access by modifying browser signature.
313 * @param bool $detailed
314 * @return object result
316 function report_security_check_google($detailed=false) {
317 global $CFG;
319 $result = new stdClass();
320 $result->issue = 'report_security_check_google';
321 $result->name = get_string('check_google_name', 'report_security');
322 $result->info = null;
323 $result->details = null;
324 $result->status = null;
325 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
327 if (empty($CFG->opentogoogle)) {
328 $result->status = REPORT_SECURITY_OK;
329 $result->info = get_string('check_google_ok', 'report_security');
330 } else if (!empty($CFG->guestloginbutton)) {
331 $result->status = REPORT_SECURITY_INFO;
332 $result->info = get_string('check_google_info', 'report_security');
333 } else {
334 $result->status = REPORT_SECURITY_SERIOUS;
335 $result->info = get_string('check_google_error', 'report_security');
338 if ($detailed) {
339 $result->details = get_string('check_google_details', 'report_security');
342 return $result;
346 * Verifies email confirmation - spammers were changing mails very often
347 * @param bool $detailed
348 * @return object result
350 function report_security_check_emailchangeconfirmation($detailed=false) {
351 global $CFG;
353 $result = new stdClass();
354 $result->issue = 'report_security_check_emailchangeconfirmation';
355 $result->name = get_string('check_emailchangeconfirmation_name', 'report_security');
356 $result->info = null;
357 $result->details = null;
358 $result->status = null;
359 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
361 if (empty($CFG->emailchangeconfirmation)) {
362 if (empty($CFG->allowemailaddresses)) {
363 $result->status = REPORT_SECURITY_WARNING;
364 $result->info = get_string('check_emailchangeconfirmation_error', 'report_security');
365 } else {
366 $result->status = REPORT_SECURITY_INFO;
367 $result->info = get_string('check_emailchangeconfirmation_info', 'report_security');
369 } else {
370 $result->status = REPORT_SECURITY_OK;
371 $result->info = get_string('check_emailchangeconfirmation_ok', 'report_security');
374 if ($detailed) {
375 $result->details = get_string('check_emailchangeconfirmation_details', 'report_security');
378 return $result;
382 * Verifies if https enabled only secure cookies allowed,
383 * this prevents redirections and sending of cookies to unsecure port.
384 * @param bool $detailed
385 * @return object result
387 function report_security_check_cookiesecure($detailed=false) {
388 global $CFG;
390 if (!is_https()) {
391 return null;
394 $result = new stdClass();
395 $result->issue = 'report_security_check_cookiesecure';
396 $result->name = get_string('check_cookiesecure_name', 'report_security');
397 $result->info = null;
398 $result->details = null;
399 $result->status = null;
400 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=httpsecurity\">".get_string('httpsecurity', 'admin').'</a>';
402 if (!is_moodle_cookie_secure()) {
403 $result->status = REPORT_SECURITY_SERIOUS;
404 $result->info = get_string('check_cookiesecure_error', 'report_security');
405 } else {
406 $result->status = REPORT_SECURITY_OK;
407 $result->info = get_string('check_cookiesecure_ok', 'report_security');
410 if ($detailed) {
411 $result->details = get_string('check_cookiesecure_details', 'report_security');
414 return $result;
418 * Verifies config.php is not writable anymore after installation,
419 * config files were changed on several outdated server.
420 * @param bool $detailed
421 * @return object result
423 function report_security_check_configrw($detailed=false) {
424 global $CFG;
426 $result = new stdClass();
427 $result->issue = 'report_security_check_configrw';
428 $result->name = get_string('check_configrw_name', 'report_security');
429 $result->info = null;
430 $result->details = null;
431 $result->status = null;
432 $result->link = null;
434 if (is_writable($CFG->dirroot.'/config.php')) {
435 $result->status = REPORT_SECURITY_WARNING;
436 $result->info = get_string('check_configrw_warning', 'report_security');
437 } else {
438 $result->status = REPORT_SECURITY_OK;
439 $result->info = get_string('check_configrw_ok', 'report_security');
442 if ($detailed) {
443 $result->details = get_string('check_configrw_details', 'report_security');
446 return $result;
451 * Lists all users with XSS risk, it would be great to combine this with risk trusts in user table,
452 * unfortunately nobody implemented user trust UI yet :-(
453 * @param bool $detailed
454 * @return object result
456 function report_security_check_riskxss($detailed=false) {
457 global $DB;
459 $result = new stdClass();
460 $result->issue = 'report_security_check_riskxss';
461 $result->name = get_string('check_riskxss_name', 'report_security');
462 $result->info = null;
463 $result->details = null;
464 $result->status = REPORT_SECURITY_WARNING;
465 $result->link = null;
467 $params = array('capallow'=>CAP_ALLOW);
469 $sqlfrom = "FROM (SELECT rcx.*
470 FROM {role_capabilities} rcx
471 JOIN {capabilities} cap ON (cap.name = rcx.capability AND ".$DB->sql_bitand('cap.riskbitmask', RISK_XSS)." <> 0)
472 WHERE rcx.permission = :capallow) rc,
473 {context} c,
474 {context} sc,
475 {role_assignments} ra,
476 {user} u
477 WHERE c.id = rc.contextid
478 AND (sc.path = c.path OR sc.path LIKE ".$DB->sql_concat('c.path', "'/%'")." OR c.path LIKE ".$DB->sql_concat('sc.path', "'/%'").")
479 AND u.id = ra.userid AND u.deleted = 0
480 AND ra.contextid = sc.id AND ra.roleid = rc.roleid";
482 $count = $DB->count_records_sql("SELECT COUNT(DISTINCT u.id) $sqlfrom", $params);
484 $result->info = get_string('check_riskxss_warning', 'report_security', $count);
486 if ($detailed) {
487 $userfields = user_picture::fields('u');
488 $users = $DB->get_records_sql("SELECT DISTINCT $userfields $sqlfrom", $params);
489 foreach ($users as $uid=>$user) {
490 $users[$uid] = fullname($user);
492 $users = implode(', ', $users);
493 $result->details = get_string('check_riskxss_details', 'report_security', $users);
496 return $result;
500 * Verifies sanity of default user role.
501 * @param bool $detailed
502 * @return object result
504 function report_security_check_defaultuserrole($detailed=false) {
505 global $DB, $CFG;
507 $result = new stdClass();
508 $result->issue = 'report_security_check_defaultuserrole';
509 $result->name = get_string('check_defaultuserrole_name', 'report_security');
510 $result->info = null;
511 $result->details = null;
512 $result->status = null;
513 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'</a>';
515 if (!$default_role = $DB->get_record('role', array('id'=>$CFG->defaultuserroleid))) {
516 $result->status = REPORT_SECURITY_WARNING;
517 $result->info = get_string('check_defaultuserrole_notset', 'report_security');
518 $result->details = $result->info;
520 return $result;
523 // risky caps - usually very dangerous
524 $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$default_role->id);
525 $sql = "SELECT COUNT(DISTINCT rc.contextid)
526 FROM {role_capabilities} rc
527 JOIN {capabilities} cap ON cap.name = rc.capability
528 WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
529 AND rc.permission = :capallow
530 AND rc.roleid = :roleid";
532 $riskycount = $DB->count_records_sql($sql, $params);
534 // it may have either none or 'user' archetype - nothing else, or else it would break during upgrades badly
535 if ($default_role->archetype === '' or $default_role->archetype === 'user') {
536 $legacyok = true;
537 } else {
538 $legacyok = false;
541 if ($riskycount or !$legacyok) {
542 $result->status = REPORT_SECURITY_CRITICAL;
543 $result->info = get_string('check_defaultuserrole_error', 'report_security', role_get_name($default_role));
545 } else {
546 $result->status = REPORT_SECURITY_OK;
547 $result->info = get_string('check_defaultuserrole_ok', 'report_security');
550 if ($detailed) {
551 $result->details = get_string('check_defaultuserrole_details', 'report_security');
554 return $result;
558 * Verifies sanity of guest role
559 * @param bool $detailed
560 * @return object result
562 function report_security_check_guestrole($detailed=false) {
563 global $DB, $CFG;
565 $result = new stdClass();
566 $result->issue = 'report_security_check_guestrole';
567 $result->name = get_string('check_guestrole_name', 'report_security');
568 $result->info = null;
569 $result->details = null;
570 $result->status = null;
571 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'</a>';
573 if (!$guest_role = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
574 $result->status = REPORT_SECURITY_WARNING;
575 $result->info = get_string('check_guestrole_notset', 'report_security');
576 $result->details = $result->info;
578 return $result;
581 // risky caps - usually very dangerous
582 $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$guest_role->id);
583 $sql = "SELECT COUNT(DISTINCT rc.contextid)
584 FROM {role_capabilities} rc
585 JOIN {capabilities} cap ON cap.name = rc.capability
586 WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
587 AND rc.permission = :capallow
588 AND rc.roleid = :roleid";
590 $riskycount = $DB->count_records_sql($sql, $params);
592 // it may have either no or 'guest' archetype - nothing else, or else it would break during upgrades badly
593 if ($guest_role->archetype === '' or $guest_role->archetype === 'guest') {
594 $legacyok = true;
595 } else {
596 $legacyok = false;
599 if ($riskycount or !$legacyok) {
600 $result->status = REPORT_SECURITY_CRITICAL;
601 $result->info = get_string('check_guestrole_error', 'report_security', format_string($guest_role->name));
603 } else {
604 $result->status = REPORT_SECURITY_OK;
605 $result->info = get_string('check_guestrole_ok', 'report_security');
608 if ($detailed) {
609 $result->details = get_string('check_guestrole_details', 'report_security');
612 return $result;
616 * Verifies sanity of frontpage role
617 * @param bool $detailed
618 * @return object result
620 function report_security_check_frontpagerole($detailed=false) {
621 global $DB, $CFG;
623 $result = new stdClass();
624 $result->issue = 'report_security_check_frontpagerole';
625 $result->name = get_string('check_frontpagerole_name', 'report_security');
626 $result->info = null;
627 $result->details = null;
628 $result->status = null;
629 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=frontpagesettings\">".get_string('frontpagesettings','admin').'</a>';
631 if (!$frontpage_role = $DB->get_record('role', array('id'=>$CFG->defaultfrontpageroleid))) {
632 $result->status = REPORT_SECURITY_INFO;
633 $result->info = get_string('check_frontpagerole_notset', 'report_security');
634 $result->details = get_string('check_frontpagerole_details', 'report_security');
636 return $result;
639 // risky caps - usually very dangerous
640 $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$frontpage_role->id);
641 $sql = "SELECT COUNT(DISTINCT rc.contextid)
642 FROM {role_capabilities} rc
643 JOIN {capabilities} cap ON cap.name = rc.capability
644 WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
645 AND rc.permission = :capallow
646 AND rc.roleid = :roleid";
648 $riskycount = $DB->count_records_sql($sql, $params);
650 // there is no legacy role type for frontpage yet - anyway we can not allow teachers or admins there!
651 if ($frontpage_role->archetype === 'teacher' or $frontpage_role->archetype === 'editingteacher'
652 or $frontpage_role->archetype === 'coursecreator' or $frontpage_role->archetype === 'manager') {
653 $legacyok = false;
654 } else {
655 $legacyok = true;
658 if ($riskycount or !$legacyok) {
659 $result->status = REPORT_SECURITY_CRITICAL;
660 $result->info = get_string('check_frontpagerole_error', 'report_security', format_string($frontpage_role->name));
662 } else {
663 $result->status = REPORT_SECURITY_OK;
664 $result->info = get_string('check_frontpagerole_ok', 'report_security');
667 if ($detailed) {
668 $result->details = get_string('check_frontpagerole_details', 'report_security');
671 return $result;
675 * Lists all admins.
676 * @param bool $detailed
677 * @return object result
679 function report_security_check_riskadmin($detailed=false) {
680 global $DB, $CFG;
682 $result = new stdClass();
683 $result->issue = 'report_security_check_riskadmin';
684 $result->name = get_string('check_riskadmin_name', 'report_security');
685 $result->info = null;
686 $result->details = null;
687 $result->status = null;
688 $result->link = null;
690 $userfields = user_picture::fields('u');
691 $sql = "SELECT $userfields
692 FROM {user} u
693 WHERE u.id IN ($CFG->siteadmins)";
695 $admins = $DB->get_records_sql($sql);
696 $admincount = count($admins);
698 if ($detailed) {
699 foreach ($admins as $uid=>$user) {
700 $url = "$CFG->wwwroot/user/view.php?id=$user->id";
701 $admins[$uid] = '<li><a href="'.$url.'">'.fullname($user).' ('.$user->email.')</a></li>';
703 $admins = '<ul>'.implode('', $admins).'</ul>';
706 $result->status = REPORT_SECURITY_OK;
707 $result->info = get_string('check_riskadmin_ok', 'report_security', $admincount);
709 if ($detailed) {
710 $result->details = get_string('check_riskadmin_detailsok', 'report_security', $admins);
713 return $result;
717 * Lists all roles that have the ability to backup user data, as well as users
718 * @param bool $detailed
719 * @return object result
721 function report_security_check_riskbackup($detailed=false) {
722 global $CFG, $DB;
724 $result = new stdClass();
725 $result->issue = 'report_security_check_riskbackup';
726 $result->name = get_string('check_riskbackup_name', 'report_security');
727 $result->info = null;
728 $result->details = null;
729 $result->status = null;
730 $result->link = null;
732 $syscontext = context_system::instance();
734 $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'contextid'=>$syscontext->id);
735 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, r.archetype
736 FROM {role} r
737 JOIN {role_capabilities} rc ON rc.roleid = r.id
738 WHERE rc.capability = :capability
739 AND rc.contextid = :contextid
740 AND rc.permission = :permission";
741 $systemroles = $DB->get_records_sql($sql, $params);
743 $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'contextid'=>$syscontext->id);
744 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, r.archetype, rc.contextid
745 FROM {role} r
746 JOIN {role_capabilities} rc ON rc.roleid = r.id
747 WHERE rc.capability = :capability
748 AND rc.contextid <> :contextid
749 AND rc.permission = :permission";
750 $overriddenroles = $DB->get_records_sql($sql, $params);
752 // list of users that are able to backup personal info
753 // note: "sc" is context where is role assigned,
754 // "c" is context where is role overridden or system context if in role definition
755 $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'context1'=>CONTEXT_COURSE, 'context2'=>CONTEXT_COURSE);
757 $sqluserinfo = "
758 FROM (SELECT rcx.*
759 FROM {role_capabilities} rcx
760 WHERE rcx.permission = :permission AND rcx.capability = :capability) rc,
761 {context} c,
762 {context} sc,
763 {role_assignments} ra,
764 {user} u
765 WHERE c.id = rc.contextid
766 AND (sc.path = c.path OR sc.path LIKE ".$DB->sql_concat('c.path', "'/%'")." OR c.path LIKE ".$DB->sql_concat('sc.path', "'/%'").")
767 AND u.id = ra.userid AND u.deleted = 0
768 AND ra.contextid = sc.id AND ra.roleid = rc.roleid
769 AND sc.contextlevel <= :context1 AND c.contextlevel <= :context2";
771 $usercount = $DB->count_records_sql("SELECT COUNT('x') FROM (SELECT DISTINCT u.id $sqluserinfo) userinfo", $params);
772 $systemrolecount = empty($systemroles) ? 0 : count($systemroles);
773 $overriddenrolecount = empty($overriddenroles) ? 0 : count($overriddenroles);
775 if (max($usercount, $systemrolecount, $overriddenrolecount) > 0) {
776 $result->status = REPORT_SECURITY_WARNING;
777 } else {
778 $result->status = REPORT_SECURITY_OK;
781 $a = (object)array('rolecount'=>$systemrolecount,'overridecount'=>$overriddenrolecount,'usercount'=>$usercount);
782 $result->info = get_string('check_riskbackup_warning', 'report_security', $a);
784 if ($detailed) {
786 $result->details = ''; // Will be added to later
788 // Make a list of roles
789 if ($systemroles) {
790 $links = array();
791 foreach ($systemroles as $role) {
792 $role->name = role_get_name($role);
793 $role->url = "$CFG->wwwroot/$CFG->admin/roles/manage.php?action=edit&amp;roleid=$role->id";
794 $links[] = '<li>'.get_string('check_riskbackup_editrole', 'report_security', $role).'</li>';
796 $links = '<ul>'.implode($links).'</ul>';
797 $result->details .= get_string('check_riskbackup_details_systemroles', 'report_security', $links);
800 // Make a list of overrides to roles
801 $rolelinks2 = array();
802 if ($overriddenroles) {
803 $links = array();
804 foreach ($overriddenroles as $role) {
805 $role->name = $role->localname;
806 $context = context::instance_by_id($role->contextid);
807 $role->name = role_get_name($role, $context, ROLENAME_BOTH);
808 $role->contextname = $context->get_context_name();
809 $role->url = "$CFG->wwwroot/$CFG->admin/roles/override.php?contextid=$role->contextid&amp;roleid=$role->id";
810 $links[] = '<li>'.get_string('check_riskbackup_editoverride', 'report_security', $role).'</li>';
812 $links = '<ul>'.implode('', $links).'</ul>';
813 $result->details .= get_string('check_riskbackup_details_overriddenroles', 'report_security', $links);
816 // Get a list of affected users as well
817 $users = array();
819 list($sort, $sortparams) = users_order_by_sql('u');
820 $userfields = user_picture::fields('u');
821 $rs = $DB->get_recordset_sql("SELECT DISTINCT $userfields, ra.contextid, ra.roleid
822 $sqluserinfo ORDER BY $sort", array_merge($params, $sortparams));
824 foreach ($rs as $user) {
825 $context = context::instance_by_id($user->contextid);
826 $url = "$CFG->wwwroot/$CFG->admin/roles/assign.php?contextid=$user->contextid&amp;roleid=$user->roleid";
827 $a = (object)array('fullname'=>fullname($user), 'url'=>$url, 'email'=>$user->email,
828 'contextname'=>$context->get_context_name());
829 $users[] = '<li>'.get_string('check_riskbackup_unassign', 'report_security', $a).'</li>';
831 $rs->close();
832 if (!empty($users)) {
833 $users = '<ul>'.implode('', $users).'</ul>';
834 $result->details .= get_string('check_riskbackup_details_users', 'report_security', $users);
838 return $result;
842 * Verifies the status of web cron
844 * @param bool $detailed
845 * @return object result
847 function report_security_check_webcron($detailed = false) {
848 global $CFG;
850 $croncli = $CFG->cronclionly;
851 $cronremotepassword = $CFG->cronremotepassword;
853 $result = new stdClass();
854 $result->issue = 'report_security_check_webcron';
855 $result->name = get_string('check_webcron_name', 'report_security');
856 $result->details = null;
857 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">"
858 .get_string('sitepolicies', 'admin').'</a>';
860 if (empty($croncli) && empty($cronremotepassword)) {
861 $result->status = REPORT_SECURITY_WARNING;
862 $result->info = get_string('check_webcron_warning', 'report_security');
863 } else {
864 $result->status = REPORT_SECURITY_OK;
865 $result->info = get_string('check_webcron_ok', 'report_security');
868 if ($detailed) {
869 $result->details = get_string('check_webcron_details', 'report_security');
872 return $result;
876 * Verifies the status of preventexecpath
878 * @param bool $detailed
879 * @return object result
881 function report_security_check_preventexecpath($detailed = false) {
882 global $CFG;
884 $result = new stdClass();
885 $result->issue = 'report_security_check_preventexecpath';
886 $result->name = get_string('check_preventexecpath_name', 'report_security');
887 $result->details = null;
888 $result->link = null;
890 if (empty($CFG->preventexecpath)) {
891 $result->status = REPORT_SECURITY_WARNING;
892 $result->info = get_string('check_preventexecpath_warning', 'report_security');
893 if ($detailed) {
894 $result->details = get_string('check_preventexecpath_details', 'report_security');
896 } else {
897 $result->status = REPORT_SECURITY_OK;
898 $result->info = get_string('check_preventexecpath_ok', 'report_security');
901 return $result;
905 * Check the presence of the vendor directory.
907 * @param bool $detailed Return detailed info.
908 * @return object Result data.
910 function report_security_check_vendordir($detailed = false) {
911 global $CFG;
913 $result = (object)[
914 'issue' => 'report_security_check_vendordir',
915 'name' => get_string('check_vendordir_name', 'report_security'),
916 'info' => get_string('check_vendordir_info', 'report_security'),
917 'details' => null,
918 'status' => null,
919 'link' => null,
922 if (is_dir($CFG->dirroot.'/vendor')) {
923 $result->status = REPORT_SECURITY_WARNING;
924 } else {
925 $result->status = REPORT_SECURITY_OK;
928 if ($detailed) {
929 $result->details = get_string('check_vendordir_details', 'report_security', ['path' => $CFG->dirroot.'/vendor']);
932 return $result;
936 * Check the presence of the node_modules directory.
938 * @param bool $detailed Return detailed info.
939 * @return object Result data.
941 function report_security_check_nodemodules($detailed = false) {
942 global $CFG;
944 $result = (object)[
945 'issue' => 'report_security_check_nodemodules',
946 'name' => get_string('check_nodemodules_name', 'report_security'),
947 'info' => get_string('check_nodemodules_info', 'report_security'),
948 'details' => null,
949 'status' => null,
950 'link' => null,
953 if (is_dir($CFG->dirroot.'/node_modules')) {
954 $result->status = REPORT_SECURITY_WARNING;
955 } else {
956 $result->status = REPORT_SECURITY_OK;
959 if ($detailed) {
960 $result->details = get_string('check_nodemodules_details', 'report_security', ['path' => $CFG->dirroot.'/node_modules']);
963 return $result;