| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php // $Id: auth.php,v 1.27.2.7 2008/08/13 23:31:01 peterbulmer Exp $ 2 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 */ 14 15 if (!defined('MOODLE_INTERNAL')) { 16 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page 17 } 18 19 require_once($CFG->libdir.'/authlib.php'); 20 21 /** 22 * Moodle Network authentication plugin. 23 */ 24 class auth_plugin_mnet extends auth_plugin_base { 25 26 /** 27 * Constructor. 28 */ 29 function auth_plugin_mnet() { 30 $this->authtype = 'mnet'; 31 $this->config = get_config('auth/mnet'); 32 } 33 34 /** 35 * Provides the allowed RPC services from this class as an array. 36 * @return array Allowed RPC services. 37 */ 38 function mnet_publishes() { 39 40 $sso_idp = array(); 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', 45 'update_enrolments'); 46 47 $sso_sp = array(); 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'); 51 52 return array($sso_idp, $sso_sp); 53 } 54 55 /** 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. 59 * 60 * @param string $username The username 61 * @param string $password The password 62 * @return bool Authentication success or failure. 63 */ 64 function user_login($username, $password) { 65 return false; // error("Remote MNET users cannot login locally."); 66 } 67 68 /** 69 * Return user data for the provided token, compare with user_agent string. 70 * 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 74 */ 75 function user_authorise($token, $useragent) { 76 global $CFG, $MNET, $SITE, $MNET_REMOTE_CLIENT; 77 require_once $CFG->dirroot . '/mnet/xmlrpc/server.php'; 78 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')); 82 exit; 83 } 84 85 // check session confirm timeout 86 if ($mnet_session->confirm_timeout < time()) { 87 echo mnet_server_fault(2, get_string('authfail_sessiontimedout', 'mnet')); 88 exit; 89 } 90 91 // session okay, try getting the user 92 if (!$user = get_record('user', 'id', $mnet_session->userid)) { 93 echo mnet_server_fault(3, get_string('authfail_usermismatch', 'mnet')); 94 exit; 95 } 96 97 $userdata = array(); 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)); 121 } 122 } 123 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)); 127 } 128 129 $sql = " 130 SELECT 131 h.name as hostname, 132 h.wwwroot, 133 h.id as hostid, 134 count(c.id) as count 135 FROM 136 {$CFG->prefix}mnet_enrol_course c, 137 {$CFG->prefix}mnet_enrol_assignments a, 138 {$CFG->prefix}mnet_host h 139 WHERE 140 c.id = a.courseid AND 141 c.hostid = h.id AND 142 a.userid = '{$user->id}' AND 143 c.hostid != '{$MNET_REMOTE_CLIENT->id}' 144 GROUP BY 145 h.name, 146 h.id, 147 h.wwwroot"; 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); 151 } 152 } 153 154 return $userdata; 155 } 156 157 /** 158 * Generate a random string for use as an RPC session token. 159 */ 160 function generate_token() { 161 return sha1(str_shuffle('' . mt_rand() . time())); 162 } 163 164 /** 165 * Starts an RPC jump session and returns the jump redirect URL. 166 */ 167 function start_jump_session($mnethostid, $wantsurl) { 168 global $CFG; 169 global $USER; 170 global $MNET; 171 require_once $CFG->dirroot . '/mnet/xmlrpc/client.php'; 172 173 // check remote login permissions 174 if (! has_capability('moodle/site:mnetlogintoremote', get_context_instance(CONTEXT_SYSTEM)) 175 or is_mnet_remote_user($USER) 176 or $USER->username == 'guest' 177 or empty($USER->id)) { 178 print_error('notpermittedtojump', 'mnet'); 179 } 180 181 // check for SSO publish permission first 182 if ($this->has_service($mnethostid, 'sso_sp') == false) { 183 print_error('hostnotconfiguredforsso', 'mnet'); 184 } 185 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'); 190 } 191 192 // get the host info 193 $mnet_peer = new mnet_peer(); 194 $mnet_peer->set_id($mnethostid); 195 196 // set up the session 197 $mnet_session = get_record('mnet_session', 198 'userid', $USER->id, 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 print_error('databaseerror', 'mnet'); 213 } 214 } else { 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 print_error('databaseerror', 'mnet'); 222 } 223 } 224 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}"; 229 230 return $url; 231 } 232 233 /** 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. 237 * 238 * @param string $token The random session token. 239 * @param string $remotewwwroot The ID provider wwwroot. 240 * @return array The local user record. 241 */ 242 function confirm_mnet_session($token, $remotewwwroot) { 243 global $CFG, $MNET, $SESSION; 244 require_once $CFG->dirroot . '/mnet/xmlrpc/client.php'; 245 246 // verify the remote host is configured locally before attempting RPC call 247 if (! $remotehost = get_record('mnet_host', 'wwwroot', $remotewwwroot, 'deleted', 0)) { 248 print_error('notpermittedtoland', 'mnet'); 249 } 250 251 // get the originating (ID provider) host info 252 $remotepeer = new mnet_peer(); 253 $remotepeer->set_wwwroot($remotewwwroot); 254 255 // set up the RPC request 256 $mnetrequest = new mnet_xmlrpc_client(); 257 $mnetrequest->set_method('auth/mnet/auth.php/user_authorise'); 258 259 // set $token and $useragent parameters 260 $mnetrequest->add_param($token); 261 $mnetrequest->add_param(sha1($_SERVER['HTTP_USER_AGENT'])); 262 263 // Thunderbirds are go! Do RPC call and store response 264 if ($mnetrequest->send($remotepeer) === true) { 265 $remoteuser = (object) $mnetrequest->response; 266 } else { 267 foreach ($mnetrequest->error as $errormessage) { 268 list($code, $message) = array_map('trim',explode(':', $errormessage, 2)); 269 if($code == 702) { 270 $site = get_site(); 271 print_error('mnet_session_prohibited', 'mnet', $remotewwwroot, format_string($site->fullname)); 272 exit; 273 } 274 $message .= "ERROR $code:<br/>$errormessage<br/>"; 275 } 276 error("RPC auth/mnet/user_authorise:<br/>$message"); 277 } 278 unset($mnetrequest); 279 280 if (empty($remoteuser) or empty($remoteuser->username)) { 281 print_error('unknownerror', 'mnet'); 282 exit; 283 } 284 285 $firsttime = false; 286 287 // get the local record for the remote user 288 $localuser = get_record('user', 'username', addslashes($remoteuser->username), 'mnethostid', $remotehost->id); 289 290 // add the remote user to the database if necessary, and if allowed 291 // TODO: refactor into a separate function 292 if (empty($localuser) || ! $localuser->id) { 293 if (empty($this->config->auto_add_remote_users)) { 294 print_error('nolocaluser', 'mnet'); 295 } 296 $remoteuser->mnethostid = $remotehost->id; 297 if (! insert_record('user', addslashes_object($remoteuser))) { 298 print_error('databaseerror', 'mnet'); 299 } 300 $firsttime = true; 301 if (! $localuser = get_record('user', 'username', addslashes($remoteuser->username), 'mnethostid', $remotehost->id)) { 302 print_error('nolocaluser', 'mnet'); 303 } 304 } 305 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)); 309 } 310 311 $session_gc_maxlifetime = 1440; 312 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; 317 continue; 318 } 319 320 // TODO: fetch image if it has changed 321 if ($key == 'imagehash') { 322 $dirname = make_user_directory($localuser->id, true); 323 $filename = "$dirname/f1.jpg"; 324 325 $localhash = ''; 326 if (file_exists($filename)) { 327 $localhash = sha1(file_get_contents($filename)); 328 } elseif (!file_exists($dirname)) { 329 mkdir($dirname); 330 } 331 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; 342 } 343 if (strlen($fetchrequest->response['f2']) > 0) { 344 $imagecontents = base64_decode($fetchrequest->response['f2']); 345 file_put_contents($dirname.'/f2.jpg', $imagecontents); 346 } 347 } 348 } 349 } 350 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, 360 'url' => $url, 361 'count' => $count); 362 } 363 } 364 } 365 366 $localuser->{$key} = $val; 367 } 368 369 $localuser->mnethostid = $remotepeer->id; 370 371 $bool = update_record('user', addslashes_object($localuser)); 372 if (!$bool) { 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 "); 376 } 377 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 print_error('databaseerror', 'mnet'); 396 } 397 } else { 398 $mnet_session->expires = time() + (integer)$session_gc_maxlifetime; 399 update_record('mnet_session', addslashes_object($mnet_session)); 400 } 401 402 if (!$firsttime) { 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'); 408 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 418 $sql = "SELECT c.id, 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); 426 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]); 432 continue; 433 } 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); 439 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; 444 } else { 445 $courses[$id]->defaultrolename = $defaultrolename; 446 } 447 // coerce to array 448 $courses[$id] = (array)$courses[$id]; 449 } 450 } else { 451 // if the array is empty, send it anyway 452 // we may be clearing out stale entries 453 $courses = array(); 454 } 455 $mnetrequest->add_param($courses); 456 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)); 461 } 462 } 463 464 return $localuser; 465 } 466 467 /** 468 * Invoke this function _on_ the IDP to update it with enrolment info local to 469 * the SP right after calling user_authorise() 470 * 471 * Normally called by the SP after calling 472 * 473 * @param string $username The username 474 * @param string $courses Assoc array of courses following the structure of mnet_enrol_course 475 * @return bool 476 */ 477 function update_enrolments($username, $courses) { 478 global $MNET_REMOTE_CLIENT, $CFG; 479 480 if (empty($username) || !is_array($courses)) { 481 return false; 482 } 483 // make sure it is a user we have an in active session 484 // with that host... 485 $userid = get_field('mnet_session', 'userid', 486 'username', addslashes($username), 487 'mnethostid', (int)$MNET_REMOTE_CLIENT->id); 488 if (!$userid) { 489 return false; 490 } 491 492 if (empty($courses)) { // no courses? clear out quickly 493 delete_records('mnet_enrol_assignments', 494 'hostid', (int)$MNET_REMOTE_CLIENT->id, 495 'userid', $userid); 496 return true; 497 } 498 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 502 $sql = ' 503 SELECT 504 c.remoteid, 505 c.id, 506 c.cat_id, 507 c.cat_name, 508 c.cat_description, 509 c.sortorder, 510 c.fullname, 511 c.shortname, 512 c.idnumber, 513 c.summary, 514 c.startdate, 515 c.cost, 516 c.currency, 517 c.defaultroleid, 518 c.defaultrolename, 519 a.id as assignmentid 520 FROM 521 '.$CFG->prefix.'mnet_enrol_course c 522 LEFT JOIN 523 '.$CFG->prefix.'mnet_enrol_assignments a 524 ON 525 (a.courseid = c.id AND 526 a.hostid = c.hostid AND 527 a.userid = \''.$userid.'\') 528 WHERE 529 c.hostid = \''.(int)$MNET_REMOTE_CLIENT->id.'\''; 530 531 $currentcourses = get_records_sql($sql); 532 533 $local_courseid_array = array(); 534 foreach($courses as $course) { 535 536 $course['remoteid'] = $course['id']; 537 $course['hostid'] = (int)$MNET_REMOTE_CLIENT->id; 538 $userisregd = false; 539 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; 545 } else { 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; 550 551 $saveflag = false; 552 553 foreach($course as $key => $value) { 554 if ($currentcourse->$key != $value) { 555 $saveflag = true; 556 $currentcourse->$key = $value; 557 } 558 } 559 560 if ($saveflag) { 561 update_record('mnet_enrol_course', addslashes_object($currentcourse)); 562 } 563 564 if (isset($currentcourse->assignmentid) && is_numeric($currentcourse->assignmentid)) { 565 $userisregd = true; 566 } 567 } 568 569 // By this point, we should always have a $dataObj->id 570 $local_courseid_array[] = $course['id']; 571 572 // Do we have a record for this assignment? 573 if ($userisregd) { 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. 577 } else { 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)); 585 } 586 } 587 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); 592 } 593 594 /** 595 * Returns true if this authentication plugin is 'internal'. 596 * 597 * @return bool 598 */ 599 function is_internal() { 600 return false; 601 } 602 603 /** 604 * Returns true if this authentication plugin can change the user's 605 * password. 606 * 607 * @return bool 608 */ 609 function can_change_password() { 610 //TODO: it should be able to redirect, right? 611 return false; 612 } 613 614 /** 615 * Returns the URL for changing the user's pw, or false if the default can 616 * be used. 617 * 618 * @return string 619 */ 620 function change_password_url() { 621 return ''; 622 } 623 624 /** 625 * Prints a form for configuring this authentication plugin. 626 * 627 * This function is called from admin/auth.php, and outputs a full page with 628 * a form for configuring this plugin. 629 * 630 * @param array $page An object containing all the data for this page. 631 */ 632 function config_form($config, $err, $user_fields) { 633 global $CFG; 634 635 $query = " 636 SELECT 637 h.id, 638 h.name as hostname, 639 h.wwwroot, 640 h2idp.publish as idppublish, 641 h2idp.subscribe as idpsubscribe, 642 idp.name as idpname, 643 h2sp.publish as sppublish, 644 h2sp.subscribe as spsubscribe, 645 sp.name as spname 646 FROM 647 {$CFG->prefix}mnet_host h 648 LEFT JOIN 649 {$CFG->prefix}mnet_host2service h2idp 650 ON 651 (h.id = h2idp.hostid AND 652 (h2idp.publish = 1 OR 653 h2idp.subscribe = 1)) 654 INNER JOIN 655 {$CFG->prefix}mnet_service idp 656 ON 657 (h2idp.serviceid = idp.id AND 658 idp.name = 'sso_idp') 659 LEFT JOIN 660 {$CFG->prefix}mnet_host2service h2sp 661 ON 662 (h.id = h2sp.hostid AND 663 (h2sp.publish = 1 OR 664 h2sp.subscribe = 1)) 665 INNER JOIN 666 {$CFG->prefix}mnet_service sp 667 ON 668 (h2sp.serviceid = sp.id AND 669 sp.name = 'sso_sp') 670 WHERE 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} 674 ORDER BY 675 h.name ASC"; 676 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); 683 } 684 if(!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) { 685 $id_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot); 686 } 687 } 688 } 689 690 include "config.html"; 691 } 692 693 /** 694 * Processes and stores configuration data for this authentication plugin. 695 */ 696 function process_config($config) { 697 // set to defaults if undefined 698 if (!isset ($config->rpc_negotiation_timeout)) { 699 $config->rpc_negotiation_timeout = '30'; 700 } 701 if (!isset ($config->auto_add_remote_users)) { 702 $config->auto_add_remote_users = '0'; 703 } 704 705 // save settings 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'); 708 709 return true; 710 } 711 712 /** 713 * Poll the IdP server to let it know that a user it has authenticated is still 714 * online 715 * 716 * @return void 717 */ 718 function keepalive_client() { 719 global $CFG, $MNET; 720 $cutoff = time() - 300; // TODO - find out what the remote server's session 721 // cutoff is, and preempt that 722 723 $sql = " 724 select 725 id, 726 username, 727 mnethostid 728 from 729 {$CFG->prefix}user 730 where 731 lastaccess > '$cutoff' AND 732 mnethostid != '{$CFG->mnet_localhost_id}' 733 order by 734 mnethostid"; 735 736 $immigrants = get_records_sql($sql); 737 738 if ($immigrants == false) { 739 return true; 740 } 741 742 $usersArray = array(); 743 foreach($immigrants as $immigrant) { 744 $usersArray[$immigrant->mnethostid][] = $immigrant->username; 745 } 746 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); 751 752 $mnet_request = new mnet_xmlrpc_client(); 753 $mnet_request->set_method('auth/mnet/auth.php/keepalive_server'); 754 755 // set $token and $useragent parameters 756 $mnet_request->add_param($users); 757 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"); 761 continue; 762 } elseif ($mnet_request->response['code'] > 0) { 763 debugging($mnet_request->response['message']); 764 } 765 766 if (!isset($mnet_request->response['last log id'])) { 767 debugging("Server side error has occured on host $mnethostid\nNo log ID was received."); 768 continue; 769 } 770 } else { 771 debugging("Server side error has occured on host $mnethostid: " . 772 join("\n", $mnet_request->error)); 773 break; 774 } 775 776 $query = "SELECT 777 l.id as remoteid, 778 l.time, 779 l.userid, 780 l.ip, 781 l.course, 782 l.module, 783 l.cmid, 784 l.action, 785 l.url, 786 l.info, 787 c.fullname as coursename, 788 c.modinfo as modinfo, 789 u.username 790 FROM 791 {$CFG->prefix}user u, 792 {$CFG->prefix}log l, 793 {$CFG->prefix}course c 794 WHERE 795 l.userid = u.id AND 796 u.mnethostid = '$mnethostid' AND 797 l.id > '".$mnet_request->response['last log id']."' AND 798 c.id = l.course 799 ORDER BY 800 remoteid ASC"; 801 802 $results = get_records_sql($query); 803 804 if (false == $results) continue; 805 806 $param = array(); 807 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); 815 } 816 $result->resource_name = $modulearray[$result->cmid]; 817 } else { 818 $result->resource_name = ''; 819 } 820 821 $param[] = array ( 822 'remoteid' => $result->remoteid, 823 'time' => $result->time, 824 'userid' => $result->userid, 825 'ip' => $result->ip, 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 835 ); 836 } 837 838 unset($result); 839 840 $mnet_request = new mnet_xmlrpc_client(); 841 $mnet_request->set_method('auth/mnet/auth.php/refresh_log'); 842 843 // set $token and $useragent parameters 844 $mnet_request->add_param($param); 845 846 if ($mnet_request->send($mnet_peer) === true) { 847 if ($mnet_request->response['code'] > 0) { 848 debugging($mnet_request->response['message']); 849 } 850 } else { 851 debugging("Server side error has occured on host $mnet_peer->ip: " .join("\n", $mnet_request->error)); 852 } 853 } 854 } 855 856 /** 857 * Receives an array of log entries from an SP and adds them to the mnet_log 858 * table 859 * 860 * @param array $array An array of usernames 861 * @return string "All ok" or an error message 862 */ 863 function refresh_log($array) { 864 global $CFG, $MNET_REMOTE_CLIENT; 865 866 // We don't want to output anything to the client machine 867 $start = ob_start(); 868 869 $returnString = ''; 870 begin_sql(); 871 $useridarray = array(); 872 873 foreach($array as $logEntry) { 874 $logEntryObj = (object)$logEntry; 875 $logEntryObj->hostid = $MNET_REMOTE_CLIENT->id; 876 877 if (isset($useridarray[$logEntryObj->username])) { 878 $logEntryObj->userid = $useridarray[$logEntryObj->username]; 879 } else { 880 $logEntryObj->userid = get_field('user','id','username',$logEntryObj->username); 881 if ($logEntryObj->userid == false) { 882 $logEntryObj->userid = 0; 883 } 884 $useridarray[$logEntryObj->username] = $logEntryObj->userid; 885 } 886 887 unset($logEntryObj->username); 888 889 $insertok = insert_record('mnet_log', addslashes_object($logEntryObj), false); 890 891 if ($insertok) { 892 $MNET_REMOTE_CLIENT->last_log_id = $logEntryObj->remoteid; 893 } else { 894 $returnString .= 'Record with id '.$logEntryObj->remoteid." failed to insert.\n"; 895 } 896 } 897 $MNET_REMOTE_CLIENT->commit(); 898 commit_sql(); 899 900 $end = ob_end_clean(); 901 902 if (empty($returnString)) return array('code' => 0, 'message' => 'All ok'); 903 return array('code' => 1, 'message' => $returnString); 904 } 905 906 /** 907 * Receives an array of usernames from a remote machine and prods their 908 * sessions to keep them alive 909 * 910 * @param array $array An array of usernames 911 * @return string "All ok" or an error message 912 */ 913 function keepalive_server($array) { 914 global $MNET_REMOTE_CLIENT, $CFG; 915 916 $CFG->usesid = true; 917 // Addslashes to all usernames, so we can build the query string real 918 // simply with 'implode' 919 $array = array_map('addslashes', $array); 920 921 // We don't want to output anything to the client machine 922 $start = ob_start(); 923 924 // We'll get session records in batches of 30 925 $superArray = array_chunk($array, 30); 926 927 $returnString = ''; 928 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); 934 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"; 939 } else { 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 950 // already. 951 $sesscache = clone($_SESSION); 952 $sessidcache = session_id(); 953 session_write_close(); 954 unset($_SESSION); 955 956 $uc = ini_get('session.use_cookies'); 957 ini_set('session.use_cookies', false); 958 foreach($results as $emigrant) { 959 960 unset($_SESSION); 961 session_name('MoodleSession'.$CFG->sessioncookie); 962 session_id($emigrant->session_id); 963 session_start(); 964 session_write_close(); 965 } 966 967 ini_set('session.use_cookies', $uc); 968 session_name('MoodleSession'.$CFG->sessioncookie); 969 session_id($sessidcache); 970 session_start(); 971 $_SESSION = clone($sesscache); 972 session_write_close(); 973 } 974 } 975 976 $end = ob_end_clean(); 977 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); 980 } 981 982 /** 983 * Cron function will be called automatically by cron.php every 5 minutes 984 * 985 * @return void 986 */ 987 function cron() { 988 989 // run the keepalive client 990 $this->keepalive_client(); 991 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"); 998 } 999 } 1000 1001 /** 1002 * Cleanup any remote mnet_sessions, kill the local mnet_session data 1003 * 1004 * This is called by require_logout in moodlelib 1005 * 1006 * @return void 1007 */ 1008 function prelogout_hook() { 1009 global $MNET, $CFG, $USER; 1010 if (!is_enabled_auth('mnet')) { 1011 return; 1012 } 1013 1014 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 1015 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'])); 1019 1020 // Else the user has hit 'logout' at a Service Provider Moodle: 1021 } else { 1022 $this->kill_parent($USER->username, sha1($_SERVER['HTTP_USER_AGENT'])); 1023 1024 } 1025 } 1026 1027 /** 1028 * The SP uses this function to kill the session on the parent IdP 1029 * 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 1033 */ 1034 function kill_parent($username, $useragent) { 1035 global $CFG, $USER; 1036 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 1037 $sql = " 1038 select 1039 * 1040 from 1041 {$CFG->prefix}mnet_session s 1042 where 1043 s.username = '".addslashes($username)."' AND 1044 s.useragent = '$useragent' AND 1045 s.mnethostid = '{$USER->mnethostid}'"; 1046 1047 $mnetsessions = get_records_sql($sql); 1048 1049 $ignore = delete_records('mnet_session', 1050 'username', addslashes($username), 1051 'useragent', $useragent, 1052 'mnethostid', $USER->mnethostid); 1053 1054 if (false != $mnetsessions) { 1055 $mnet_peer = new mnet_peer(); 1056 $mnet_peer->set_id($USER->mnethostid); 1057 1058 $mnet_request = new mnet_xmlrpc_client(); 1059 $mnet_request->set_method('auth/mnet/auth.php/kill_children'); 1060 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)); 1066 return false; 1067 } 1068 } 1069 1070 $_SESSION = array(); 1071 return true; 1072 } 1073 1074 /** 1075 * The IdP uses this function to kill child sessions on other hosts 1076 * 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 1080 */ 1081 function kill_children($username, $useragent) { 1082 global $CFG, $USER, $MNET_REMOTE_CLIENT; 1083 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 1084 1085 $userid = get_field('user', 'id', 'mnethostid', $CFG->mnet_localhost_id, 'username', addslashes($username)); 1086 1087 $returnstring = ''; 1088 $sql = " 1089 select 1090 * 1091 from 1092 {$CFG->prefix}mnet_session s 1093 where 1094 s.userid = '{$userid}' AND 1095 s.useragent = '{$useragent}'"; 1096 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; 1101 } else { 1102 $excludeid = -1; 1103 } 1104 1105 $mnetsessions = get_records_sql($sql); 1106 1107 if (false == $mnetsessions) { 1108 $returnstring .= "Could find no remote sessions\n$sql\n"; 1109 $mnetsessions = array(); 1110 } 1111 1112 foreach($mnetsessions as $mnetsession) { 1113 $returnstring .= "Deleting session\n"; 1114 1115 if ($mnetsession->mnethostid == $excludeid) continue; 1116 1117 $mnet_peer = new mnet_peer(); 1118 $mnet_peer->set_id($mnetsession->mnethostid); 1119 1120 $mnet_request = new mnet_xmlrpc_client(); 1121 $mnet_request->set_method('auth/mnet/auth.php/kill_child'); 1122 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 $mnetsession->mnethostid: " . 1128 join("\n", $mnet_request->error)); 1129 } 1130 } 1131 1132 $ignore = delete_records('mnet_session', 1133 'useragent', $useragent, 1134 'userid', $userid); 1135 1136 if (isset($MNET_REMOTE_CLIENT) && isset($MNET_REMOTE_CLIENT->id)) { 1137 $start = ob_start(); 1138 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(); 1144 unset($_SESSION); 1145 1146 1147 session_id($mnetsession->session_id); 1148 session_start(); 1149 session_unregister("USER"); 1150 session_unregister("SESSION"); 1151 unset($_SESSION); 1152 $_SESSION = array(); 1153 session_write_close(); 1154 1155 1156 ini_set('session.use_cookies', $uc); 1157 session_name('MoodleSession'.$CFG->sessioncookie); 1158 session_id($sessidcache); 1159 session_start(); 1160 $_SESSION = clone($sesscache); 1161 session_write_close(); 1162 1163 $end = ob_end_clean(); 1164 } else { 1165 $_SESSION = array(); 1166 } 1167 return $returnstring; 1168 } 1169 1170 /** 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. 1174 * 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 1178 */ 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(); 1184 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(); 1190 unset($_SESSION); 1191 1192 1193 session_id($session->session_id); 1194 session_start(); 1195 session_unregister("USER"); 1196 session_unregister("SESSION"); 1197 unset($_SESSION); 1198 $_SESSION = array(); 1199 session_write_close(); 1200 1201 1202 ini_set('session.use_cookies', $uc); 1203 session_name('MoodleSession'.$CFG->sessioncookie); 1204 session_id($sessidcache); 1205 session_start(); 1206 $_SESSION = clone($sesscache); 1207 session_write_close(); 1208 1209 $end = ob_end_clean(); 1210 return true; 1211 } 1212 return false; 1213 } 1214 1215 /** 1216 * To delete a host, we must delete all current sessions that users from 1217 * that host are currently engaged in. 1218 * 1219 * @param string $sessionidarray An array of session hashes 1220 * @return bool True on success 1221 */ 1222 function end_local_sessions(&$sessionArray) { 1223 global $CFG; 1224 if (is_array($sessionArray)) { 1225 $start = ob_start(); 1226 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(); 1232 unset($_SESSION); 1233 1234 while($session = array_pop($sessionArray)) { 1235 session_id($session->session_id); 1236 session_start(); 1237 session_unregister("USER"); 1238 session_unregister("SESSION"); 1239 unset($_SESSION); 1240 $_SESSION = array(); 1241 session_write_close(); 1242 } 1243 1244 ini_set('session.use_cookies', $uc); 1245 session_name('MoodleSession'.$CFG->sessioncookie); 1246 session_id($sessidcache); 1247 session_start(); 1248 $_SESSION = clone($sesscache); 1249 1250 $end = ob_end_clean(); 1251 return true; 1252 } 1253 return false; 1254 } 1255 1256 /** 1257 * Returns the user's image as a base64 encoded string. 1258 * 1259 * @param int $userid The id of the user 1260 * @return string The encoded image 1261 */ 1262 function fetch_user_image($username) { 1263 global $CFG; 1264 1265 if ($user = get_record('user', 'username', addslashes($username), 'mnethostid', $CFG->mnet_localhost_id)) { 1266 $filename1 = make_user_directory($user->id, true) . "/f1.jpg"; 1267 $filename2 = make_user_directory($user->id, true) . "/f2.jpg"; 1268 $return = array(); 1269 if (file_exists($filename1)) { 1270 $return['f1'] = base64_encode(file_get_contents($filename1)); 1271 } 1272 if (file_exists($filename2)) { 1273 $return['f2'] = base64_encode(file_get_contents($filename2)); 1274 } 1275 return $return; 1276 } 1277 return false; 1278 } 1279 1280 /** 1281 * Returns the theme information and logo url as strings. 1282 * 1283 * @return string The theme info 1284 */ 1285 function fetch_theme_info() { 1286 global $CFG; 1287 1288 $themename = "$CFG->theme"; 1289 $logourl = "$CFG->wwwroot/theme/$CFG->theme/images/logo.jpg"; 1290 1291 $return['themename'] = $themename; 1292 $return['logourl'] = $logourl; 1293 return $return; 1294 } 1295 1296 /** 1297 * Determines if an MNET host is providing the nominated service. 1298 * 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 1302 */ 1303 function has_service($mnethostid, $servicename) { 1304 global $CFG; 1305 1306 $sql = " 1307 SELECT 1308 svc.id as serviceid, 1309 svc.name, 1310 svc.description, 1311 svc.offer, 1312 svc.apiversion, 1313 h2s.id as h2s_id 1314 FROM 1315 {$CFG->prefix}mnet_host h, 1316 {$CFG->prefix}mnet_service svc, 1317 {$CFG->prefix}mnet_host2service h2s 1318 WHERE 1319 h.deleted = '0' AND 1320 h.id = h2s.hostid AND 1321 h2s.hostid = '$mnethostid' AND 1322 h2s.serviceid = svc.id AND 1323 svc.name = '$servicename' AND 1324 h2s.subscribe = '1'"; 1325 1326 return get_records_sql($sql); 1327 } 1328 1329 /** 1330 * Checks the MNET access control table to see if the username/mnethost 1331 * is permitted to login to this moodle. 1332 * 1333 * @param string $username The username 1334 * @param int $mnethostid The id of the remote mnethost 1335 * @return bool Whether the user can login from the remote host 1336 */ 1337 function can_login_remotely($username, $mnethostid) { 1338 $accessctrl = 'allow'; 1339 $aclrecord = get_record('mnet_sso_access_control', 'username', addslashes($username), 'mnet_host_id', $mnethostid); 1340 if (!empty($aclrecord)) { 1341 $accessctrl = $aclrecord->accessctrl; 1342 } 1343 return $accessctrl == 'allow'; 1344 } 1345 1346 function logoutpage_hook() { 1347 global $USER, $CFG, $redirect; 1348 1349 if (!empty($USER->mnethostid) and $USER->mnethostid != $CFG->mnet_localhost_id) { 1350 $host = get_record('mnet_host', 'id', $USER->mnethostid); 1351 $redirect = $host->wwwroot.'/'; 1352 } 1353 } 1354 1355 } 1356 1357 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |