MDL-32941 use + operator instead array merge to avoid duplicate. Sort resulting array...
[moodle.git] / report / security / locallib.php
blobb2ff5946a091e4196cc01951b9e8f3b606c7042a
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_globals',
43 'report_security_check_unsecuredataroot',
44 'report_security_check_displayerrors',
45 'report_security_check_noauth',
46 'report_security_check_embed',
47 'report_security_check_mediafilterswf',
48 'report_security_check_openprofiles',
49 'report_security_check_google',
50 'report_security_check_passwordpolicy',
51 'report_security_check_passwordsaltmain',
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',
65 function report_security_doc_link($issue, $name) {
66 global $CFG, $OUTPUT;
68 if (empty($CFG->docroot)) {
69 return $name;
72 return $OUTPUT->doc_link('report/security/'.$issue, $name);
75 ///=============================================
76 /// Issue checks
77 ///=============================================
80 /**
81 * Verifies register globals PHP setting.
82 * @param bool $detailed
83 * @return object result
85 function report_security_check_globals($detailed=false) {
86 $result = new stdClass();
87 $result->issue = 'report_security_check_globals';
88 $result->name = get_string('check_globals_name', 'report_security');
89 $result->info = null;
90 $result->details = null;
91 $result->status = null;
92 $result->link = null;
94 if (ini_get_bool('register_globals')) {
95 $result->status = REPORT_SECURITY_CRITICAL;
96 $result->info = get_string('check_globals_error', 'report_security');
97 } else {
98 $result->status = REPORT_SECURITY_OK;
99 $result->info = get_string('check_globals_ok', 'report_security');
102 if ($detailed) {
103 $result->details = get_string('check_globals_details', 'report_security');
106 return $result;
110 * Verifies unsupported noauth setting
111 * @param bool $detailed
112 * @return object result
114 function report_security_check_noauth($detailed=false) {
115 global $CFG;
117 $result = new stdClass();
118 $result->issue = 'report_security_check_noauth';
119 $result->name = get_string('check_noauth_name', 'report_security');
120 $result->info = null;
121 $result->details = null;
122 $result->status = null;
123 $result->link = null;
124 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=manageauths\">".get_string('authsettings', 'admin').'</a>';
126 if (is_enabled_auth('none')) {
127 $result->status = REPORT_SECURITY_CRITICAL;
128 $result->info = get_string('check_noauth_error', 'report_security');
129 } else {
130 $result->status = REPORT_SECURITY_OK;
131 $result->info = get_string('check_noauth_ok', 'report_security');
134 if ($detailed) {
135 $result->details = get_string('check_noauth_details', 'report_security');
138 return $result;
142 * Verifies if password policy set
143 * @param bool $detailed
144 * @return object result
146 function report_security_check_passwordpolicy($detailed=false) {
147 global $CFG;
149 $result = new stdClass();
150 $result->issue = 'report_security_check_passwordpolicy';
151 $result->name = get_string('check_passwordpolicy_name', 'report_security');
152 $result->info = null;
153 $result->details = null;
154 $result->status = null;
155 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
157 if (empty($CFG->passwordpolicy)) {
158 $result->status = REPORT_SECURITY_WARNING;
159 $result->info = get_string('check_passwordpolicy_error', 'report_security');
160 } else {
161 $result->status = REPORT_SECURITY_OK;
162 $result->info = get_string('check_passwordpolicy_ok', 'report_security');
165 if ($detailed) {
166 $result->details = get_string('check_passwordpolicy_details', 'report_security');
169 return $result;
173 * Verifies sloppy embedding - this should have been removed long ago!!
174 * @param bool $detailed
175 * @return object result
177 function report_security_check_embed($detailed=false) {
178 global $CFG;
180 $result = new stdClass();
181 $result->issue = 'report_security_check_embed';
182 $result->name = get_string('check_embed_name', 'report_security');
183 $result->info = null;
184 $result->details = null;
185 $result->status = null;
186 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
188 if (!empty($CFG->allowobjectembed)) {
189 $result->status = REPORT_SECURITY_CRITICAL;
190 $result->info = get_string('check_embed_error', 'report_security');
191 } else {
192 $result->status = REPORT_SECURITY_OK;
193 $result->info = get_string('check_embed_ok', 'report_security');
196 if ($detailed) {
197 $result->details = get_string('check_embed_details', 'report_security');
200 return $result;
204 * Verifies sloppy swf embedding - this should have been removed long ago!!
205 * @param bool $detailed
206 * @return object result
208 function report_security_check_mediafilterswf($detailed=false) {
209 global $CFG;
211 $result = new stdClass();
212 $result->issue = 'report_security_check_mediafilterswf';
213 $result->name = get_string('check_mediafilterswf_name', 'report_security');
214 $result->info = null;
215 $result->details = null;
216 $result->status = null;
217 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=filtersettingfiltermediaplugin\">".get_string('filtersettings', 'admin').'</a>';
219 $activefilters = filter_get_globally_enabled();
221 if (array_search('filter/mediaplugin', $activefilters) !== false and !empty($CFG->filter_mediaplugin_enable_swf)) {
222 $result->status = REPORT_SECURITY_CRITICAL;
223 $result->info = get_string('check_mediafilterswf_error', 'report_security');
224 } else {
225 $result->status = REPORT_SECURITY_OK;
226 $result->info = get_string('check_mediafilterswf_ok', 'report_security');
229 if ($detailed) {
230 $result->details = get_string('check_mediafilterswf_details', 'report_security');
233 return $result;
237 * Verifies fatal misconfiguration of dataroot
238 * @param bool $detailed
239 * @return object result
241 function report_security_check_unsecuredataroot($detailed=false) {
242 global $CFG;
244 $result = new stdClass();
245 $result->issue = 'report_security_check_unsecuredataroot';
246 $result->name = get_string('check_unsecuredataroot_name', 'report_security');
247 $result->info = null;
248 $result->details = null;
249 $result->status = null;
250 $result->link = null;
252 $insecuredataroot = is_dataroot_insecure(true);
254 if ($insecuredataroot == INSECURE_DATAROOT_WARNING) {
255 $result->status = REPORT_SECURITY_SERIOUS;
256 $result->info = get_string('check_unsecuredataroot_warning', 'report_security', $CFG->dataroot);
258 } else if ($insecuredataroot == INSECURE_DATAROOT_ERROR) {
259 $result->status = REPORT_SECURITY_CRITICAL;
260 $result->info = get_string('check_unsecuredataroot_error', 'report_security', $CFG->dataroot);
262 } else {
263 $result->status = REPORT_SECURITY_OK;
264 $result->info = get_string('check_unsecuredataroot_ok', 'report_security');
267 if ($detailed) {
268 $result->details = get_string('check_unsecuredataroot_details', 'report_security');
271 return $result;
275 * Verifies displaying of errors - problem for lib files and 3rd party code
276 * because we can not disable debugging in these scripts (they do not include config.php)
277 * @param bool $detailed
278 * @return object result
280 function report_security_check_displayerrors($detailed=false) {
281 $result = new stdClass();
282 $result->issue = 'report_security_check_displayerrors';
283 $result->name = get_string('check_displayerrors_name', 'report_security');
284 $result->info = null;
285 $result->details = null;
286 $result->status = null;
287 $result->link = null;
289 if (defined('WARN_DISPLAY_ERRORS_ENABLED')) {
290 $result->status = REPORT_SECURITY_WARNING;
291 $result->info = get_string('check_displayerrors_error', 'report_security');
292 } else {
293 $result->status = REPORT_SECURITY_OK;
294 $result->info = get_string('check_displayerrors_ok', 'report_security');
297 if ($detailed) {
298 $result->details = get_string('check_displayerrors_details', 'report_security');
301 return $result;
305 * Verifies open profiles - originally open by default, not anymore because spammer abused it a lot
306 * @param bool $detailed
307 * @return object result
309 function report_security_check_openprofiles($detailed=false) {
310 global $CFG;
312 $result = new stdClass();
313 $result->issue = 'report_security_check_openprofiles';
314 $result->name = get_string('check_openprofiles_name', 'report_security');
315 $result->info = null;
316 $result->details = null;
317 $result->status = null;
318 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
320 if (empty($CFG->forcelogin) and empty($CFG->forceloginforprofiles)) {
321 $result->status = REPORT_SECURITY_WARNING;
322 $result->info = get_string('check_openprofiles_error', 'report_security');
323 } else {
324 $result->status = REPORT_SECURITY_OK;
325 $result->info = get_string('check_openprofiles_ok', 'report_security');
328 if ($detailed) {
329 $result->details = get_string('check_openprofiles_details', 'report_security');
332 return $result;
336 * Verifies google access not combined with disabled guest access
337 * because attackers might gain guest access by modifying browser signature.
338 * @param bool $detailed
339 * @return object result
341 function report_security_check_google($detailed=false) {
342 global $CFG;
344 $result = new stdClass();
345 $result->issue = 'report_security_check_google';
346 $result->name = get_string('check_google_name', 'report_security');
347 $result->info = null;
348 $result->details = null;
349 $result->status = null;
350 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
352 if (empty($CFG->opentogoogle)) {
353 $result->status = REPORT_SECURITY_OK;
354 $result->info = get_string('check_google_ok', 'report_security');
355 } else if (!empty($CFG->guestloginbutton)) {
356 $result->status = REPORT_SECURITY_INFO;
357 $result->info = get_string('check_google_info', 'report_security');
358 } else {
359 $result->status = REPORT_SECURITY_SERIOUS;
360 $result->info = get_string('check_google_error', 'report_security');
363 if ($detailed) {
364 $result->details = get_string('check_google_details', 'report_security');
367 return $result;
371 * Verifies email confirmation - spammers were changing mails very often
372 * @param bool $detailed
373 * @return object result
375 function report_security_check_emailchangeconfirmation($detailed=false) {
376 global $CFG;
378 $result = new stdClass();
379 $result->issue = 'report_security_check_emailchangeconfirmation';
380 $result->name = get_string('check_emailchangeconfirmation_name', 'report_security');
381 $result->info = null;
382 $result->details = null;
383 $result->status = null;
384 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
386 if (empty($CFG->emailchangeconfirmation)) {
387 if (empty($CFG->allowemailaddresses)) {
388 $result->status = REPORT_SECURITY_WARNING;
389 $result->info = get_string('check_emailchangeconfirmation_error', 'report_security');
390 } else {
391 $result->status = REPORT_SECURITY_INFO;
392 $result->info = get_string('check_emailchangeconfirmation_info', 'report_security');
394 } else {
395 $result->status = REPORT_SECURITY_OK;
396 $result->info = get_string('check_emailchangeconfirmation_ok', 'report_security');
399 if ($detailed) {
400 $result->details = get_string('check_emailchangeconfirmation_details', 'report_security');
403 return $result;
407 * Verifies if https enabled only secure cookies allowed,
408 * this prevents redirections and sending of cookies to unsecure port.
409 * @param bool $detailed
410 * @return object result
412 function report_security_check_cookiesecure($detailed=false) {
413 global $CFG;
415 if (strpos($CFG->wwwroot, 'https://') !== 0) {
416 return null;
419 $result = new stdClass();
420 $result->issue = 'report_security_check_cookiesecure';
421 $result->name = get_string('check_cookiesecure_name', 'report_security');
422 $result->info = null;
423 $result->details = null;
424 $result->status = null;
425 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=httpsecurity\">".get_string('httpsecurity', 'admin').'</a>';
427 if (empty($CFG->cookiesecure)) {
428 $result->status = REPORT_SECURITY_SERIOUS;
429 $result->info = get_string('check_cookiesecure_error', 'report_security');
430 } else {
431 $result->status = REPORT_SECURITY_OK;
432 $result->info = get_string('check_cookiesecure_ok', 'report_security');
435 if ($detailed) {
436 $result->details = get_string('check_cookiesecure_details', 'report_security');
439 return $result;
443 * Verifies config.php is not writable anymore after installation,
444 * config files were changed on several outdated server.
445 * @param bool $detailed
446 * @return object result
448 function report_security_check_configrw($detailed=false) {
449 global $CFG;
451 $result = new stdClass();
452 $result->issue = 'report_security_check_configrw';
453 $result->name = get_string('check_configrw_name', 'report_security');
454 $result->info = null;
455 $result->details = null;
456 $result->status = null;
457 $result->link = null;
459 if (is_writable($CFG->dirroot.'/config.php')) {
460 $result->status = REPORT_SECURITY_WARNING;
461 $result->info = get_string('check_configrw_warning', 'report_security');
462 } else {
463 $result->status = REPORT_SECURITY_OK;
464 $result->info = get_string('check_configrw_ok', 'report_security');
467 if ($detailed) {
468 $result->details = get_string('check_configrw_details', 'report_security');
471 return $result;
474 function report_security_check_passwordsaltmain($detailed=false) {
475 global $CFG;
477 $result = new stdClass();
478 $result->issue = 'report_security_check_passwordsaltmain';
479 $result->name = get_string('check_passwordsaltmain_name', 'report_security');
480 $result->info = null;
481 $result->details = null;
482 $result->status = null;
483 $result->link = null;
485 if (empty($CFG->passwordsaltmain)) {
486 $result->status = REPORT_SECURITY_WARNING;
487 $result->info = get_string('check_passwordsaltmain_warning', 'report_security');
488 } else if ($CFG->passwordsaltmain === 'some long random string here with lots of characters'
489 || trim($CFG->passwordsaltmain) === '' || preg_match('/^([a-z0-9]{0,10})$/i', $CFG->passwordsaltmain)) {
490 $result->status = REPORT_SECURITY_WARNING;
491 $result->info = get_string('check_passwordsaltmain_weak', 'report_security');
492 } else {
493 $result->status = REPORT_SECURITY_OK;
494 $result->info = get_string('check_passwordsaltmain_ok', 'report_security');
497 if ($detailed) {
498 $result->details = get_string('check_passwordsaltmain_details', 'report_security', get_docs_url('report/security/report_security_check_passwordsaltmain'));
501 return $result;
505 * Lists all users with XSS risk, it would be great to combine this with risk trusts in user table,
506 * unfortunately nobody implemented user trust UI yet :-(
507 * @param bool $detailed
508 * @return object result
510 function report_security_check_riskxss($detailed=false) {
511 global $DB;
513 $result = new stdClass();
514 $result->issue = 'report_security_check_riskxss';
515 $result->name = get_string('check_riskxss_name', 'report_security');
516 $result->info = null;
517 $result->details = null;
518 $result->status = REPORT_SECURITY_WARNING;
519 $result->link = null;
521 $params = array('capallow'=>CAP_ALLOW);
523 $sqlfrom = "FROM (SELECT rcx.*
524 FROM {role_capabilities} rcx
525 JOIN {capabilities} cap ON (cap.name = rcx.capability AND ".$DB->sql_bitand('cap.riskbitmask', RISK_XSS)." <> 0)
526 WHERE rcx.permission = :capallow) rc,
527 {context} c,
528 {context} sc,
529 {role_assignments} ra,
530 {user} u
531 WHERE c.id = rc.contextid
532 AND (sc.path = c.path OR sc.path LIKE ".$DB->sql_concat('c.path', "'/%'")." OR c.path LIKE ".$DB->sql_concat('sc.path', "'/%'").")
533 AND u.id = ra.userid AND u.deleted = 0
534 AND ra.contextid = sc.id AND ra.roleid = rc.roleid";
536 $count = $DB->count_records_sql("SELECT COUNT(DISTINCT u.id) $sqlfrom", $params);
538 $result->info = get_string('check_riskxss_warning', 'report_security', $count);
540 if ($detailed) {
541 $users = $DB->get_records_sql("SELECT DISTINCT u.id, u.firstname, u.lastname, u.picture, u.imagealt $sqlfrom", $params);
542 foreach ($users as $uid=>$user) {
543 $users[$uid] = fullname($user);
545 $users = implode(', ', $users);
546 $result->details = get_string('check_riskxss_details', 'report_security', $users);
549 return $result;
553 * Verifies sanity of default user role.
554 * @param bool $detailed
555 * @return object result
557 function report_security_check_defaultuserrole($detailed=false) {
558 global $DB, $CFG;
560 $result = new stdClass();
561 $result->issue = 'report_security_check_defaultuserrole';
562 $result->name = get_string('check_defaultuserrole_name', 'report_security');
563 $result->info = null;
564 $result->details = null;
565 $result->status = null;
566 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'</a>';;
568 if (!$default_role = $DB->get_record('role', array('id'=>$CFG->defaultuserroleid))) {
569 $result->status = REPORT_SECURITY_WARNING;
570 $result->info = get_string('check_defaultuserrole_notset', 'report_security');
571 $result->details = $result->info;
573 return $result;
576 // risky caps - usually very dangerous
577 $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$default_role->id);
578 $sql = "SELECT COUNT(DISTINCT rc.contextid)
579 FROM {role_capabilities} rc
580 JOIN {capabilities} cap ON cap.name = rc.capability
581 WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
582 AND rc.permission = :capallow
583 AND rc.roleid = :roleid";
585 $riskycount = $DB->count_records_sql($sql, $params);
587 // it may have either none or 'user' archetype - nothing else, or else it would break during upgrades badly
588 if ($default_role->archetype === '' or $default_role->archetype === 'user') {
589 $legacyok = true;
590 } else {
591 $legacyok = false;
594 if ($riskycount or !$legacyok) {
595 $result->status = REPORT_SECURITY_CRITICAL;
596 $result->info = get_string('check_defaultuserrole_error', 'report_security', format_string($default_role->name));
598 } else {
599 $result->status = REPORT_SECURITY_OK;
600 $result->info = get_string('check_defaultuserrole_ok', 'report_security');
603 if ($detailed) {
604 $result->details = get_string('check_defaultuserrole_details', 'report_security');
607 return $result;
611 * Verifies sanity of guest role
612 * @param bool $detailed
613 * @return object result
615 function report_security_check_guestrole($detailed=false) {
616 global $DB, $CFG;
618 $result = new stdClass();
619 $result->issue = 'report_security_check_guestrole';
620 $result->name = get_string('check_guestrole_name', 'report_security');
621 $result->info = null;
622 $result->details = null;
623 $result->status = null;
624 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'</a>';;
626 if (!$guest_role = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
627 $result->status = REPORT_SECURITY_WARNING;
628 $result->info = get_string('check_guestrole_notset', 'report_security');
629 $result->details = $result->info;
631 return $result;
634 // risky caps - usually very dangerous
635 $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$guest_role->id);
636 $sql = "SELECT COUNT(DISTINCT rc.contextid)
637 FROM {role_capabilities} rc
638 JOIN {capabilities} cap ON cap.name = rc.capability
639 WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
640 AND rc.permission = :capallow
641 AND rc.roleid = :roleid";
643 $riskycount = $DB->count_records_sql($sql, $params);
645 // it may have either no or 'guest' archetype - nothing else, or else it would break during upgrades badly
646 if ($guest_role->archetype === '' or $guest_role->archetype === 'guest') {
647 $legacyok = true;
648 } else {
649 $legacyok = false;
652 if ($riskycount or !$legacyok) {
653 $result->status = REPORT_SECURITY_CRITICAL;
654 $result->info = get_string('check_guestrole_error', 'report_security', format_string($guest_role->name));
656 } else {
657 $result->status = REPORT_SECURITY_OK;
658 $result->info = get_string('check_guestrole_ok', 'report_security');
661 if ($detailed) {
662 $result->details = get_string('check_guestrole_details', 'report_security');
665 return $result;
669 * Verifies sanity of frontpage role
670 * @param bool $detailed
671 * @return object result
673 function report_security_check_frontpagerole($detailed=false) {
674 global $DB, $CFG;
676 $result = new stdClass();
677 $result->issue = 'report_security_check_frontpagerole';
678 $result->name = get_string('check_frontpagerole_name', 'report_security');
679 $result->info = null;
680 $result->details = null;
681 $result->status = null;
682 $result->link = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=frontpagesettings\">".get_string('frontpagesettings','admin').'</a>';;
684 if (!$frontpage_role = $DB->get_record('role', array('id'=>$CFG->defaultfrontpageroleid))) {
685 $result->status = REPORT_SECURITY_INFO;
686 $result->info = get_string('check_frontpagerole_notset', 'report_security');
687 $result->details = get_string('check_frontpagerole_details', 'report_security');
689 return $result;
692 // risky caps - usually very dangerous
693 $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$frontpage_role->id);
694 $sql = "SELECT COUNT(DISTINCT rc.contextid)
695 FROM {role_capabilities} rc
696 JOIN {capabilities} cap ON cap.name = rc.capability
697 WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
698 AND rc.permission = :capallow
699 AND rc.roleid = :roleid";
701 $riskycount = $DB->count_records_sql($sql, $params);
703 // there is no legacy role type for frontpage yet - anyway we can not allow teachers or admins there!
704 if ($frontpage_role->archetype === 'teacher' or $frontpage_role->archetype === 'editingteacher'
705 or $frontpage_role->archetype === 'coursecreator' or $frontpage_role->archetype === 'manager') {
706 $legacyok = false;
707 } else {
708 $legacyok = true;
711 if ($riskycount or !$legacyok) {
712 $result->status = REPORT_SECURITY_CRITICAL;
713 $result->info = get_string('check_frontpagerole_error', 'report_security', format_string($frontpage_role->name));
715 } else {
716 $result->status = REPORT_SECURITY_OK;
717 $result->info = get_string('check_frontpagerole_ok', 'report_security');
720 if ($detailed) {
721 $result->details = get_string('check_frontpagerole_details', 'report_security');
724 return $result;
728 * Lists all admins.
729 * @param bool $detailed
730 * @return object result
732 function report_security_check_riskadmin($detailed=false) {
733 global $DB, $CFG;
735 $result = new stdClass();
736 $result->issue = 'report_security_check_riskadmin';
737 $result->name = get_string('check_riskadmin_name', 'report_security');
738 $result->info = null;
739 $result->details = null;
740 $result->status = null;
741 $result->link = null;
743 $sql = "SELECT u.id, u.firstname, u.lastname, u.picture, u.imagealt, u.email
744 FROM {user} u
745 WHERE u.id IN ($CFG->siteadmins)";
747 $admins = $DB->get_records_sql($sql);
748 $admincount = count($admins);
750 if ($detailed) {
751 foreach ($admins as $uid=>$user) {
752 $url = "$CFG->wwwroot/user/view.php?id=$user->id";
753 $admins[$uid] = '<li><a href="'.$url.'">'.fullname($user).' ('.$user->email.')</a></li>';
755 $admins = '<ul>'.implode('', $admins).'</ul>';
758 $result->status = REPORT_SECURITY_OK;
759 $result->info = get_string('check_riskadmin_ok', 'report_security', $admincount);
761 if ($detailed) {
762 $result->details = get_string('check_riskadmin_detailsok', 'report_security', $admins);
765 return $result;
769 * Lists all roles that have the ability to backup user data, as well as users
770 * @param bool $detailed
771 * @return object result
773 function report_security_check_riskbackup($detailed=false) {
774 global $CFG, $DB;
776 $result = new stdClass();
777 $result->issue = 'report_security_check_riskbackup';
778 $result->name = get_string('check_riskbackup_name', 'report_security');
779 $result->info = null;
780 $result->details = null;
781 $result->status = null;
782 $result->link = null;
784 $syscontext = get_context_instance(CONTEXT_SYSTEM);
786 $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'contextid'=>$syscontext->id);
787 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, r.archetype
788 FROM {role} r
789 JOIN {role_capabilities} rc ON rc.roleid = r.id
790 WHERE rc.capability = :capability
791 AND rc.contextid = :contextid
792 AND rc.permission = :permission";
793 $systemroles = $DB->get_records_sql($sql, $params);
795 $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'contextid'=>$syscontext->id);
796 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, r.archetype, rc.contextid
797 FROM {role} r
798 JOIN {role_capabilities} rc ON rc.roleid = r.id
799 WHERE rc.capability = :capability
800 AND rc.contextid <> :contextid
801 AND rc.permission = :permission";
802 $overriddenroles = $DB->get_records_sql($sql, $params);
804 // list of users that are able to backup personal info
805 // note: "sc" is context where is role assigned,
806 // "c" is context where is role overridden or system context if in role definition
807 $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'context1'=>CONTEXT_COURSE, 'context2'=>CONTEXT_COURSE);
809 $sqluserinfo = "
810 FROM (SELECT rcx.*
811 FROM {role_capabilities} rcx
812 WHERE rcx.permission = :permission AND rcx.capability = :capability) rc,
813 {context} c,
814 {context} sc,
815 {role_assignments} ra,
816 {user} u
817 WHERE c.id = rc.contextid
818 AND (sc.path = c.path OR sc.path LIKE ".$DB->sql_concat('c.path', "'/%'")." OR c.path LIKE ".$DB->sql_concat('sc.path', "'/%'").")
819 AND u.id = ra.userid AND u.deleted = 0
820 AND ra.contextid = sc.id AND ra.roleid = rc.roleid
821 AND sc.contextlevel <= :context1 AND c.contextlevel <= :context2";
823 $usercount = $DB->count_records_sql("SELECT COUNT('x') FROM (SELECT DISTINCT u.id $sqluserinfo) userinfo", $params);
824 $systemrolecount = empty($systemroles) ? 0 : count($systemroles);
825 $overriddenrolecount = empty($overriddenroles) ? 0 : count($overriddenroles);
827 $result->status = REPORT_SECURITY_WARNING; // there is always at least one admin
828 $a = (object)array('rolecount'=>$systemrolecount,'overridecount'=>$overriddenrolecount,'usercount'=>$usercount);
829 $result->info = get_string('check_riskbackup_warning', 'report_security', $a);
831 if ($detailed) {
833 $result->details = ''; // Will be added to later
835 // Make a list of roles
836 if ($systemroles) {
837 $links = array();
838 foreach ($systemroles as $role) {
839 $role->url = "$CFG->wwwroot/$CFG->admin/roles/manage.php?action=edit&amp;roleid=$role->id";
840 $links[] = '<li>'.get_string('check_riskbackup_editrole', 'report_security', $role).'</li>';
842 $links = '<ul>'.implode($links).'</ul>';
843 $result->details .= get_string('check_riskbackup_details_systemroles', 'report_security', $links);
846 // Make a list of overrides to roles
847 $rolelinks2 = array();
848 if ($overriddenroles) {
849 $links = array();
850 foreach ($overriddenroles as $role) {
851 $context = get_context_instance_by_id($role->contextid);
852 if ($context->contextlevel == CONTEXT_COURSE) {
853 $role->name = role_get_name($role, $context);
855 $role->contextname = print_context_name($context);
856 $role->url = "$CFG->wwwroot/$CFG->admin/roles/override.php?contextid=$role->contextid&amp;roleid=$role->id";
857 $links[] = '<li>'.get_string('check_riskbackup_editoverride', 'report_security', $role).'</li>';
859 $links = '<ul>'.implode('', $links).'</ul>';
860 $result->details .= get_string('check_riskbackup_details_overriddenroles', 'report_security', $links);
863 // Get a list of affected users as well
864 $users = array();
866 $rs = $DB->get_recordset_sql("SELECT DISTINCT u.id, u.firstname, u.lastname, u.picture, u.imagealt, u.email, ra.contextid, ra.roleid
867 $sqluserinfo ORDER BY u.lastname, u.firstname", $params);
869 foreach ($rs as $user) {
870 $context = get_context_instance_by_id($user->contextid);
871 $url = "$CFG->wwwroot/$CFG->admin/roles/assign.php?contextid=$user->contextid&amp;roleid=$user->roleid";
872 $a = (object)array('fullname'=>fullname($user), 'url'=>$url, 'email'=>$user->email,
873 'contextname'=>print_context_name($context));
874 $users[] = '<li>'.get_string('check_riskbackup_unassign', 'report_security', $a).'</li>';
876 if (!empty($users)) {
877 $users = '<ul>'.implode('', $users).'</ul>';
878 $result->details .= get_string('check_riskbackup_details_users', 'report_security', $users);
882 return $result;