Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 282 $firsttime = false; 283 284 // get the local record for the remote user 285 $localuser = $DB->get_record('user', array('username'=>$remoteuser->username, 'mnethostid'=>$remotehost->id)); 286 287 // add the remote user to the database if necessary, and if allowed 288 // TODO: refactor into a separate function 289 if (empty($localuser) || ! $localuser->id) { 290 /* 291 if (empty($this->config->auto_add_remote_users)) { 292 print_error('nolocaluser', 'mnet'); 293 } See MDL-21327 for why this is commented out 294 */ 295 $remoteuser->mnethostid = $remotehost->id; 296 $remoteuser->firstaccess = 0; 297 $remoteuser->confirmed = 1; 298 299 $remoteuser->id = user_create_user($remoteuser, false); 300 $firsttime = true; 301 $localuser = $remoteuser; 302 } 303 304 // check sso access control list for permission first 305 if (!$this->can_login_remotely($localuser->username, $remotehost->id)) { 306 print_error('sso_mnet_login_refused', 'mnet', '', array('user'=>$localuser->username, 'host'=>$remotehost->name)); 307 } 308 309 $fs = get_file_storage(); 310 311 // update the local user record with remote user data 312 foreach ((array) $remoteuser as $key => $val) { 313 314 if ($key == '_mnet_userpicture_timemodified' and empty($CFG->disableuserimages) and isset($remoteuser->picture)) { 315 // update the user picture if there is a newer verion at the identity provider 316 $usercontext = context_user::instance($localuser->id, MUST_EXIST); 317 if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) { 318 $localtimemodified = $usericonfile->get_timemodified(); 319 } else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) { 320 $localtimemodified = $usericonfile->get_timemodified(); 321 } else { 322 $localtimemodified = 0; 323 } 324 325 if (!empty($val) and $localtimemodified < $val) { 326 mnet_debug('refetching the user picture from the identity provider host'); 327 $fetchrequest = new mnet_xmlrpc_client(); 328 $fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image'); 329 $fetchrequest->add_param($localuser->username); 330 if ($fetchrequest->send($remotepeer) === true) { 331 if (strlen($fetchrequest->response['f1']) > 0) { 332 $imagefilename = $CFG->tempdir . '/mnet-usericon-' . $localuser->id; 333 $imagecontents = base64_decode($fetchrequest->response['f1']); 334 file_put_contents($imagefilename, $imagecontents); 335 if ($newrev = process_new_icon($usercontext, 'user', 'icon', 0, $imagefilename)) { 336 $localuser->picture = $newrev; 337 } 338 unlink($imagefilename); 339 } 340 // note that since Moodle 2.0 we ignore $fetchrequest->response['f2'] 341 // the mimetype information provided is ignored and the type of the file is detected 342 // by process_new_icon() 343 } 344 } 345 } 346 347 if($key == 'myhosts') { 348 $localuser->mnet_foreign_host_array = array(); 349 foreach($val as $rhost) { 350 $name = clean_param($rhost['name'], PARAM_ALPHANUM); 351 $url = clean_param($rhost['url'], PARAM_URL); 352 $count = clean_param($rhost['count'], PARAM_INT); 353 $url_is_local = stristr($url , $CFG->wwwroot); 354 if (!empty($name) && !empty($count) && empty($url_is_local)) { 355 $localuser->mnet_foreign_host_array[] = array('name' => $name, 356 'url' => $url, 357 'count' => $count); 358 } 359 } 360 } 361 362 $localuser->{$key} = $val; 363 } 364 365 $localuser->mnethostid = $remotepeer->id; 366 user_update_user($localuser, false); 367 368 if (!$firsttime) { 369 // repeat customer! let the IDP know about enrolments 370 // we have for this user. 371 // set up the RPC request 372 $mnetrequest = new mnet_xmlrpc_client(); 373 $mnetrequest->set_method('auth/mnet/auth.php/update_enrolments'); 374 375 // pass username and an assoc array of "my courses" 376 // with info so that the IDP can maintain mnetservice_enrol_enrolments 377 $mnetrequest->add_param($remoteuser->username); 378 $fields = 'id, category, sortorder, fullname, shortname, idnumber, summary, startdate, visible'; 379 $courses = enrol_get_users_courses($localuser->id, false, $fields); 380 if (is_array($courses) && !empty($courses)) { 381 // Second request to do the JOINs that we'd have done 382 // inside enrol_get_users_courses() if we had been allowed 383 $sql = "SELECT c.id, 384 cc.name AS cat_name, cc.description AS cat_description 385 FROM {course} c 386 JOIN {course_categories} cc ON c.category = cc.id 387 WHERE c.id IN (" . join(',',array_keys($courses)) . ')'; 388 $extra = $DB->get_records_sql($sql); 389 390 $keys = array_keys($courses); 391 $studentroles = get_archetype_roles('student'); 392 if (!empty($studentroles)) { 393 $defaultrole = reset($studentroles); 394 //$defaultrole = get_default_course_role($ccache[$shortname]); //TODO: rewrite this completely, there is no default course role any more!!! 395 foreach ($keys AS $id) { 396 if ($courses[$id]->visible == 0) { 397 unset($courses[$id]); 398 continue; 399 } 400 $courses[$id]->cat_id = $courses[$id]->category; 401 $courses[$id]->defaultroleid = $defaultrole->id; 402 unset($courses[$id]->category); 403 unset($courses[$id]->visible); 404 405 $courses[$id]->cat_name = $extra[$id]->cat_name; 406 $courses[$id]->cat_description = $extra[$id]->cat_description; 407 $courses[$id]->defaultrolename = $defaultrole->name; 408 // coerce to array 409 $courses[$id] = (array)$courses[$id]; 410 } 411 } else { 412 throw new moodle_exception('unknownrole', 'error', '', 'student'); 413 } 414 } else { 415 // if the array is empty, send it anyway 416 // we may be clearing out stale entries 417 $courses = array(); 418 } 419 $mnetrequest->add_param($courses); 420 421 // Call 0800-RPC Now! -- we don't care too much if it fails 422 // as it's just informational. 423 if ($mnetrequest->send($remotepeer) === false) { 424 // error_log(print_r($mnetrequest->error,1)); 425 } 426 } 427 428 return $localuser; 429 } 430 431 432 /** 433 * creates (or updates) the mnet session once 434 * {@see confirm_mnet_session} and {@see complete_user_login} have both been called 435 * 436 * @param stdclass $user the local user (must exist already 437 * @param string $token the jump/land token 438 * @param mnet_peer $remotepeer the mnet_peer object of this users's idp 439 */ 440 public function update_mnet_session($user, $token, $remotepeer) { 441 global $DB; 442 $session_gc_maxlifetime = 1440; 443 if (isset($user->session_gc_maxlifetime)) { 444 $session_gc_maxlifetime = $user->session_gc_maxlifetime; 445 } 446 if (!$mnet_session = $DB->get_record('mnet_session', 447 array('userid'=>$user->id, 'mnethostid'=>$remotepeer->id, 448 'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])))) { 449 $mnet_session = new stdClass(); 450 $mnet_session->mnethostid = $remotepeer->id; 451 $mnet_session->userid = $user->id; 452 $mnet_session->username = $user->username; 453 $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']); 454 $mnet_session->token = $token; // Needed to support simultaneous sessions 455 // and preserving DB rec uniqueness 456 $mnet_session->confirm_timeout = time(); 457 $mnet_session->expires = time() + (integer)$session_gc_maxlifetime; 458 $mnet_session->session_id = session_id(); 459 $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session); 460 } else { 461 $mnet_session->expires = time() + (integer)$session_gc_maxlifetime; 462 $DB->update_record('mnet_session', $mnet_session); 463 } 464 } 465 466 467 468 /** 469 * Invoke this function _on_ the IDP to update it with enrolment info local to 470 * the SP right after calling user_authorise() 471 * 472 * Normally called by the SP after calling user_authorise() 473 * 474 * @param string $username The username 475 * @param array $courses Assoc array of courses following the structure of mnetservice_enrol_courses 476 * @return bool 477 */ 478 function update_enrolments($username, $courses) { 479 global $CFG, $DB; 480 $remoteclient = get_mnet_remote_client(); 481 482 if (empty($username) || !is_array($courses)) { 483 return false; 484 } 485 // make sure it is a user we have an in active session 486 // with that host... 487 $mnetsessions = $DB->get_records('mnet_session', array('username' => $username, 'mnethostid' => $remoteclient->id), '', 'id, userid'); 488 $userid = null; 489 foreach ($mnetsessions as $mnetsession) { 490 if (is_null($userid)) { 491 $userid = $mnetsession->userid; 492 continue; 493 } 494 if ($userid != $mnetsession->userid) { 495 throw new mnet_server_exception(3, 'authfail_usermismatch'); 496 } 497 } 498 499 if (empty($courses)) { // no courses? clear out quickly 500 $DB->delete_records('mnetservice_enrol_enrolments', array('hostid'=>$remoteclient->id, 'userid'=>$userid)); 501 return true; 502 } 503 504 // IMPORTANT: Ask for remoteid as the first element in the query, so 505 // that the array that comes back is indexed on the same field as the 506 // array that we have received from the remote client 507 $sql = "SELECT c.remoteid, c.id, c.categoryid AS cat_id, c.categoryname AS cat_name, c.sortorder, 508 c.fullname, c.shortname, c.idnumber, c.summary, c.summaryformat, c.startdate, 509 e.id AS enrolmentid 510 FROM {mnetservice_enrol_courses} c 511 LEFT JOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid) 512 WHERE e.userid = ? AND c.hostid = ?"; 513 514 $currentcourses = $DB->get_records_sql($sql, array($userid, $remoteclient->id)); 515 516 $local_courseid_array = array(); 517 foreach($courses as $ix => $course) { 518 519 $course['remoteid'] = $course['id']; 520 $course['hostid'] = (int)$remoteclient->id; 521 $userisregd = false; 522 523 // if we do not have the the information about the remote course, it is not available 524 // to us for remote enrolment - skip 525 if (array_key_exists($course['remoteid'], $currentcourses)) { 526 // Pointer to current course: 527 $currentcourse =& $currentcourses[$course['remoteid']]; 528 // We have a record - is it up-to-date? 529 $course['id'] = $currentcourse->id; 530 531 $saveflag = false; 532 533 foreach($course as $key => $value) { 534 if ($currentcourse->$key != $value) { 535 $saveflag = true; 536 $currentcourse->$key = $value; 537 } 538 } 539 540 if ($saveflag) { 541 $DB->update_record('mnetervice_enrol_courses', $currentcourse); 542 } 543 544 if (isset($currentcourse->enrolmentid) && is_numeric($currentcourse->enrolmentid)) { 545 $userisregd = true; 546 } 547 } else { 548 unset ($courses[$ix]); 549 continue; 550 } 551 552 // By this point, we should always have a $dataObj->id 553 $local_courseid_array[] = $course['id']; 554 555 // Do we have a record for this assignment? 556 if ($userisregd) { 557 // Yes - we know about this one already 558 // We don't want to do updates because the new data is probably 559 // 'less complete' than the data we have. 560 } else { 561 // No - create a record 562 $assignObj = new stdClass(); 563 $assignObj->userid = $userid; 564 $assignObj->hostid = (int)$remoteclient->id; 565 $assignObj->remotecourseid = $course['remoteid']; 566 $assignObj->rolename = $course['defaultrolename']; 567 $assignObj->id = $DB->insert_record('mnetservice_enrol_enrolments', $assignObj); 568 } 569 } 570 571 // Clean up courses that the user is no longer enrolled in. 572 if (!empty($local_courseid_array)) { 573 $local_courseid_string = implode(', ', $local_courseid_array); 574 $whereclause = " userid = ? AND hostid = ? AND remotecourseid NOT IN ($local_courseid_string)"; 575 $DB->delete_records_select('mnetservice_enrol_enrolments', $whereclause, array($userid, $remoteclient->id)); 576 } 577 } 578 579 function prevent_local_passwords() { 580 return true; 581 } 582 583 /** 584 * Returns true if this authentication plugin is 'internal'. 585 * 586 * @return bool 587 */ 588 function is_internal() { 589 return false; 590 } 591 592 /** 593 * Returns true if this authentication plugin can change the user's 594 * password. 595 * 596 * @return bool 597 */ 598 function can_change_password() { 599 //TODO: it should be able to redirect, right? 600 return false; 601 } 602 603 /** 604 * Returns the URL for changing the user's pw, or false if the default can 605 * be used. 606 * 607 * @return moodle_url 608 */ 609 function change_password_url() { 610 return null; 611 } 612 613 /** 614 * Poll the IdP server to let it know that a user it has authenticated is still 615 * online 616 * 617 * @return void 618 */ 619 function keepalive_client() { 620 global $CFG, $DB; 621 $cutoff = time() - 300; // TODO - find out what the remote server's session 622 // cutoff is, and preempt that 623 624 $sql = " 625 select 626 id, 627 username, 628 mnethostid 629 from 630 {user} 631 where 632 lastaccess > ? AND 633 mnethostid != ? 634 order by 635 mnethostid"; 636 637 $immigrants = $DB->get_records_sql($sql, array($cutoff, $CFG->mnet_localhost_id)); 638 639 if ($immigrants == false) { 640 return true; 641 } 642 643 $usersArray = array(); 644 foreach($immigrants as $immigrant) { 645 $usersArray[$immigrant->mnethostid][] = $immigrant->username; 646 } 647 648 require_once $CFG->dirroot . '/mnet/xmlrpc/client.php'; 649 foreach($usersArray as $mnethostid => $users) { 650 $mnet_peer = new mnet_peer(); 651 $mnet_peer->set_id($mnethostid); 652 653 $mnet_request = new mnet_xmlrpc_client(); 654 $mnet_request->set_method('auth/mnet/auth.php/keepalive_server'); 655 656 // set $token and $useragent parameters 657 $mnet_request->add_param($users); 658 659 if ($mnet_request->send($mnet_peer) === true) { 660 if (!isset($mnet_request->response['code'])) { 661 debugging("Server side error has occured on host $mnethostid"); 662 continue; 663 } elseif ($mnet_request->response['code'] > 0) { 664 debugging($mnet_request->response['message']); 665 } 666 667 if (!isset($mnet_request->response['last log id'])) { 668 debugging("Server side error has occured on host $mnethostid\nNo log ID was received."); 669 continue; 670 } 671 } else { 672 debugging("Server side error has occured on host $mnethostid: " . 673 join("\n", $mnet_request->error)); 674 break; 675 } 676 } 677 } 678 679 /** 680 * Receives an array of log entries from an SP and adds them to the mnet_log 681 * table 682 * 683 * @deprecated since Moodle 2.8 Please don't use this function for recording mnet logs. 684 * @param array $array An array of usernames 685 * @return string "All ok" or an error message 686 */ 687 function refresh_log($array) { 688 debugging('refresh_log() is deprecated, The transfer of logs through mnet are no longer recorded.', DEBUG_DEVELOPER); 689 return array('code' => 0, 'message' => 'All ok'); 690 } 691 692 /** 693 * Receives an array of usernames from a remote machine and prods their 694 * sessions to keep them alive 695 * 696 * @param array $array An array of usernames 697 * @return string "All ok" or an error message 698 */ 699 function keepalive_server($array) { 700 global $CFG, $DB; 701 $remoteclient = get_mnet_remote_client(); 702 703 // We don't want to output anything to the client machine 704 $start = ob_start(); 705 706 // We'll get session records in batches of 30 707 $superArray = array_chunk($array, 30); 708 709 $returnString = ''; 710 711 foreach($superArray as $subArray) { 712 $subArray = array_values($subArray); 713 $results = $DB->get_records_list('mnet_session', 'username', $subArray, '', 'id, session_id, username'); 714 715 if ($results == false) { 716 // We seem to have a username that breaks our query: 717 // TODO: Handle this error appropriately 718 $returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n"; 719 } else { 720 foreach($results as $emigrant) { 721 \core\session\manager::touch_session($emigrant->session_id); 722 } 723 } 724 } 725 726 $end = ob_end_clean(); 727 728 if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $remoteclient->last_log_id); 729 return array('code' => 1, 'message' => $returnString, 'last log id' => $remoteclient->last_log_id); 730 } 731 732 /** 733 * Cleanup any remote mnet_sessions, kill the local mnet_session data 734 * 735 * This is called by require_logout in moodlelib 736 * 737 * @return void 738 */ 739 function prelogout_hook() { 740 global $CFG, $USER; 741 742 if (!is_enabled_auth('mnet')) { 743 return; 744 } 745 746 // If the user is local to this Moodle: 747 if ($USER->mnethostid == $this->mnet->id) { 748 $this->kill_children($USER->username, sha1($_SERVER['HTTP_USER_AGENT'])); 749 750 // Else the user has hit 'logout' at a Service Provider Moodle: 751 } else { 752 $this->kill_parent($USER->username, sha1($_SERVER['HTTP_USER_AGENT'])); 753 754 } 755 } 756 757 /** 758 * The SP uses this function to kill the session on the parent IdP 759 * 760 * @param string $username Username for session to kill 761 * @param string $useragent SHA1 hash of user agent to look for 762 * @return string A plaintext report of what has happened 763 */ 764 function kill_parent($username, $useragent) { 765 global $CFG, $USER, $DB; 766 767 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 768 $sql = " 769 select 770 * 771 from 772 {mnet_session} s 773 where 774 s.username = ? AND 775 s.useragent = ? AND 776 s.mnethostid = ?"; 777 778 $mnetsessions = $DB->get_records_sql($sql, array($username, $useragent, $USER->mnethostid)); 779 780 $ignore = $DB->delete_records('mnet_session', 781 array('username'=>$username, 782 'useragent'=>$useragent, 783 'mnethostid'=>$USER->mnethostid)); 784 785 if (false != $mnetsessions) { 786 $mnet_peer = new mnet_peer(); 787 $mnet_peer->set_id($USER->mnethostid); 788 789 $mnet_request = new mnet_xmlrpc_client(); 790 $mnet_request->set_method('auth/mnet/auth.php/kill_children'); 791 792 // set $token and $useragent parameters 793 $mnet_request->add_param($username); 794 $mnet_request->add_param($useragent); 795 if ($mnet_request->send($mnet_peer) === false) { 796 debugging(join("\n", $mnet_request->error)); 797 return false; 798 } 799 } 800 801 return true; 802 } 803 804 /** 805 * The IdP uses this function to kill child sessions on other hosts 806 * 807 * @param string $username Username for session to kill 808 * @param string $useragent SHA1 hash of user agent to look for 809 * @return string A plaintext report of what has happened 810 */ 811 function kill_children($username, $useragent) { 812 global $CFG, $USER, $DB; 813 $remoteclient = null; 814 if (defined('MNET_SERVER')) { 815 $remoteclient = get_mnet_remote_client(); 816 } 817 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 818 819 $userid = $DB->get_field('user', 'id', array('mnethostid'=>$CFG->mnet_localhost_id, 'username'=>$username)); 820 821 $returnstring = ''; 822 823 $mnetsessions = $DB->get_records('mnet_session', array('userid' => $userid, 'useragent' => $useragent)); 824 825 if (false == $mnetsessions) { 826 $returnstring .= "Could find no remote sessions\n"; 827 $mnetsessions = array(); 828 } 829 830 foreach($mnetsessions as $mnetsession) { 831 // If this script is being executed by a remote peer, that means the user has clicked 832 // logout on that peer, and the session on that peer can be deleted natively. 833 // Skip over it. 834 if (isset($remoteclient->id) && ($mnetsession->mnethostid == $remoteclient->id)) { 835 continue; 836 } 837 $returnstring .= "Deleting session\n"; 838 839 $mnet_peer = new mnet_peer(); 840 $mnet_peer->set_id($mnetsession->mnethostid); 841 842 $mnet_request = new mnet_xmlrpc_client(); 843 $mnet_request->set_method('auth/mnet/auth.php/kill_child'); 844 845 // set $token and $useragent parameters 846 $mnet_request->add_param($username); 847 $mnet_request->add_param($useragent); 848 if ($mnet_request->send($mnet_peer) === false) { 849 debugging("Server side error has occured on host $mnetsession->mnethostid: " . 850 join("\n", $mnet_request->error)); 851 } 852 } 853 854 $ignore = $DB->delete_records('mnet_session', 855 array('useragent'=>$useragent, 'userid'=>$userid)); 856 857 if (isset($remoteclient) && isset($remoteclient->id)) { 858 \core\session\manager::kill_user_sessions($userid); 859 } 860 return $returnstring; 861 } 862 863 /** 864 * When the IdP requests that child sessions are terminated, 865 * this function will be called on each of the child hosts. The machine that 866 * calls the function (over xmlrpc) provides us with the mnethostid we need. 867 * 868 * @param string $username Username for session to kill 869 * @param string $useragent SHA1 hash of user agent to look for 870 * @return bool True on success 871 */ 872 function kill_child($username, $useragent) { 873 global $CFG, $DB; 874 $remoteclient = get_mnet_remote_client(); 875 $session = $DB->get_record('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent)); 876 $DB->delete_records('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent)); 877 if (false != $session) { 878 \core\session\manager::kill_session($session->session_id); 879 return true; 880 } 881 return false; 882 } 883 884 /** 885 * To delete a host, we must delete all current sessions that users from 886 * that host are currently engaged in. 887 * 888 * @param string $sessionidarray An array of session hashes 889 * @return bool True on success 890 */ 891 function end_local_sessions(&$sessionArray) { 892 global $CFG; 893 if (is_array($sessionArray)) { 894 while($session = array_pop($sessionArray)) { 895 \core\session\manager::kill_session($session->session_id); 896 } 897 return true; 898 } 899 return false; 900 } 901 902 /** 903 * Returns the user's profile image info 904 * 905 * If the user exists and has a profile picture, the returned array will contain keys: 906 * f1 - the content of the default 100x100px image 907 * f1_mimetype - the mimetype of the f1 file 908 * f2 - the content of the 35x35px variant of the image 909 * f2_mimetype - the mimetype of the f2 file 910 * 911 * The mimetype information was added in Moodle 2.0. In Moodle 1.x, images are always jpegs. 912 * 913 * @see process_new_icon() 914 * @uses mnet_remote_client callable via MNet XML-RPC 915 * @param int $username The id of the user 916 * @return false|array false if user not found, empty array if no picture exists, array with data otherwise 917 */ 918 function fetch_user_image($username) { 919 global $CFG, $DB; 920 921 if ($user = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id))) { 922 $fs = get_file_storage(); 923 $usercontext = context_user::instance($user->id, MUST_EXIST); 924 $return = array(); 925 if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) { 926 $return['f1'] = base64_encode($f1->get_content()); 927 $return['f1_mimetype'] = $f1->get_mimetype(); 928 } else if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) { 929 $return['f1'] = base64_encode($f1->get_content()); 930 $return['f1_mimetype'] = $f1->get_mimetype(); 931 } 932 if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.png')) { 933 $return['f2'] = base64_encode($f2->get_content()); 934 $return['f2_mimetype'] = $f2->get_mimetype(); 935 } else if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.jpg')) { 936 $return['f2'] = base64_encode($f2->get_content()); 937 $return['f2_mimetype'] = $f2->get_mimetype(); 938 } 939 return $return; 940 } 941 return false; 942 } 943 944 /** 945 * Returns the theme information and logo url as strings. 946 * 947 * @return string The theme info 948 */ 949 function fetch_theme_info() { 950 global $CFG; 951 952 $themename = "$CFG->theme"; 953 $logourl = "$CFG->wwwroot/theme/$CFG->theme/images/logo.jpg"; 954 955 $return['themename'] = $themename; 956 $return['logourl'] = $logourl; 957 return $return; 958 } 959 960 /** 961 * Determines if an MNET host is providing the nominated service. 962 * 963 * @param int $mnethostid The id of the remote host 964 * @param string $servicename The name of the service 965 * @return bool Whether the service is available on the remote host 966 */ 967 function has_service($mnethostid, $servicename) { 968 global $CFG, $DB; 969 970 $sql = " 971 SELECT 972 svc.id as serviceid, 973 svc.name, 974 svc.description, 975 svc.offer, 976 svc.apiversion, 977 h2s.id as h2s_id 978 FROM 979 {mnet_host} h, 980 {mnet_service} svc, 981 {mnet_host2service} h2s 982 WHERE 983 h.deleted = '0' AND 984 h.id = h2s.hostid AND 985 h2s.hostid = ? AND 986 h2s.serviceid = svc.id AND 987 svc.name = ? AND 988 h2s.subscribe = '1'"; 989 990 return $DB->get_records_sql($sql, array($mnethostid, $servicename)); 991 } 992 993 /** 994 * Checks the MNET access control table to see if the username/mnethost 995 * is permitted to login to this moodle. 996 * 997 * @param string $username The username 998 * @param int $mnethostid The id of the remote mnethost 999 * @return bool Whether the user can login from the remote host 1000 */ 1001 function can_login_remotely($username, $mnethostid) { 1002 global $DB; 1003 1004 $accessctrl = 'allow'; 1005 $aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnethostid)); 1006 if (!empty($aclrecord)) { 1007 $accessctrl = $aclrecord->accessctrl; 1008 } 1009 return $accessctrl == 'allow'; 1010 } 1011 1012 function logoutpage_hook() { 1013 global $USER, $CFG, $redirect, $DB; 1014 1015 if (!empty($USER->mnethostid) and $USER->mnethostid != $CFG->mnet_localhost_id) { 1016 $host = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid)); 1017 $redirect = $host->wwwroot.'/'; 1018 } 1019 } 1020 1021 /** 1022 * Trims a log line from mnet peer to limit each part to a length which can be stored in our DB 1023 * 1024 * @param object $logline The log information to be trimmed 1025 * @return object The passed logline object trimmed to not exceed storable limits 1026 */ 1027 function trim_logline ($logline) { 1028 $limits = array('ip' => 15, 'coursename' => 40, 'module' => 20, 'action' => 40, 1029 'url' => 255); 1030 foreach ($limits as $property => $limit) { 1031 if (isset($logline->$property)) { 1032 $logline->$property = substr($logline->$property, 0, $limit); 1033 } 1034 } 1035 1036 return $logline; 1037 } 1038 1039 /** 1040 * Returns a list of MNet IdPs that the user can roam from. 1041 * 1042 * @param string $wantsurl The relative url fragment the user wants to get to. 1043 * @return array List of arrays with keys url, icon and name. 1044 */ 1045 function loginpage_idp_list($wantsurl) { 1046 global $DB, $CFG; 1047 1048 // strip off wwwroot, since the remote site will prefix it's return url with this 1049 $wantsurl = preg_replace('/(' . preg_quote($CFG->wwwroot, '/') . ')/', '', $wantsurl); 1050 1051 $sql = "SELECT DISTINCT h.id, h.wwwroot, h.name, a.sso_jump_url, a.name as application 1052 FROM {mnet_host} h 1053 JOIN {mnet_host2service} m ON h.id = m.hostid 1054 JOIN {mnet_service} s ON s.id = m.serviceid 1055 JOIN {mnet_application} a ON h.applicationid = a.id 1056 WHERE s.name = ? AND h.deleted = ? AND m.publish = ?"; 1057 $params = array('sso_sp', 0, 1); 1058 1059 if (!empty($CFG->mnet_all_hosts_id)) { 1060 $sql .= " AND h.id <> ?"; 1061 $params[] = $CFG->mnet_all_hosts_id; 1062 } 1063 1064 if (!$hosts = $DB->get_records_sql($sql, $params)) { 1065 return array(); 1066 } 1067 1068 $idps = array(); 1069 foreach ($hosts as $host) { 1070 $idps[] = array( 1071 'url' => new moodle_url($host->wwwroot . $host->sso_jump_url, array('hostwwwroot' => $CFG->wwwroot, 'wantsurl' => $wantsurl, 'remoteurl' => 1)), 1072 'icon' => new pix_icon('i/' . $host->application . '_host', $host->name), 1073 'name' => $host->name, 1074 ); 1075 } 1076 return $idps; 1077 } 1078 1079 /** 1080 * Test if settings are correct, print info to output. 1081 */ 1082 public function test_settings() { 1083 global $CFG, $OUTPUT, $DB; 1084 1085 // Generate warning if MNET is disabled. 1086 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') { 1087 echo $OUTPUT->notification(get_string('mnetdisabled', 'mnet'), 'notifyproblem'); 1088 return; 1089 } 1090 1091 // Generate full list of ID and service providers. 1092 $query = " 1093 SELECT 1094 h.id, 1095 h.name as hostname, 1096 h.wwwroot, 1097 h2idp.publish as idppublish, 1098 h2idp.subscribe as idpsubscribe, 1099 idp.name as idpname, 1100 h2sp.publish as sppublish, 1101 h2sp.subscribe as spsubscribe, 1102 sp.name as spname 1103 FROM 1104 {mnet_host} h 1105 LEFT JOIN 1106 {mnet_host2service} h2idp 1107 ON 1108 (h.id = h2idp.hostid AND 1109 (h2idp.publish = 1 OR 1110 h2idp.subscribe = 1)) 1111 INNER JOIN 1112 {mnet_service} idp 1113 ON 1114 (h2idp.serviceid = idp.id AND 1115 idp.name = 'sso_idp') 1116 LEFT JOIN 1117 {mnet_host2service} h2sp 1118 ON 1119 (h.id = h2sp.hostid AND 1120 (h2sp.publish = 1 OR 1121 h2sp.subscribe = 1)) 1122 INNER JOIN 1123 {mnet_service} sp 1124 ON 1125 (h2sp.serviceid = sp.id AND 1126 sp.name = 'sso_sp') 1127 WHERE 1128 ((h2idp.publish = 1 AND h2sp.subscribe = 1) OR 1129 (h2sp.publish = 1 AND h2idp.subscribe = 1)) AND 1130 h.id != ? 1131 ORDER BY 1132 h.name ASC"; 1133 1134 $idproviders = array(); 1135 $serviceproviders = array(); 1136 if ($resultset = $DB->get_records_sql($query, array($CFG->mnet_localhost_id))) { 1137 foreach ($resultset as $hostservice) { 1138 if (!empty($hostservice->idppublish) && !empty($hostservice->spsubscribe)) { 1139 $serviceproviders[] = array('id' => $hostservice->id, 1140 'name' => $hostservice->hostname, 1141 'wwwroot' => $hostservice->wwwroot); 1142 } 1143 if (!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) { 1144 $idproviders[] = array('id' => $hostservice->id, 1145 'name' => $hostservice->hostname, 1146 'wwwroot' => $hostservice->wwwroot); 1147 } 1148 } 1149 } 1150 1151 // ID Providers. 1152 $table = html_writer::start_tag('table', array('class' => 'generaltable')); 1153 1154 $count = 0; 1155 foreach ($idproviders as $host) { 1156 $table .= html_writer::start_tag('tr'); 1157 $table .= html_writer::start_tag('td'); 1158 $table .= $host['name']; 1159 $table .= html_writer::end_tag('td'); 1160 $table .= html_writer::start_tag('td'); 1161 $table .= $host['wwwroot']; 1162 $table .= html_writer::end_tag('td'); 1163 $table .= html_writer::end_tag('tr'); 1164 $count++; 1165 } 1166 $table .= html_writer::end_tag('table'); 1167 1168 if ($count > 0) { 1169 echo html_writer::tag('h3', get_string('auth_mnet_roamin', 'auth_mnet')); 1170 echo $table; 1171 } 1172 1173 // Service Providers. 1174 unset($table); 1175 $table = html_writer::start_tag('table', array('class' => 'generaltable')); 1176 $count = 0; 1177 foreach ($serviceproviders as $host) { 1178 $table .= html_writer::start_tag('tr'); 1179 $table .= html_writer::start_tag('td'); 1180 $table .= $host['name']; 1181 $table .= html_writer::end_tag('td'); 1182 $table .= html_writer::start_tag('td'); 1183 $table .= $host['wwwroot']; 1184 $table .= html_writer::end_tag('td'); 1185 $table .= html_writer::end_tag('tr'); 1186 $count++; 1187 } 1188 $table .= html_writer::end_tag('table'); 1189 if ($count > 0) { 1190 echo html_writer::tag('h3', get_string('auth_mnet_roamout', 'auth_mnet')); 1191 echo $table; 1192 } 1193 } 1194 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body