Merge branch 'MDL-40918_master' of https://github.com/markn86/moodle
[moodle.git] / report / security / locallib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Lib functions
19  *
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
24  */
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')");
38 }
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_emailchangeconfirmation',
52         'report_security_check_cookiesecure',
53         'report_security_check_configrw',
54         'report_security_check_riskxss',
55         'report_security_check_riskadmin',
56         'report_security_check_riskbackup',
57         'report_security_check_defaultuserrole',
58         'report_security_check_guestrole',
59         'report_security_check_frontpagerole',
61     );
62 }
64 function report_security_doc_link($issue, $name) {
65     global $CFG, $OUTPUT;
67     if (empty($CFG->docroot)) {
68         return $name;
69     }
71     return $OUTPUT->doc_link('report/security/'.$issue, $name);
72 }
74 ///=============================================
75 ///               Issue checks
76 ///=============================================
79 /**
80  * Verifies register globals PHP setting.
81  * @param bool $detailed
82  * @return object result
83  */
84 function report_security_check_globals($detailed=false) {
85     $result = new stdClass();
86     $result->issue   = 'report_security_check_globals';
87     $result->name    = get_string('check_globals_name', 'report_security');
88     $result->info    = null;
89     $result->details = null;
90     $result->status  = null;
91     $result->link    = null;
93     if (ini_get_bool('register_globals')) {
94         $result->status = REPORT_SECURITY_CRITICAL;
95         $result->info   = get_string('check_globals_error', 'report_security');
96     } else {
97         $result->status = REPORT_SECURITY_OK;
98         $result->info   = get_string('check_globals_ok', 'report_security');
99     }
101     if ($detailed) {
102         $result->details = get_string('check_globals_details', 'report_security');
103     }
105     return $result;
108 /**
109  * Verifies unsupported noauth setting
110  * @param bool $detailed
111  * @return object result
112  */
113 function report_security_check_noauth($detailed=false) {
114     global $CFG;
116     $result = new stdClass();
117     $result->issue   = 'report_security_check_noauth';
118     $result->name    = get_string('check_noauth_name', 'report_security');
119     $result->info    = null;
120     $result->details = null;
121     $result->status  = null;
122     $result->link    = null;
123     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=manageauths\">".get_string('authsettings', 'admin').'</a>';
125     if (is_enabled_auth('none')) {
126         $result->status = REPORT_SECURITY_CRITICAL;
127         $result->info   = get_string('check_noauth_error', 'report_security');
128     } else {
129         $result->status = REPORT_SECURITY_OK;
130         $result->info   = get_string('check_noauth_ok', 'report_security');
131     }
133     if ($detailed) {
134         $result->details = get_string('check_noauth_details', 'report_security');
135     }
137     return $result;
140 /**
141  * Verifies if password policy set
142  * @param bool $detailed
143  * @return object result
144  */
145 function report_security_check_passwordpolicy($detailed=false) {
146     global $CFG;
148     $result = new stdClass();
149     $result->issue   = 'report_security_check_passwordpolicy';
150     $result->name    = get_string('check_passwordpolicy_name', 'report_security');
151     $result->info    = null;
152     $result->details = null;
153     $result->status  = null;
154     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
156     if (empty($CFG->passwordpolicy)) {
157         $result->status = REPORT_SECURITY_WARNING;
158         $result->info   = get_string('check_passwordpolicy_error', 'report_security');
159     } else {
160         $result->status = REPORT_SECURITY_OK;
161         $result->info   = get_string('check_passwordpolicy_ok', 'report_security');
162     }
164     if ($detailed) {
165         $result->details = get_string('check_passwordpolicy_details', 'report_security');
166     }
168     return $result;
171 /**
172  * Verifies sloppy embedding - this should have been removed long ago!!
173  * @param bool $detailed
174  * @return object result
175  */
176 function report_security_check_embed($detailed=false) {
177     global $CFG;
179     $result = new stdClass();
180     $result->issue   = 'report_security_check_embed';
181     $result->name    = get_string('check_embed_name', 'report_security');
182     $result->info    = null;
183     $result->details = null;
184     $result->status  = null;
185     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
187     if (!empty($CFG->allowobjectembed)) {
188         $result->status = REPORT_SECURITY_CRITICAL;
189         $result->info   = get_string('check_embed_error', 'report_security');
190     } else {
191         $result->status = REPORT_SECURITY_OK;
192         $result->info   = get_string('check_embed_ok', 'report_security');
193     }
195     if ($detailed) {
196         $result->details = get_string('check_embed_details', 'report_security');
197     }
199     return $result;
202 /**
203  * Verifies sloppy swf embedding - this should have been removed long ago!!
204  * @param bool $detailed
205  * @return object result
206  */
207 function report_security_check_mediafilterswf($detailed=false) {
208     global $CFG;
210     $result = new stdClass();
211     $result->issue   = 'report_security_check_mediafilterswf';
212     $result->name    = get_string('check_mediafilterswf_name', 'report_security');
213     $result->info    = null;
214     $result->details = null;
215     $result->status  = null;
216     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=filtersettingfiltermediaplugin\">".get_string('filtersettings', 'admin').'</a>';
218     $activefilters = filter_get_globally_enabled();
220     if (array_search('mediaplugin', $activefilters) !== false and !empty($CFG->filter_mediaplugin_enable_swf)) {
221         $result->status = REPORT_SECURITY_CRITICAL;
222         $result->info   = get_string('check_mediafilterswf_error', 'report_security');
223     } else {
224         $result->status = REPORT_SECURITY_OK;
225         $result->info   = get_string('check_mediafilterswf_ok', 'report_security');
226     }
228     if ($detailed) {
229         $result->details = get_string('check_mediafilterswf_details', 'report_security');
230     }
232     return $result;
235 /**
236  * Verifies fatal misconfiguration of dataroot
237  * @param bool $detailed
238  * @return object result
239  */
240 function report_security_check_unsecuredataroot($detailed=false) {
241     global $CFG;
243     $result = new stdClass();
244     $result->issue   = 'report_security_check_unsecuredataroot';
245     $result->name    = get_string('check_unsecuredataroot_name', 'report_security');
246     $result->info    = null;
247     $result->details = null;
248     $result->status  = null;
249     $result->link    = null;
251     $insecuredataroot = is_dataroot_insecure(true);
253     if ($insecuredataroot == INSECURE_DATAROOT_WARNING) {
254         $result->status = REPORT_SECURITY_SERIOUS;
255         $result->info   = get_string('check_unsecuredataroot_warning', 'report_security', $CFG->dataroot);
257     } else if ($insecuredataroot == INSECURE_DATAROOT_ERROR) {
258         $result->status = REPORT_SECURITY_CRITICAL;
259         $result->info   = get_string('check_unsecuredataroot_error', 'report_security', $CFG->dataroot);
261     } else {
262         $result->status = REPORT_SECURITY_OK;
263         $result->info   = get_string('check_unsecuredataroot_ok', 'report_security');
264     }
266     if ($detailed) {
267         $result->details = get_string('check_unsecuredataroot_details', 'report_security');
268     }
270     return $result;
273 /**
274  * Verifies displaying of errors - problem for lib files and 3rd party code
275  * because we can not disable debugging in these scripts (they do not include config.php)
276  * @param bool $detailed
277  * @return object result
278  */
279 function report_security_check_displayerrors($detailed=false) {
280     $result = new stdClass();
281     $result->issue   = 'report_security_check_displayerrors';
282     $result->name    = get_string('check_displayerrors_name', 'report_security');
283     $result->info    = null;
284     $result->details = null;
285     $result->status  = null;
286     $result->link    = null;
288     if (defined('WARN_DISPLAY_ERRORS_ENABLED')) {
289         $result->status = REPORT_SECURITY_WARNING;
290         $result->info   = get_string('check_displayerrors_error', 'report_security');
291     } else {
292         $result->status = REPORT_SECURITY_OK;
293         $result->info   = get_string('check_displayerrors_ok', 'report_security');
294     }
296     if ($detailed) {
297         $result->details = get_string('check_displayerrors_details', 'report_security');
298     }
300     return $result;
303 /**
304  * Verifies open profiles - originally open by default, not anymore because spammer abused it a lot
305  * @param bool $detailed
306  * @return object result
307  */
308 function report_security_check_openprofiles($detailed=false) {
309     global $CFG;
311     $result = new stdClass();
312     $result->issue   = 'report_security_check_openprofiles';
313     $result->name    = get_string('check_openprofiles_name', 'report_security');
314     $result->info    = null;
315     $result->details = null;
316     $result->status  = null;
317     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
319     if (empty($CFG->forcelogin) and empty($CFG->forceloginforprofiles)) {
320         $result->status = REPORT_SECURITY_WARNING;
321         $result->info   = get_string('check_openprofiles_error', 'report_security');
322     } else {
323         $result->status = REPORT_SECURITY_OK;
324         $result->info   = get_string('check_openprofiles_ok', 'report_security');
325     }
327     if ($detailed) {
328         $result->details = get_string('check_openprofiles_details', 'report_security');
329     }
331     return $result;
334 /**
335  * Verifies google access not combined with disabled guest access
336  * because attackers might gain guest access by modifying browser signature.
337  * @param bool $detailed
338  * @return object result
339  */
340 function report_security_check_google($detailed=false) {
341     global $CFG;
343     $result = new stdClass();
344     $result->issue   = 'report_security_check_google';
345     $result->name    = get_string('check_google_name', 'report_security');
346     $result->info    = null;
347     $result->details = null;
348     $result->status  = null;
349     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
351     if (empty($CFG->opentogoogle)) {
352         $result->status = REPORT_SECURITY_OK;
353         $result->info   = get_string('check_google_ok', 'report_security');
354     } else if (!empty($CFG->guestloginbutton)) {
355         $result->status = REPORT_SECURITY_INFO;
356         $result->info   = get_string('check_google_info', 'report_security');
357     } else {
358         $result->status = REPORT_SECURITY_SERIOUS;
359         $result->info   = get_string('check_google_error', 'report_security');
360     }
362     if ($detailed) {
363         $result->details = get_string('check_google_details', 'report_security');
364     }
366     return $result;
369 /**
370  * Verifies email confirmation - spammers were changing mails very often
371  * @param bool $detailed
372  * @return object result
373  */
374 function report_security_check_emailchangeconfirmation($detailed=false) {
375     global $CFG;
377     $result = new stdClass();
378     $result->issue   = 'report_security_check_emailchangeconfirmation';
379     $result->name    = get_string('check_emailchangeconfirmation_name', 'report_security');
380     $result->info    = null;
381     $result->details = null;
382     $result->status  = null;
383     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
385     if (empty($CFG->emailchangeconfirmation)) {
386         if (empty($CFG->allowemailaddresses)) {
387             $result->status = REPORT_SECURITY_WARNING;
388             $result->info   = get_string('check_emailchangeconfirmation_error', 'report_security');
389         } else {
390             $result->status = REPORT_SECURITY_INFO;
391             $result->info   = get_string('check_emailchangeconfirmation_info', 'report_security');
392         }
393     } else {
394         $result->status = REPORT_SECURITY_OK;
395         $result->info   = get_string('check_emailchangeconfirmation_ok', 'report_security');
396     }
398     if ($detailed) {
399         $result->details = get_string('check_emailchangeconfirmation_details', 'report_security');
400     }
402     return $result;
405 /**
406  * Verifies if https enabled only secure cookies allowed,
407  * this prevents redirections and sending of cookies to unsecure port.
408  * @param bool $detailed
409  * @return object result
410  */
411 function report_security_check_cookiesecure($detailed=false) {
412     global $CFG;
414     if (strpos($CFG->wwwroot, 'https://') !== 0) {
415         return null;
416     }
418     $result = new stdClass();
419     $result->issue   = 'report_security_check_cookiesecure';
420     $result->name    = get_string('check_cookiesecure_name', 'report_security');
421     $result->info    = null;
422     $result->details = null;
423     $result->status  = null;
424     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=httpsecurity\">".get_string('httpsecurity', 'admin').'</a>';
426     if (empty($CFG->cookiesecure)) {
427         $result->status = REPORT_SECURITY_SERIOUS;
428         $result->info   = get_string('check_cookiesecure_error', 'report_security');
429     } else {
430         $result->status = REPORT_SECURITY_OK;
431         $result->info   = get_string('check_cookiesecure_ok', 'report_security');
432     }
434     if ($detailed) {
435         $result->details = get_string('check_cookiesecure_details', 'report_security');
436     }
438     return $result;
441 /**
442  * Verifies config.php is not writable anymore after installation,
443  * config files were changed on several outdated server.
444  * @param bool $detailed
445  * @return object result
446  */
447 function report_security_check_configrw($detailed=false) {
448     global $CFG;
450     $result = new stdClass();
451     $result->issue   = 'report_security_check_configrw';
452     $result->name    = get_string('check_configrw_name', 'report_security');
453     $result->info    = null;
454     $result->details = null;
455     $result->status  = null;
456     $result->link    = null;
458     if (is_writable($CFG->dirroot.'/config.php')) {
459         $result->status = REPORT_SECURITY_WARNING;
460         $result->info   = get_string('check_configrw_warning', 'report_security');
461     } else {
462         $result->status = REPORT_SECURITY_OK;
463         $result->info   = get_string('check_configrw_ok', 'report_security');
464     }
466     if ($detailed) {
467         $result->details = get_string('check_configrw_details', 'report_security');
468     }
470     return $result;
474 /**
475  * Lists all users with XSS risk, it would be great to combine this with risk trusts in user table,
476  * unfortunately nobody implemented user trust UI yet :-(
477  * @param bool $detailed
478  * @return object result
479  */
480 function report_security_check_riskxss($detailed=false) {
481     global $DB;
483     $result = new stdClass();
484     $result->issue   = 'report_security_check_riskxss';
485     $result->name    = get_string('check_riskxss_name', 'report_security');
486     $result->info    = null;
487     $result->details = null;
488     $result->status  = REPORT_SECURITY_WARNING;
489     $result->link    = null;
491     $params = array('capallow'=>CAP_ALLOW);
493     $sqlfrom = "FROM (SELECT rcx.*
494                        FROM {role_capabilities} rcx
495                        JOIN {capabilities} cap ON (cap.name = rcx.capability AND ".$DB->sql_bitand('cap.riskbitmask', RISK_XSS)." <> 0)
496                        WHERE rcx.permission = :capallow) rc,
497                      {context} c,
498                      {context} sc,
499                      {role_assignments} ra,
500                      {user} u
501                WHERE c.id = rc.contextid
502                      AND (sc.path = c.path OR sc.path LIKE ".$DB->sql_concat('c.path', "'/%'")." OR c.path LIKE ".$DB->sql_concat('sc.path', "'/%'").")
503                      AND u.id = ra.userid AND u.deleted = 0
504                      AND ra.contextid = sc.id AND ra.roleid = rc.roleid";
506     $count = $DB->count_records_sql("SELECT COUNT(DISTINCT u.id) $sqlfrom", $params);
508     $result->info = get_string('check_riskxss_warning', 'report_security', $count);
510     if ($detailed) {
511         $userfields = user_picture::fields('u');
512         $users = $DB->get_records_sql("SELECT DISTINCT $userfields $sqlfrom", $params);
513         foreach ($users as $uid=>$user) {
514             $users[$uid] = fullname($user);
515         }
516         $users = implode(', ', $users);
517         $result->details = get_string('check_riskxss_details', 'report_security', $users);
518     }
520     return $result;
523 /**
524  * Verifies sanity of default user role.
525  * @param bool $detailed
526  * @return object result
527  */
528 function report_security_check_defaultuserrole($detailed=false) {
529     global $DB, $CFG;
531     $result = new stdClass();
532     $result->issue   = 'report_security_check_defaultuserrole';
533     $result->name    = get_string('check_defaultuserrole_name', 'report_security');
534     $result->info    = null;
535     $result->details = null;
536     $result->status  = null;
537     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'</a>';
539     if (!$default_role = $DB->get_record('role', array('id'=>$CFG->defaultuserroleid))) {
540         $result->status  = REPORT_SECURITY_WARNING;
541         $result->info    = get_string('check_defaultuserrole_notset', 'report_security');
542         $result->details = $result->info;
544         return $result;
545     }
547     // risky caps - usually very dangerous
548     $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$default_role->id);
549     $sql = "SELECT COUNT(DISTINCT rc.contextid)
550               FROM {role_capabilities} rc
551               JOIN {capabilities} cap ON cap.name = rc.capability
552              WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
553                    AND rc.permission = :capallow
554                    AND rc.roleid = :roleid";
556     $riskycount = $DB->count_records_sql($sql, $params);
558     // it may have either none or 'user' archetype - nothing else, or else it would break during upgrades badly
559     if ($default_role->archetype === '' or $default_role->archetype === 'user') {
560         $legacyok = true;
561     } else {
562         $legacyok = false;
563     }
565     if ($riskycount or !$legacyok) {
566         $result->status  = REPORT_SECURITY_CRITICAL;
567         $result->info    = get_string('check_defaultuserrole_error', 'report_security', role_get_name($default_role));
569     } else {
570         $result->status  = REPORT_SECURITY_OK;
571         $result->info    = get_string('check_defaultuserrole_ok', 'report_security');
572     }
574     if ($detailed) {
575         $result->details = get_string('check_defaultuserrole_details', 'report_security');
576     }
578     return $result;
581 /**
582  * Verifies sanity of guest role
583  * @param bool $detailed
584  * @return object result
585  */
586 function report_security_check_guestrole($detailed=false) {
587     global $DB, $CFG;
589     $result = new stdClass();
590     $result->issue   = 'report_security_check_guestrole';
591     $result->name    = get_string('check_guestrole_name', 'report_security');
592     $result->info    = null;
593     $result->details = null;
594     $result->status  = null;
595     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'</a>';
597     if (!$guest_role = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
598         $result->status  = REPORT_SECURITY_WARNING;
599         $result->info    = get_string('check_guestrole_notset', 'report_security');
600         $result->details = $result->info;
602         return $result;
603     }
605     // risky caps - usually very dangerous
606     $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$guest_role->id);
607     $sql = "SELECT COUNT(DISTINCT rc.contextid)
608               FROM {role_capabilities} rc
609               JOIN {capabilities} cap ON cap.name = rc.capability
610              WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
611                    AND rc.permission = :capallow
612                    AND rc.roleid = :roleid";
614     $riskycount = $DB->count_records_sql($sql, $params);
616     // it may have either no or 'guest' archetype - nothing else, or else it would break during upgrades badly
617     if ($guest_role->archetype === '' or $guest_role->archetype === 'guest') {
618         $legacyok = true;
619     } else {
620         $legacyok = false;
621     }
623     if ($riskycount or !$legacyok) {
624         $result->status  = REPORT_SECURITY_CRITICAL;
625         $result->info    = get_string('check_guestrole_error', 'report_security', format_string($guest_role->name));
627     } else {
628         $result->status  = REPORT_SECURITY_OK;
629         $result->info    = get_string('check_guestrole_ok', 'report_security');
630     }
632     if ($detailed) {
633         $result->details = get_string('check_guestrole_details', 'report_security');
634     }
636     return $result;
639 /**
640  * Verifies sanity of frontpage role
641  * @param bool $detailed
642  * @return object result
643  */
644 function report_security_check_frontpagerole($detailed=false) {
645     global $DB, $CFG;
647     $result = new stdClass();
648     $result->issue   = 'report_security_check_frontpagerole';
649     $result->name    = get_string('check_frontpagerole_name', 'report_security');
650     $result->info    = null;
651     $result->details = null;
652     $result->status  = null;
653     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=frontpagesettings\">".get_string('frontpagesettings','admin').'</a>';
655     if (!$frontpage_role = $DB->get_record('role', array('id'=>$CFG->defaultfrontpageroleid))) {
656         $result->status  = REPORT_SECURITY_INFO;
657         $result->info    = get_string('check_frontpagerole_notset', 'report_security');
658         $result->details = get_string('check_frontpagerole_details', 'report_security');
660         return $result;
661     }
663     // risky caps - usually very dangerous
664     $params = array('capallow'=>CAP_ALLOW, 'roleid'=>$frontpage_role->id);
665     $sql = "SELECT COUNT(DISTINCT rc.contextid)
666               FROM {role_capabilities} rc
667               JOIN {capabilities} cap ON cap.name = rc.capability
668              WHERE ".$DB->sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0
669                    AND rc.permission = :capallow
670                    AND rc.roleid = :roleid";
672     $riskycount = $DB->count_records_sql($sql, $params);
674     // there is no legacy role type for frontpage yet - anyway we can not allow teachers or admins there!
675     if ($frontpage_role->archetype === 'teacher' or $frontpage_role->archetype === 'editingteacher'
676       or $frontpage_role->archetype === 'coursecreator' or $frontpage_role->archetype === 'manager') {
677         $legacyok = false;
678     } else {
679         $legacyok = true;
680     }
682     if ($riskycount or !$legacyok) {
683         $result->status  = REPORT_SECURITY_CRITICAL;
684         $result->info    = get_string('check_frontpagerole_error', 'report_security', format_string($frontpage_role->name));
686     } else {
687         $result->status  = REPORT_SECURITY_OK;
688         $result->info    = get_string('check_frontpagerole_ok', 'report_security');
689     }
691     if ($detailed) {
692         $result->details = get_string('check_frontpagerole_details', 'report_security');
693     }
695     return $result;
698 /**
699  * Lists all admins.
700  * @param bool $detailed
701  * @return object result
702  */
703 function report_security_check_riskadmin($detailed=false) {
704     global $DB, $CFG;
706     $result = new stdClass();
707     $result->issue   = 'report_security_check_riskadmin';
708     $result->name    = get_string('check_riskadmin_name', 'report_security');
709     $result->info    = null;
710     $result->details = null;
711     $result->status  = null;
712     $result->link    = null;
714     $userfields = user_picture::fields('u');
715     $sql = "SELECT $userfields
716               FROM {user} u
717              WHERE u.id IN ($CFG->siteadmins)";
719     $admins = $DB->get_records_sql($sql);
720     $admincount = count($admins);
722     if ($detailed) {
723         foreach ($admins as $uid=>$user) {
724             $url = "$CFG->wwwroot/user/view.php?id=$user->id";
725             $admins[$uid] = '<li><a href="'.$url.'">'.fullname($user).' ('.$user->email.')</a></li>';
726         }
727         $admins = '<ul>'.implode('', $admins).'</ul>';
728     }
730     $result->status  = REPORT_SECURITY_OK;
731     $result->info = get_string('check_riskadmin_ok', 'report_security', $admincount);
733     if ($detailed) {
734         $result->details = get_string('check_riskadmin_detailsok', 'report_security', $admins);
735     }
737     return $result;
740 /**
741  * Lists all roles that have the ability to backup user data, as well as users
742  * @param bool $detailed
743  * @return object result
744  */
745 function report_security_check_riskbackup($detailed=false) {
746     global $CFG, $DB;
748     $result = new stdClass();
749     $result->issue   = 'report_security_check_riskbackup';
750     $result->name    = get_string('check_riskbackup_name', 'report_security');
751     $result->info    = null;
752     $result->details = null;
753     $result->status  = null;
754     $result->link    = null;
756     $syscontext = context_system::instance();
758     $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'contextid'=>$syscontext->id);
759     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, r.archetype
760               FROM {role} r
761               JOIN {role_capabilities} rc ON rc.roleid = r.id
762              WHERE rc.capability = :capability
763                AND rc.contextid  = :contextid
764                AND rc.permission = :permission";
765     $systemroles = $DB->get_records_sql($sql, $params);
767     $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'contextid'=>$syscontext->id);
768     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, r.archetype, rc.contextid
769               FROM {role} r
770               JOIN {role_capabilities} rc ON rc.roleid = r.id
771              WHERE rc.capability = :capability
772                AND rc.contextid <> :contextid
773                AND rc.permission = :permission";
774     $overriddenroles = $DB->get_records_sql($sql, $params);
776     // list of users that are able to backup personal info
777     // note: "sc" is context where is role assigned,
778     //       "c" is context where is role overridden or system context if in role definition
779     $params = array('capability'=>'moodle/backup:userinfo', 'permission'=>CAP_ALLOW, 'context1'=>CONTEXT_COURSE, 'context2'=>CONTEXT_COURSE);
781     $sqluserinfo = "
782         FROM (SELECT rcx.*
783                 FROM {role_capabilities} rcx
784                WHERE rcx.permission = :permission AND rcx.capability = :capability) rc,
785              {context} c,
786              {context} sc,
787              {role_assignments} ra,
788              {user} u
789        WHERE c.id = rc.contextid
790              AND (sc.path = c.path OR sc.path LIKE ".$DB->sql_concat('c.path', "'/%'")." OR c.path LIKE ".$DB->sql_concat('sc.path', "'/%'").")
791              AND u.id = ra.userid AND u.deleted = 0
792              AND ra.contextid = sc.id AND ra.roleid = rc.roleid
793              AND sc.contextlevel <= :context1 AND c.contextlevel <= :context2";
795     $usercount = $DB->count_records_sql("SELECT COUNT('x') FROM (SELECT DISTINCT u.id $sqluserinfo) userinfo", $params);
796     $systemrolecount = empty($systemroles) ? 0 : count($systemroles);
797     $overriddenrolecount = empty($overriddenroles) ? 0 : count($overriddenroles);
799     $result->status  = REPORT_SECURITY_WARNING; // there is always at least one admin
800     $a = (object)array('rolecount'=>$systemrolecount,'overridecount'=>$overriddenrolecount,'usercount'=>$usercount);
801     $result->info = get_string('check_riskbackup_warning', 'report_security', $a);
803     if ($detailed) {
805         $result->details = '';  // Will be added to later
807         // Make a list of roles
808         if ($systemroles) {
809             $links = array();
810             foreach ($systemroles as $role) {
811                 $role->name = role_get_name($role);
812                 $role->url = "$CFG->wwwroot/$CFG->admin/roles/manage.php?action=edit&amp;roleid=$role->id";
813                 $links[] = '<li>'.get_string('check_riskbackup_editrole', 'report_security', $role).'</li>';
814             }
815             $links = '<ul>'.implode($links).'</ul>';
816             $result->details .= get_string('check_riskbackup_details_systemroles', 'report_security', $links);
817         }
819         // Make a list of overrides to roles
820         $rolelinks2 = array();
821         if ($overriddenroles) {
822             $links = array();
823             foreach ($overriddenroles as $role) {
824                 $role->name = $role->localname;
825                 $context = context::instance_by_id($role->contextid);
826                 $role->name = role_get_name($role, $context, ROLENAME_BOTH);
827                 $role->contextname = $context->get_context_name();
828                 $role->url = "$CFG->wwwroot/$CFG->admin/roles/override.php?contextid=$role->contextid&amp;roleid=$role->id";
829                 $links[] = '<li>'.get_string('check_riskbackup_editoverride', 'report_security', $role).'</li>';
830             }
831             $links = '<ul>'.implode('', $links).'</ul>';
832             $result->details .= get_string('check_riskbackup_details_overriddenroles', 'report_security', $links);
833         }
835         // Get a list of affected users as well
836         $users = array();
838         list($sort, $sortparams) = users_order_by_sql('u');
839         $userfields = user_picture::fields('u');
840         $rs = $DB->get_recordset_sql("SELECT DISTINCT $userfields, ra.contextid, ra.roleid
841             $sqluserinfo ORDER BY $sort", array_merge($params, $sortparams));
843         foreach ($rs as $user) {
844             $context = context::instance_by_id($user->contextid);
845             $url = "$CFG->wwwroot/$CFG->admin/roles/assign.php?contextid=$user->contextid&amp;roleid=$user->roleid";
846             $a = (object)array('fullname'=>fullname($user), 'url'=>$url, 'email'=>$user->email,
847                                'contextname'=>$context->get_context_name());
848             $users[] = '<li>'.get_string('check_riskbackup_unassign', 'report_security', $a).'</li>';
849         }
850         if (!empty($users)) {
851             $users = '<ul>'.implode('', $users).'</ul>';
852             $result->details .= get_string('check_riskbackup_details_users', 'report_security', $users);
853         }
854     }
856     return $result;