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