[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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)) { 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, 'visible DESC,sortorder ASC'); 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 * Prints a form for configuring this authentication plugin. 621 * 622 * This function is called from admin/auth.php, and outputs a full page with 623 * a form for configuring this plugin. 624 * 625 * @param object $config 626 * @param object $err 627 * @param array $user_fields 628 */ 629 function config_form($config, $err, $user_fields) { 630 global $CFG, $DB; 631 632 $query = " 633 SELECT 634 h.id, 635 h.name as hostname, 636 h.wwwroot, 637 h2idp.publish as idppublish, 638 h2idp.subscribe as idpsubscribe, 639 idp.name as idpname, 640 h2sp.publish as sppublish, 641 h2sp.subscribe as spsubscribe, 642 sp.name as spname 643 FROM 644 {mnet_host} h 645 LEFT JOIN 646 {mnet_host2service} h2idp 647 ON 648 (h.id = h2idp.hostid AND 649 (h2idp.publish = 1 OR 650 h2idp.subscribe = 1)) 651 INNER JOIN 652 {mnet_service} idp 653 ON 654 (h2idp.serviceid = idp.id AND 655 idp.name = 'sso_idp') 656 LEFT JOIN 657 {mnet_host2service} h2sp 658 ON 659 (h.id = h2sp.hostid AND 660 (h2sp.publish = 1 OR 661 h2sp.subscribe = 1)) 662 INNER JOIN 663 {mnet_service} sp 664 ON 665 (h2sp.serviceid = sp.id AND 666 sp.name = 'sso_sp') 667 WHERE 668 ((h2idp.publish = 1 AND h2sp.subscribe = 1) OR 669 (h2sp.publish = 1 AND h2idp.subscribe = 1)) AND 670 h.id != ? 671 ORDER BY 672 h.name ASC"; 673 674 $id_providers = array(); 675 $service_providers = array(); 676 if ($resultset = $DB->get_records_sql($query, array($CFG->mnet_localhost_id))) { 677 foreach($resultset as $hostservice) { 678 if(!empty($hostservice->idppublish) && !empty($hostservice->spsubscribe)) { 679 $service_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot); 680 } 681 if(!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) { 682 $id_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot); 683 } 684 } 685 } 686 687 include "config.html"; 688 } 689 690 /** 691 * Processes and stores configuration data for this authentication plugin. 692 */ 693 function process_config($config) { 694 // set to defaults if undefined 695 if (!isset ($config->rpc_negotiation_timeout)) { 696 $config->rpc_negotiation_timeout = '30'; 697 } 698 /* 699 if (!isset ($config->auto_add_remote_users)) { 700 $config->auto_add_remote_users = '0'; 701 } See MDL-21327 for why this is commented out 702 set_config('auto_add_remote_users', $config->auto_add_remote_users, 'auth_mnet'); 703 */ 704 705 // save settings 706 set_config('rpc_negotiation_timeout', $config->rpc_negotiation_timeout, 'auth_mnet'); 707 708 return true; 709 } 710 711 /** 712 * Poll the IdP server to let it know that a user it has authenticated is still 713 * online 714 * 715 * @return void 716 */ 717 function keepalive_client() { 718 global $CFG, $DB; 719 $cutoff = time() - 300; // TODO - find out what the remote server's session 720 // cutoff is, and preempt that 721 722 $sql = " 723 select 724 id, 725 username, 726 mnethostid 727 from 728 {user} 729 where 730 lastaccess > ? AND 731 mnethostid != ? 732 order by 733 mnethostid"; 734 735 $immigrants = $DB->get_records_sql($sql, array($cutoff, $CFG->mnet_localhost_id)); 736 737 if ($immigrants == false) { 738 return true; 739 } 740 741 $usersArray = array(); 742 foreach($immigrants as $immigrant) { 743 $usersArray[$immigrant->mnethostid][] = $immigrant->username; 744 } 745 746 require_once $CFG->dirroot . '/mnet/xmlrpc/client.php'; 747 foreach($usersArray as $mnethostid => $users) { 748 $mnet_peer = new mnet_peer(); 749 $mnet_peer->set_id($mnethostid); 750 751 $mnet_request = new mnet_xmlrpc_client(); 752 $mnet_request->set_method('auth/mnet/auth.php/keepalive_server'); 753 754 // set $token and $useragent parameters 755 $mnet_request->add_param($users); 756 757 if ($mnet_request->send($mnet_peer) === true) { 758 if (!isset($mnet_request->response['code'])) { 759 debugging("Server side error has occured on host $mnethostid"); 760 continue; 761 } elseif ($mnet_request->response['code'] > 0) { 762 debugging($mnet_request->response['message']); 763 } 764 765 if (!isset($mnet_request->response['last log id'])) { 766 debugging("Server side error has occured on host $mnethostid\nNo log ID was received."); 767 continue; 768 } 769 } else { 770 debugging("Server side error has occured on host $mnethostid: " . 771 join("\n", $mnet_request->error)); 772 break; 773 } 774 } 775 } 776 777 /** 778 * Receives an array of log entries from an SP and adds them to the mnet_log 779 * table 780 * 781 * @deprecated since Moodle 2.8 Please don't use this function for recording mnet logs. 782 * @param array $array An array of usernames 783 * @return string "All ok" or an error message 784 */ 785 function refresh_log($array) { 786 debugging('refresh_log() is deprecated, The transfer of logs through mnet are no longer recorded.', DEBUG_DEVELOPER); 787 return array('code' => 0, 'message' => 'All ok'); 788 } 789 790 /** 791 * Receives an array of usernames from a remote machine and prods their 792 * sessions to keep them alive 793 * 794 * @param array $array An array of usernames 795 * @return string "All ok" or an error message 796 */ 797 function keepalive_server($array) { 798 global $CFG, $DB; 799 $remoteclient = get_mnet_remote_client(); 800 801 // We don't want to output anything to the client machine 802 $start = ob_start(); 803 804 // We'll get session records in batches of 30 805 $superArray = array_chunk($array, 30); 806 807 $returnString = ''; 808 809 foreach($superArray as $subArray) { 810 $subArray = array_values($subArray); 811 $instring = "('".implode("', '",$subArray)."')"; 812 $query = "select id, session_id, username from {mnet_session} where username in $instring"; 813 $results = $DB->get_records_sql($query); 814 815 if ($results == false) { 816 // We seem to have a username that breaks our query: 817 // TODO: Handle this error appropriately 818 $returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n"; 819 } else { 820 foreach($results as $emigrant) { 821 \core\session\manager::touch_session($emigrant->session_id); 822 } 823 } 824 } 825 826 $end = ob_end_clean(); 827 828 if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $remoteclient->last_log_id); 829 return array('code' => 1, 'message' => $returnString, 'last log id' => $remoteclient->last_log_id); 830 } 831 832 /** 833 * Cron function will be called automatically by cron.php every 5 minutes 834 * 835 * @return void 836 */ 837 function cron() { 838 global $DB; 839 840 // run the keepalive client 841 $this->keepalive_client(); 842 843 $random100 = rand(0,100); 844 if ($random100 < 10) { // Approximately 10% of the time. 845 // nuke olden sessions 846 $longtime = time() - (1 * 3600 * 24); 847 $DB->delete_records_select('mnet_session', "expires < ?", array($longtime)); 848 } 849 } 850 851 /** 852 * Cleanup any remote mnet_sessions, kill the local mnet_session data 853 * 854 * This is called by require_logout in moodlelib 855 * 856 * @return void 857 */ 858 function prelogout_hook() { 859 global $CFG, $USER; 860 861 if (!is_enabled_auth('mnet')) { 862 return; 863 } 864 865 // If the user is local to this Moodle: 866 if ($USER->mnethostid == $this->mnet->id) { 867 $this->kill_children($USER->username, sha1($_SERVER['HTTP_USER_AGENT'])); 868 869 // Else the user has hit 'logout' at a Service Provider Moodle: 870 } else { 871 $this->kill_parent($USER->username, sha1($_SERVER['HTTP_USER_AGENT'])); 872 873 } 874 } 875 876 /** 877 * The SP uses this function to kill the session on the parent IdP 878 * 879 * @param string $username Username for session to kill 880 * @param string $useragent SHA1 hash of user agent to look for 881 * @return string A plaintext report of what has happened 882 */ 883 function kill_parent($username, $useragent) { 884 global $CFG, $USER, $DB; 885 886 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 887 $sql = " 888 select 889 * 890 from 891 {mnet_session} s 892 where 893 s.username = ? AND 894 s.useragent = ? AND 895 s.mnethostid = ?"; 896 897 $mnetsessions = $DB->get_records_sql($sql, array($username, $useragent, $USER->mnethostid)); 898 899 $ignore = $DB->delete_records('mnet_session', 900 array('username'=>$username, 901 'useragent'=>$useragent, 902 'mnethostid'=>$USER->mnethostid)); 903 904 if (false != $mnetsessions) { 905 $mnet_peer = new mnet_peer(); 906 $mnet_peer->set_id($USER->mnethostid); 907 908 $mnet_request = new mnet_xmlrpc_client(); 909 $mnet_request->set_method('auth/mnet/auth.php/kill_children'); 910 911 // set $token and $useragent parameters 912 $mnet_request->add_param($username); 913 $mnet_request->add_param($useragent); 914 if ($mnet_request->send($mnet_peer) === false) { 915 debugging(join("\n", $mnet_request->error)); 916 return false; 917 } 918 } 919 920 return true; 921 } 922 923 /** 924 * The IdP uses this function to kill child sessions on other hosts 925 * 926 * @param string $username Username for session to kill 927 * @param string $useragent SHA1 hash of user agent to look for 928 * @return string A plaintext report of what has happened 929 */ 930 function kill_children($username, $useragent) { 931 global $CFG, $USER, $DB; 932 $remoteclient = null; 933 if (defined('MNET_SERVER')) { 934 $remoteclient = get_mnet_remote_client(); 935 } 936 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 937 938 $userid = $DB->get_field('user', 'id', array('mnethostid'=>$CFG->mnet_localhost_id, 'username'=>$username)); 939 940 $returnstring = ''; 941 942 $mnetsessions = $DB->get_records('mnet_session', array('userid' => $userid, 'useragent' => $useragent)); 943 944 if (false == $mnetsessions) { 945 $returnstring .= "Could find no remote sessions\n"; 946 $mnetsessions = array(); 947 } 948 949 foreach($mnetsessions as $mnetsession) { 950 // If this script is being executed by a remote peer, that means the user has clicked 951 // logout on that peer, and the session on that peer can be deleted natively. 952 // Skip over it. 953 if (isset($remoteclient->id) && ($mnetsession->mnethostid == $remoteclient->id)) { 954 continue; 955 } 956 $returnstring .= "Deleting session\n"; 957 958 $mnet_peer = new mnet_peer(); 959 $mnet_peer->set_id($mnetsession->mnethostid); 960 961 $mnet_request = new mnet_xmlrpc_client(); 962 $mnet_request->set_method('auth/mnet/auth.php/kill_child'); 963 964 // set $token and $useragent parameters 965 $mnet_request->add_param($username); 966 $mnet_request->add_param($useragent); 967 if ($mnet_request->send($mnet_peer) === false) { 968 debugging("Server side error has occured on host $mnetsession->mnethostid: " . 969 join("\n", $mnet_request->error)); 970 } 971 } 972 973 $ignore = $DB->delete_records('mnet_session', 974 array('useragent'=>$useragent, 'userid'=>$userid)); 975 976 if (isset($remoteclient) && isset($remoteclient->id)) { 977 \core\session\manager::kill_user_sessions($userid); 978 } 979 return $returnstring; 980 } 981 982 /** 983 * When the IdP requests that child sessions are terminated, 984 * this function will be called on each of the child hosts. The machine that 985 * calls the function (over xmlrpc) provides us with the mnethostid we need. 986 * 987 * @param string $username Username for session to kill 988 * @param string $useragent SHA1 hash of user agent to look for 989 * @return bool True on success 990 */ 991 function kill_child($username, $useragent) { 992 global $CFG, $DB; 993 $remoteclient = get_mnet_remote_client(); 994 $session = $DB->get_record('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent)); 995 $DB->delete_records('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent)); 996 if (false != $session) { 997 \core\session\manager::kill_session($session->session_id); 998 return true; 999 } 1000 return false; 1001 } 1002 1003 /** 1004 * To delete a host, we must delete all current sessions that users from 1005 * that host are currently engaged in. 1006 * 1007 * @param string $sessionidarray An array of session hashes 1008 * @return bool True on success 1009 */ 1010 function end_local_sessions(&$sessionArray) { 1011 global $CFG; 1012 if (is_array($sessionArray)) { 1013 while($session = array_pop($sessionArray)) { 1014 \core\session\manager::kill_session($session->session_id); 1015 } 1016 return true; 1017 } 1018 return false; 1019 } 1020 1021 /** 1022 * Returns the user's profile image info 1023 * 1024 * If the user exists and has a profile picture, the returned array will contain keys: 1025 * f1 - the content of the default 100x100px image 1026 * f1_mimetype - the mimetype of the f1 file 1027 * f2 - the content of the 35x35px variant of the image 1028 * f2_mimetype - the mimetype of the f2 file 1029 * 1030 * The mimetype information was added in Moodle 2.0. In Moodle 1.x, images are always jpegs. 1031 * 1032 * @see process_new_icon() 1033 * @uses mnet_remote_client callable via MNet XML-RPC 1034 * @param int $username The id of the user 1035 * @return false|array false if user not found, empty array if no picture exists, array with data otherwise 1036 */ 1037 function fetch_user_image($username) { 1038 global $CFG, $DB; 1039 1040 if ($user = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id))) { 1041 $fs = get_file_storage(); 1042 $usercontext = context_user::instance($user->id, MUST_EXIST); 1043 $return = array(); 1044 if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) { 1045 $return['f1'] = base64_encode($f1->get_content()); 1046 $return['f1_mimetype'] = $f1->get_mimetype(); 1047 } else if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) { 1048 $return['f1'] = base64_encode($f1->get_content()); 1049 $return['f1_mimetype'] = $f1->get_mimetype(); 1050 } 1051 if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.png')) { 1052 $return['f2'] = base64_encode($f2->get_content()); 1053 $return['f2_mimetype'] = $f2->get_mimetype(); 1054 } else if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.jpg')) { 1055 $return['f2'] = base64_encode($f2->get_content()); 1056 $return['f2_mimetype'] = $f2->get_mimetype(); 1057 } 1058 return $return; 1059 } 1060 return false; 1061 } 1062 1063 /** 1064 * Returns the theme information and logo url as strings. 1065 * 1066 * @return string The theme info 1067 */ 1068 function fetch_theme_info() { 1069 global $CFG; 1070 1071 $themename = "$CFG->theme"; 1072 $logourl = "$CFG->wwwroot/theme/$CFG->theme/images/logo.jpg"; 1073 1074 $return['themename'] = $themename; 1075 $return['logourl'] = $logourl; 1076 return $return; 1077 } 1078 1079 /** 1080 * Determines if an MNET host is providing the nominated service. 1081 * 1082 * @param int $mnethostid The id of the remote host 1083 * @param string $servicename The name of the service 1084 * @return bool Whether the service is available on the remote host 1085 */ 1086 function has_service($mnethostid, $servicename) { 1087 global $CFG, $DB; 1088 1089 $sql = " 1090 SELECT 1091 svc.id as serviceid, 1092 svc.name, 1093 svc.description, 1094 svc.offer, 1095 svc.apiversion, 1096 h2s.id as h2s_id 1097 FROM 1098 {mnet_host} h, 1099 {mnet_service} svc, 1100 {mnet_host2service} h2s 1101 WHERE 1102 h.deleted = '0' AND 1103 h.id = h2s.hostid AND 1104 h2s.hostid = ? AND 1105 h2s.serviceid = svc.id AND 1106 svc.name = ? AND 1107 h2s.subscribe = '1'"; 1108 1109 return $DB->get_records_sql($sql, array($mnethostid, $servicename)); 1110 } 1111 1112 /** 1113 * Checks the MNET access control table to see if the username/mnethost 1114 * is permitted to login to this moodle. 1115 * 1116 * @param string $username The username 1117 * @param int $mnethostid The id of the remote mnethost 1118 * @return bool Whether the user can login from the remote host 1119 */ 1120 function can_login_remotely($username, $mnethostid) { 1121 global $DB; 1122 1123 $accessctrl = 'allow'; 1124 $aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnethostid)); 1125 if (!empty($aclrecord)) { 1126 $accessctrl = $aclrecord->accessctrl; 1127 } 1128 return $accessctrl == 'allow'; 1129 } 1130 1131 function logoutpage_hook() { 1132 global $USER, $CFG, $redirect, $DB; 1133 1134 if (!empty($USER->mnethostid) and $USER->mnethostid != $CFG->mnet_localhost_id) { 1135 $host = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid)); 1136 $redirect = $host->wwwroot.'/'; 1137 } 1138 } 1139 1140 /** 1141 * Trims a log line from mnet peer to limit each part to a length which can be stored in our DB 1142 * 1143 * @param object $logline The log information to be trimmed 1144 * @return object The passed logline object trimmed to not exceed storable limits 1145 */ 1146 function trim_logline ($logline) { 1147 $limits = array('ip' => 15, 'coursename' => 40, 'module' => 20, 'action' => 40, 1148 'url' => 255); 1149 foreach ($limits as $property => $limit) { 1150 if (isset($logline->$property)) { 1151 $logline->$property = substr($logline->$property, 0, $limit); 1152 } 1153 } 1154 1155 return $logline; 1156 } 1157 1158 /** 1159 * Returns a list of potential IdPs that this authentication plugin supports. 1160 * This is used to provide links on the login page. 1161 * 1162 * @param string $wantsurl the relative url fragment the user wants to get to. You can use this to compose a returnurl, for example 1163 * 1164 * @return array like: 1165 * array( 1166 * array( 1167 * 'url' => 'http://someurl', 1168 * 'icon' => new pix_icon(...), 1169 * 'name' => get_string('somename', 'auth_yourplugin'), 1170 * ), 1171 * ) 1172 */ 1173 function loginpage_idp_list($wantsurl) { 1174 global $DB, $CFG; 1175 1176 // strip off wwwroot, since the remote site will prefix it's return url with this 1177 $wantsurl = preg_replace('/(' . preg_quote($CFG->wwwroot, '/') . '|' . preg_quote($CFG->httpswwwroot, '/') . ')/', '', $wantsurl); 1178 1179 $sql = "SELECT DISTINCT h.id, h.wwwroot, h.name, a.sso_jump_url, a.name as application 1180 FROM {mnet_host} h 1181 JOIN {mnet_host2service} m ON h.id = m.hostid 1182 JOIN {mnet_service} s ON s.id = m.serviceid 1183 JOIN {mnet_application} a ON h.applicationid = a.id 1184 WHERE s.name = ? AND h.deleted = ? AND m.publish = ?"; 1185 $params = array('sso_sp', 0, 1); 1186 1187 if (!empty($CFG->mnet_all_hosts_id)) { 1188 $sql .= " AND h.id <> ?"; 1189 $params[] = $CFG->mnet_all_hosts_id; 1190 } 1191 1192 if (!$hosts = $DB->get_records_sql($sql, $params)) { 1193 return array(); 1194 } 1195 1196 $idps = array(); 1197 foreach ($hosts as $host) { 1198 $idps[] = array( 1199 'url' => new moodle_url($host->wwwroot . $host->sso_jump_url, array('hostwwwroot' => $CFG->wwwroot, 'wantsurl' => $wantsurl, 'remoteurl' => 1)), 1200 'icon' => new pix_icon('i/' . $host->application . '_host', $host->name), 1201 'name' => $host->name, 1202 ); 1203 } 1204 return $idps; 1205 } 1206 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |