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; // error("Remote MNET users cannot login locally.");
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;
77 require_once $CFG->dirroot
. '/mnet/xmlrpc/server.php';
79 $mnet_session = get_record('mnet_session', '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 = get_complete_user_data('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 = "{$CFG->dataroot}/users/{$user->id}/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 {$CFG->prefix}mnet_enrol_course c,
137 {$CFG->prefix}mnet_enrol_assignments a,
138 {$CFG->prefix}mnet_host h
140 c.id = a.courseid AND
142 a.userid = '{$user->id}' AND
143 c.hostid != '{$MNET_REMOTE_CLIENT->id}'
148 if ($courses = get_records_sql($sql)) {
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 function start_jump_session($mnethostid, $wantsurl) {
171 require_once $CFG->dirroot
. '/mnet/xmlrpc/client.php';
173 // check remote login permissions
174 if (! has_capability('moodle/site:mnetlogintoremote', get_context_instance(CONTEXT_SYSTEM
, SITEID
))
175 or is_mnet_remote_user($USER)
176 or $USER->username
== 'guest'
177 or empty($USER->id
)) {
178 error(get_string('notpermittedtojump', 'mnet'));
181 // check for SSO publish permission first
182 if ($this->has_service($mnethostid, 'sso_sp') == false) {
183 error(get_string('hostnotconfiguredforsso', 'mnet'));
186 // set RPC timeout to 30 seconds if not configured
187 // TODO: Is this needed/useful/problematic?
188 if (empty($this->config
->rpc_negotiation_timeout
)) {
189 set_config('rpc_negotiation_timeout', '30', 'auth/mnet');
193 $mnet_peer = new mnet_peer();
194 $mnet_peer->set_id($mnethostid);
196 // set up the session
197 $mnet_session = get_record('mnet_session',
199 'mnethostid', $mnethostid,
200 'useragent', sha1($_SERVER['HTTP_USER_AGENT']));
201 if ($mnet_session == false) {
202 $mnet_session = new object();
203 $mnet_session->mnethostid
= $mnethostid;
204 $mnet_session->userid
= $USER->id
;
205 $mnet_session->username
= $USER->username
;
206 $mnet_session->useragent
= sha1($_SERVER['HTTP_USER_AGENT']);
207 $mnet_session->token
= $this->generate_token();
208 $mnet_session->confirm_timeout
= time() +
$this->config
->rpc_negotiation_timeout
;
209 $mnet_session->expires
= time() +
(integer)ini_get('session.gc_maxlifetime');
210 $mnet_session->session_id
= session_id();
211 if (! $mnet_session->id
= insert_record('mnet_session', addslashes_object($mnet_session))) {
212 error(get_string('databaseerror', 'mnet'));
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 if (false == update_record('mnet_session', addslashes_object($mnet_session))) {
221 error(get_string('databaseerror', 'mnet'));
225 // construct the redirection URL
226 //$transport = mnet_get_protocol($mnet_peer->transport);
227 $wantsurl = urlencode($wantsurl);
228 $url = "{$mnet_peer->wwwroot}{$mnet_peer->application->sso_land_url}?token={$mnet_session->token}&idp={$MNET->wwwroot}&wantsurl={$wantsurl}";
234 * This function confirms the remote (ID provider) host's mnet session
235 * by communicating the token and UA over the XMLRPC transport layer, and
236 * returns the local user record on success.
238 * @param string $token The random session token.
239 * @param string $remotewwwroot The ID provider wwwroot.
240 * @return array The local user record.
242 function confirm_mnet_session($token, $remotewwwroot) {
243 global $CFG, $MNET, $SESSION;
244 require_once $CFG->dirroot
. '/mnet/xmlrpc/client.php';
246 // verify the remote host is configured locally before attempting RPC call
247 if (! $remotehost = get_record('mnet_host', 'wwwroot', $remotewwwroot)) {
248 error(get_string('notpermittedtoland', 'mnet'));
251 // get the originating (ID provider) host info
252 $remotepeer = new mnet_peer();
253 $remotepeer->set_wwwroot($remotewwwroot);
255 // set up the RPC request
256 $mnetrequest = new mnet_xmlrpc_client();
257 $mnetrequest->set_method('auth/mnet/auth.php/user_authorise');
259 // set $token and $useragent parameters
260 $mnetrequest->add_param($token);
261 $mnetrequest->add_param(sha1($_SERVER['HTTP_USER_AGENT']));
263 // Thunderbirds are go! Do RPC call and store response
264 if ($mnetrequest->send($remotepeer) === true) {
265 $remoteuser = (object) $mnetrequest->response
;
267 foreach ($mnetrequest->error
as $errormessage) {
268 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
271 print_error('mnet_session_prohibited','mnet', $remotewwwroot, format_string($site->fullname
));
274 $message .= "ERROR $code:<br/>$errormessage<br/>";
276 error("RPC auth/mnet/user_authorise:<br/>$message");
280 if (empty($remoteuser) or empty($remoteuser->username
)) {
281 print_error('unknownerror', 'mnet');
287 // get the local record for the remote user
288 $localuser = get_record('user', 'username', $remoteuser->username
, 'mnethostid', $remotehost->id
);
290 // add the remote user to the database if necessary, and if allowed
291 // TODO: refactor into a separate function
292 if (! $localuser->id
) {
293 if (empty($this->config
->auto_add_remote_users
)) {
294 error(get_string('nolocaluser', 'mnet'));
296 $remoteuser->mnethostid
= $remotehost->id
;
297 if (! insert_record('user', addslashes_object($remoteuser))) {
298 error(get_string('databaseerror', 'mnet'));
301 if (! $localuser = get_record('user', 'username', addslashes($remoteuser->username
), 'mnethostid', $remotehost->id
)) {
302 error(get_string('nolocaluser', 'mnet'));
306 // check sso access control list for permission first
307 if (!$this->can_login_remotely($localuser->username
, $remotehost->id
)) {
308 print_error('sso_mnet_login_refused', 'mnet', '', array($localuser->username
, $remotehost->name
));
311 $session_gc_maxlifetime = 1440;
313 // update the local user record with remote user data
314 foreach ((array) $remoteuser as $key => $val) {
315 if ($key == 'session.gc_maxlifetime') {
316 $session_gc_maxlifetime = $val;
320 // TODO: fetch image if it has changed
321 if ($key == 'imagehash') {
322 $dirname = "{$CFG->dataroot}/users/{$localuser->id}";
323 $filename = "$dirname/f1.jpg";
326 if (file_exists($filename)) {
327 $localhash = sha1(file_get_contents($filename));
328 } elseif (!file_exists($dirname)) {
332 if ($localhash != $val) {
333 // fetch image from remote host
334 $fetchrequest = new mnet_xmlrpc_client();
335 $fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image');
336 $fetchrequest->add_param($localuser->username
);
337 if ($fetchrequest->send($remotepeer) === true) {
338 if (strlen($fetchrequest->response
['f1']) > 0) {
339 $imagecontents = base64_decode($fetchrequest->response
['f1']);
340 file_put_contents($filename, $imagecontents);
341 $localuser->picture
= 1;
343 if (strlen($fetchrequest->response
['f2']) > 0) {
344 $imagecontents = base64_decode($fetchrequest->response
['f2']);
345 file_put_contents($dirname.'/f2.jpg', $imagecontents);
351 if($key == 'myhosts') {
352 $localuser->mnet_foreign_host_array
= array();
353 foreach($val as $rhost) {
354 $name = clean_param($rhost['name'], PARAM_ALPHANUM
);
355 $url = clean_param($rhost['url'], PARAM_URL
);
356 $count = clean_param($rhost['count'], PARAM_INT
);
357 $url_is_local = stristr($url , $CFG->wwwroot
);
358 if (!empty($name) && !empty($count) && empty($url_is_local)) {
359 $localuser->mnet_foreign_host_array
[] = array('name' => $name,
366 $localuser->{$key} = $val;
369 $localuser->mnethostid
= $remotepeer->id
;
371 $bool = update_record('user', addslashes_object($localuser));
373 // TODO: Jonathan to clean up mess
374 // Actually, this should never happen (modulo race conditions) - ML
375 error("updating user failed in mnet/auth/confirm_mnet_session ");
378 // set up the session
379 $mnet_session = get_record('mnet_session',
380 'userid', $localuser->id
,
381 'mnethostid', $remotepeer->id
,
382 'useragent', sha1($_SERVER['HTTP_USER_AGENT']));
383 if ($mnet_session == false) {
384 $mnet_session = new object();
385 $mnet_session->mnethostid
= $remotepeer->id
;
386 $mnet_session->userid
= $localuser->id
;
387 $mnet_session->username
= $localuser->username
;
388 $mnet_session->useragent
= sha1($_SERVER['HTTP_USER_AGENT']);
389 $mnet_session->token
= $token; // Needed to support simultaneous sessions
390 // and preserving DB rec uniqueness
391 $mnet_session->confirm_timeout
= time();
392 $mnet_session->expires
= time() +
(integer)$session_gc_maxlifetime;
393 $mnet_session->session_id
= session_id();
394 if (! $mnet_session->id
= insert_record('mnet_session', addslashes_object($mnet_session))) {
395 error(get_string('databaseerror', 'mnet'));
398 $mnet_session->expires
= time() +
(integer)$session_gc_maxlifetime;
399 update_record('mnet_session', addslashes_object($mnet_session));
403 // repeat customer! let the IDP know about enrolments
404 // we have for this user.
405 // set up the RPC request
406 $mnetrequest = new mnet_xmlrpc_client();
407 $mnetrequest->set_method('auth/mnet/auth.php/update_enrolments');
409 // pass username and an assoc array of "my courses"
410 // with info so that the IDP can maintain mnet_enrol_assignments
411 $mnetrequest->add_param($remoteuser->username
);
412 $fields = 'id, category, sortorder, fullname, shortname, idnumber, summary,
413 startdate, cost, currency, defaultrole, visible';
414 $courses = get_my_courses($localuser->id
, 'visible DESC,sortorder ASC', $fields);
415 if (is_array($courses) && !empty($courses)) {
416 // Second request to do the JOINs that we'd have done
417 // inside get_my_courses() if we had been allowed
419 cc.name AS cat_name, cc.description AS cat_description,
420 r.shortname as defaultrolename
421 FROM {$CFG->prefix}course c
422 JOIN {$CFG->prefix}course_categories cc ON c.category = cc.id
423 LEFT OUTER JOIN {$CFG->prefix}role r ON c.defaultrole = r.id
424 WHERE c.id IN (" . join(',',array_keys($courses)) . ')';
425 $extra = get_records_sql($sql);
427 $keys = array_keys($courses);
428 $defaultrolename = get_field('role', 'shortname', 'id', $CFG->defaultcourseroleid
);
429 foreach ($keys AS $id) {
430 if ($courses[$id]->visible
== 0) {
431 unset($courses[$id]);
434 $courses[$id]->cat_id
= $courses[$id]->category
;
435 $courses[$id]->defaultroleid
= $courses[$id]->defaultrole
;
436 unset($courses[$id]->category
);
437 unset($courses[$id]->defaultrole
);
438 unset($courses[$id]->visible
);
440 $courses[$id]->cat_name
= $extra[$id]->cat_name
;
441 $courses[$id]->cat_description
= $extra[$id]->cat_description
;
442 if (!empty($extra[$id]->defaultrolename
)) {
443 $courses[$id]->defaultrolename
= $extra[$id]->defaultrolename
;
445 $courses[$id]->defaultrolename
= $defaultrolename;
448 $courses[$id] = (array)$courses[$id];
451 // if the array is empty, send it anyway
452 // we may be clearing out stale entries
455 $mnetrequest->add_param($courses);
457 // Call 0800-RPC Now! -- we don't care too much if it fails
458 // as it's just informational.
459 if ($mnetrequest->send($remotepeer) === false) {
460 // error_log(print_r($mnetrequest->error,1));
468 * Invoke this function _on_ the IDP to update it with enrolment info local to
469 * the SP right after calling user_authorise()
471 * Normally called by the SP after calling
473 * @param string $username The username
474 * @param string $courses Assoc array of courses following the structure of mnet_enrol_course
477 function update_enrolments($username, $courses) {
478 global $MNET_REMOTE_CLIENT, $CFG;
480 if (empty($username) ||
!is_array($courses)) {
483 // make sure it is a user we have an in active session
485 $userid = get_field('mnet_session', 'userid',
486 'username', addslashes($username),
487 'mnethostid', (int)$MNET_REMOTE_CLIENT->id
);
492 if (empty($courses)) { // no courses? clear out quickly
493 delete_records('mnet_enrol_assignments',
494 'hostid', (int)$MNET_REMOTE_CLIENT->id
,
499 // IMPORTANT: Ask for remoteid as the first element in the query, so
500 // that the array that comes back is indexed on the same field as the
501 // array that we have received from the remote client
521 '.$CFG->prefix
.'mnet_enrol_course c
523 '.$CFG->prefix
.'mnet_enrol_assignments a
525 (a.courseid = c.id AND
526 a.hostid = c.hostid AND
527 a.userid = \''.$userid.'\')
529 c.hostid = \''.(int)$MNET_REMOTE_CLIENT->id
.'\'';
531 $currentcourses = get_records_sql($sql);
533 $local_courseid_array = array();
534 foreach($courses as $course) {
536 $course['remoteid'] = $course['id'];
537 $course['hostid'] = (int)$MNET_REMOTE_CLIENT->id
;
540 // First up - do we have a record for this course?
541 if (!array_key_exists($course['remoteid'], $currentcourses)) {
542 // No record - we must create it
543 $course['id'] = insert_record('mnet_enrol_course', addslashes_object((object)$course));
544 $currentcourse = (object)$course;
546 // Pointer to current course:
547 $currentcourse =& $currentcourses[$course['remoteid']];
548 // We have a record - is it up-to-date?
549 $course['id'] = $currentcourse->id
;
553 foreach($course as $key => $value) {
554 if ($currentcourse->$key != $value) {
556 $currentcourse->$key = $value;
561 update_record('mnet_enrol_course', addslashes_object($currentcourse));
564 if (isset($currentcourse->assignmentid
) && is_numeric($currentcourse->assignmentid
)) {
569 // By this point, we should always have a $dataObj->id
570 $local_courseid_array[] = $course['id'];
572 // Do we have a record for this assignment?
574 // Yes - we know about this one already
575 // We don't want to do updates because the new data is probably
576 // 'less complete' than the data we have.
578 // No - create a record
579 $assignObj = new stdClass();
580 $assignObj->userid
= $userid;
581 $assignObj->hostid
= (int)$MNET_REMOTE_CLIENT->id
;
582 $assignObj->courseid
= $course['id'];
583 $assignObj->rolename
= $course['defaultrolename'];
584 $assignObj->id
= insert_record('mnet_enrol_assignments', addslashes_object($assignObj));
588 // Clean up courses that the user is no longer enrolled in.
589 $local_courseid_string = implode(', ', $local_courseid_array);
590 $whereclause = " userid = '$userid' AND hostid = '{$MNET_REMOTE_CLIENT->id}' AND courseid NOT IN ($local_courseid_string)";
591 delete_records_select('mnet_enrol_assignments', $whereclause);
595 * Returns true if this authentication plugin is 'internal'.
599 function is_internal() {
604 * Returns true if this authentication plugin can change the user's
609 function can_change_password() {
610 //TODO: it should be able to redirect, right?
615 * Returns the URL for changing the user's pw, or false if the default can
620 function change_password_url() {
625 * Prints a form for configuring this authentication plugin.
627 * This function is called from admin/auth.php, and outputs a full page with
628 * a form for configuring this plugin.
630 * @param array $page An object containing all the data for this page.
632 function config_form($config, $err, $user_fields) {
640 h2idp.publish as idppublish,
641 h2idp.subscribe as idpsubscribe,
643 h2sp.publish as sppublish,
644 h2sp.subscribe as spsubscribe,
647 {$CFG->prefix}mnet_host h
649 {$CFG->prefix}mnet_host2service h2idp
651 (h.id = h2idp.hostid AND
652 (h2idp.publish = 1 OR
653 h2idp.subscribe = 1))
655 {$CFG->prefix}mnet_service idp
657 (h2idp.serviceid = idp.id AND
658 idp.name = 'sso_idp')
660 {$CFG->prefix}mnet_host2service h2sp
662 (h.id = h2sp.hostid AND
666 {$CFG->prefix}mnet_service sp
668 (h2sp.serviceid = sp.id AND
671 ((h2idp.publish = 1 AND h2sp.subscribe = 1) OR
672 (h2sp.publish = 1 AND h2idp.subscribe = 1)) AND
673 h.id != {$CFG->mnet_localhost_id}
677 $id_providers = array();
678 $service_providers = array();
679 if ($resultset = get_records_sql($query)) {
680 foreach($resultset as $hostservice) {
681 if(!empty($hostservice->idppublish
) && !empty($hostservice->spsubscribe
)) {
682 $service_providers[]= array('id' => $hostservice->id
, 'name' => $hostservice->hostname
, 'wwwroot' => $hostservice->wwwroot
);
684 if(!empty($hostservice->idpsubscribe
) && !empty($hostservice->sppublish
)) {
685 $id_providers[]= array('id' => $hostservice->id
, 'name' => $hostservice->hostname
, 'wwwroot' => $hostservice->wwwroot
);
690 include "config.html";
694 * Processes and stores configuration data for this authentication plugin.
696 function process_config($config) {
697 // set to defaults if undefined
698 if (!isset ($config->rpc_negotiation_timeout
)) {
699 $config->rpc_negotiation_timeout
= '30';
701 if (!isset ($config->auto_add_remote_users
)) {
702 $config->auto_add_remote_users
= '0';
706 set_config('rpc_negotiation_timeout', $config->rpc_negotiation_timeout
, 'auth/mnet');
707 set_config('auto_add_remote_users', $config->auto_add_remote_users
, 'auth/mnet');
713 * Poll the IdP server to let it know that a user it has authenticated is still
718 function keepalive_client() {
720 $cutoff = time() - 300; // TODO - find out what the remote server's session
721 // cutoff is, and preempt that
731 lastaccess > '$cutoff' AND
732 mnethostid != '{$CFG->mnet_localhost_id}'
736 $immigrants = get_records_sql($sql);
738 if ($immigrants == false) {
742 $usersArray = array();
743 foreach($immigrants as $immigrant) {
744 $usersArray[$immigrant->mnethostid
][] = $immigrant->username
;
747 require_once $CFG->dirroot
. '/mnet/xmlrpc/client.php';
748 foreach($usersArray as $mnethostid => $users) {
749 $mnet_peer = new mnet_peer();
750 $mnet_peer->set_id($mnethostid);
752 $mnet_request = new mnet_xmlrpc_client();
753 $mnet_request->set_method('auth/mnet/auth.php/keepalive_server');
755 // set $token and $useragent parameters
756 $mnet_request->add_param($users);
758 if ($mnet_request->send($mnet_peer) === true) {
759 if (!isset($mnet_request->response
['code'])) {
760 debugging("Server side error has occured on host $mnethostid");
762 } elseif ($mnet_request->response
['code'] > 0) {
763 debugging($mnet_request->response
['message']);
766 if (!isset($mnet_request->response
['last log id'])) {
767 debugging("Server side error has occured on host $mnethostid\nNo log ID was received.");
771 debugging("Server side error has occured on host $mnethostid: " .
772 join("\n", $mnet_request->error
));
787 c.fullname as coursename,
788 c.modinfo as modinfo,
791 {$CFG->prefix}user u,
793 {$CFG->prefix}course c
796 u.mnethostid = '$mnethostid' AND
797 l.id > '".$mnet_request->response
['last log id']."' AND
802 $results = get_records_sql($query);
804 if (false == $results) continue;
808 foreach($results as $result) {
809 if (!empty($result->modinfo
) && !empty($result->cmid
)) {
810 $modinfo = unserialize($result->modinfo
);
811 unset($result->modinfo
);
812 $modulearray = array();
813 foreach($modinfo as $module) {
814 $modulearray[$module->cm
] = urldecode($module->name
);
816 $result->resource_name
= $modulearray[$result->cmid
];
818 $result->resource_name
= '';
822 'remoteid' => $result->remoteid
,
823 'time' => $result->time
,
824 'userid' => $result->userid
,
826 'course' => $result->course
,
827 'coursename' => $result->coursename
,
828 'module' => $result->module
,
829 'cmid' => $result->cmid
,
830 'action' => $result->action
,
831 'url' => $result->url
,
832 'info' => $result->info
,
833 'resource_name' => $result->resource_name
,
834 'username' => $result->username
840 $mnet_request = new mnet_xmlrpc_client();
841 $mnet_request->set_method('auth/mnet/auth.php/refresh_log');
843 // set $token and $useragent parameters
844 $mnet_request->add_param($param);
846 if ($mnet_request->send($mnet_peer) === true) {
847 if ($mnet_request->response
['code'] > 0) {
848 debugging($mnet_request->response
['message']);
851 debugging("Server side error has occured on host $mnet_peer->ip: " .join("\n", $mnet_request->error
));
857 * Receives an array of log entries from an SP and adds them to the mnet_log
860 * @param array $array An array of usernames
861 * @return string "All ok" or an error message
863 function refresh_log($array) {
864 global $CFG, $MNET_REMOTE_CLIENT;
866 // We don't want to output anything to the client machine
871 $useridarray = array();
873 foreach($array as $logEntry) {
874 $logEntryObj = (object)$logEntry;
875 $logEntryObj->hostid
= $MNET_REMOTE_CLIENT->id
;
877 if (isset($useridarray[$logEntryObj->username
])) {
878 $logEntryObj->userid
= $useridarray[$logEntryObj->username
];
880 $logEntryObj->userid
= get_field('user','id','username',$logEntryObj->username
);
881 if ($logEntryObj->userid
== false) {
882 $logEntryObj->userid
= 0;
884 $useridarray[$logEntryObj->username
] = $logEntryObj->userid
;
887 unset($logEntryObj->username
);
889 $insertok = insert_record('mnet_log', addslashes_object($logEntryObj), false);
892 $MNET_REMOTE_CLIENT->last_log_id
= $logEntryObj->remoteid
;
894 $returnString .= 'Record with id '.$logEntryObj->remoteid
." failed to insert.\n";
897 $MNET_REMOTE_CLIENT->commit();
900 $end = ob_end_clean();
902 if (empty($returnString)) return array('code' => 0, 'message' => 'All ok');
903 return array('code' => 1, 'message' => $returnString);
907 * Receives an array of usernames from a remote machine and prods their
908 * sessions to keep them alive
910 * @param array $array An array of usernames
911 * @return string "All ok" or an error message
913 function keepalive_server($array) {
914 global $MNET_REMOTE_CLIENT, $CFG;
917 // Addslashes to all usernames, so we can build the query string real
918 // simply with 'implode'
919 $array = array_map('addslashes', $array);
921 // We don't want to output anything to the client machine
924 // We'll get session records in batches of 30
925 $superArray = array_chunk($array, 30);
929 foreach($superArray as $subArray) {
930 $subArray = array_values($subArray);
931 $instring = "('".implode("', '",$subArray)."')";
932 $query = "select id, session_id, username from {$CFG->prefix}mnet_session where username in $instring";
933 $results = get_records_sql($query);
935 if ($results == false) {
936 // We seem to have a username that breaks our query:
937 // TODO: Handle this error appropriately
938 $returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n";
940 // TODO: This process of killing and re-starting the session
941 // will cause PHP to forget any custom session_set_save_handler
942 // stuff. Subsequent attempts to prod existing sessions will
943 // fail, because PHP will look in wherever the default place
944 // may be (files?) and probably create a new session with the
945 // right session ID in that location. If it doesn't have write-
946 // access to that location, then it will fail... not sure how
947 // apparent that will be.
948 // There is no way to capture what the custom session handler
949 // is and then reset it on each pass - I checked that out
951 $sesscache = clone($_SESSION);
952 $sessidcache = session_id();
953 session_write_close();
956 $uc = ini_get('session.use_cookies');
957 ini_set('session.use_cookies', false);
958 foreach($results as $emigrant) {
961 session_name('MoodleSession'.$CFG->sessioncookie
);
962 session_id($emigrant->session_id
);
964 session_write_close();
967 ini_set('session.use_cookies', $uc);
968 session_name('MoodleSession'.$CFG->sessioncookie
);
969 session_id($sessidcache);
971 $_SESSION = clone($sesscache);
972 session_write_close();
976 $end = ob_end_clean();
978 if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $MNET_REMOTE_CLIENT->last_log_id
);
979 return array('code' => 1, 'message' => $returnString, 'last log id' => $MNET_REMOTE_CLIENT->last_log_id
);
983 * Cron function will be called automatically by cron.php every 5 minutes
989 // run the keepalive client
990 $this->keepalive_client();
992 // admin/cron.php should have run srand for us
993 $random100 = rand(0,100);
994 if ($random100 < 10) { // Approximately 10% of the time.
995 // nuke olden sessions
996 $longtime = time() - (1 * 3600 * 24);
997 delete_records_select('mnet_session', "expires < $longtime");
1002 * Cleanup any remote mnet_sessions, kill the local mnet_session data
1004 * This is called by require_logout in moodlelib
1008 function prelogout_hook() {
1009 global $MNET, $CFG, $USER;
1010 if (!is_enabled_auth('mnet')) {
1014 require_once $CFG->dirroot
.'/mnet/xmlrpc/client.php';
1016 // If the user is local to this Moodle:
1017 if ($USER->mnethostid
== $MNET->id
) {
1018 $this->kill_children($USER->username
, sha1($_SERVER['HTTP_USER_AGENT']));
1020 // Else the user has hit 'logout' at a Service Provider Moodle:
1022 $this->kill_parent($USER->username
, sha1($_SERVER['HTTP_USER_AGENT']));
1028 * The SP uses this function to kill the session on the parent IdP
1030 * @param string $username Username for session to kill
1031 * @param string $useragent SHA1 hash of user agent to look for
1032 * @return string A plaintext report of what has happened
1034 function kill_parent($username, $useragent) {
1036 require_once $CFG->dirroot
.'/mnet/xmlrpc/client.php';
1041 {$CFG->prefix}mnet_session s
1043 s.username = '".addslashes($username)."' AND
1044 s.useragent = '$useragent' AND
1045 s.mnethostid = '{$USER->mnethostid}'";
1047 $mnetsessions = get_records_sql($sql);
1049 $ignore = delete_records('mnet_session',
1050 'username', addslashes($username),
1051 'useragent', $useragent,
1052 'mnethostid', $USER->mnethostid
);
1054 if (false != $mnetsessions) {
1055 $mnet_peer = new mnet_peer();
1056 $mnet_peer->set_id($USER->mnethostid
);
1058 $mnet_request = new mnet_xmlrpc_client();
1059 $mnet_request->set_method('auth/mnet/auth.php/kill_children');
1061 // set $token and $useragent parameters
1062 $mnet_request->add_param($username);
1063 $mnet_request->add_param($useragent);
1064 if ($mnet_request->send($mnet_peer) === false) {
1065 debugging(join("\n", $mnet_request->error
));
1070 $_SESSION = array();
1075 * The IdP uses this function to kill child sessions on other hosts
1077 * @param string $username Username for session to kill
1078 * @param string $useragent SHA1 hash of user agent to look for
1079 * @return string A plaintext report of what has happened
1081 function kill_children($username, $useragent) {
1082 global $CFG, $USER, $MNET_REMOTE_CLIENT;
1083 require_once $CFG->dirroot
.'/mnet/xmlrpc/client.php';
1085 $userid = get_field('user', 'id', 'mnethostid', $CFG->mnet_localhost_id
, 'username', addslashes($username));
1092 {$CFG->prefix}mnet_session s
1094 s.userid = '{$userid}' AND
1095 s.useragent = '{$useragent}'";
1097 // If we are being executed from a remote machine (client) we don't have
1098 // to kill the moodle session on that machine.
1099 if (isset($MNET_REMOTE_CLIENT) && isset($MNET_REMOTE_CLIENT->id
)) {
1100 $excludeid = $MNET_REMOTE_CLIENT->id
;
1105 $mnetsessions = get_records_sql($sql);
1107 if (false == $mnetsessions) {
1108 $returnstring .= "Could find no remote sessions\n$sql\n";
1109 $mnetsessions = array();
1112 foreach($mnetsessions as $mnetsession) {
1113 $returnstring .= "Deleting session\n";
1115 if ($mnetsession->mnethostid
== $excludeid) continue;
1117 $mnet_peer = new mnet_peer();
1118 $mnet_peer->set_id($mnetsession->mnethostid
);
1120 $mnet_request = new mnet_xmlrpc_client();
1121 $mnet_request->set_method('auth/mnet/auth.php/kill_child');
1123 // set $token and $useragent parameters
1124 $mnet_request->add_param($username);
1125 $mnet_request->add_param($useragent);
1126 if ($mnet_request->send($mnet_peer) === false) {
1127 debugging("Server side error has occured on host $mnethostid: " .
1128 join("\n", $mnet_request->error
));
1132 $ignore = delete_records('mnet_session',
1133 'useragent', $useragent,
1136 if (isset($MNET_REMOTE_CLIENT) && isset($MNET_REMOTE_CLIENT->id
)) {
1137 $start = ob_start();
1139 $uc = ini_get('session.use_cookies');
1140 ini_set('session.use_cookies', false);
1141 $sesscache = clone($_SESSION);
1142 $sessidcache = session_id();
1143 session_write_close();
1147 session_id($mnetsession->session_id
);
1149 session_unregister("USER");
1150 session_unregister("SESSION");
1152 $_SESSION = array();
1153 session_write_close();
1156 ini_set('session.use_cookies', $uc);
1157 session_name('MoodleSession'.$CFG->sessioncookie
);
1158 session_id($sessidcache);
1160 $_SESSION = clone($sesscache);
1161 session_write_close();
1163 $end = ob_end_clean();
1165 $_SESSION = array();
1167 return $returnstring;
1171 * TODO:Untested When the IdP requests that child sessions are terminated,
1172 * this function will be called on each of the child hosts. The machine that
1173 * calls the function (over xmlrpc) provides us with the mnethostid we need.
1175 * @param string $username Username for session to kill
1176 * @param string $useragent SHA1 hash of user agent to look for
1177 * @return bool True on success
1179 function kill_child($username, $useragent) {
1180 global $CFG, $MNET_REMOTE_CLIENT;
1181 $session = get_record('mnet_session', 'username', addslashes($username), 'mnethostid', $MNET_REMOTE_CLIENT->id
, 'useragent', $useragent);
1182 if (false != $session) {
1183 $start = ob_start();
1185 $uc = ini_get('session.use_cookies');
1186 ini_set('session.use_cookies', false);
1187 $sesscache = clone($_SESSION);
1188 $sessidcache = session_id();
1189 session_write_close();
1193 session_id($session->session_id
);
1195 session_unregister("USER");
1196 session_unregister("SESSION");
1198 $_SESSION = array();
1199 session_write_close();
1202 ini_set('session.use_cookies', $uc);
1203 session_name('MoodleSession'.$CFG->sessioncookie
);
1204 session_id($sessidcache);
1206 $_SESSION = clone($sesscache);
1207 session_write_close();
1209 $end = ob_end_clean();
1216 * To delete a host, we must delete all current sessions that users from
1217 * that host are currently engaged in.
1219 * @param string $sessionidarray An array of session hashes
1220 * @return bool True on success
1222 function end_local_sessions(&$sessionArray) {
1224 if (is_array($sessionArray)) {
1225 $start = ob_start();
1227 $uc = ini_get('session.use_cookies');
1228 ini_set('session.use_cookies', false);
1229 $sesscache = clone($_SESSION);
1230 $sessidcache = session_id();
1231 session_write_close();
1234 while($session = array_pop($sessionArray)) {
1235 session_id($session->session_id
);
1237 session_unregister("USER");
1238 session_unregister("SESSION");
1240 $_SESSION = array();
1241 session_write_close();
1244 ini_set('session.use_cookies', $uc);
1245 session_name('MoodleSession'.$CFG->sessioncookie
);
1246 session_id($sessidcache);
1248 $_SESSION = clone($sesscache);
1250 $end = ob_end_clean();
1257 * Returns the user's image as a base64 encoded string.
1259 * @param int $userid The id of the user
1260 * @return string The encoded image
1262 function fetch_user_image($username) {
1265 if ($user = get_record('user', 'username', addslashes($username), 'mnethostid', $CFG->mnet_localhost_id
)) {
1266 $filename1 = "{$CFG->dataroot}/users/{$user->id}/f1.jpg";
1267 $filename2 = "{$CFG->dataroot}/users/{$user->id}/f2.jpg";
1269 if (file_exists($filename1)) {
1270 $return['f1'] = base64_encode(file_get_contents($filename1));
1272 if (file_exists($filename2)) {
1273 $return['f2'] = base64_encode(file_get_contents($filename2));
1281 * Returns the theme information and logo url as strings.
1283 * @return string The theme info
1285 function fetch_theme_info() {
1288 $themename = "$CFG->theme";
1289 $logourl = "$CFG->wwwroot/theme/$CFG->theme/images/logo.jpg";
1291 $return['themename'] = $themename;
1292 $return['logourl'] = $logourl;
1297 * Determines if an MNET host is providing the nominated service.
1299 * @param int $mnethostid The id of the remote host
1300 * @param string $servicename The name of the service
1301 * @return bool Whether the service is available on the remote host
1303 function has_service($mnethostid, $servicename) {
1308 svc.id as serviceid,
1315 {$CFG->prefix}mnet_service svc,
1316 {$CFG->prefix}mnet_host2service h2s
1318 h2s.hostid = '$mnethostid' AND
1319 h2s.serviceid = svc.id AND
1320 svc.name = '$servicename' AND
1321 h2s.subscribe = '1'";
1323 return get_records_sql($sql);
1327 * Checks the MNET access control table to see if the username/mnethost
1328 * is permitted to login to this moodle.
1330 * @param string $username The username
1331 * @param int $mnethostid The id of the remote mnethost
1332 * @return bool Whether the user can login from the remote host
1334 function can_login_remotely($username, $mnethostid) {
1335 $accessctrl = 'allow';
1336 $aclrecord = get_record('mnet_sso_access_control', 'username', addslashes($username), 'mnet_host_id', $mnethostid);
1337 if (!empty($aclrecord)) {
1338 $accessctrl = $aclrecord->accessctrl
;
1340 return $accessctrl == 'allow';
1343 function logoutpage_hook() {
1344 global $USER, $CFG, $redirect;
1346 if (!empty($USER->mnethostid
) and $USER->mnethostid
!= $CFG->mnet_localhost_id
) {
1347 $host = get_record('mnet_host', 'id', $USER->mnethostid
);
1348 $redirect = $host->wwwroot
.'/';