MDL-41436 Allow user=-1 in get_fast_modinfo()
[moodle.git] / auth / mnet / auth.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  * Authentication Plugin: Moodle Network Authentication
19  * Multiple host authentication support for Moodle Network.
20  *
21  * @package auth_mnet
22  * @author Martin Dougiamas
23  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
24  */
26 defined('MOODLE_INTERNAL') || die();
28 require_once($CFG->libdir.'/authlib.php');
30 /**
31  * Moodle Network authentication plugin.
32  */
33 class auth_plugin_mnet extends auth_plugin_base {
35     /**
36      * Constructor.
37      */
38     function auth_plugin_mnet() {
39         $this->authtype = 'mnet';
40         $this->config = get_config('auth_mnet');
41         $this->mnet = get_mnet_environment();
42     }
44     /**
45      * This function is normally used to determine if the username and password
46      * are correct for local logins. Always returns false, as local users do not
47      * need to login over mnet xmlrpc.
48      *
49      * @param string $username The username
50      * @param string $password The password
51      * @return bool Authentication success or failure.
52      */
53     function user_login($username, $password) {
54         return false; // print_error("mnetlocal");
55     }
57     /**
58      * Return user data for the provided token, compare with user_agent string.
59      *
60      * @param  string $token    The unique ID provided by remotehost.
61      * @param  string $UA       User Agent string.
62      * @return array  $userdata Array of user info for remote host
63      */
64     function user_authorise($token, $useragent) {
65         global $CFG, $SITE, $DB;
66         $remoteclient = get_mnet_remote_client();
67         require_once $CFG->dirroot . '/mnet/xmlrpc/serverlib.php';
69         $mnet_session = $DB->get_record('mnet_session', array('token'=>$token, 'useragent'=>$useragent));
70         if (empty($mnet_session)) {
71             throw new mnet_server_exception(1, 'authfail_nosessionexists');
72         }
74         // check session confirm timeout
75         if ($mnet_session->confirm_timeout < time()) {
76             throw new mnet_server_exception(2, 'authfail_sessiontimedout');
77         }
79         // session okay, try getting the user
80         if (!$user = $DB->get_record('user', array('id'=>$mnet_session->userid))) {
81             throw new mnet_server_exception(3, 'authfail_usermismatch');
82         }
84         $userdata = mnet_strip_user((array)$user, mnet_fields_to_send($remoteclient));
86         // extra special ones
87         $userdata['auth']                    = 'mnet';
88         $userdata['wwwroot']                 = $this->mnet->wwwroot;
89         $userdata['session.gc_maxlifetime']  = ini_get('session.gc_maxlifetime');
91         if (array_key_exists('picture', $userdata) && !empty($user->picture)) {
92             $fs = get_file_storage();
93             $usercontext = context_user::instance($user->id, MUST_EXIST);
94             if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {
95                 $userdata['_mnet_userpicture_timemodified'] = $usericonfile->get_timemodified();
96                 $userdata['_mnet_userpicture_mimetype'] = $usericonfile->get_mimetype();
97             } else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {
98                 $userdata['_mnet_userpicture_timemodified'] = $usericonfile->get_timemodified();
99                 $userdata['_mnet_userpicture_mimetype'] = $usericonfile->get_mimetype();
100             }
101         }
103         $userdata['myhosts'] = array();
104         if ($courses = enrol_get_users_courses($user->id, false)) {
105             $userdata['myhosts'][] = array('name'=> $SITE->shortname, 'url' => $CFG->wwwroot, 'count' => count($courses));
106         }
108         $sql = "SELECT h.name AS hostname, h.wwwroot, h.id AS hostid,
109                        COUNT(c.id) AS count
110                   FROM {mnetservice_enrol_courses} c
111                   JOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid)
112                   JOIN {mnet_host} h ON h.id = c.hostid
113                  WHERE e.userid = ? AND c.hostid = ?
114               GROUP BY h.name, h.wwwroot, h.id";
116         if ($courses = $DB->get_records_sql($sql, array($user->id, $remoteclient->id))) {
117             foreach($courses as $course) {
118                 $userdata['myhosts'][] = array('name'=> $course->hostname, 'url' => $CFG->wwwroot.'/auth/mnet/jump.php?hostid='.$course->hostid, 'count' => $course->count);
119             }
120         }
122         return $userdata;
123     }
125     /**
126      * Generate a random string for use as an RPC session token.
127      */
128     function generate_token() {
129         return sha1(str_shuffle('' . mt_rand() . time()));
130     }
132     /**
133      * Starts an RPC jump session and returns the jump redirect URL.
134      *
135      * @param int $mnethostid id of the mnet host to jump to
136      * @param string $wantsurl url to redirect to after the jump (usually on remote system)
137      * @param boolean $wantsurlbackhere defaults to false, means that the remote system should bounce us back here
138      *                                  rather than somewhere inside *its* wwwroot
139      */
140     function start_jump_session($mnethostid, $wantsurl, $wantsurlbackhere=false) {
141         global $CFG, $USER, $DB;
142         require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
144         if (session_is_loggedinas()) {
145             print_error('notpermittedtojumpas', 'mnet');
146         }
148         // check remote login permissions
149         if (! has_capability('moodle/site:mnetlogintoremote', context_system::instance())
150                 or is_mnet_remote_user($USER)
151                 or isguestuser()
152                 or !isloggedin()) {
153             print_error('notpermittedtojump', 'mnet');
154         }
156         // check for SSO publish permission first
157         if ($this->has_service($mnethostid, 'sso_sp') == false) {
158             print_error('hostnotconfiguredforsso', 'mnet');
159         }
161         // set RPC timeout to 30 seconds if not configured
162         if (empty($this->config->rpc_negotiation_timeout)) {
163             $this->config->rpc_negotiation_timeout = 30;
164             set_config('rpc_negotiation_timeout', '30', 'auth_mnet');
165         }
167         // get the host info
168         $mnet_peer = new mnet_peer();
169         $mnet_peer->set_id($mnethostid);
171         // set up the session
172         $mnet_session = $DB->get_record('mnet_session',
173                                    array('userid'=>$USER->id, 'mnethostid'=>$mnethostid,
174                                    'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])));
175         if ($mnet_session == false) {
176             $mnet_session = new stdClass();
177             $mnet_session->mnethostid = $mnethostid;
178             $mnet_session->userid = $USER->id;
179             $mnet_session->username = $USER->username;
180             $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
181             $mnet_session->token = $this->generate_token();
182             $mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout;
183             $mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');
184             $mnet_session->session_id = session_id();
185             $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session);
186         } else {
187             $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
188             $mnet_session->token = $this->generate_token();
189             $mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout;
190             $mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');
191             $mnet_session->session_id = session_id();
192             $DB->update_record('mnet_session', $mnet_session);
193         }
195         // construct the redirection URL
196         //$transport = mnet_get_protocol($mnet_peer->transport);
197         $wantsurl = urlencode($wantsurl);
198         $url = "{$mnet_peer->wwwroot}{$mnet_peer->application->sso_land_url}?token={$mnet_session->token}&idp={$this->mnet->wwwroot}&wantsurl={$wantsurl}";
199         if ($wantsurlbackhere) {
200             $url .= '&remoteurl=1';
201         }
203         return $url;
204     }
206     /**
207      * This function confirms the remote (ID provider) host's mnet session
208      * by communicating the token and UA over the XMLRPC transport layer, and
209      * returns the local user record on success.
210      *
211      *   @param string    $token           The random session token.
212      *   @param mnet_peer $remotepeer   The ID provider mnet_peer object.
213      *   @return array The local user record.
214      */
215     function confirm_mnet_session($token, $remotepeer) {
216         global $CFG, $DB;
217         require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
218         require_once $CFG->libdir . '/gdlib.php';
219         require_once($CFG->dirroot.'/user/lib.php');
221         // verify the remote host is configured locally before attempting RPC call
222         if (! $remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotepeer->wwwroot, 'deleted' => 0))) {
223             print_error('notpermittedtoland', 'mnet');
224         }
226         // set up the RPC request
227         $mnetrequest = new mnet_xmlrpc_client();
228         $mnetrequest->set_method('auth/mnet/auth.php/user_authorise');
230         // set $token and $useragent parameters
231         $mnetrequest->add_param($token);
232         $mnetrequest->add_param(sha1($_SERVER['HTTP_USER_AGENT']));
234         // Thunderbirds are go! Do RPC call and store response
235         if ($mnetrequest->send($remotepeer) === true) {
236             $remoteuser = (object) $mnetrequest->response;
237         } else {
238             foreach ($mnetrequest->error as $errormessage) {
239                 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
240                 if($code == 702) {
241                     $site = get_site();
242                     print_error('mnet_session_prohibited', 'mnet', $remotepeer->wwwroot, format_string($site->fullname));
243                     exit;
244                 }
245                 $message .= "ERROR $code:<br/>$errormessage<br/>";
246             }
247             print_error("rpcerror", '', '', $message);
248         }
249         unset($mnetrequest);
251         if (empty($remoteuser) or empty($remoteuser->username)) {
252             print_error('unknownerror', 'mnet');
253             exit;
254         }
256         if (user_not_fully_set_up($remoteuser)) {
257             print_error('notenoughidpinfo', 'mnet');
258             exit;
259         }
261         $remoteuser = mnet_strip_user($remoteuser, mnet_fields_to_import($remotepeer));
263         $remoteuser->auth = 'mnet';
264         $remoteuser->wwwroot = $remotepeer->wwwroot;
266         // the user may roam from Moodle 1.x where lang has _utf8 suffix
267         // also, make sure that the lang is actually installed, otherwise set site default
268         if (isset($remoteuser->lang)) {
269             $remoteuser->lang = clean_param(str_replace('_utf8', '', $remoteuser->lang), PARAM_LANG);
270         }
271         if (empty($remoteuser->lang)) {
272             if (!empty($CFG->lang)) {
273                 $remoteuser->lang = $CFG->lang;
274             } else {
275                 $remoteuser->lang = 'en';
276             }
277         }
278         $firsttime = false;
280         // get the local record for the remote user
281         $localuser = $DB->get_record('user', array('username'=>$remoteuser->username, 'mnethostid'=>$remotehost->id));
283         // add the remote user to the database if necessary, and if allowed
284         // TODO: refactor into a separate function
285         if (empty($localuser) || ! $localuser->id) {
286             /*
287             if (empty($this->config->auto_add_remote_users)) {
288                 print_error('nolocaluser', 'mnet');
289             } See MDL-21327   for why this is commented out
290             */
291             $remoteuser->mnethostid = $remotehost->id;
292             $remoteuser->firstaccess = time(); // First time user in this server, grab it here
293             $remoteuser->confirmed = 1;
295             $remoteuser->id = $DB->insert_record('user', $remoteuser);
296             $firsttime = true;
297             $localuser = $remoteuser;
298         }
300         // check sso access control list for permission first
301         if (!$this->can_login_remotely($localuser->username, $remotehost->id)) {
302             print_error('sso_mnet_login_refused', 'mnet', '', array('user'=>$localuser->username, 'host'=>$remotehost->name));
303         }
305         $fs = get_file_storage();
307         // update the local user record with remote user data
308         foreach ((array) $remoteuser as $key => $val) {
310             if ($key == '_mnet_userpicture_timemodified' and empty($CFG->disableuserimages) and isset($remoteuser->picture)) {
311                 // update the user picture if there is a newer verion at the identity provider
312                 $usercontext = context_user::instance($localuser->id, MUST_EXIST);
313                 if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {
314                     $localtimemodified = $usericonfile->get_timemodified();
315                 } else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {
316                     $localtimemodified = $usericonfile->get_timemodified();
317                 } else {
318                     $localtimemodified = 0;
319                 }
321                 if (!empty($val) and $localtimemodified < $val) {
322                     mnet_debug('refetching the user picture from the identity provider host');
323                     $fetchrequest = new mnet_xmlrpc_client();
324                     $fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image');
325                     $fetchrequest->add_param($localuser->username);
326                     if ($fetchrequest->send($remotepeer) === true) {
327                         if (strlen($fetchrequest->response['f1']) > 0) {
328                             $imagefilename = $CFG->tempdir . '/mnet-usericon-' . $localuser->id;
329                             $imagecontents = base64_decode($fetchrequest->response['f1']);
330                             file_put_contents($imagefilename, $imagecontents);
331                             if ($newrev = process_new_icon($usercontext, 'user', 'icon', 0, $imagefilename)) {
332                                 $localuser->picture = $newrev;
333                             }
334                             unlink($imagefilename);
335                         }
336                         // note that since Moodle 2.0 we ignore $fetchrequest->response['f2']
337                         // the mimetype information provided is ignored and the type of the file is detected
338                         // by process_new_icon()
339                     }
340                 }
341             }
343             if($key == 'myhosts') {
344                 $localuser->mnet_foreign_host_array = array();
345                 foreach($val as $rhost) {
346                     $name  = clean_param($rhost['name'], PARAM_ALPHANUM);
347                     $url   = clean_param($rhost['url'], PARAM_URL);
348                     $count = clean_param($rhost['count'], PARAM_INT);
349                     $url_is_local = stristr($url , $CFG->wwwroot);
350                     if (!empty($name) && !empty($count) && empty($url_is_local)) {
351                         $localuser->mnet_foreign_host_array[] = array('name'  => $name,
352                                                                       'url'   => $url,
353                                                                       'count' => $count);
354                     }
355                 }
356             }
358             $localuser->{$key} = $val;
359         }
361         $localuser->mnethostid = $remotepeer->id;
362         if (empty($localuser->firstaccess)) { // Now firstaccess, grab it here
363             $localuser->firstaccess = time();
364         }
365         user_update_user($localuser, false);
367         if (!$firsttime) {
368             // repeat customer! let the IDP know about enrolments
369             // we have for this user.
370             // set up the RPC request
371             $mnetrequest = new mnet_xmlrpc_client();
372             $mnetrequest->set_method('auth/mnet/auth.php/update_enrolments');
374             // pass username and an assoc array of "my courses"
375             // with info so that the IDP can maintain mnetservice_enrol_enrolments
376             $mnetrequest->add_param($remoteuser->username);
377             $fields = 'id, category, sortorder, fullname, shortname, idnumber, summary, startdate, visible';
378             $courses = enrol_get_users_courses($localuser->id, false, $fields, 'visible DESC,sortorder ASC');
379             if (is_array($courses) && !empty($courses)) {
380                 // Second request to do the JOINs that we'd have done
381                 // inside enrol_get_users_courses() if we had been allowed
382                 $sql = "SELECT c.id,
383                                cc.name AS cat_name, cc.description AS cat_description
384                           FROM {course} c
385                           JOIN {course_categories} cc ON c.category = cc.id
386                          WHERE c.id IN (" . join(',',array_keys($courses)) . ')';
387                 $extra = $DB->get_records_sql($sql);
389                 $keys = array_keys($courses);
390                 $studentroles = get_archetype_roles('student');
391                 if (!empty($studentroles)) {
392                     $defaultrole = reset($studentroles);
393                     //$defaultrole = get_default_course_role($ccache[$shortname]); //TODO: rewrite this completely, there is no default course role any more!!!
394                     foreach ($keys AS $id) {
395                         if ($courses[$id]->visible == 0) {
396                             unset($courses[$id]);
397                             continue;
398                         }
399                         $courses[$id]->cat_id          = $courses[$id]->category;
400                         $courses[$id]->defaultroleid   = $defaultrole->id;
401                         unset($courses[$id]->category);
402                         unset($courses[$id]->visible);
404                         $courses[$id]->cat_name        = $extra[$id]->cat_name;
405                         $courses[$id]->cat_description = $extra[$id]->cat_description;
406                         $courses[$id]->defaultrolename = $defaultrole->name;
407                         // coerce to array
408                         $courses[$id] = (array)$courses[$id];
409                     }
410                 } else {
411                     throw new moodle_exception('unknownrole', 'error', '', 'student');
412                 }
413             } else {
414                 // if the array is empty, send it anyway
415                 // we may be clearing out stale entries
416                 $courses = array();
417             }
418             $mnetrequest->add_param($courses);
420             // Call 0800-RPC Now! -- we don't care too much if it fails
421             // as it's just informational.
422             if ($mnetrequest->send($remotepeer) === false) {
423                 // error_log(print_r($mnetrequest->error,1));
424             }
425         }
427         return $localuser;
428     }
431     /**
432      * creates (or updates) the mnet session once
433      * {@see confirm_mnet_session} and {@see complete_user_login} have both been called
434      *
435      * @param stdclass  $user the local user (must exist already
436      * @param string    $token the jump/land token
437      * @param mnet_peer $remotepeer the mnet_peer object of this users's idp
438      */
439     public function update_mnet_session($user, $token, $remotepeer) {
440         global $DB;
441         $session_gc_maxlifetime = 1440;
442         if (isset($user->session_gc_maxlifetime)) {
443             $session_gc_maxlifetime = $user->session_gc_maxlifetime;
444         }
445         if (!$mnet_session = $DB->get_record('mnet_session',
446                                    array('userid'=>$user->id, 'mnethostid'=>$remotepeer->id,
447                                    'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])))) {
448             $mnet_session = new stdClass();
449             $mnet_session->mnethostid = $remotepeer->id;
450             $mnet_session->userid = $user->id;
451             $mnet_session->username = $user->username;
452             $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
453             $mnet_session->token = $token; // Needed to support simultaneous sessions
454                                            // and preserving DB rec uniqueness
455             $mnet_session->confirm_timeout = time();
456             $mnet_session->expires = time() + (integer)$session_gc_maxlifetime;
457             $mnet_session->session_id = session_id();
458             $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session);
459         } else {
460             $mnet_session->expires = time() + (integer)$session_gc_maxlifetime;
461             $DB->update_record('mnet_session', $mnet_session);
462         }
463     }
467     /**
468      * Invoke this function _on_ the IDP to update it with enrolment info local to
469      * the SP right after calling user_authorise()
470      *
471      * Normally called by the SP after calling user_authorise()
472      *
473      * @param string $username The username
474      * @param array $courses  Assoc array of courses following the structure of mnetservice_enrol_courses
475      * @return bool
476      */
477     function update_enrolments($username, $courses) {
478         global $CFG, $DB;
479         $remoteclient = get_mnet_remote_client();
481         if (empty($username) || !is_array($courses)) {
482             return false;
483         }
484         // make sure it is a user we have an in active session
485         // with that host...
486         $mnetsessions = $DB->get_records('mnet_session', array('username' => $username, 'mnethostid' => $remoteclient->id), '', 'id, userid');
487         $userid = null;
488         foreach ($mnetsessions as $mnetsession) {
489             if (is_null($userid)) {
490                 $userid = $mnetsession->userid;
491                 continue;
492             }
493             if ($userid != $mnetsession->userid) {
494                 throw new mnet_server_exception(3, 'authfail_usermismatch');
495             }
496         }
498         if (empty($courses)) { // no courses? clear out quickly
499             $DB->delete_records('mnetservice_enrol_enrolments', array('hostid'=>$remoteclient->id, 'userid'=>$userid));
500             return true;
501         }
503         // IMPORTANT: Ask for remoteid as the first element in the query, so
504         // that the array that comes back is indexed on the same field as the
505         // array that we have received from the remote client
506         $sql = "SELECT c.remoteid, c.id, c.categoryid AS cat_id, c.categoryname AS cat_name, c.sortorder,
507                        c.fullname, c.shortname, c.idnumber, c.summary, c.summaryformat, c.startdate,
508                        e.id AS enrolmentid
509                   FROM {mnetservice_enrol_courses} c
510              LEFT JOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid)
511                  WHERE e.userid = ? AND c.hostid = ?";
513         $currentcourses = $DB->get_records_sql($sql, array($userid, $remoteclient->id));
515         $local_courseid_array = array();
516         foreach($courses as $ix => $course) {
518             $course['remoteid'] = $course['id'];
519             $course['hostid']   =  (int)$remoteclient->id;
520             $userisregd         = false;
522             // if we do not have the the information about the remote course, it is not available
523             // to us for remote enrolment - skip
524             if (array_key_exists($course['remoteid'], $currentcourses)) {
525                 // Pointer to current course:
526                 $currentcourse =& $currentcourses[$course['remoteid']];
527                 // We have a record - is it up-to-date?
528                 $course['id'] = $currentcourse->id;
530                 $saveflag = false;
532                 foreach($course as $key => $value) {
533                     if ($currentcourse->$key != $value) {
534                         $saveflag = true;
535                         $currentcourse->$key = $value;
536                     }
537                 }
539                 if ($saveflag) {
540                     $DB->update_record('mnetervice_enrol_courses', $currentcourse);
541                 }
543                 if (isset($currentcourse->enrolmentid) && is_numeric($currentcourse->enrolmentid)) {
544                     $userisregd = true;
545                 }
546             } else {
547                 unset ($courses[$ix]);
548                 continue;
549             }
551             // By this point, we should always have a $dataObj->id
552             $local_courseid_array[] = $course['id'];
554             // Do we have a record for this assignment?
555             if ($userisregd) {
556                 // Yes - we know about this one already
557                 // We don't want to do updates because the new data is probably
558                 // 'less complete' than the data we have.
559             } else {
560                 // No - create a record
561                 $assignObj = new stdClass();
562                 $assignObj->userid    = $userid;
563                 $assignObj->hostid    = (int)$remoteclient->id;
564                 $assignObj->remotecourseid = $course['remoteid'];
565                 $assignObj->rolename  = $course['defaultrolename'];
566                 $assignObj->id = $DB->insert_record('mnetservice_enrol_enrolments', $assignObj);
567             }
568         }
570         // Clean up courses that the user is no longer enrolled in.
571         if (!empty($local_courseid_array)) {
572             $local_courseid_string = implode(', ', $local_courseid_array);
573             $whereclause = " userid = ? AND hostid = ? AND remotecourseid NOT IN ($local_courseid_string)";
574             $DB->delete_records_select('mnetservice_enrol_enrolments', $whereclause, array($userid, $remoteclient->id));
575         }
576     }
578     function prevent_local_passwords() {
579         return true;
580     }
582     /**
583      * Returns true if this authentication plugin is 'internal'.
584      *
585      * @return bool
586      */
587     function is_internal() {
588         return false;
589     }
591     /**
592      * Returns true if this authentication plugin can change the user's
593      * password.
594      *
595      * @return bool
596      */
597     function can_change_password() {
598         //TODO: it should be able to redirect, right?
599         return false;
600     }
602     /**
603      * Returns the URL for changing the user's pw, or false if the default can
604      * be used.
605      *
606      * @return moodle_url
607      */
608     function change_password_url() {
609         return null;
610     }
612     /**
613      * Prints a form for configuring this authentication plugin.
614      *
615      * This function is called from admin/auth.php, and outputs a full page with
616      * a form for configuring this plugin.
617      *
618      * @param object $config
619      * @param object $err
620      * @param array $user_fields
621      */
622     function config_form($config, $err, $user_fields) {
623         global $CFG, $DB;
625          $query = "
626             SELECT
627                 h.id,
628                 h.name as hostname,
629                 h.wwwroot,
630                 h2idp.publish as idppublish,
631                 h2idp.subscribe as idpsubscribe,
632                 idp.name as idpname,
633                 h2sp.publish as sppublish,
634                 h2sp.subscribe as spsubscribe,
635                 sp.name as spname
636             FROM
637                 {mnet_host} h
638             LEFT JOIN
639                 {mnet_host2service} h2idp
640             ON
641                (h.id = h2idp.hostid AND
642                (h2idp.publish = 1 OR
643                 h2idp.subscribe = 1))
644             INNER JOIN
645                 {mnet_service} idp
646             ON
647                (h2idp.serviceid = idp.id AND
648                 idp.name = 'sso_idp')
649             LEFT JOIN
650                 {mnet_host2service} h2sp
651             ON
652                (h.id = h2sp.hostid AND
653                (h2sp.publish = 1 OR
654                 h2sp.subscribe = 1))
655             INNER JOIN
656                 {mnet_service} sp
657             ON
658                (h2sp.serviceid = sp.id AND
659                 sp.name = 'sso_sp')
660             WHERE
661                ((h2idp.publish = 1 AND h2sp.subscribe = 1) OR
662                (h2sp.publish = 1 AND h2idp.subscribe = 1)) AND
663                 h.id != ?
664             ORDER BY
665                 h.name ASC";
667         $id_providers       = array();
668         $service_providers  = array();
669         if ($resultset = $DB->get_records_sql($query, array($CFG->mnet_localhost_id))) {
670             foreach($resultset as $hostservice) {
671                 if(!empty($hostservice->idppublish) && !empty($hostservice->spsubscribe)) {
672                     $service_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot);
673                 }
674                 if(!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) {
675                     $id_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot);
676                 }
677             }
678         }
680         include "config.html";
681     }
683     /**
684      * Processes and stores configuration data for this authentication plugin.
685      */
686     function process_config($config) {
687         // set to defaults if undefined
688         if (!isset ($config->rpc_negotiation_timeout)) {
689             $config->rpc_negotiation_timeout = '30';
690         }
691         /*
692         if (!isset ($config->auto_add_remote_users)) {
693             $config->auto_add_remote_users = '0';
694         } See MDL-21327   for why this is commented out
695         set_config('auto_add_remote_users',   $config->auto_add_remote_users,   'auth_mnet');
696         */
698         // save settings
699         set_config('rpc_negotiation_timeout', $config->rpc_negotiation_timeout, 'auth_mnet');
701         return true;
702     }
704     /**
705      * Poll the IdP server to let it know that a user it has authenticated is still
706      * online
707      *
708      * @return  void
709      */
710     function keepalive_client() {
711         global $CFG, $DB;
712         $cutoff = time() - 300; // TODO - find out what the remote server's session
713                                 // cutoff is, and preempt that
715         $sql = "
716             select
717                 id,
718                 username,
719                 mnethostid
720             from
721                 {user}
722             where
723                 lastaccess > ? AND
724                 mnethostid != ?
725             order by
726                 mnethostid";
728         $immigrants = $DB->get_records_sql($sql, array($cutoff, $CFG->mnet_localhost_id));
730         if ($immigrants == false) {
731             return true;
732         }
734         $usersArray = array();
735         foreach($immigrants as $immigrant) {
736             $usersArray[$immigrant->mnethostid][] = $immigrant->username;
737         }
739         require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
740         foreach($usersArray as $mnethostid => $users) {
741             $mnet_peer = new mnet_peer();
742             $mnet_peer->set_id($mnethostid);
744             $mnet_request = new mnet_xmlrpc_client();
745             $mnet_request->set_method('auth/mnet/auth.php/keepalive_server');
747             // set $token and $useragent parameters
748             $mnet_request->add_param($users);
750             if ($mnet_request->send($mnet_peer) === true) {
751                 if (!isset($mnet_request->response['code'])) {
752                     debugging("Server side error has occured on host $mnethostid");
753                     continue;
754                 } elseif ($mnet_request->response['code'] > 0) {
755                     debugging($mnet_request->response['message']);
756                 }
758                 if (!isset($mnet_request->response['last log id'])) {
759                     debugging("Server side error has occured on host $mnethostid\nNo log ID was received.");
760                     continue;
761                 }
762             } else {
763                 debugging("Server side error has occured on host $mnethostid: " .
764                           join("\n", $mnet_request->error));
765                 break;
766             }
767             $mnethostlogssql = "
768             SELECT
769                 l.id as remoteid, l.time, l.userid, l.ip, l.course, l.module, l.cmid,
770                 l.action, l.url, l.info, u.username
771             FROM
772                 {user} u
773                 INNER JOIN {log} l on l.userid = u.id
774             WHERE
775                 u.mnethostid = ?
776                 AND l.id > ?
777                 AND l.course IS NOT NULL
778             ORDER by l.id ASC
779             LIMIT 500";
781             $mnethostlogs = $DB->get_records_sql($mnethostlogssql, array($mnethostid, $mnet_request->response['last log id']));
783             if ($mnethostlogs == false) {
784                 continue;
785             }
787             $processedlogs = array();
789             foreach($mnethostlogs as $hostlog) {
790                 try {
791                     // Get impersonalised course information. If it is cached there will be no DB queries.
792                     $modinfo = get_fast_modinfo($hostlog->course, -1);
793                     $hostlog->coursename = $modinfo->get_course()->fullname;
794                     if (!empty($hostlog->cmid) && isset($modinfo->cms[$hostlog->cmid])) {
795                         $hostlog->resource_name = $modinfo->cms[$hostlog->cmid]->name;
796                     } else {
797                         $hostlog->resource_name = '';
798                     }
799                 } catch (moodle_exception $e) {
800                     // Course not found
801                     continue;
802                 }
804                 $processedlogs[] = array (
805                                     'remoteid'      => $hostlog->remoteid,
806                                     'time'          => $hostlog->time,
807                                     'userid'        => $hostlog->userid,
808                                     'ip'            => $hostlog->ip,
809                                     'course'        => $hostlog->course,
810                                     'coursename'    => $hostlog->coursename,
811                                     'module'        => $hostlog->module,
812                                     'cmid'          => $hostlog->cmid,
813                                     'action'        => $hostlog->action,
814                                     'url'           => $hostlog->url,
815                                     'info'          => $hostlog->info,
816                                     'resource_name' => $hostlog->resource_name,
817                                     'username'      => $hostlog->username
818                                  );
819             }
821             unset($hostlog);
823             $mnet_request = new mnet_xmlrpc_client();
824             $mnet_request->set_method('auth/mnet/auth.php/refresh_log');
826             // set $token and $useragent parameters
827             $mnet_request->add_param($processedlogs);
829             if ($mnet_request->send($mnet_peer) === true) {
830                 if ($mnet_request->response['code'] > 0) {
831                     debugging($mnet_request->response['message']);
832                 }
833             } else {
834                 debugging("Server side error has occured on host $mnet_peer->ip: " .join("\n", $mnet_request->error));
835             }
836         }
837     }
839     /**
840      * Receives an array of log entries from an SP and adds them to the mnet_log
841      * table
842      *
843      * @param   array   $array      An array of usernames
844      * @return  string              "All ok" or an error message
845      */
846     function refresh_log($array) {
847         global $CFG, $DB;
848         $remoteclient = get_mnet_remote_client();
850         // We don't want to output anything to the client machine
851         $start = ob_start();
853         $returnString = '';
854         $transaction = $DB->start_delegated_transaction();
855         $useridarray = array();
857         foreach($array as $logEntry) {
858             $logEntryObj = (object)$logEntry;
859             $logEntryObj->hostid = $remoteclient->id;
861             if (isset($useridarray[$logEntryObj->username])) {
862                 $logEntryObj->userid = $useridarray[$logEntryObj->username];
863             } else {
864                 $logEntryObj->userid = $DB->get_field('user', 'id', array('username'=>$logEntryObj->username, 'mnethostid'=>(int)$logEntryObj->hostid));
865                 if ($logEntryObj->userid == false) {
866                     $logEntryObj->userid = 0;
867                 }
868                 $useridarray[$logEntryObj->username] = $logEntryObj->userid;
869             }
871             unset($logEntryObj->username);
873             $logEntryObj = $this->trim_logline($logEntryObj);
874             $insertok = $DB->insert_record('mnet_log', $logEntryObj, false);
876             if ($insertok) {
877                 $remoteclient->last_log_id = $logEntryObj->remoteid;
878             } else {
879                 $returnString .= 'Record with id '.$logEntryObj->remoteid." failed to insert.\n";
880             }
881         }
882         $remoteclient->commit();
883         $transaction->allow_commit();
885         $end = ob_end_clean();
887         if (empty($returnString)) return array('code' => 0, 'message' => 'All ok');
888         return array('code' => 1, 'message' => $returnString);
889     }
891     /**
892      * Receives an array of usernames from a remote machine and prods their
893      * sessions to keep them alive
894      *
895      * @param   array   $array      An array of usernames
896      * @return  string              "All ok" or an error message
897      */
898     function keepalive_server($array) {
899         global $CFG, $DB;
900         $remoteclient = get_mnet_remote_client();
902         // We don't want to output anything to the client machine
903         $start = ob_start();
905         // We'll get session records in batches of 30
906         $superArray = array_chunk($array, 30);
908         $returnString = '';
910         foreach($superArray as $subArray) {
911             $subArray = array_values($subArray);
912             $instring = "('".implode("', '",$subArray)."')";
913             $query = "select id, session_id, username from {mnet_session} where username in $instring";
914             $results = $DB->get_records_sql($query);
916             if ($results == false) {
917                 // We seem to have a username that breaks our query:
918                 // TODO: Handle this error appropriately
919                 $returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n";
920             } else {
921                 foreach($results as $emigrant) {
922                     session_touch($emigrant->session_id);
923                 }
924             }
925         }
927         $end = ob_end_clean();
929         if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $remoteclient->last_log_id);
930         return array('code' => 1, 'message' => $returnString, 'last log id' => $remoteclient->last_log_id);
931     }
933     /**
934      * Cron function will be called automatically by cron.php every 5 minutes
935      *
936      * @return void
937      */
938     function cron() {
939         global $DB;
941         // run the keepalive client
942         $this->keepalive_client();
944         // admin/cron.php should have run srand for us
945         $random100 = rand(0,100);
946         if ($random100 < 10) {     // Approximately 10% of the time.
947             // nuke olden sessions
948             $longtime = time() - (1 * 3600 * 24);
949             $DB->delete_records_select('mnet_session', "expires < ?", array($longtime));
950         }
951     }
953     /**
954      * Cleanup any remote mnet_sessions, kill the local mnet_session data
955      *
956      * This is called by require_logout in moodlelib
957      *
958      * @return   void
959      */
960     function prelogout_hook() {
961         global $CFG, $USER;
963         if (!is_enabled_auth('mnet')) {
964             return;
965         }
967         // If the user is local to this Moodle:
968         if ($USER->mnethostid == $this->mnet->id) {
969             $this->kill_children($USER->username, sha1($_SERVER['HTTP_USER_AGENT']));
971         // Else the user has hit 'logout' at a Service Provider Moodle:
972         } else {
973             $this->kill_parent($USER->username, sha1($_SERVER['HTTP_USER_AGENT']));
975         }
976     }
978     /**
979      * The SP uses this function to kill the session on the parent IdP
980      *
981      * @param   string  $username       Username for session to kill
982      * @param   string  $useragent      SHA1 hash of user agent to look for
983      * @return  string                  A plaintext report of what has happened
984      */
985     function kill_parent($username, $useragent) {
986         global $CFG, $USER, $DB;
988         require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
989         $sql = "
990             select
991                 *
992             from
993                 {mnet_session} s
994             where
995                 s.username   = ? AND
996                 s.useragent  = ? AND
997                 s.mnethostid = ?";
999         $mnetsessions = $DB->get_records_sql($sql, array($username, $useragent, $USER->mnethostid));
1001         $ignore = $DB->delete_records('mnet_session',
1002                                  array('username'=>$username,
1003                                  'useragent'=>$useragent,
1004                                  'mnethostid'=>$USER->mnethostid));
1006         if (false != $mnetsessions) {
1007             $mnet_peer = new mnet_peer();
1008             $mnet_peer->set_id($USER->mnethostid);
1010             $mnet_request = new mnet_xmlrpc_client();
1011             $mnet_request->set_method('auth/mnet/auth.php/kill_children');
1013             // set $token and $useragent parameters
1014             $mnet_request->add_param($username);
1015             $mnet_request->add_param($useragent);
1016             if ($mnet_request->send($mnet_peer) === false) {
1017                 debugging(join("\n", $mnet_request->error));
1018                 return false;
1019             }
1020         }
1022         return true;
1023     }
1025     /**
1026      * The IdP uses this function to kill child sessions on other hosts
1027      *
1028      * @param   string  $username       Username for session to kill
1029      * @param   string  $useragent      SHA1 hash of user agent to look for
1030      * @return  string                  A plaintext report of what has happened
1031      */
1032     function kill_children($username, $useragent) {
1033         global $CFG, $USER, $DB;
1034         $remoteclient = null;
1035         if (defined('MNET_SERVER')) {
1036             $remoteclient = get_mnet_remote_client();
1037         }
1038         require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
1040         $userid = $DB->get_field('user', 'id', array('mnethostid'=>$CFG->mnet_localhost_id, 'username'=>$username));
1042         $returnstring = '';
1044         $mnetsessions = $DB->get_records('mnet_session', array('userid' => $userid, 'useragent' => $useragent));
1046         if (false == $mnetsessions) {
1047             $returnstring .= "Could find no remote sessions\n";
1048             $mnetsessions = array();
1049         }
1051         foreach($mnetsessions as $mnetsession) {
1052             // If this script is being executed by a remote peer, that means the user has clicked
1053             // logout on that peer, and the session on that peer can be deleted natively.
1054             // Skip over it.
1055             if (isset($remoteclient->id) && ($mnetsession->mnethostid == $remoteclient->id)) {
1056                 continue;
1057             }
1058             $returnstring .=  "Deleting session\n";
1060             $mnet_peer = new mnet_peer();
1061             $mnet_peer->set_id($mnetsession->mnethostid);
1063             $mnet_request = new mnet_xmlrpc_client();
1064             $mnet_request->set_method('auth/mnet/auth.php/kill_child');
1066             // set $token and $useragent parameters
1067             $mnet_request->add_param($username);
1068             $mnet_request->add_param($useragent);
1069             if ($mnet_request->send($mnet_peer) === false) {
1070                 debugging("Server side error has occured on host $mnetsession->mnethostid: " .
1071                           join("\n", $mnet_request->error));
1072             }
1073         }
1075         $ignore = $DB->delete_records('mnet_session',
1076                                  array('useragent'=>$useragent, 'userid'=>$userid));
1078         if (isset($remoteclient) && isset($remoteclient->id)) {
1079             session_kill_user($userid);
1080         }
1081         return $returnstring;
1082     }
1084     /**
1085      * When the IdP requests that child sessions are terminated,
1086      * this function will be called on each of the child hosts. The machine that
1087      * calls the function (over xmlrpc) provides us with the mnethostid we need.
1088      *
1089      * @param   string  $username       Username for session to kill
1090      * @param   string  $useragent      SHA1 hash of user agent to look for
1091      * @return  bool                    True on success
1092      */
1093     function kill_child($username, $useragent) {
1094         global $CFG, $DB;
1095         $remoteclient = get_mnet_remote_client();
1096         $session = $DB->get_record('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent));
1097         $DB->delete_records('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent));
1098         if (false != $session) {
1099             session_kill($session->session_id);
1100             return true;
1101         }
1102         return false;
1103     }
1105     /**
1106      * To delete a host, we must delete all current sessions that users from
1107      * that host are currently engaged in.
1108      *
1109      * @param   string  $sessionidarray   An array of session hashes
1110      * @return  bool                      True on success
1111      */
1112     function end_local_sessions(&$sessionArray) {
1113         global $CFG;
1114         if (is_array($sessionArray)) {
1115             while($session = array_pop($sessionArray)) {
1116                 session_kill($session->session_id);
1117             }
1118             return true;
1119         }
1120         return false;
1121     }
1123     /**
1124      * Returns the user's profile image info
1125      *
1126      * If the user exists and has a profile picture, the returned array will contain keys:
1127      *  f1          - the content of the default 100x100px image
1128      *  f1_mimetype - the mimetype of the f1 file
1129      *  f2          - the content of the 35x35px variant of the image
1130      *  f2_mimetype - the mimetype of the f2 file
1131      *
1132      * The mimetype information was added in Moodle 2.0. In Moodle 1.x, images are always jpegs.
1133      *
1134      * @see process_new_icon()
1135      * @uses mnet_remote_client callable via MNet XML-RPC
1136      * @param int $userid The id of the user
1137      * @return false|array false if user not found, empty array if no picture exists, array with data otherwise
1138      */
1139     function fetch_user_image($username) {
1140         global $CFG, $DB;
1142         if ($user = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id))) {
1143             $fs = get_file_storage();
1144             $usercontext = context_user::instance($user->id, MUST_EXIST);
1145             $return = array();
1146             if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {
1147                 $return['f1'] = base64_encode($f1->get_content());
1148                 $return['f1_mimetype'] = $f1->get_mimetype();
1149             } else if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {
1150                 $return['f1'] = base64_encode($f1->get_content());
1151                 $return['f1_mimetype'] = $f1->get_mimetype();
1152             }
1153             if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.png')) {
1154                 $return['f2'] = base64_encode($f2->get_content());
1155                 $return['f2_mimetype'] = $f2->get_mimetype();
1156             } else if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.jpg')) {
1157                 $return['f2'] = base64_encode($f2->get_content());
1158                 $return['f2_mimetype'] = $f2->get_mimetype();
1159             }
1160             return $return;
1161         }
1162         return false;
1163     }
1165     /**
1166      * Returns the theme information and logo url as strings.
1167      *
1168      * @return string     The theme info
1169      */
1170     function fetch_theme_info() {
1171         global $CFG;
1173         $themename = "$CFG->theme";
1174         $logourl   = "$CFG->wwwroot/theme/$CFG->theme/images/logo.jpg";
1176         $return['themename'] = $themename;
1177         $return['logourl'] = $logourl;
1178         return $return;
1179     }
1181     /**
1182      * Determines if an MNET host is providing the nominated service.
1183      *
1184      * @param int    $mnethostid   The id of the remote host
1185      * @param string $servicename  The name of the service
1186      * @return bool                Whether the service is available on the remote host
1187      */
1188     function has_service($mnethostid, $servicename) {
1189         global $CFG, $DB;
1191         $sql = "
1192             SELECT
1193                 svc.id as serviceid,
1194                 svc.name,
1195                 svc.description,
1196                 svc.offer,
1197                 svc.apiversion,
1198                 h2s.id as h2s_id
1199             FROM
1200                 {mnet_host} h,
1201                 {mnet_service} svc,
1202                 {mnet_host2service} h2s
1203             WHERE
1204                 h.deleted = '0' AND
1205                 h.id = h2s.hostid AND
1206                 h2s.hostid = ? AND
1207                 h2s.serviceid = svc.id AND
1208                 svc.name = ? AND
1209                 h2s.subscribe = '1'";
1211         return $DB->get_records_sql($sql, array($mnethostid, $servicename));
1212     }
1214     /**
1215      * Checks the MNET access control table to see if the username/mnethost
1216      * is permitted to login to this moodle.
1217      *
1218      * @param string $username   The username
1219      * @param int    $mnethostid The id of the remote mnethost
1220      * @return bool              Whether the user can login from the remote host
1221      */
1222     function can_login_remotely($username, $mnethostid) {
1223         global $DB;
1225         $accessctrl = 'allow';
1226         $aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnethostid));
1227         if (!empty($aclrecord)) {
1228             $accessctrl = $aclrecord->accessctrl;
1229         }
1230         return $accessctrl == 'allow';
1231     }
1233     function logoutpage_hook() {
1234         global $USER, $CFG, $redirect, $DB;
1236         if (!empty($USER->mnethostid) and $USER->mnethostid != $CFG->mnet_localhost_id) {
1237             $host = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid));
1238             $redirect = $host->wwwroot.'/';
1239         }
1240     }
1242     /**
1243      * Trims a log line from mnet peer to limit each part to a length which can be stored in our DB
1244      *
1245      * @param object $logline The log information to be trimmed
1246      * @return object The passed logline object trimmed to not exceed storable limits
1247      */
1248     function trim_logline ($logline) {
1249         $limits = array('ip' => 15, 'coursename' => 40, 'module' => 20, 'action' => 40,
1250                         'url' => 255);
1251         foreach ($limits as $property => $limit) {
1252             if (isset($logline->$property)) {
1253                 $logline->$property = substr($logline->$property, 0, $limit);
1254             }
1255         }
1257         return $logline;
1258     }
1260     /**
1261      * Returns a list of potential IdPs that this authentication plugin supports.
1262      * This is used to provide links on the login page.
1263      *
1264      * @param string $wantsurl the relative url fragment the user wants to get to.  You can use this to compose a returnurl, for example
1265      *
1266      * @return array like:
1267      *              array(
1268      *                  array(
1269      *                      'url' => 'http://someurl',
1270      *                      'icon' => new pix_icon(...),
1271      *                      'name' => get_string('somename', 'auth_yourplugin'),
1272      *                 ),
1273      *             )
1274      */
1275     function loginpage_idp_list($wantsurl) {
1276         global $DB, $CFG;
1278         // strip off wwwroot, since the remote site will prefix it's return url with this
1279         $wantsurl = preg_replace('/(' . preg_quote($CFG->wwwroot, '/') . '|' . preg_quote($CFG->httpswwwroot, '/') . ')/', '', $wantsurl);
1281         $sql = "SELECT DISTINCT h.id, h.wwwroot, h.name, a.sso_jump_url, a.name as application
1282                   FROM {mnet_host} h
1283                   JOIN {mnet_host2service} m ON h.id = m.hostid
1284                   JOIN {mnet_service} s ON s.id = m.serviceid
1285                   JOIN {mnet_application} a ON h.applicationid = a.id
1286                  WHERE s.name = ? AND h.deleted = ? AND m.publish = ?";
1287         $params = array('sso_sp', 0, 1);
1289         if (!empty($CFG->mnet_all_hosts_id)) {
1290             $sql .= " AND h.id <> ?";
1291             $params[] = $CFG->mnet_all_hosts_id;
1292         }
1294         if (!$hosts = $DB->get_records_sql($sql, $params)) {
1295             return array();
1296         }
1298         $idps = array();
1299         foreach ($hosts as $host) {
1300             $idps[] = array(
1301                 'url'  => new moodle_url($host->wwwroot . $host->sso_jump_url, array('hostwwwroot' => $CFG->wwwroot, 'wantsurl' => $wantsurl, 'remoteurl' => 1)),
1302                 'icon' => new pix_icon('i/' . $host->application . '_host', $host->name),
1303                 'name' => $host->name,
1304             );
1305         }
1306         return $idps;
1307     }