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