2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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() {
37 $PAGE->requires
->js_init_code("Y.one('#timewarning').addClass('timewarninghidden')");
40 function report_security_get_issue_list() {
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) {
68 if (empty($CFG->docroot
)) {
72 return $OUTPUT->doc_link('report/security/'.$issue, $name);
75 ///=============================================
77 ///=============================================
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');
90 $result->details
= null;
91 $result->status
= null;
94 if (ini_get_bool('register_globals')) {
95 $result->status
= REPORT_SECURITY_CRITICAL
;
96 $result->info
= get_string('check_globals_error', 'report_security');
98 $result->status
= REPORT_SECURITY_OK
;
99 $result->info
= get_string('check_globals_ok', 'report_security');
103 $result->details
= get_string('check_globals_details', 'report_security');
110 * Verifies unsupported noauth setting
111 * @param bool $detailed
112 * @return object result
114 function report_security_check_noauth($detailed=false) {
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');
130 $result->status
= REPORT_SECURITY_OK
;
131 $result->info
= get_string('check_noauth_ok', 'report_security');
135 $result->details
= get_string('check_noauth_details', 'report_security');
142 * Verifies if password policy set
143 * @param bool $detailed
144 * @return object result
146 function report_security_check_passwordpolicy($detailed=false) {
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');
161 $result->status
= REPORT_SECURITY_OK
;
162 $result->info
= get_string('check_passwordpolicy_ok', 'report_security');
166 $result->details
= get_string('check_passwordpolicy_details', 'report_security');
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) {
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');
192 $result->status
= REPORT_SECURITY_OK
;
193 $result->info
= get_string('check_embed_ok', 'report_security');
197 $result->details
= get_string('check_embed_details', 'report_security');
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) {
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');
225 $result->status
= REPORT_SECURITY_OK
;
226 $result->info
= get_string('check_mediafilterswf_ok', 'report_security');
230 $result->details
= get_string('check_mediafilterswf_details', 'report_security');
237 * Verifies fatal misconfiguration of dataroot
238 * @param bool $detailed
239 * @return object result
241 function report_security_check_unsecuredataroot($detailed=false) {
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
);
263 $result->status
= REPORT_SECURITY_OK
;
264 $result->info
= get_string('check_unsecuredataroot_ok', 'report_security');
268 $result->details
= get_string('check_unsecuredataroot_details', 'report_security');
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');
293 $result->status
= REPORT_SECURITY_OK
;
294 $result->info
= get_string('check_displayerrors_ok', 'report_security');
298 $result->details
= get_string('check_displayerrors_details', 'report_security');
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) {
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');
324 $result->status
= REPORT_SECURITY_OK
;
325 $result->info
= get_string('check_openprofiles_ok', 'report_security');
329 $result->details
= get_string('check_openprofiles_details', 'report_security');
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) {
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');
359 $result->status
= REPORT_SECURITY_SERIOUS
;
360 $result->info
= get_string('check_google_error', 'report_security');
364 $result->details
= get_string('check_google_details', 'report_security');
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) {
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');
391 $result->status
= REPORT_SECURITY_INFO
;
392 $result->info
= get_string('check_emailchangeconfirmation_info', 'report_security');
395 $result->status
= REPORT_SECURITY_OK
;
396 $result->info
= get_string('check_emailchangeconfirmation_ok', 'report_security');
400 $result->details
= get_string('check_emailchangeconfirmation_details', 'report_security');
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) {
415 if (strpos($CFG->wwwroot
, 'https://') !== 0) {
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');
431 $result->status
= REPORT_SECURITY_OK
;
432 $result->info
= get_string('check_cookiesecure_ok', 'report_security');
436 $result->details
= get_string('check_cookiesecure_details', 'report_security');
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) {
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');
463 $result->status
= REPORT_SECURITY_OK
;
464 $result->info
= get_string('check_configrw_ok', 'report_security');
468 $result->details
= get_string('check_configrw_details', 'report_security');
474 function report_security_check_passwordsaltmain($detailed=false) {
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');
493 $result->status
= REPORT_SECURITY_OK
;
494 $result->info
= get_string('check_passwordsaltmain_ok', 'report_security');
498 $result->details
= get_string('check_passwordsaltmain_details', 'report_security', get_docs_url('report/security/report_security_check_passwordsaltmain'));
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) {
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,
529 {role_assignments} ra,
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);
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);
553 * Verifies sanity of default user role.
554 * @param bool $detailed
555 * @return object result
557 function report_security_check_defaultuserrole($detailed=false) {
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
;
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') {
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
));
599 $result->status
= REPORT_SECURITY_OK
;
600 $result->info
= get_string('check_defaultuserrole_ok', 'report_security');
604 $result->details
= get_string('check_defaultuserrole_details', 'report_security');
611 * Verifies sanity of guest role
612 * @param bool $detailed
613 * @return object result
615 function report_security_check_guestrole($detailed=false) {
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
;
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') {
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
));
657 $result->status
= REPORT_SECURITY_OK
;
658 $result->info
= get_string('check_guestrole_ok', 'report_security');
662 $result->details
= get_string('check_guestrole_details', 'report_security');
669 * Verifies sanity of frontpage role
670 * @param bool $detailed
671 * @return object result
673 function report_security_check_frontpagerole($detailed=false) {
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');
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') {
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
));
716 $result->status
= REPORT_SECURITY_OK
;
717 $result->info
= get_string('check_frontpagerole_ok', 'report_security');
721 $result->details
= get_string('check_frontpagerole_details', 'report_security');
729 * @param bool $detailed
730 * @return object result
732 function report_security_check_riskadmin($detailed=false) {
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
745 WHERE u.id IN ($CFG->siteadmins)";
747 $admins = $DB->get_records_sql($sql);
748 $admincount = count($admins);
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);
762 $result->details
= get_string('check_riskadmin_detailsok', 'report_security', $admins);
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) {
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
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
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
);
811 FROM {role_capabilities} rcx
812 WHERE rcx.permission = :permission AND rcx.capability = :capability) rc,
815 {role_assignments} ra,
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);
833 $result->details
= ''; // Will be added to later
835 // Make a list of roles
838 foreach ($systemroles as $role) {
839 $role->url
= "$CFG->wwwroot/$CFG->admin/roles/manage.php?action=edit&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) {
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&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
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&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);