4 * @author Martin Dougiamas
5 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
6 * @package moodle multiauth
8 * Authentication Plugin: Moodle Network Authentication
10 * Multiple host authentication support for Moodle Network.
12 * 2006-11-01 File created.
15 if (!defined('MOODLE_INTERNAL')) {
16 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
19 require_once($CFG->libdir.'/authlib.php');
22 * Moodle Network authentication plugin.
24 class auth_plugin_mnet extends auth_plugin_base {
29 function auth_plugin_mnet() {
30 $this->authtype = 'mnet';
31 $this->config = get_config('auth/mnet');
35 * Provides the allowed RPC services from this class as an array.
36 * @return array Allowed RPC services.
38 function mnet_publishes() {
41 $sso_idp['name'] = 'sso_idp'; // Name & Description go in lang file
42 $sso_idp['apiversion'] = 1;
43 $sso_idp['methods'] = array('user_authorise','keepalive_server', 'kill_children',
44 'refresh_log', 'fetch_user_image', 'fetch_theme_info',
48 $sso_sp['name'] = 'sso_sp'; // Name & Description go in lang file
49 $sso_sp['apiversion'] = 1;
50 $sso_sp['methods'] = array('keepalive_client','kill_child');
52 return array($sso_idp, $sso_sp);
56 * This function is normally used to determine if the username and password
57 * are correct for local logins. Always returns false, as local users do not
58 * need to login over mnet xmlrpc.
60 * @param string $username The username
61 * @param string $password The password
62 * @return bool Authentication success or failure.
64 function user_login($username, $password) {
65 return false; // print_error("mnetlocal");
69 * Return user data for the provided token, compare with user_agent string.
71 * @param string $token The unique ID provided by remotehost.
72 * @param string $UA User Agent string.
73 * @return array $userdata Array of user info for remote host
75 function user_authorise($token, $useragent) {
76 global $CFG, $MNET, $SITE, $MNET_REMOTE_CLIENT, $DB;
77 require_once $CFG->dirroot . '/mnet/xmlrpc/server.php';
79 $mnet_session = $DB->get_record('mnet_session', array('token'=>$token, 'useragent'=>$useragent));
80 if (empty($mnet_session)) {
81 echo mnet_server_fault(1, get_string('authfail_nosessionexists', 'mnet'));
85 // check session confirm timeout
86 if ($mnet_session->confirm_timeout < time()) {
87 echo mnet_server_fault(2, get_string('authfail_sessiontimedout', 'mnet'));
91 // session okay, try getting the user
92 if (!$user = $DB->get_record('user', array('id'=>$mnet_session->userid))) {
93 echo mnet_server_fault(3, get_string('authfail_usermismatch', 'mnet'));
98 $userdata['username'] = $user->username;
99 $userdata['email'] = $user->email;
100 $userdata['auth'] = 'mnet';
101 $userdata['confirmed'] = $user->confirmed;
102 $userdata['deleted'] = $user->deleted;
103 $userdata['firstname'] = $user->firstname;
104 $userdata['lastname'] = $user->lastname;
105 $userdata['city'] = $user->city;
106 $userdata['country'] = $user->country;
107 $userdata['lang'] = $user->lang;
108 $userdata['timezone'] = $user->timezone;
109 $userdata['description'] = $user->description;
110 $userdata['mailformat'] = $user->mailformat;
111 $userdata['maildigest'] = $user->maildigest;
112 $userdata['maildisplay'] = $user->maildisplay;
113 $userdata['htmleditor'] = $user->htmleditor;
114 $userdata['wwwroot'] = $MNET->wwwroot;
115 $userdata['session.gc_maxlifetime'] = ini_get('session.gc_maxlifetime');
116 $userdata['picture'] = $user->picture;
117 if (!empty($user->picture)) {
118 $imagefile = make_user_directory($user->id, true) . "/f1.jpg";
119 if (file_exists($imagefile)) {
120 $userdata['imagehash'] = sha1(file_get_contents($imagefile));
124 $userdata['myhosts'] = array();
125 if($courses = get_my_courses($user->id, 'id', 'id, visible')) {
126 $userdata['myhosts'][] = array('name'=> $SITE->shortname, 'url' => $CFG->wwwroot, 'count' => count($courses));
136 {mnet_enrol_course} c,
137 {mnet_enrol_assignments} a,
140 c.id = a.courseid AND
148 if ($courses = $DB->get_records_sql($sql, array($user->id, $MNET_REMOTE_CLIENT->id))) {
149 foreach($courses as $course) {
150 $userdata['myhosts'][] = array('name'=> $course->hostname, 'url' => $CFG->wwwroot.'/auth/mnet/jump.php?hostid='.$course->hostid, 'count' => $course->count);
158 * Generate a random string for use as an RPC session token.
160 function generate_token() {
161 return sha1(str_shuffle('' . mt_rand() . time()));
165 * Starts an RPC jump session and returns the jump redirect URL.
167 * @param int $mnethostid id of the mnet host to jump to
168 * @param string $wantsurl url to redirect to after the jump (usually on remote system)
169 * @param boolean $wantsurlbackhere defaults to false, means that the remote system should bounce us back here
170 * rather than somewhere inside *its* wwwroot
172 function start_jump_session($mnethostid, $wantsurl, $wantsurlbackhere=false) {
173 global $CFG, $USER, $MNET, $DB;
174 require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
176 // check remote login permissions
177 if (! has_capability('moodle/site:mnetlogintoremote', get_context_instance(CONTEXT_SYSTEM))
178 or is_mnet_remote_user($USER)
179 or $USER->username == 'guest'
180 or empty($USER->id)) {
181 print_error('notpermittedtojump', 'mnet');
184 // check for SSO publish permission first
185 if ($this->has_service($mnethostid, 'sso_sp') == false) {
186 print_error('hostnotconfiguredforsso', 'mnet');
189 // set RPC timeout to 30 seconds if not configured
190 // TODO: Is this needed/useful/problematic?
191 if (empty($this->config->rpc_negotiation_timeout)) {
192 set_config('rpc_negotiation_timeout', '30', 'auth/mnet');
196 $mnet_peer = new mnet_peer();
197 $mnet_peer->set_id($mnethostid);
199 // set up the session
200 $mnet_session = $DB->get_record('mnet_session',
201 array('userid'=>$USER->id, 'mnethostid'=>$mnethostid,
202 'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])));
203 if ($mnet_session == false) {
204 $mnet_session = new object();
205 $mnet_session->mnethostid = $mnethostid;
206 $mnet_session->userid = $USER->id;
207 $mnet_session->username = $USER->username;
208 $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
209 $mnet_session->token = $this->generate_token();
210 $mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout;
211 $mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');
212 $mnet_session->session_id = session_id();
213 $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session);
215 $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
216 $mnet_session->token = $this->generate_token();
217 $mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout;
218 $mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');
219 $mnet_session->session_id = session_id();
220 $DB->update_record('mnet_session', $mnet_session);
223 // construct the redirection URL
224 //$transport = mnet_get_protocol($mnet_peer->transport);
225 $wantsurl = urlencode($wantsurl);
226 $url = "{$mnet_peer->wwwroot}{$mnet_peer->application->sso_land_url}?token={$mnet_session->token}&idp={$MNET->wwwroot}&wantsurl={$wantsurl}";
227 if ($wantsurlbackhere) {
228 $url .= '&remoteurl=1';
235 * This function confirms the remote (ID provider) host's mnet session
236 * by communicating the token and UA over the XMLRPC transport layer, and
237 * returns the local user record on success.
239 * @param string $token The random session token.
240 * @param string $remotewwwroot The ID provider wwwroot.
241 * @return array The local user record.
243 function confirm_mnet_session($token, $remotewwwroot) {
244 global $CFG, $MNET, $SESSION, $DB;
245 require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
247 // verify the remote host is configured locally before attempting RPC call
248 if (! $remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotewwwroot, 'deleted' => 0))) {
249 print_error('notpermittedtoland', 'mnet');
252 // get the originating (ID provider) host info
253 $remotepeer = new mnet_peer();
254 $remotepeer->set_wwwroot($remotewwwroot);
256 // set up the RPC request
257 $mnetrequest = new mnet_xmlrpc_client();
258 $mnetrequest->set_method('auth/mnet/auth.php/user_authorise');
260 // set $token and $useragent parameters
261 $mnetrequest->add_param($token);
262 $mnetrequest->add_param(sha1($_SERVER['HTTP_USER_AGENT']));
264 // Thunderbirds are go! Do RPC call and store response
265 if ($mnetrequest->send($remotepeer) === true) {
266 $remoteuser = (object) $mnetrequest->response;
268 foreach ($mnetrequest->error as $errormessage) {
269 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
272 print_error('mnet_session_prohibited', 'mnet', $remotewwwroot, format_string($site->fullname));
275 $message .= "ERROR $code:<br/>$errormessage<br/>";
277 print_error("rpcerror", '', '', $message);
281 if (empty($remoteuser) or empty($remoteuser->username)) {
282 print_error('unknownerror', 'mnet');
288 // get the local record for the remote user
289 $localuser = $DB->get_record('user', array('username'=>$remoteuser->username, 'mnethostid'=>$remotehost->id));
291 // add the remote user to the database if necessary, and if allowed
292 // TODO: refactor into a separate function
293 if (empty($localuser) || ! $localuser->id) {
294 if (empty($this->config->auto_add_remote_users)) {
295 print_error('nolocaluser', 'mnet');
297 $remoteuser->mnethostid = $remotehost->id;
298 $DB->insert_record('user', $remoteuser);
300 if (! $localuser = $DB->get_record('user', array('username'=>$remoteuser->username, 'mnethostid'=>$remotehost->id))) {
301 print_error('nolocaluser', 'mnet');
305 // check sso access control list for permission first
306 if (!$this->can_login_remotely($localuser->username, $remotehost->id)) {
307 print_error('sso_mnet_login_refused', 'mnet', '', array($localuser->username, $remotehost->name));
310 $session_gc_maxlifetime = 1440;
312 // update the local user record with remote user data
313 foreach ((array) $remoteuser as $key => $val) {
314 if ($key == 'session.gc_maxlifetime') {
315 $session_gc_maxlifetime = $val;
319 // TODO: fetch image if it has changed
320 if ($key == 'imagehash') {
321 $dirname = make_user_directory($localuser->id, true);
322 $filename = "$dirname/f1.jpg";
325 if (file_exists($filename)) {
326 $localhash = sha1(file_get_contents($filename));
327 } elseif (!file_exists($dirname)) {
331 if ($localhash != $val) {
332 // fetch image from remote host
333 $fetchrequest = new mnet_xmlrpc_client();
334 $fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image');
335 $fetchrequest->add_param($localuser->username);
336 if ($fetchrequest->send($remotepeer) === true) {
337 if (strlen($fetchrequest->response['f1']) > 0) {
338 $imagecontents = base64_decode($fetchrequest->response['f1']);
339 file_put_contents($filename, $imagecontents);
340 $localuser->picture = 1;
342 if (strlen($fetchrequest->response['f2']) > 0) {
343 $imagecontents = base64_decode($fetchrequest->response['f2']);
344 file_put_contents($dirname.'/f2.jpg', $imagecontents);
350 if($key == 'myhosts') {
351 $localuser->mnet_foreign_host_array = array();
352 foreach($val as $rhost) {
353 $name = clean_param($rhost['name'], PARAM_ALPHANUM);
354 $url = clean_param($rhost['url'], PARAM_URL);
355 $count = clean_param($rhost['count'], PARAM_INT);
356 $url_is_local = stristr($url , $CFG->wwwroot);
357 if (!empty($name) && !empty($count) && empty($url_is_local)) {
358 $localuser->mnet_foreign_host_array[] = array('name' => $name,
365 $localuser->{$key} = $val;
368 $localuser->mnethostid = $remotepeer->id;
370 $DB->update_record('user', $localuser);
372 // set up the session
373 $mnet_session = $DB->get_record('mnet_session',
374 array('userid'=>$localuser->id, 'mnethostid'=>$remotepeer->id,
375 'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])));
376 if ($mnet_session == false) {
377 $mnet_session = new object();
378 $mnet_session->mnethostid = $remotepeer->id;
379 $mnet_session->userid = $localuser->id;
380 $mnet_session->username = $localuser->username;
381 $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
382 $mnet_session->token = $token; // Needed to support simultaneous sessions
383 // and preserving DB rec uniqueness
384 $mnet_session->confirm_timeout = time();
385 $mnet_session->expires = time() + (integer)$session_gc_maxlifetime;
386 $mnet_session->session_id = session_id();
387 $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session);
389 $mnet_session->expires = time() + (integer)$session_gc_maxlifetime;
390 $DB->update_record('mnet_session', $mnet_session);
394 // repeat customer! let the IDP know about enrolments
395 // we have for this user.
396 // set up the RPC request
397 $mnetrequest = new mnet_xmlrpc_client();
398 $mnetrequest->set_method('auth/mnet/auth.php/update_enrolments');
400 // pass username and an assoc array of "my courses"
401 // with info so that the IDP can maintain mnet_enrol_assignments
402 $mnetrequest->add_param($remoteuser->username);
403 $fields = 'id, category, sortorder, fullname, shortname, idnumber, summary,
404 startdate, cost, currency, defaultrole, visible';
405 $courses = get_my_courses($localuser->id, 'visible DESC,sortorder ASC', $fields);
406 if (is_array($courses) && !empty($courses)) {
407 // Second request to do the JOINs that we'd have done
408 // inside get_my_courses() if we had been allowed
410 cc.name AS cat_name, cc.description AS cat_description,
411 r.shortname as defaultrolename
413 JOIN {course_categories} cc ON c.category = cc.id
414 LEFT OUTER JOIN {role} r ON c.defaultrole = r.id
415 WHERE c.id IN (" . join(',',array_keys($courses)) . ')';
416 $extra = $DB->get_records_sql($sql);
418 $keys = array_keys($courses);
419 $defaultrolename = $DB->get_field('role', 'shortname', array('id'=>$CFG->defaultcourseroleid));
420 foreach ($keys AS $id) {
421 if ($courses[$id]->visible == 0) {
422 unset($courses[$id]);
425 $courses[$id]->cat_id = $courses[$id]->category;
426 $courses[$id]->defaultroleid = $courses[$id]->defaultrole;
427 unset($courses[$id]->category);
428 unset($courses[$id]->defaultrole);
429 unset($courses[$id]->visible);
431 $courses[$id]->cat_name = $extra[$id]->cat_name;
432 $courses[$id]->cat_description = $extra[$id]->cat_description;
433 if (!empty($extra[$id]->defaultrolename)) {
434 $courses[$id]->defaultrolename = $extra[$id]->defaultrolename;
436 $courses[$id]->defaultrolename = $defaultrolename;
439 $courses[$id] = (array)$courses[$id];
442 // if the array is empty, send it anyway
443 // we may be clearing out stale entries
446 $mnetrequest->add_param($courses);
448 // Call 0800-RPC Now! -- we don't care too much if it fails
449 // as it's just informational.
450 if ($mnetrequest->send($remotepeer) === false) {
451 // error_log(print_r($mnetrequest->error,1));
459 * Invoke this function _on_ the IDP to update it with enrolment info local to
460 * the SP right after calling user_authorise()
462 * Normally called by the SP after calling
464 * @param string $username The username
465 * @param string $courses Assoc array of courses following the structure of mnet_enrol_course
468 function update_enrolments($username, $courses) {
469 global $MNET_REMOTE_CLIENT, $CFG, $DB;
471 if (empty($username) || !is_array($courses)) {
474 // make sure it is a user we have an in active session
476 $userid = $DB->get_field('mnet_session', 'userid',
477 array('username'=>$username, 'mnethostid'=>$MNET_REMOTE_CLIENT->id));
482 if (empty($courses)) { // no courses? clear out quickly
483 $DB->delete_records('mnet_enrol_assignments', array('hostid'=>$MNET_REMOTE_CLIENT->id, 'userid'=>$userid));
487 // IMPORTANT: Ask for remoteid as the first element in the query, so
488 // that the array that comes back is indexed on the same field as the
489 // array that we have received from the remote client
509 {mnet_enrol_course} c
510 LEFT JOIN {mnet_enrol_assignments} a
512 (a.courseid = c.id AND
513 a.hostid = c.hostid AND
518 $currentcourses = $DB->get_records_sql($sql, array($userid, $MNET_REMOTE_CLIENT->id));
520 $local_courseid_array = array();
521 foreach($courses as $course) {
523 $course['remoteid'] = $course['id'];
524 $course['hostid'] = (int)$MNET_REMOTE_CLIENT->id;
527 // First up - do we have a record for this course?
528 if (!array_key_exists($course['remoteid'], $currentcourses)) {
529 // No record - we must create it
530 $course['id'] = $DB->insert_record('mnet_enrol_course', (object)$course);
531 $currentcourse = (object)$course;
533 // Pointer to current course:
534 $currentcourse =& $currentcourses[$course['remoteid']];
535 // We have a record - is it up-to-date?
536 $course['id'] = $currentcourse->id;
540 foreach($course as $key => $value) {
541 if ($currentcourse->$key != $value) {
543 $currentcourse->$key = $value;
548 $DB->update_record('mnet_enrol_course', $currentcourse);
551 if (isset($currentcourse->assignmentid) && is_numeric($currentcourse->assignmentid)) {
556 // By this point, we should always have a $dataObj->id
557 $local_courseid_array[] = $course['id'];
559 // Do we have a record for this assignment?
561 // Yes - we know about this one already
562 // We don't want to do updates because the new data is probably
563 // 'less complete' than the data we have.
565 // No - create a record
566 $assignObj = new stdClass();
567 $assignObj->userid = $userid;
568 $assignObj->hostid = (int)$MNET_REMOTE_CLIENT->id;
569 $assignObj->courseid = $course['id'];
570 $assignObj->rolename = $course['defaultrolename'];
571 $assignObj->id = $DB->insert_record('mnet_enrol_assignments', $assignObj);
575 // Clean up courses that the user is no longer enrolled in.
576 $local_courseid_string = implode(', ', $local_courseid_array);
577 $whereclause = " userid = ? AND hostid = ? AND courseid NOT IN ($local_courseid_string)";
578 $DB->delete_records_select('mnet_enrol_assignments', $whereclause, array($userid, $MNET_REMOTE_CLIENT->id));
582 * Returns true if this authentication plugin is 'internal'.
586 function is_internal() {
591 * Returns true if this authentication plugin can change the user's
596 function can_change_password() {
597 //TODO: it should be able to redirect, right?
602 * Returns the URL for changing the user's pw, or false if the default can
607 function change_password_url() {
612 * Prints a form for configuring this authentication plugin.
614 * This function is called from admin/auth.php, and outputs a full page with
615 * a form for configuring this plugin.
617 * @param array $page An object containing all the data for this page.
619 function config_form($config, $err, $user_fields) {
627 h2idp.publish as idppublish,
628 h2idp.subscribe as idpsubscribe,
630 h2sp.publish as sppublish,
631 h2sp.subscribe as spsubscribe,
636 {mnet_host2service} h2idp
638 (h.id = h2idp.hostid AND
639 (h2idp.publish = 1 OR
640 h2idp.subscribe = 1))
644 (h2idp.serviceid = idp.id AND
645 idp.name = 'sso_idp')
647 {mnet_host2service} h2sp
649 (h.id = h2sp.hostid AND
655 (h2sp.serviceid = sp.id AND
658 ((h2idp.publish = 1 AND h2sp.subscribe = 1) OR
659 (h2sp.publish = 1 AND h2idp.subscribe = 1)) AND
664 $id_providers = array();
665 $service_providers = array();
666 if ($resultset = $DB->get_records_sql($query, array($CFG->mnet_localhost_id))) {
667 foreach($resultset as $hostservice) {
668 if(!empty($hostservice->idppublish) && !empty($hostservice->spsubscribe)) {
669 $service_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot);
671 if(!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) {
672 $id_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot);
677 include "config.html";
681 * Processes and stores configuration data for this authentication plugin.
683 function process_config($config) {
684 // set to defaults if undefined
685 if (!isset ($config->rpc_negotiation_timeout)) {
686 $config->rpc_negotiation_timeout = '30';
688 if (!isset ($config->auto_add_remote_users)) {
689 $config->auto_add_remote_users = '0';
693 set_config('rpc_negotiation_timeout', $config->rpc_negotiation_timeout, 'auth/mnet');
694 set_config('auto_add_remote_users', $config->auto_add_remote_users, 'auth/mnet');
700 * Poll the IdP server to let it know that a user it has authenticated is still
705 function keepalive_client() {
706 global $CFG, $MNET, $DB;
707 $cutoff = time() - 300; // TODO - find out what the remote server's session
708 // cutoff is, and preempt that
723 $immigrants = $DB->get_records_sql($sql, array($cutoff, $CFG->mnet_localhost_id));
725 if ($immigrants == false) {
729 $usersArray = array();
730 foreach($immigrants as $immigrant) {
731 $usersArray[$immigrant->mnethostid][] = $immigrant->username;
734 require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
735 foreach($usersArray as $mnethostid => $users) {
736 $mnet_peer = new mnet_peer();
737 $mnet_peer->set_id($mnethostid);
739 $mnet_request = new mnet_xmlrpc_client();
740 $mnet_request->set_method('auth/mnet/auth.php/keepalive_server');
742 // set $token and $useragent parameters
743 $mnet_request->add_param($users);
745 if ($mnet_request->send($mnet_peer) === true) {
746 if (!isset($mnet_request->response['code'])) {
747 debugging("Server side error has occured on host $mnethostid");
749 } elseif ($mnet_request->response['code'] > 0) {
750 debugging($mnet_request->response['message']);
753 if (!isset($mnet_request->response['last log id'])) {
754 debugging("Server side error has occured on host $mnethostid\nNo log ID was received.");
758 debugging("Server side error has occured on host $mnethostid: " .
759 join("\n", $mnet_request->error));
764 mhostlogs.remoteid, mhostlogs.time, mhostlogs.userid, mhostlogs.ip,
765 mhostlogs.course, mhostlogs.module, mhostlogs.cmid, mhostlogs.action,
766 mhostlogs.url, mhostlogs.info, mhostlogs.username, c.fullname as coursename,
771 l.id as remoteid, l.time, l.userid, l.ip, l.course, l.module, l.cmid,
772 l.action, l.url, l.info, u.username
775 INNER JOIN {log} l on l.userid = u.id
779 ORDER BY remoteid ASC
782 INNER JOIN {course} c on c.id = mhostlogs.course
783 ORDER by mhostlogs.remoteid ASC";
785 $mnethostlogs = $DB->get_records_sql($mnethostlogssql, array($mnethostid, $mnet_request->response['last log id']));
787 if ($mnethostlogs == false) {
791 $processedlogs = array();
793 foreach($mnethostlogs as $hostlog) {
794 // Extract the name of the relevant module instance from the
795 // course modinfo if possible.
796 if (!empty($hostlog->modinfo) && !empty($hostlog->cmid)) {
797 $modinfo = unserialize($hostlog->modinfo);
798 unset($hostlog->modinfo);
799 $modulearray = array();
800 foreach($modinfo as $module) {
801 $modulearray[$module->cm] = urldecode($module->name);
803 $hostlog->resource_name = $modulearray[$hostlog->cmid];
805 $hostlog->resource_name = '';
808 $processedlogs[] = array (
809 'remoteid' => $hostlog->remoteid,
810 'time' => $hostlog->time,
811 'userid' => $hostlog->userid,
812 'ip' => $hostlog->ip,
813 'course' => $hostlog->course,
814 'coursename' => $hostlog->coursename,
815 'module' => $hostlog->module,
816 'cmid' => $hostlog->cmid,
817 'action' => $hostlog->action,
818 'url' => $hostlog->url,
819 'info' => $hostlog->info,
820 'resource_name' => $hostlog->resource_name,
821 'username' => $hostlog->username
827 $mnet_request = new mnet_xmlrpc_client();
828 $mnet_request->set_method('auth/mnet/auth.php/refresh_log');
830 // set $token and $useragent parameters
831 $mnet_request->add_param($processedlogs);
833 if ($mnet_request->send($mnet_peer) === true) {
834 if ($mnet_request->response['code'] > 0) {
835 debugging($mnet_request->response['message']);
838 debugging("Server side error has occured on host $mnet_peer->ip: " .join("\n", $mnet_request->error));
844 * Receives an array of log entries from an SP and adds them to the mnet_log
847 * @param array $array An array of usernames
848 * @return string "All ok" or an error message
850 function refresh_log($array) {
851 global $CFG, $MNET_REMOTE_CLIENT, $DB;
853 // We don't want to output anything to the client machine
858 $useridarray = array();
860 foreach($array as $logEntry) {
861 $logEntryObj = (object)$logEntry;
862 $logEntryObj->hostid = $MNET_REMOTE_CLIENT->id;
864 if (isset($useridarray[$logEntryObj->username])) {
865 $logEntryObj->userid = $useridarray[$logEntryObj->username];
867 $logEntryObj->userid = $DB->get_field('user', 'id', array('username'=>$logEntryObj->username, 'mnethostid'=>(int)$logEntryObj->hostid));
868 if ($logEntryObj->userid == false) {
869 $logEntryObj->userid = 0;
871 $useridarray[$logEntryObj->username] = $logEntryObj->userid;
874 unset($logEntryObj->username);
876 $logEntryObj = $this->trim_logline($logEntryObj);
877 $insertok = $DB->insert_record('mnet_log', $logEntryObj, false);
880 $MNET_REMOTE_CLIENT->last_log_id = $logEntryObj->remoteid;
882 $returnString .= 'Record with id '.$logEntryObj->remoteid." failed to insert.\n";
885 $MNET_REMOTE_CLIENT->commit();
888 $end = ob_end_clean();
890 if (empty($returnString)) return array('code' => 0, 'message' => 'All ok');
891 return array('code' => 1, 'message' => $returnString);
895 * Receives an array of usernames from a remote machine and prods their
896 * sessions to keep them alive
898 * @param array $array An array of usernames
899 * @return string "All ok" or an error message
901 function keepalive_server($array) {
902 global $MNET_REMOTE_CLIENT, $CFG, $DB;
906 // We don't want to output anything to the client machine
909 // We'll get session records in batches of 30
910 $superArray = array_chunk($array, 30);
914 foreach($superArray as $subArray) {
915 $subArray = array_values($subArray);
916 $instring = "('".implode("', '",$subArray)."')";
917 $query = "select id, session_id, username from {mnet_session} where username in $instring";
918 $results = $DB->get_records_sql($query);
920 if ($results == false) {
921 // We seem to have a username that breaks our query:
922 // TODO: Handle this error appropriately
923 $returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n";
925 // TODO: This process of killing and re-starting the session
926 // will cause PHP to forget any custom session_set_save_handler
927 // stuff. Subsequent attempts to prod existing sessions will
928 // fail, because PHP will look in wherever the default place
929 // may be (files?) and probably create a new session with the
930 // right session ID in that location. If it doesn't have write-
931 // access to that location, then it will fail... not sure how
932 // apparent that will be.
933 // There is no way to capture what the custom session handler
934 // is and then reset it on each pass - I checked that out
936 $sesscache = clone($_SESSION);
937 $sessidcache = session_id();
938 session_write_close();
941 $uc = ini_get('session.use_cookies');
942 ini_set('session.use_cookies', false);
943 foreach($results as $emigrant) {
946 session_name('MoodleSession'.$CFG->sessioncookie);
947 session_id($emigrant->session_id);
949 session_write_close();
952 ini_set('session.use_cookies', $uc);
953 session_name('MoodleSession'.$CFG->sessioncookie);
954 session_id($sessidcache);
956 $_SESSION = clone($sesscache);
957 session_write_close();
961 $end = ob_end_clean();
963 if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $MNET_REMOTE_CLIENT->last_log_id);
964 return array('code' => 1, 'message' => $returnString, 'last log id' => $MNET_REMOTE_CLIENT->last_log_id);
968 * Cron function will be called automatically by cron.php every 5 minutes
975 // run the keepalive client
976 $this->keepalive_client();
978 // admin/cron.php should have run srand for us
979 $random100 = rand(0,100);
980 if ($random100 < 10) { // Approximately 10% of the time.
981 // nuke olden sessions
982 $longtime = time() - (1 * 3600 * 24);
983 $DB->delete_records_select('mnet_session', "expires < ?", array($longtime));
988 * Cleanup any remote mnet_sessions, kill the local mnet_session data
990 * This is called by require_logout in moodlelib
994 function prelogout_hook() {
995 global $MNET, $CFG, $USER;
996 if (!is_enabled_auth('mnet')) {
1000 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
1002 // If the user is local to this Moodle:
1003 if ($USER->mnethostid == $MNET->id) {
1004 $this->kill_children($USER->username, sha1($_SERVER['HTTP_USER_AGENT']));
1006 // Else the user has hit 'logout' at a Service Provider Moodle:
1008 $this->kill_parent($USER->username, sha1($_SERVER['HTTP_USER_AGENT']));
1014 * The SP uses this function to kill the session on the parent IdP
1016 * @param string $username Username for session to kill
1017 * @param string $useragent SHA1 hash of user agent to look for
1018 * @return string A plaintext report of what has happened
1020 function kill_parent($username, $useragent) {
1021 global $CFG, $USER, $DB;
1023 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
1034 $mnetsessions = $DB->get_records_sql($sql, array($username, $useragent, $USER->mnethostid));
1036 $ignore = $DB->delete_records('mnet_session',
1037 array('username'=>$username,
1038 'useragent'=>$useragent,
1039 'mnethostid'=>$USER->mnethostid));
1041 if (false != $mnetsessions) {
1042 $mnet_peer = new mnet_peer();
1043 $mnet_peer->set_id($USER->mnethostid);
1045 $mnet_request = new mnet_xmlrpc_client();
1046 $mnet_request->set_method('auth/mnet/auth.php/kill_children');
1048 // set $token and $useragent parameters
1049 $mnet_request->add_param($username);
1050 $mnet_request->add_param($useragent);
1051 if ($mnet_request->send($mnet_peer) === false) {
1052 debugging(join("\n", $mnet_request->error));
1057 $_SESSION = array();
1062 * The IdP uses this function to kill child sessions on other hosts
1064 * @param string $username Username for session to kill
1065 * @param string $useragent SHA1 hash of user agent to look for
1066 * @return string A plaintext report of what has happened
1068 function kill_children($username, $useragent) {
1069 global $CFG, $USER, $MNET_REMOTE_CLIENT, $DB;
1070 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
1072 $userid = $DB->get_field('user', 'id', array('mnethostid'=>$CFG->mnet_localhost_id, 'username'=>$username));
1076 $mnetsessions = $DB->get_records('mnet_session', array('userid' => $userid, 'useragent' => $useragent));
1078 if (false == $mnetsessions) {
1079 $returnstring .= "Could find no remote sessions\n";
1080 $mnetsessions = array();
1083 foreach($mnetsessions as $mnetsession) {
1084 // If this script is being executed by a remote peer, that means the user has clicked
1085 // logout on that peer, and the session on that peer can be deleted natively.
1087 if (isset($MNET_REMOTE_CLIENT->id) && ($mnetsession->mnethostid == $MNET_REMOTE_CLIENT->id)) {
1090 $returnstring .= "Deleting session\n";
1092 $mnet_peer = new mnet_peer();
1093 $mnet_peer->set_id($mnetsession->mnethostid);
1095 $mnet_request = new mnet_xmlrpc_client();
1096 $mnet_request->set_method('auth/mnet/auth.php/kill_child');
1098 // set $token and $useragent parameters
1099 $mnet_request->add_param($username);
1100 $mnet_request->add_param($useragent);
1101 if ($mnet_request->send($mnet_peer) === false) {
1102 debugging("Server side error has occured on host $mnetsession->mnethostid: " .
1103 join("\n", $mnet_request->error));
1107 $ignore = $DB->delete_records('mnet_session',
1108 array('useragent'=>$useragent, 'userid'=>$userid));
1110 if (isset($MNET_REMOTE_CLIENT) && isset($MNET_REMOTE_CLIENT->id)) {
1111 $start = ob_start();
1113 // Save current session and cookie-use status
1114 $cookieuse = ini_get('session.use_cookies');
1115 ini_set('session.use_cookies', false);
1116 $sesscache = $_SESSION;
1117 $sessidcache = session_id();
1119 // Replace existing mnet session with user session & unset
1120 session_write_close();
1122 session_id($mnetsession->session_id);
1124 session_unregister("USER");
1125 session_unregister("SESSION");
1127 $_SESSION = array();
1128 session_write_close();
1130 // Restore previous info
1131 ini_set('session.use_cookies', $cookieuse);
1132 session_name('MoodleSession'.$CFG->sessioncookie);
1133 session_id($sessidcache);
1135 $_SESSION = $sesscache;
1136 session_write_close();
1138 $end = ob_end_clean();
1140 $_SESSION = array();
1142 return $returnstring;
1146 * TODO:Untested When the IdP requests that child sessions are terminated,
1147 * this function will be called on each of the child hosts. The machine that
1148 * calls the function (over xmlrpc) provides us with the mnethostid we need.
1150 * @param string $username Username for session to kill
1151 * @param string $useragent SHA1 hash of user agent to look for
1152 * @return bool True on success
1154 function kill_child($username, $useragent) {
1155 global $CFG, $MNET_REMOTE_CLIENT, $DB;
1156 $session = $DB->get_record('mnet_session', array('username'=>$username, 'mnethostid'=>$MNET_REMOTE_CLIENT->id, 'useragent'=>$useragent));
1157 if (false != $session) {
1158 $start = ob_start();
1160 $uc = ini_get('session.use_cookies');
1161 ini_set('session.use_cookies', false);
1162 $sesscache = clone($_SESSION);
1163 $sessidcache = session_id();
1164 session_write_close();
1168 session_id($session->session_id);
1170 session_unregister("USER");
1171 session_unregister("SESSION");
1173 $_SESSION = array();
1174 session_write_close();
1177 ini_set('session.use_cookies', $uc);
1178 session_name('MoodleSession'.$CFG->sessioncookie);
1179 session_id($sessidcache);
1181 $_SESSION = clone($sesscache);
1182 session_write_close();
1184 $end = ob_end_clean();
1191 * To delete a host, we must delete all current sessions that users from
1192 * that host are currently engaged in.
1194 * @param string $sessionidarray An array of session hashes
1195 * @return bool True on success
1197 function end_local_sessions(&$sessionArray) {
1199 if (is_array($sessionArray)) {
1200 $start = ob_start();
1202 $uc = ini_get('session.use_cookies');
1203 ini_set('session.use_cookies', false);
1204 $sesscache = clone($_SESSION);
1205 $sessidcache = session_id();
1206 session_write_close();
1209 while($session = array_pop($sessionArray)) {
1210 session_id($session->session_id);
1212 session_unregister("USER");
1213 session_unregister("SESSION");
1215 $_SESSION = array();
1216 session_write_close();
1219 ini_set('session.use_cookies', $uc);
1220 session_name('MoodleSession'.$CFG->sessioncookie);
1221 session_id($sessidcache);
1223 $_SESSION = clone($sesscache);
1225 $end = ob_end_clean();
1232 * Returns the user's image as a base64 encoded string.
1234 * @param int $userid The id of the user
1235 * @return string The encoded image
1237 function fetch_user_image($username) {
1240 if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) {
1241 $filename1 = make_user_directory($user->id, true) . "/f1.jpg";
1242 $filename2 = make_user_directory($user->id, true) . "/f2.jpg";
1244 if (file_exists($filename1)) {
1245 $return['f1'] = base64_encode(file_get_contents($filename1));
1247 if (file_exists($filename2)) {
1248 $return['f2'] = base64_encode(file_get_contents($filename2));
1256 * Returns the theme information and logo url as strings.
1258 * @return string The theme info
1260 function fetch_theme_info() {
1263 $themename = "$CFG->theme";
1264 $logourl = "$CFG->wwwroot/theme/$CFG->theme/images/logo.jpg";
1266 $return['themename'] = $themename;
1267 $return['logourl'] = $logourl;
1272 * Determines if an MNET host is providing the nominated service.
1274 * @param int $mnethostid The id of the remote host
1275 * @param string $servicename The name of the service
1276 * @return bool Whether the service is available on the remote host
1278 function has_service($mnethostid, $servicename) {
1283 svc.id as serviceid,
1292 {mnet_host2service} h2s
1295 h.id = h2s.hostid AND
1297 h2s.serviceid = svc.id AND
1299 h2s.subscribe = '1'";
1301 return $DB->get_records_sql($sql, array($mnethostid, $servicename));
1305 * Checks the MNET access control table to see if the username/mnethost
1306 * is permitted to login to this moodle.
1308 * @param string $username The username
1309 * @param int $mnethostid The id of the remote mnethost
1310 * @return bool Whether the user can login from the remote host
1312 function can_login_remotely($username, $mnethostid) {
1315 $accessctrl = 'allow';
1316 $aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnethostid));
1317 if (!empty($aclrecord)) {
1318 $accessctrl = $aclrecord->accessctrl;
1320 return $accessctrl == 'allow';
1323 function logoutpage_hook() {
1324 global $USER, $CFG, $redirect, $DB;
1326 if (!empty($USER->mnethostid) and $USER->mnethostid != $CFG->mnet_localhost_id) {
1327 $host = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid));
1328 $redirect = $host->wwwroot.'/';
1333 * Trims a log line from mnet peer to limit each part to a length which can be stored in our DB
1335 * @param object $logline The log information to be trimmed
1336 * @return object The passed logline object trimmed to not exceed storable limits
1338 function trim_logline ($logline) {
1339 $limits = array('ip' => 15, 'coursename' => 40, 'module' => 20, 'action' => 40,
1341 foreach ($limits as $property => $limit) {
1342 if (isset($logline->$property)) {
1343 $logline->$property = substr($logline->$property, 0, $limit);