Revert "MDL-36838 moodle->mahara mnet SSO failure in FF17"
[moodle.git] / auth / mnet / auth.php
1 <?php
3 /**
4  * @author Martin Dougiamas
5  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
6  * @package moodle multiauth
7  *
8  * Authentication Plugin: Moodle Network Authentication
9  *
10  * Multiple host authentication support for Moodle Network.
11  *
12  * 2006-11-01  File created.
13  */
15 if (!defined('MOODLE_INTERNAL')) {
16     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
17 }
19 require_once($CFG->libdir.'/authlib.php');
21 /**
22  * Moodle Network authentication plugin.
23  */
24 class auth_plugin_mnet extends auth_plugin_base {
26     /**
27      * Constructor.
28      */
29     function auth_plugin_mnet() {
30         $this->authtype = 'mnet';
31         $this->config = get_config('auth_mnet');
32         $this->mnet = get_mnet_environment();
33     }
35     /**
36      * This function is normally used to determine if the username and password
37      * are correct for local logins. Always returns false, as local users do not
38      * need to login over mnet xmlrpc.
39      *
40      * @param string $username The username
41      * @param string $password The password
42      * @return bool Authentication success or failure.
43      */
44     function user_login($username, $password) {
45         return false; // print_error("mnetlocal");
46     }
48     /**
49      * Return user data for the provided token, compare with user_agent string.
50      *
51      * @param  string $token    The unique ID provided by remotehost.
52      * @param  string $UA       User Agent string.
53      * @return array  $userdata Array of user info for remote host
54      */
55     function user_authorise($token, $useragent) {
56         global $CFG, $SITE, $DB;
57         $remoteclient = get_mnet_remote_client();
58         require_once $CFG->dirroot . '/mnet/xmlrpc/serverlib.php';
60         $mnet_session = $DB->get_record('mnet_session', array('token'=>$token, 'useragent'=>$useragent));
61         if (empty($mnet_session)) {
62             throw new mnet_server_exception(1, 'authfail_nosessionexists');
63         }
65         // check session confirm timeout
66         if ($mnet_session->confirm_timeout < time()) {
67             throw new mnet_server_exception(2, 'authfail_sessiontimedout');
68         }
70         // session okay, try getting the user
71         if (!$user = $DB->get_record('user', array('id'=>$mnet_session->userid))) {
72             throw new mnet_server_exception(3, 'authfail_usermismatch');
73         }
75         $userdata = mnet_strip_user((array)$user, mnet_fields_to_send($remoteclient));
77         // extra special ones
78         $userdata['auth']                    = 'mnet';
79         $userdata['wwwroot']                 = $this->mnet->wwwroot;
80         $userdata['session.gc_maxlifetime']  = ini_get('session.gc_maxlifetime');
82         if (array_key_exists('picture', $userdata) && !empty($user->picture)) {
83             $fs = get_file_storage();
84             $usercontext = context_user::instance($user->id, MUST_EXIST);
85             if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {
86                 $userdata['_mnet_userpicture_timemodified'] = $usericonfile->get_timemodified();
87                 $userdata['_mnet_userpicture_mimetype'] = $usericonfile->get_mimetype();
88             } else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {
89                 $userdata['_mnet_userpicture_timemodified'] = $usericonfile->get_timemodified();
90                 $userdata['_mnet_userpicture_mimetype'] = $usericonfile->get_mimetype();
91             }
92         }
94         $userdata['myhosts'] = array();
95         if ($courses = enrol_get_users_courses($user->id, false)) {
96             $userdata['myhosts'][] = array('name'=> $SITE->shortname, 'url' => $CFG->wwwroot, 'count' => count($courses));
97         }
99         $sql = "SELECT h.name AS hostname, h.wwwroot, h.id AS hostid,
100                        COUNT(c.id) AS count
101                   FROM {mnetservice_enrol_courses} c
102                   JOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid)
103                   JOIN {mnet_host} h ON h.id = c.hostid
104                  WHERE e.userid = ? AND c.hostid = ?
105               GROUP BY h.name, h.wwwroot, h.id";
107         if ($courses = $DB->get_records_sql($sql, array($user->id, $remoteclient->id))) {
108             foreach($courses as $course) {
109                 $userdata['myhosts'][] = array('name'=> $course->hostname, 'url' => $CFG->wwwroot.'/auth/mnet/jump.php?hostid='.$course->hostid, 'count' => $course->count);
110             }
111         }
113         return $userdata;
114     }
116     /**
117      * Generate a random string for use as an RPC session token.
118      */
119     function generate_token() {
120         return sha1(str_shuffle('' . mt_rand() . time()));
121     }
123     /**
124      * Starts an RPC jump session and returns the jump redirect URL.
125      *
126      * @param int $mnethostid id of the mnet host to jump to
127      * @param string $wantsurl url to redirect to after the jump (usually on remote system)
128      * @param boolean $wantsurlbackhere defaults to false, means that the remote system should bounce us back here
129      *                                  rather than somewhere inside *its* wwwroot
130      */
131     function start_jump_session($mnethostid, $wantsurl, $wantsurlbackhere=false) {
132         global $CFG, $USER, $DB;
133         require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
135         if (session_is_loggedinas()) {
136             print_error('notpermittedtojumpas', 'mnet');
137         }
139         // check remote login permissions
140         if (! has_capability('moodle/site:mnetlogintoremote', get_system_context())
141                 or is_mnet_remote_user($USER)
142                 or isguestuser()
143                 or !isloggedin()) {
144             print_error('notpermittedtojump', 'mnet');
145         }
147         // check for SSO publish permission first
148         if ($this->has_service($mnethostid, 'sso_sp') == false) {
149             print_error('hostnotconfiguredforsso', 'mnet');
150         }
152         // set RPC timeout to 30 seconds if not configured
153         if (empty($this->config->rpc_negotiation_timeout)) {
154             $this->config->rpc_negotiation_timeout = 30;
155             set_config('rpc_negotiation_timeout', '30', 'auth_mnet');
156         }
158         // get the host info
159         $mnet_peer = new mnet_peer();
160         $mnet_peer->set_id($mnethostid);
162         // set up the session
163         $mnet_session = $DB->get_record('mnet_session',
164                                    array('userid'=>$USER->id, 'mnethostid'=>$mnethostid,
165                                    'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])));
166         if ($mnet_session == false) {
167             $mnet_session = new stdClass();
168             $mnet_session->mnethostid = $mnethostid;
169             $mnet_session->userid = $USER->id;
170             $mnet_session->username = $USER->username;
171             $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
172             $mnet_session->token = $this->generate_token();
173             $mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout;
174             $mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');
175             $mnet_session->session_id = session_id();
176             $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session);
177         } else {
178             $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
179             $mnet_session->token = $this->generate_token();
180             $mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout;
181             $mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');
182             $mnet_session->session_id = session_id();
183             $DB->update_record('mnet_session', $mnet_session);
184         }
186         // construct the redirection URL
187         //$transport = mnet_get_protocol($mnet_peer->transport);
188         $wantsurl = urlencode($wantsurl);
189         $url = "{$mnet_peer->wwwroot}{$mnet_peer->application->sso_land_url}?token={$mnet_session->token}&idp={$this->mnet->wwwroot}&wantsurl={$wantsurl}";
190         if ($wantsurlbackhere) {
191             $url .= '&remoteurl=1';
192         }
194         return $url;
195     }
197     /**
198      * This function confirms the remote (ID provider) host's mnet session
199      * by communicating the token and UA over the XMLRPC transport layer, and
200      * returns the local user record on success.
201      *
202      *   @param string    $token           The random session token.
203      *   @param mnet_peer $remotepeer   The ID provider mnet_peer object.
204      *   @return array The local user record.
205      */
206     function confirm_mnet_session($token, $remotepeer) {
207         global $CFG, $DB;
208         require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
209         require_once $CFG->libdir . '/gdlib.php';
211         // verify the remote host is configured locally before attempting RPC call
212         if (! $remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotepeer->wwwroot, 'deleted' => 0))) {
213             print_error('notpermittedtoland', 'mnet');
214         }
216         // set up the RPC request
217         $mnetrequest = new mnet_xmlrpc_client();
218         $mnetrequest->set_method('auth/mnet/auth.php/user_authorise');
220         // set $token and $useragent parameters
221         $mnetrequest->add_param($token);
222         $mnetrequest->add_param(sha1($_SERVER['HTTP_USER_AGENT']));
224         // Thunderbirds are go! Do RPC call and store response
225         if ($mnetrequest->send($remotepeer) === true) {
226             $remoteuser = (object) $mnetrequest->response;
227         } else {
228             foreach ($mnetrequest->error as $errormessage) {
229                 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
230                 if($code == 702) {
231                     $site = get_site();
232                     print_error('mnet_session_prohibited', 'mnet', $remotepeer->wwwroot, format_string($site->fullname));
233                     exit;
234                 }
235                 $message .= "ERROR $code:<br/>$errormessage<br/>";
236             }
237             print_error("rpcerror", '', '', $message);
238         }
239         unset($mnetrequest);
241         if (empty($remoteuser) or empty($remoteuser->username)) {
242             print_error('unknownerror', 'mnet');
243             exit;
244         }
246         if (user_not_fully_set_up($remoteuser)) {
247             print_error('notenoughidpinfo', 'mnet');
248             exit;
249         }
251         $remoteuser = mnet_strip_user($remoteuser, mnet_fields_to_import($remotepeer));
253         $remoteuser->auth = 'mnet';
254         $remoteuser->wwwroot = $remotepeer->wwwroot;
256         // the user may roam from Moodle 1.x where lang has _utf8 suffix
257         // also, make sure that the lang is actually installed, otherwise set site default
258         if (isset($remoteuser->lang)) {
259             $remoteuser->lang = clean_param(str_replace('_utf8', '', $remoteuser->lang), PARAM_LANG);
260         }
261         if (empty($remoteuser->lang)) {
262             if (!empty($CFG->lang)) {
263                 $remoteuser->lang = $CFG->lang;
264             } else {
265                 $remoteuser->lang = 'en';
266             }
267         }
268         $firsttime = false;
270         // get the local record for the remote user
271         $localuser = $DB->get_record('user', array('username'=>$remoteuser->username, 'mnethostid'=>$remotehost->id));
273         // add the remote user to the database if necessary, and if allowed
274         // TODO: refactor into a separate function
275         if (empty($localuser) || ! $localuser->id) {
276             /*
277             if (empty($this->config->auto_add_remote_users)) {
278                 print_error('nolocaluser', 'mnet');
279             } See MDL-21327   for why this is commented out
280             */
281             $remoteuser->mnethostid = $remotehost->id;
282             $remoteuser->firstaccess = time(); // First time user in this server, grab it here
283             $remoteuser->confirmed = 1;
285             $remoteuser->id = $DB->insert_record('user', $remoteuser);
286             $firsttime = true;
287             $localuser = $remoteuser;
288         }
290         // check sso access control list for permission first
291         if (!$this->can_login_remotely($localuser->username, $remotehost->id)) {
292             print_error('sso_mnet_login_refused', 'mnet', '', array('user'=>$localuser->username, 'host'=>$remotehost->name));
293         }
295         $fs = get_file_storage();
297         // update the local user record with remote user data
298         foreach ((array) $remoteuser as $key => $val) {
300             if ($key == '_mnet_userpicture_timemodified' and empty($CFG->disableuserimages) and isset($remoteuser->picture)) {
301                 // update the user picture if there is a newer verion at the identity provider
302                 $usercontext = context_user::instance($localuser->id, MUST_EXIST);
303                 if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {
304                     $localtimemodified = $usericonfile->get_timemodified();
305                 } else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {
306                     $localtimemodified = $usericonfile->get_timemodified();
307                 } else {
308                     $localtimemodified = 0;
309                 }
311                 if (!empty($val) and $localtimemodified < $val) {
312                     mnet_debug('refetching the user picture from the identity provider host');
313                     $fetchrequest = new mnet_xmlrpc_client();
314                     $fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image');
315                     $fetchrequest->add_param($localuser->username);
316                     if ($fetchrequest->send($remotepeer) === true) {
317                         if (strlen($fetchrequest->response['f1']) > 0) {
318                             $imagefilename = $CFG->tempdir . '/mnet-usericon-' . $localuser->id;
319                             $imagecontents = base64_decode($fetchrequest->response['f1']);
320                             file_put_contents($imagefilename, $imagecontents);
321                             if ($newrev = process_new_icon($usercontext, 'user', 'icon', 0, $imagefilename)) {
322                                 $localuser->picture = $newrev;
323                             }
324                             unlink($imagefilename);
325                         }
326                         // note that since Moodle 2.0 we ignore $fetchrequest->response['f2']
327                         // the mimetype information provided is ignored and the type of the file is detected
328                         // by process_new_icon()
329                     }
330                 }
331             }
333             if($key == 'myhosts') {
334                 $localuser->mnet_foreign_host_array = array();
335                 foreach($val as $rhost) {
336                     $name  = clean_param($rhost['name'], PARAM_ALPHANUM);
337                     $url   = clean_param($rhost['url'], PARAM_URL);
338                     $count = clean_param($rhost['count'], PARAM_INT);
339                     $url_is_local = stristr($url , $CFG->wwwroot);
340                     if (!empty($name) && !empty($count) && empty($url_is_local)) {
341                         $localuser->mnet_foreign_host_array[] = array('name'  => $name,
342                                                                       'url'   => $url,
343                                                                       'count' => $count);
344                     }
345                 }
346             }
348             $localuser->{$key} = $val;
349         }
351         $localuser->mnethostid = $remotepeer->id;
352         if (empty($localuser->firstaccess)) { // Now firstaccess, grab it here
353             $localuser->firstaccess = time();
354         }
356         $DB->update_record('user', $localuser);
358         if (!$firsttime) {
359             // repeat customer! let the IDP know about enrolments
360             // we have for this user.
361             // set up the RPC request
362             $mnetrequest = new mnet_xmlrpc_client();
363             $mnetrequest->set_method('auth/mnet/auth.php/update_enrolments');
365             // pass username and an assoc array of "my courses"
366             // with info so that the IDP can maintain mnetservice_enrol_enrolments
367             $mnetrequest->add_param($remoteuser->username);
368             $fields = 'id, category, sortorder, fullname, shortname, idnumber, summary, startdate, visible';
369             $courses = enrol_get_users_courses($localuser->id, false, $fields, 'visible DESC,sortorder ASC');
370             if (is_array($courses) && !empty($courses)) {
371                 // Second request to do the JOINs that we'd have done
372                 // inside enrol_get_users_courses() if we had been allowed
373                 $sql = "SELECT c.id,
374                                cc.name AS cat_name, cc.description AS cat_description
375                           FROM {course} c
376                           JOIN {course_categories} cc ON c.category = cc.id
377                          WHERE c.id IN (" . join(',',array_keys($courses)) . ')';
378                 $extra = $DB->get_records_sql($sql);
380                 $keys = array_keys($courses);
381                 $studentroles = get_archetype_roles('student');
382                 if (!empty($studentroles)) {
383                     $defaultrole = reset($studentroles);
384                     //$defaultrole = get_default_course_role($ccache[$shortname]); //TODO: rewrite this completely, there is no default course role any more!!!
385                     foreach ($keys AS $id) {
386                         if ($courses[$id]->visible == 0) {
387                             unset($courses[$id]);
388                             continue;
389                         }
390                         $courses[$id]->cat_id          = $courses[$id]->category;
391                         $courses[$id]->defaultroleid   = $defaultrole->id;
392                         unset($courses[$id]->category);
393                         unset($courses[$id]->visible);
395                         $courses[$id]->cat_name        = $extra[$id]->cat_name;
396                         $courses[$id]->cat_description = $extra[$id]->cat_description;
397                         $courses[$id]->defaultrolename = $defaultrole->name;
398                         // coerce to array
399                         $courses[$id] = (array)$courses[$id];
400                     }
401                 } else {
402                     throw new moodle_exception('unknownrole', 'error', '', 'student');
403                 }
404             } else {
405                 // if the array is empty, send it anyway
406                 // we may be clearing out stale entries
407                 $courses = array();
408             }
409             $mnetrequest->add_param($courses);
411             // Call 0800-RPC Now! -- we don't care too much if it fails
412             // as it's just informational.
413             if ($mnetrequest->send($remotepeer) === false) {
414                 // error_log(print_r($mnetrequest->error,1));
415             }
416         }
418         return $localuser;
419     }
422     /**
423      * creates (or updates) the mnet session once
424      * {@see confirm_mnet_session} and {@see complete_user_login} have both been called
425      *
426      * @param stdclass  $user the local user (must exist already
427      * @param string    $token the jump/land token
428      * @param mnet_peer $remotepeer the mnet_peer object of this users's idp
429      */
430     public function update_mnet_session($user, $token, $remotepeer) {
431         global $DB;
432         $session_gc_maxlifetime = 1440;
433         if (isset($user->session_gc_maxlifetime)) {
434             $session_gc_maxlifetime = $user->session_gc_maxlifetime;
435         }
436         if (!$mnet_session = $DB->get_record('mnet_session',
437                                    array('userid'=>$user->id, 'mnethostid'=>$remotepeer->id,
438                                    'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])))) {
439             $mnet_session = new stdClass();
440             $mnet_session->mnethostid = $remotepeer->id;
441             $mnet_session->userid = $user->id;
442             $mnet_session->username = $user->username;
443             $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
444             $mnet_session->token = $token; // Needed to support simultaneous sessions
445                                            // and preserving DB rec uniqueness
446             $mnet_session->confirm_timeout = time();
447             $mnet_session->expires = time() + (integer)$session_gc_maxlifetime;
448             $mnet_session->session_id = session_id();
449             $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session);
450         } else {
451             $mnet_session->expires = time() + (integer)$session_gc_maxlifetime;
452             $DB->update_record('mnet_session', $mnet_session);
453         }
454     }
458     /**
459      * Invoke this function _on_ the IDP to update it with enrolment info local to
460      * the SP right after calling user_authorise()
461      *
462      * Normally called by the SP after calling user_authorise()
463      *
464      * @param string $username The username
465      * @param array $courses  Assoc array of courses following the structure of mnetservice_enrol_courses
466      * @return bool
467      */
468     function update_enrolments($username, $courses) {
469         global $CFG, $DB;
470         $remoteclient = get_mnet_remote_client();
472         if (empty($username) || !is_array($courses)) {
473             return false;
474         }
475         // make sure it is a user we have an in active session
476         // with that host...
477         $mnetsessions = $DB->get_records('mnet_session', array('username' => $username, 'mnethostid' => $remoteclient->id), '', 'id, userid');
478         $userid = null;
479         foreach ($mnetsessions as $mnetsession) {
480             if (is_null($userid)) {
481                 $userid = $mnetsession->userid;
482                 continue;
483             }
484             if ($userid != $mnetsession->userid) {
485                 throw new mnet_server_exception(3, 'authfail_usermismatch');
486             }
487         }
489         if (empty($courses)) { // no courses? clear out quickly
490             $DB->delete_records('mnetservice_enrol_enrolments', array('hostid'=>$remoteclient->id, 'userid'=>$userid));
491             return true;
492         }
494         // IMPORTANT: Ask for remoteid as the first element in the query, so
495         // that the array that comes back is indexed on the same field as the
496         // array that we have received from the remote client
497         $sql = "SELECT c.remoteid, c.id, c.categoryid AS cat_id, c.categoryname AS cat_name, c.sortorder,
498                        c.fullname, c.shortname, c.idnumber, c.summary, c.summaryformat, c.startdate,
499                        e.id AS enrolmentid
500                   FROM {mnetservice_enrol_courses} c
501              LEFT JOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid)
502                  WHERE e.userid = ? AND c.hostid = ?";
504         $currentcourses = $DB->get_records_sql($sql, array($userid, $remoteclient->id));
506         $local_courseid_array = array();
507         foreach($courses as $ix => $course) {
509             $course['remoteid'] = $course['id'];
510             $course['hostid']   =  (int)$remoteclient->id;
511             $userisregd         = false;
513             // if we do not have the the information about the remote course, it is not available
514             // to us for remote enrolment - skip
515             if (array_key_exists($course['remoteid'], $currentcourses)) {
516                 // Pointer to current course:
517                 $currentcourse =& $currentcourses[$course['remoteid']];
518                 // We have a record - is it up-to-date?
519                 $course['id'] = $currentcourse->id;
521                 $saveflag = false;
523                 foreach($course as $key => $value) {
524                     if ($currentcourse->$key != $value) {
525                         $saveflag = true;
526                         $currentcourse->$key = $value;
527                     }
528                 }
530                 if ($saveflag) {
531                     $DB->update_record('mnetervice_enrol_courses', $currentcourse);
532                 }
534                 if (isset($currentcourse->enrolmentid) && is_numeric($currentcourse->enrolmentid)) {
535                     $userisregd = true;
536                 }
537             } else {
538                 unset ($courses[$ix]);
539                 continue;
540             }
542             // By this point, we should always have a $dataObj->id
543             $local_courseid_array[] = $course['id'];
545             // Do we have a record for this assignment?
546             if ($userisregd) {
547                 // Yes - we know about this one already
548                 // We don't want to do updates because the new data is probably
549                 // 'less complete' than the data we have.
550             } else {
551                 // No - create a record
552                 $assignObj = new stdClass();
553                 $assignObj->userid    = $userid;
554                 $assignObj->hostid    = (int)$remoteclient->id;
555                 $assignObj->remotecourseid = $course['remoteid'];
556                 $assignObj->rolename  = $course['defaultrolename'];
557                 $assignObj->id = $DB->insert_record('mnetservice_enrol_enrolments', $assignObj);
558             }
559         }
561         // Clean up courses that the user is no longer enrolled in.
562         if (!empty($local_courseid_array)) {
563             $local_courseid_string = implode(', ', $local_courseid_array);
564             $whereclause = " userid = ? AND hostid = ? AND remotecourseid NOT IN ($local_courseid_string)";
565             $DB->delete_records_select('mnetservice_enrol_enrolments', $whereclause, array($userid, $remoteclient->id));
566         }
567     }
569     function prevent_local_passwords() {
570         return true;
571     }
573     /**
574      * Returns true if this authentication plugin is 'internal'.
575      *
576      * @return bool
577      */
578     function is_internal() {
579         return false;
580     }
582     /**
583      * Returns true if this authentication plugin can change the user's
584      * password.
585      *
586      * @return bool
587      */
588     function can_change_password() {
589         //TODO: it should be able to redirect, right?
590         return false;
591     }
593     /**
594      * Returns the URL for changing the user's pw, or false if the default can
595      * be used.
596      *
597      * @return moodle_url
598      */
599     function change_password_url() {
600         return null;
601     }
603     /**
604      * Prints a form for configuring this authentication plugin.
605      *
606      * This function is called from admin/auth.php, and outputs a full page with
607      * a form for configuring this plugin.
608      *
609      * @param object $config
610      * @param object $err
611      * @param array $user_fields
612      */
613     function config_form($config, $err, $user_fields) {
614         global $CFG, $DB;
616          $query = "
617             SELECT
618                 h.id,
619                 h.name as hostname,
620                 h.wwwroot,
621                 h2idp.publish as idppublish,
622                 h2idp.subscribe as idpsubscribe,
623                 idp.name as idpname,
624                 h2sp.publish as sppublish,
625                 h2sp.subscribe as spsubscribe,
626                 sp.name as spname
627             FROM
628                 {mnet_host} h
629             LEFT JOIN
630                 {mnet_host2service} h2idp
631             ON
632                (h.id = h2idp.hostid AND
633                (h2idp.publish = 1 OR
634                 h2idp.subscribe = 1))
635             INNER JOIN
636                 {mnet_service} idp
637             ON
638                (h2idp.serviceid = idp.id AND
639                 idp.name = 'sso_idp')
640             LEFT JOIN
641                 {mnet_host2service} h2sp
642             ON
643                (h.id = h2sp.hostid AND
644                (h2sp.publish = 1 OR
645                 h2sp.subscribe = 1))
646             INNER JOIN
647                 {mnet_service} sp
648             ON
649                (h2sp.serviceid = sp.id AND
650                 sp.name = 'sso_sp')
651             WHERE
652                ((h2idp.publish = 1 AND h2sp.subscribe = 1) OR
653                (h2sp.publish = 1 AND h2idp.subscribe = 1)) AND
654                 h.id != ?
655             ORDER BY
656                 h.name ASC";
658         $id_providers       = array();
659         $service_providers  = array();
660         if ($resultset = $DB->get_records_sql($query, array($CFG->mnet_localhost_id))) {
661             foreach($resultset as $hostservice) {
662                 if(!empty($hostservice->idppublish) && !empty($hostservice->spsubscribe)) {
663                     $service_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot);
664                 }
665                 if(!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) {
666                     $id_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot);
667                 }
668             }
669         }
671         include "config.html";
672     }
674     /**
675      * Processes and stores configuration data for this authentication plugin.
676      */
677     function process_config($config) {
678         // set to defaults if undefined
679         if (!isset ($config->rpc_negotiation_timeout)) {
680             $config->rpc_negotiation_timeout = '30';
681         }
682         /*
683         if (!isset ($config->auto_add_remote_users)) {
684             $config->auto_add_remote_users = '0';
685         } See MDL-21327   for why this is commented out
686         set_config('auto_add_remote_users',   $config->auto_add_remote_users,   'auth_mnet');
687         */
689         // save settings
690         set_config('rpc_negotiation_timeout', $config->rpc_negotiation_timeout, 'auth_mnet');
692         return true;
693     }
695     /**
696      * Poll the IdP server to let it know that a user it has authenticated is still
697      * online
698      *
699      * @return  void
700      */
701     function keepalive_client() {
702         global $CFG, $DB;
703         $cutoff = time() - 300; // TODO - find out what the remote server's session
704                                 // cutoff is, and preempt that
706         $sql = "
707             select
708                 id,
709                 username,
710                 mnethostid
711             from
712                 {user}
713             where
714                 lastaccess > ? AND
715                 mnethostid != ?
716             order by
717                 mnethostid";
719         $immigrants = $DB->get_records_sql($sql, array($cutoff, $CFG->mnet_localhost_id));
721         if ($immigrants == false) {
722             return true;
723         }
725         $usersArray = array();
726         foreach($immigrants as $immigrant) {
727             $usersArray[$immigrant->mnethostid][] = $immigrant->username;
728         }
730         require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
731         foreach($usersArray as $mnethostid => $users) {
732             $mnet_peer = new mnet_peer();
733             $mnet_peer->set_id($mnethostid);
735             $mnet_request = new mnet_xmlrpc_client();
736             $mnet_request->set_method('auth/mnet/auth.php/keepalive_server');
738             // set $token and $useragent parameters
739             $mnet_request->add_param($users);
741             if ($mnet_request->send($mnet_peer) === true) {
742                 if (!isset($mnet_request->response['code'])) {
743                     debugging("Server side error has occured on host $mnethostid");
744                     continue;
745                 } elseif ($mnet_request->response['code'] > 0) {
746                     debugging($mnet_request->response['message']);
747                 }
749                 if (!isset($mnet_request->response['last log id'])) {
750                     debugging("Server side error has occured on host $mnethostid\nNo log ID was received.");
751                     continue;
752                 }
753             } else {
754                 debugging("Server side error has occured on host $mnethostid: " .
755                           join("\n", $mnet_request->error));
756                 break;
757             }
758             $mnethostlogssql = "
759             SELECT
760                 mhostlogs.remoteid, mhostlogs.time, mhostlogs.userid, mhostlogs.ip,
761                 mhostlogs.course, mhostlogs.module, mhostlogs.cmid, mhostlogs.action,
762                 mhostlogs.url, mhostlogs.info, mhostlogs.username, c.fullname as coursename,
763                 c.modinfo
764             FROM
765                 (
766                     SELECT
767                         l.id as remoteid, l.time, l.userid, l.ip, l.course, l.module, l.cmid,
768                         l.action, l.url, l.info, u.username
769                     FROM
770                         {user} u
771                         INNER JOIN {log} l on l.userid = u.id
772                     WHERE
773                         u.mnethostid = ?
774                         AND l.id > ?
775                     ORDER BY remoteid ASC
776                     LIMIT 500
777                 ) mhostlogs
778                 INNER JOIN {course} c on c.id = mhostlogs.course
779             ORDER by mhostlogs.remoteid ASC";
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                 // Extract the name of the relevant module instance from the
791                 // course modinfo if possible.
792                 if (!empty($hostlog->modinfo) && !empty($hostlog->cmid)) {
793                     $modinfo = unserialize($hostlog->modinfo);
794                     unset($hostlog->modinfo);
795                     $modulearray = array();
796                     foreach($modinfo as $module) {
797                         $modulearray[$module->cm] = $module->name;
798                     }
799                     $hostlog->resource_name = $modulearray[$hostlog->cmid];
800                 } else {
801                     $hostlog->resource_name = '';
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     }