AutorÃa | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Authentication Plugin: Moodle Network Authentication* Multiple host authentication support for Moodle Network.** @package auth_mnet* @author Martin Dougiamas* @license http://www.gnu.org/copyleft/gpl.html GNU Public License*/defined('MOODLE_INTERNAL') || die();require_once($CFG->libdir.'/authlib.php');/*** Moodle Network authentication plugin.*/class auth_plugin_mnet extends auth_plugin_base {/** @var mnet_environment mnet environment. */protected $mnet;/*** Constructor.*/public function __construct() {$this->authtype = 'mnet';$this->config = get_config('auth_mnet');$this->mnet = get_mnet_environment();}/*** Old syntax of class constructor. Deprecated in PHP7.** @deprecated since Moodle 3.1*/public function auth_plugin_mnet() {debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);self::__construct();}/*** This function is normally used to determine if the username and password* are correct for local logins. Always returns false, as local users do not* need to login over mnet xmlrpc.** @param string $username The username* @param string $password The password* @return bool Authentication success or failure.*/function user_login($username, $password) {return false; // Throw moodle_exception("mnetlocal").}/*** Return user data for the provided token, compare with user_agent string.** @param string $token The unique ID provided by remotehost.* @param string $useragent User Agent string.* @return array $userdata Array of user info for remote host*/function user_authorise($token, $useragent) {global $CFG, $SITE, $DB;$remoteclient = get_mnet_remote_client();require_once $CFG->dirroot . '/mnet/xmlrpc/serverlib.php';$mnet_session = $DB->get_record('mnet_session', array('token'=>$token, 'useragent'=>$useragent));if (empty($mnet_session)) {throw new mnet_server_exception(1, 'authfail_nosessionexists');}// check session confirm timeoutif ($mnet_session->confirm_timeout < time()) {throw new mnet_server_exception(2, 'authfail_sessiontimedout');}// session okay, try getting the userif (!$user = $DB->get_record('user', array('id'=>$mnet_session->userid))) {throw new mnet_server_exception(3, 'authfail_usermismatch');}$userdata = mnet_strip_user((array)$user, mnet_fields_to_send($remoteclient));// extra special ones$userdata['auth'] = 'mnet';$userdata['wwwroot'] = $this->mnet->wwwroot;$userdata['session.gc_maxlifetime'] = ini_get('session.gc_maxlifetime');if (array_key_exists('picture', $userdata) && !empty($user->picture)) {$fs = get_file_storage();$usercontext = context_user::instance($user->id, MUST_EXIST);if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {$userdata['_mnet_userpicture_timemodified'] = $usericonfile->get_timemodified();$userdata['_mnet_userpicture_mimetype'] = $usericonfile->get_mimetype();} else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {$userdata['_mnet_userpicture_timemodified'] = $usericonfile->get_timemodified();$userdata['_mnet_userpicture_mimetype'] = $usericonfile->get_mimetype();}}$userdata['myhosts'] = array();if ($courses = enrol_get_users_courses($user->id, false)) {$userdata['myhosts'][] = array('name'=> $SITE->shortname, 'url' => $CFG->wwwroot, 'count' => count($courses));}$sql = "SELECT h.name AS hostname, h.wwwroot, h.id AS hostid,COUNT(c.id) AS countFROM {mnetservice_enrol_courses} cJOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid)JOIN {mnet_host} h ON h.id = c.hostidWHERE e.userid = ? AND c.hostid = ?GROUP BY h.name, h.wwwroot, h.id";if ($courses = $DB->get_records_sql($sql, array($user->id, $remoteclient->id))) {foreach($courses as $course) {$userdata['myhosts'][] = array('name'=> $course->hostname, 'url' => $CFG->wwwroot.'/auth/mnet/jump.php?hostid='.$course->hostid, 'count' => $course->count);}}return $userdata;}/*** Generate a random string for use as an RPC session token.*/function generate_token() {return sha1(str_shuffle('' . mt_rand() . time()));}/*** Starts an RPC jump session and returns the jump redirect URL.** @param int $mnethostid id of the mnet host to jump to* @param string $wantsurl url to redirect to after the jump (usually on remote system)* @param boolean $wantsurlbackhere defaults to false, means that the remote system should bounce us back here* rather than somewhere inside *its* wwwroot*/function start_jump_session($mnethostid, $wantsurl, $wantsurlbackhere=false) {global $CFG, $USER, $DB;require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';if (\core\session\manager::is_loggedinas()) {throw new \moodle_exception('notpermittedtojumpas', 'mnet');}// check remote login permissionsif (! has_capability('moodle/site:mnetlogintoremote', context_system::instance())or is_mnet_remote_user($USER)or isguestuser()or !isloggedin()) {throw new \moodle_exception('notpermittedtojump', 'mnet');}// check for SSO publish permission firstif ($this->has_service($mnethostid, 'sso_sp') == false) {throw new \moodle_exception('hostnotconfiguredforsso', 'mnet');}// set RPC timeout to 30 seconds if not configuredif (empty($this->config->rpc_negotiation_timeout)) {$this->config->rpc_negotiation_timeout = 30;set_config('rpc_negotiation_timeout', '30', 'auth_mnet');}// get the host info$mnet_peer = new mnet_peer();$mnet_peer->set_id($mnethostid);// set up the session$mnet_session = $DB->get_record('mnet_session',array('userid'=>$USER->id, 'mnethostid'=>$mnethostid,'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])));if ($mnet_session == false) {$mnet_session = new stdClass();$mnet_session->mnethostid = $mnethostid;$mnet_session->userid = $USER->id;$mnet_session->username = $USER->username;$mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);$mnet_session->token = $this->generate_token();$mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout;$mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');$mnet_session->session_id = session_id();$mnet_session->id = $DB->insert_record('mnet_session', $mnet_session);} else {$mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);$mnet_session->token = $this->generate_token();$mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout;$mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');$mnet_session->session_id = session_id();$DB->update_record('mnet_session', $mnet_session);}// construct the redirection URL//$transport = mnet_get_protocol($mnet_peer->transport);$wantsurl = urlencode($wantsurl);$url = "{$mnet_peer->wwwroot}{$mnet_peer->application->sso_land_url}?token={$mnet_session->token}&idp={$this->mnet->wwwroot}&wantsurl={$wantsurl}";if ($wantsurlbackhere) {$url .= '&remoteurl=1';}return $url;}/*** This function confirms the remote (ID provider) host's mnet session* by communicating the token and UA over the XMLRPC transport layer, and* returns the local user record on success.** @param string $token The random session token.* @param mnet_peer $remotepeer The ID provider mnet_peer object.* @return array The local user record.*/function confirm_mnet_session($token, $remotepeer) {global $CFG, $DB;require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';require_once $CFG->libdir . '/gdlib.php';require_once($CFG->dirroot.'/user/lib.php');// verify the remote host is configured locally before attempting RPC callif (! $remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotepeer->wwwroot, 'deleted' => 0))) {throw new \moodle_exception('notpermittedtoland', 'mnet');}// set up the RPC request$mnetrequest = new mnet_xmlrpc_client();$mnetrequest->set_method('auth/mnet/auth.php/user_authorise');// set $token and $useragent parameters$mnetrequest->add_param($token);$mnetrequest->add_param(sha1($_SERVER['HTTP_USER_AGENT']));// Thunderbirds are go! Do RPC call and store responseif ($mnetrequest->send($remotepeer) === true) {$remoteuser = (object) $mnetrequest->response;} else {foreach ($mnetrequest->error as $errormessage) {list($code, $message) = array_map('trim',explode(':', $errormessage, 2));if($code == 702) {$site = get_site();throw new \moodle_exception('mnet_session_prohibited', 'mnet', $remotepeer->wwwroot,format_string($site->fullname));exit;}$message .= "ERROR $code:<br/>$errormessage<br/>";}throw new \moodle_exception("rpcerror", '', '', $message);}unset($mnetrequest);if (empty($remoteuser) or empty($remoteuser->username)) {throw new \moodle_exception('unknownerror', 'mnet');exit;}if (user_not_fully_set_up($remoteuser, false)) {throw new \moodle_exception('notenoughidpinfo', 'mnet');exit;}$remoteuser = mnet_strip_user($remoteuser, mnet_fields_to_import($remotepeer));$remoteuser->auth = 'mnet';$remoteuser->wwwroot = $remotepeer->wwwroot;// the user may roam from Moodle 1.x where lang has _utf8 suffix// also, make sure that the lang is actually installed, otherwise set site defaultif (isset($remoteuser->lang)) {$remoteuser->lang = clean_param(str_replace('_utf8', '', $remoteuser->lang), PARAM_LANG);}$firsttime = false;// get the local record for the remote user$localuser = $DB->get_record('user', array('username'=>$remoteuser->username, 'mnethostid'=>$remotehost->id));// add the remote user to the database if necessary, and if allowed// TODO: refactor into a separate functionif (empty($localuser) || ! $localuser->id) {/*if (empty($this->config->auto_add_remote_users)) {throw new \moodle_exception('nolocaluser', 'mnet');} See MDL-21327 for why this is commented out*/$remoteuser->mnethostid = $remotehost->id;$remoteuser->firstaccess = 0;$remoteuser->confirmed = 1;$remoteuser->id = user_create_user($remoteuser, false);$firsttime = true;$localuser = $remoteuser;}// check sso access control list for permission firstif (!$this->can_login_remotely($localuser->username, $remotehost->id)) {throw new \moodle_exception('sso_mnet_login_refused', 'mnet', '',array('user' => $localuser->username, 'host' => $remotehost->name));}$fs = get_file_storage();// update the local user record with remote user dataforeach ((array) $remoteuser as $key => $val) {if ($key == '_mnet_userpicture_timemodified' and empty($CFG->disableuserimages) and isset($remoteuser->picture)) {// update the user picture if there is a newer verion at the identity provider$usercontext = context_user::instance($localuser->id, MUST_EXIST);if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {$localtimemodified = $usericonfile->get_timemodified();} else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {$localtimemodified = $usericonfile->get_timemodified();} else {$localtimemodified = 0;}if (!empty($val) and $localtimemodified < $val) {mnet_debug('refetching the user picture from the identity provider host');$fetchrequest = new mnet_xmlrpc_client();$fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image');$fetchrequest->add_param($localuser->username);if ($fetchrequest->send($remotepeer) === true) {if (strlen($fetchrequest->response['f1']) > 0) {$imagefilename = $CFG->tempdir . '/mnet-usericon-' . $localuser->id;$imagecontents = base64_decode($fetchrequest->response['f1']);file_put_contents($imagefilename, $imagecontents);if ($newrev = process_new_icon($usercontext, 'user', 'icon', 0, $imagefilename)) {$localuser->picture = $newrev;}unlink($imagefilename);}// note that since Moodle 2.0 we ignore $fetchrequest->response['f2']// the mimetype information provided is ignored and the type of the file is detected// by process_new_icon()}}}if($key == 'myhosts') {$localuser->mnet_foreign_host_array = array();foreach($val as $rhost) {$name = clean_param($rhost['name'], PARAM_ALPHANUM);$url = clean_param($rhost['url'], PARAM_URL);$count = clean_param($rhost['count'], PARAM_INT);$url_is_local = stristr($url , $CFG->wwwroot);if (!empty($name) && !empty($count) && empty($url_is_local)) {$localuser->mnet_foreign_host_array[] = array('name' => $name,'url' => $url,'count' => $count);}}}$localuser->{$key} = $val;}$localuser->mnethostid = $remotepeer->id;user_update_user($localuser, false);if (!$firsttime) {// repeat customer! let the IDP know about enrolments// we have for this user.// set up the RPC request$mnetrequest = new mnet_xmlrpc_client();$mnetrequest->set_method('auth/mnet/auth.php/update_enrolments');// pass username and an assoc array of "my courses"// with info so that the IDP can maintain mnetservice_enrol_enrolments$mnetrequest->add_param($remoteuser->username);$fields = 'id, category, sortorder, fullname, shortname, idnumber, summary, startdate, visible';$courses = enrol_get_users_courses($localuser->id, false, $fields);if (is_array($courses) && !empty($courses)) {// Second request to do the JOINs that we'd have done// inside enrol_get_users_courses() if we had been allowed$sql = "SELECT c.id,cc.name AS cat_name, cc.description AS cat_descriptionFROM {course} cJOIN {course_categories} cc ON c.category = cc.idWHERE c.id IN (" . join(',',array_keys($courses)) . ')';$extra = $DB->get_records_sql($sql);$keys = array_keys($courses);$studentroles = get_archetype_roles('student');if (!empty($studentroles)) {$defaultrole = reset($studentroles);//$defaultrole = get_default_course_role($ccache[$shortname]); //TODO: rewrite this completely, there is no default course role any more!!!foreach ($keys AS $id) {if ($courses[$id]->visible == 0) {unset($courses[$id]);continue;}$courses[$id]->cat_id = $courses[$id]->category;$courses[$id]->defaultroleid = $defaultrole->id;unset($courses[$id]->category);unset($courses[$id]->visible);$courses[$id]->cat_name = $extra[$id]->cat_name;$courses[$id]->cat_description = $extra[$id]->cat_description;$courses[$id]->defaultrolename = $defaultrole->name;// coerce to array$courses[$id] = (array)$courses[$id];}} else {throw new moodle_exception('unknownrole', 'error', '', 'student');}} else {// if the array is empty, send it anyway// we may be clearing out stale entries$courses = array();}$mnetrequest->add_param($courses, 'array');// Call 0800-RPC Now! -- we don't care too much if it fails// as it's just informational.if ($mnetrequest->send($remotepeer) === false) {// error_log(print_r($mnetrequest->error,1));}}return $localuser;}/*** creates (or updates) the mnet session once* {@see confirm_mnet_session} and {@see complete_user_login} have both been called** @param stdclass $user the local user (must exist already* @param string $token the jump/land token* @param mnet_peer $remotepeer the mnet_peer object of this users's idp*/public function update_mnet_session($user, $token, $remotepeer) {global $DB;$session_gc_maxlifetime = 1440;if (isset($user->session_gc_maxlifetime)) {$session_gc_maxlifetime = $user->session_gc_maxlifetime;}if (!$mnet_session = $DB->get_record('mnet_session',array('userid'=>$user->id, 'mnethostid'=>$remotepeer->id,'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])))) {$mnet_session = new stdClass();$mnet_session->mnethostid = $remotepeer->id;$mnet_session->userid = $user->id;$mnet_session->username = $user->username;$mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);$mnet_session->token = $token; // Needed to support simultaneous sessions// and preserving DB rec uniqueness$mnet_session->confirm_timeout = time();$mnet_session->expires = time() + (integer)$session_gc_maxlifetime;$mnet_session->session_id = session_id();$mnet_session->id = $DB->insert_record('mnet_session', $mnet_session);} else {$mnet_session->expires = time() + (integer)$session_gc_maxlifetime;$DB->update_record('mnet_session', $mnet_session);}}/*** Invoke this function _on_ the IDP to update it with enrolment info local to* the SP right after calling user_authorise()** Normally called by the SP after calling user_authorise()** @param string $username The username* @param array $courses Assoc array of courses following the structure of mnetservice_enrol_courses* @return bool*/function update_enrolments($username, $courses) {global $CFG, $DB;$remoteclient = get_mnet_remote_client();if (empty($username) || !is_array($courses)) {return false;}// make sure it is a user we have an in active session// with that host...$mnetsessions = $DB->get_records('mnet_session', array('username' => $username, 'mnethostid' => $remoteclient->id), '', 'id, userid');$userid = null;foreach ($mnetsessions as $mnetsession) {if (is_null($userid)) {$userid = $mnetsession->userid;continue;}if ($userid != $mnetsession->userid) {throw new mnet_server_exception(3, 'authfail_usermismatch');}}if (empty($courses)) { // no courses? clear out quickly$DB->delete_records('mnetservice_enrol_enrolments', array('hostid'=>$remoteclient->id, 'userid'=>$userid));return true;}// IMPORTANT: Ask for remoteid as the first element in the query, so// that the array that comes back is indexed on the same field as the// array that we have received from the remote client$sql = "SELECT c.remoteid, c.id, c.categoryid AS cat_id, c.categoryname AS cat_name, c.sortorder,c.fullname, c.shortname, c.idnumber, c.summary, c.summaryformat, c.startdate,e.id AS enrolmentidFROM {mnetservice_enrol_courses} cLEFT JOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid AND e.userid = ?)WHERE c.hostid = ?";$currentcourses = $DB->get_records_sql($sql, array($userid, $remoteclient->id));$keepenrolments = array();foreach($courses as $ix => $course) {$course['remoteid'] = $course['id'];$course['hostid'] = (int)$remoteclient->id;$userisregd = false;// if we do not have the the information about the remote course, it is not available// to us for remote enrolment - skipif (array_key_exists($course['remoteid'], $currentcourses)) {// We are going to keep this enrolment, it will be updated or inserted, but will keep it.$keepenrolments[] = $course['id'];// Pointer to current course:$currentcourse =& $currentcourses[$course['remoteid']];$saveflag = false;foreach($course as $key => $value) {// Only compare what is available locally, data coming from enrolment tables have// way more information that tables used to keep the track of mnet enrolments.if (!property_exists($currentcourse, $key)) {continue;}// Don't compare ids either, they come from different databases.if ($key === 'id') {continue;}if ($currentcourse->$key != $value) {$saveflag = true;$currentcourse->$key = $value;}}if ($saveflag) {$DB->update_record('mnetservice_enrol_courses', $currentcourse);}if (isset($currentcourse->enrolmentid) && is_numeric($currentcourse->enrolmentid)) {$userisregd = true;}} else {unset ($courses[$ix]);continue;}// Do we have a record for this assignment?if ($userisregd) {// Yes - we know about this one already// We don't want to do updates because the new data is probably// 'less complete' than the data we have.} else {// No - create a record$newenrol = new stdClass();$newenrol->userid = $userid;$newenrol->hostid = (int)$remoteclient->id;$newenrol->remotecourseid = $course['remoteid'];$newenrol->rolename = $course['defaultrolename'];$newenrol->enroltype = 'mnet';$newenrol->id = $DB->insert_record('mnetservice_enrol_enrolments', $newenrol);}}// Clean up courses that the user is no longer enrolled in.list($insql, $inparams) = $DB->get_in_or_equal($keepenrolments, SQL_PARAMS_NAMED, 'param', false, null);$whereclause = ' userid = :userid AND hostid = :hostid AND remotecourseid ' . $insql;$params = array_merge(['userid' => $userid, 'hostid' => $remoteclient->id], $inparams);$DB->delete_records_select('mnetservice_enrol_enrolments', $whereclause, $params);}function prevent_local_passwords() {return true;}/*** Returns true if this authentication plugin is 'internal'.** @return bool*/function is_internal() {return false;}/*** Returns true if this authentication plugin can change the user's* password.** @return bool*/function can_change_password() {//TODO: it should be able to redirect, right?return false;}/*** Returns the URL for changing the user's pw, or false if the default can* be used.** @return moodle_url*/function change_password_url() {return null;}/*** Poll the IdP server to let it know that a user it has authenticated is still* online** @return void*/function keepalive_client() {global $CFG, $DB;$cutoff = time() - 300; // TODO - find out what the remote server's session// cutoff is, and preempt that$sql = "selectid,username,mnethostidfrom{user}wherelastaccess > ? ANDmnethostid != ?order bymnethostid";$immigrants = $DB->get_records_sql($sql, array($cutoff, $CFG->mnet_localhost_id));if ($immigrants == false) {return true;}$usersArray = array();foreach($immigrants as $immigrant) {$usersArray[$immigrant->mnethostid][] = $immigrant->username;}require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';foreach($usersArray as $mnethostid => $users) {$mnet_peer = new mnet_peer();$mnet_peer->set_id($mnethostid);$mnet_request = new mnet_xmlrpc_client();$mnet_request->set_method('auth/mnet/auth.php/keepalive_server');// set $token and $useragent parameters$mnet_request->add_param($users);if ($mnet_request->send($mnet_peer) === true) {if (!isset($mnet_request->response['code'])) {debugging("Server side error has occured on host $mnethostid");continue;} elseif ($mnet_request->response['code'] > 0) {debugging($mnet_request->response['message']);}if (!isset($mnet_request->response['last log id'])) {debugging("Server side error has occured on host $mnethostid\nNo log ID was received.");continue;}} else {debugging("Server side error has occured on host $mnethostid: " .join("\n", $mnet_request->error));break;}}}/*** Receives an array of log entries from an SP and adds them to the mnet_log* table** @deprecated since Moodle 2.8 Please don't use this function for recording mnet logs.* @param array $array An array of usernames* @return string "All ok" or an error message*/function refresh_log($array) {debugging('refresh_log() is deprecated, The transfer of logs through mnet are no longer recorded.', DEBUG_DEVELOPER);return array('code' => 0, 'message' => 'All ok');}/*** Receives an array of usernames from a remote machine and prods their* sessions to keep them alive** @param array $array An array of usernames* @return string "All ok" or an error message*/function keepalive_server($array) {global $CFG, $DB;$remoteclient = get_mnet_remote_client();// We don't want to output anything to the client machine$start = ob_start();// We'll get session records in batches of 30$superArray = array_chunk($array, 30);$returnString = '';foreach($superArray as $subArray) {$subArray = array_values($subArray);$results = $DB->get_records_list('mnet_session', 'username', $subArray, '', 'id, session_id, username');if ($results == false) {// We seem to have a username that breaks our query:// TODO: Handle this error appropriately$returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n";} else {foreach($results as $emigrant) {\core\session\manager::touch_session($emigrant->session_id);}}}$end = ob_end_clean();if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $remoteclient->last_log_id);return array('code' => 1, 'message' => $returnString, 'last log id' => $remoteclient->last_log_id);}/*** Cleanup any remote mnet_sessions, kill the local mnet_session data** This is called by require_logout in moodlelib** @return void*/function prelogout_hook() {global $CFG, $USER;if (!is_enabled_auth('mnet')) {return;}// If the user is local to this Moodle:if ($USER->mnethostid == $this->mnet->id) {$this->kill_children($USER->username, sha1($_SERVER['HTTP_USER_AGENT']));// Else the user has hit 'logout' at a Service Provider Moodle:} else {$this->kill_parent($USER->username, sha1($_SERVER['HTTP_USER_AGENT']));}}/*** The SP uses this function to kill the session on the parent IdP** @param string $username Username for session to kill* @param string $useragent SHA1 hash of user agent to look for* @return string A plaintext report of what has happened*/function kill_parent($username, $useragent) {global $CFG, $USER, $DB;require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';$sql = "select*from{mnet_session} swheres.username = ? ANDs.useragent = ? ANDs.mnethostid = ?";$mnetsessions = $DB->get_records_sql($sql, array($username, $useragent, $USER->mnethostid));$ignore = $DB->delete_records('mnet_session',array('username'=>$username,'useragent'=>$useragent,'mnethostid'=>$USER->mnethostid));if (false != $mnetsessions) {$mnet_peer = new mnet_peer();$mnet_peer->set_id($USER->mnethostid);$mnet_request = new mnet_xmlrpc_client();$mnet_request->set_method('auth/mnet/auth.php/kill_children');// set $token and $useragent parameters$mnet_request->add_param($username);$mnet_request->add_param($useragent);if ($mnet_request->send($mnet_peer) === false) {debugging(join("\n", $mnet_request->error));return false;}}return true;}/*** The IdP uses this function to kill child sessions on other hosts** @param string $username Username for session to kill* @param string $useragent SHA1 hash of user agent to look for* @return string A plaintext report of what has happened*/function kill_children($username, $useragent) {global $CFG, $USER, $DB;$remoteclient = null;if (defined('MNET_SERVER')) {$remoteclient = get_mnet_remote_client();}require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';$userid = $DB->get_field('user', 'id', array('mnethostid'=>$CFG->mnet_localhost_id, 'username'=>$username));$returnstring = '';$mnetsessions = $DB->get_records('mnet_session', array('userid' => $userid, 'useragent' => $useragent));if (false == $mnetsessions) {$returnstring .= "Could find no remote sessions\n";$mnetsessions = array();}foreach($mnetsessions as $mnetsession) {// If this script is being executed by a remote peer, that means the user has clicked// logout on that peer, and the session on that peer can be deleted natively.// Skip over it.if (isset($remoteclient->id) && ($mnetsession->mnethostid == $remoteclient->id)) {continue;}$returnstring .= "Deleting session\n";$mnet_peer = new mnet_peer();$mnet_peer->set_id($mnetsession->mnethostid);$mnet_request = new mnet_xmlrpc_client();$mnet_request->set_method('auth/mnet/auth.php/kill_child');// set $token and $useragent parameters$mnet_request->add_param($username);$mnet_request->add_param($useragent);if ($mnet_request->send($mnet_peer) === false) {debugging("Server side error has occured on host $mnetsession->mnethostid: " .join("\n", $mnet_request->error));}}$ignore = $DB->delete_records('mnet_session',array('useragent'=>$useragent, 'userid'=>$userid));if (isset($remoteclient) && isset($remoteclient->id)) {\core\session\manager::kill_user_sessions($userid);}return $returnstring;}/*** When the IdP requests that child sessions are terminated,* this function will be called on each of the child hosts. The machine that* calls the function (over xmlrpc) provides us with the mnethostid we need.** @param string $username Username for session to kill* @param string $useragent SHA1 hash of user agent to look for* @return bool True on success*/function kill_child($username, $useragent) {global $CFG, $DB;$remoteclient = get_mnet_remote_client();$session = $DB->get_record('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent));$DB->delete_records('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent));if (false != $session) {\core\session\manager::kill_session($session->session_id);return true;}return false;}/*** To delete a host, we must delete all current sessions that users from* that host are currently engaged in.** @param string $sessionidarray An array of session hashes* @return bool True on success*/function end_local_sessions(&$sessionArray) {global $CFG;if (is_array($sessionArray)) {while($session = array_pop($sessionArray)) {\core\session\manager::kill_session($session->session_id);}return true;}return false;}/*** Returns the user's profile image info** If the user exists and has a profile picture, the returned array will contain keys:* f1 - the content of the default 100x100px image* f1_mimetype - the mimetype of the f1 file* f2 - the content of the 35x35px variant of the image* f2_mimetype - the mimetype of the f2 file** The mimetype information was added in Moodle 2.0. In Moodle 1.x, images are always jpegs.** @see process_new_icon()* @uses mnet_remote_client callable via MNet XML-RPC* @param int $username The id of the user* @return false|array false if user not found, empty array if no picture exists, array with data otherwise*/function fetch_user_image($username) {global $CFG, $DB;if ($user = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id))) {$fs = get_file_storage();$usercontext = context_user::instance($user->id, MUST_EXIST);$return = array();if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {$return['f1'] = base64_encode($f1->get_content());$return['f1_mimetype'] = $f1->get_mimetype();} else if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {$return['f1'] = base64_encode($f1->get_content());$return['f1_mimetype'] = $f1->get_mimetype();}if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.png')) {$return['f2'] = base64_encode($f2->get_content());$return['f2_mimetype'] = $f2->get_mimetype();} else if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.jpg')) {$return['f2'] = base64_encode($f2->get_content());$return['f2_mimetype'] = $f2->get_mimetype();}return $return;}return false;}/*** Returns the theme information and logo url as strings.** @return string The theme info*/function fetch_theme_info() {global $CFG;$themename = "$CFG->theme";$logourl = "$CFG->wwwroot/theme/$CFG->theme/images/logo.jpg";$return['themename'] = $themename;$return['logourl'] = $logourl;return $return;}/*** Determines if an MNET host is providing the nominated service.** @param int $mnethostid The id of the remote host* @param string $servicename The name of the service* @return bool Whether the service is available on the remote host*/function has_service($mnethostid, $servicename) {global $CFG, $DB;$sql = "SELECTsvc.id as serviceid,svc.name,svc.description,svc.offer,svc.apiversion,h2s.id as h2s_idFROM{mnet_host} h,{mnet_service} svc,{mnet_host2service} h2sWHEREh.deleted = '0' ANDh.id = h2s.hostid ANDh2s.hostid = ? ANDh2s.serviceid = svc.id ANDsvc.name = ? ANDh2s.subscribe = '1'";return $DB->get_records_sql($sql, array($mnethostid, $servicename));}/*** Checks the MNET access control table to see if the username/mnethost* is permitted to login to this moodle.** @param string $username The username* @param int $mnethostid The id of the remote mnethost* @return bool Whether the user can login from the remote host*/function can_login_remotely($username, $mnethostid) {global $DB;$accessctrl = 'allow';$aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnethostid));if (!empty($aclrecord)) {$accessctrl = $aclrecord->accessctrl;}return $accessctrl == 'allow';}function logoutpage_hook() {global $USER, $CFG, $redirect, $DB;if (!empty($USER->mnethostid) and $USER->mnethostid != $CFG->mnet_localhost_id) {$host = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid));$redirect = $host->wwwroot.'/';}}/*** Trims a log line from mnet peer to limit each part to a length which can be stored in our DB** @param object $logline The log information to be trimmed* @return object The passed logline object trimmed to not exceed storable limits*/function trim_logline($logline) {$limits = array('ip' => 15, 'coursename' => 40, 'module' => 20, 'action' => 40,'url' => 255);foreach ($limits as $property => $limit) {if (isset($logline->$property)) {$logline->$property = substr($logline->$property, 0, $limit);}}return $logline;}/*** Returns a list of MNet IdPs that the user can roam from.** @param string $wantsurl The relative url fragment the user wants to get to.* @return array List of arrays with keys url, icon and name.*/function loginpage_idp_list($wantsurl) {global $DB, $CFG;// strip off wwwroot, since the remote site will prefix it's return url with this$wantsurl = preg_replace('/(' . preg_quote($CFG->wwwroot, '/') . ')/', '', $wantsurl);$sql = "SELECT DISTINCT h.id, h.wwwroot, h.name, a.sso_jump_url, a.name as applicationFROM {mnet_host} hJOIN {mnet_host2service} m ON h.id = m.hostidJOIN {mnet_service} s ON s.id = m.serviceidJOIN {mnet_application} a ON h.applicationid = a.idWHERE s.name = ? AND h.deleted = ? AND m.publish = ?";$params = array('sso_sp', 0, 1);if (!empty($CFG->mnet_all_hosts_id)) {$sql .= " AND h.id <> ?";$params[] = $CFG->mnet_all_hosts_id;}if (!$hosts = $DB->get_records_sql($sql, $params)) {return array();}$idps = array();foreach ($hosts as $host) {$idps[] = array('url' => new moodle_url($host->wwwroot . $host->sso_jump_url, array('hostwwwroot' => $CFG->wwwroot, 'wantsurl' => $wantsurl, 'remoteurl' => 1)),'icon' => new pix_icon('i/' . $host->application . '_host', $host->name),'name' => $host->name,);}return $idps;}/*** Test if settings are correct, print info to output.*/public function test_settings() {global $CFG, $OUTPUT, $DB;// Generate warning if MNET is disabled.if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {echo $OUTPUT->notification(get_string('mnetdisabled', 'mnet'), 'notifyproblem');return;}// Generate full list of ID and service providers.$query = "SELECTh.id,h.name as hostname,h.wwwroot,h2idp.publish as idppublish,h2idp.subscribe as idpsubscribe,idp.name as idpname,h2sp.publish as sppublish,h2sp.subscribe as spsubscribe,sp.name as spnameFROM{mnet_host} hLEFT JOIN{mnet_host2service} h2idpON(h.id = h2idp.hostid AND(h2idp.publish = 1 ORh2idp.subscribe = 1))INNER JOIN{mnet_service} idpON(h2idp.serviceid = idp.id ANDidp.name = 'sso_idp')LEFT JOIN{mnet_host2service} h2spON(h.id = h2sp.hostid AND(h2sp.publish = 1 ORh2sp.subscribe = 1))INNER JOIN{mnet_service} spON(h2sp.serviceid = sp.id ANDsp.name = 'sso_sp')WHERE((h2idp.publish = 1 AND h2sp.subscribe = 1) OR(h2sp.publish = 1 AND h2idp.subscribe = 1)) ANDh.id != ?ORDER BYh.name ASC";$idproviders = array();$serviceproviders = array();if ($resultset = $DB->get_records_sql($query, array($CFG->mnet_localhost_id))) {foreach ($resultset as $hostservice) {if (!empty($hostservice->idppublish) && !empty($hostservice->spsubscribe)) {$serviceproviders[] = array('id' => $hostservice->id,'name' => $hostservice->hostname,'wwwroot' => $hostservice->wwwroot);}if (!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) {$idproviders[] = array('id' => $hostservice->id,'name' => $hostservice->hostname,'wwwroot' => $hostservice->wwwroot);}}}// ID Providers.$table = html_writer::start_tag('table', array('class' => 'generaltable'));$count = 0;foreach ($idproviders as $host) {$table .= html_writer::start_tag('tr');$table .= html_writer::start_tag('td');$table .= $host['name'];$table .= html_writer::end_tag('td');$table .= html_writer::start_tag('td');$table .= $host['wwwroot'];$table .= html_writer::end_tag('td');$table .= html_writer::end_tag('tr');$count++;}$table .= html_writer::end_tag('table');if ($count > 0) {echo html_writer::tag('h3', get_string('auth_mnet_roamin', 'auth_mnet'));echo $table;}// Service Providers.unset($table);$table = html_writer::start_tag('table', array('class' => 'generaltable'));$count = 0;foreach ($serviceproviders as $host) {$table .= html_writer::start_tag('tr');$table .= html_writer::start_tag('td');$table .= $host['name'];$table .= html_writer::end_tag('td');$table .= html_writer::start_tag('td');$table .= $host['wwwroot'];$table .= html_writer::end_tag('td');$table .= html_writer::end_tag('tr');$count++;}$table .= html_writer::end_tag('table');if ($count > 0) {echo html_writer::tag('h3', get_string('auth_mnet_roamout', 'auth_mnet'));echo $table;}}}