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