moodle/auth/mnet/auth.php
mjollnir_ 2520258128 Merged Donal's changes to MNET to allow moodle to also network with mahara.
changelog follows

    [MNET-manual] added version.php, install.xml and upgrade.php patches
    Route remote users back to their home server without going through jump.php and land.php
    Display app-specific strings in the user view
    Display the application icon in the Remote Host block
    Hide the 'logs' tab if the application under review is not Moodle
    Hide the 'logs' tab if the application under review is not Moodle
    Update user record to note that picture == 1 once a picture has been
    transferred.
    Change 2 to uri - is this fluid?
    Add application paramter to bootstrap function
    Find the application
    Workaround for PHP5.2.2 bug: http://bugs.php.net/bug.php?id=41293
    $HTTP_RAW_POST_DATA was not being populated
    Ensure we get an application for our Peer
    Update the URI to use for MNET
    The default 'wantsurl' should be empty
    Use the appropriate 'land' url for the remote application
    Add hidden form elements for 'application'
    Add awareness of new Application concept
    Add awareness of new Application concept
    Add awareness of new Application concept
    Add awareness of new Application concept
    Add awareness of new Application concept
    Add awareness of new Application concept
    Add awareness of new Application concept
2007-07-14 03:00:12 +00:00

1355 lines
51 KiB
PHP

<?php
/**
* @author Martin Dougiamas
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package moodle multiauth
*
* Authentication Plugin: Moodle Network Authentication
*
* Multiple host authentication support for Moodle Network.
*
* 2006-11-01 File created.
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
require_once($CFG->libdir.'/authlib.php');
/**
* Moodle Network authentication plugin.
*/
class auth_plugin_mnet extends auth_plugin_base {
/**
* Constructor.
*/
function auth_plugin_mnet() {
$this->authtype = 'mnet';
$this->config = get_config('auth/mnet');
}
/**
* Provides the allowed RPC services from this class as an array.
* @return array Allowed RPC services.
*/
function mnet_publishes() {
$sso_idp = array();
$sso_idp['name'] = 'sso_idp'; // Name & Description go in lang file
$sso_idp['apiversion'] = 1;
$sso_idp['methods'] = array('user_authorise','keepalive_server', 'kill_children',
'refresh_log', 'fetch_user_image', 'fetch_theme_info',
'update_enrolments');
$sso_sp = array();
$sso_sp['name'] = 'sso_sp'; // Name & Description go in lang file
$sso_sp['apiversion'] = 1;
$sso_sp['methods'] = array('keepalive_client','kill_child');
return array($sso_idp, $sso_sp);
}
/**
* 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; // error("Remote MNET users cannot login locally.");
}
/**
* Return user data for the provided token, compare with user_agent string.
*
* @param string $token The unique ID provided by remotehost.
* @param string $UA User Agent string.
* @return array $userdata Array of user info for remote host
*/
function user_authorise($token, $useragent) {
global $CFG, $MNET, $SITE, $MNET_REMOTE_CLIENT;
require_once $CFG->dirroot . '/mnet/xmlrpc/server.php';
$mnet_session = get_record('mnet_session', 'token', $token, 'useragent', $useragent);
if (empty($mnet_session)) {
echo mnet_server_fault(1, get_string('authfail_nosessionexists', 'mnet'));
exit;
}
// check session confirm timeout
if ($mnet_session->confirm_timeout < time()) {
echo mnet_server_fault(2, get_string('authfail_sessiontimedout', 'mnet'));
exit;
}
// session okay, try getting the user
if (!$user = get_complete_user_data('id', $mnet_session->userid)) {
echo mnet_server_fault(3, get_string('authfail_usermismatch', 'mnet'));
exit;
}
$userdata = array();
$userdata['username'] = $user->username;
$userdata['email'] = $user->email;
$userdata['auth'] = 'mnet';
$userdata['confirmed'] = $user->confirmed;
$userdata['deleted'] = $user->deleted;
$userdata['firstname'] = $user->firstname;
$userdata['lastname'] = $user->lastname;
$userdata['city'] = $user->city;
$userdata['country'] = $user->country;
$userdata['lang'] = $user->lang;
$userdata['timezone'] = $user->timezone;
$userdata['description'] = $user->description;
$userdata['mailformat'] = $user->mailformat;
$userdata['maildigest'] = $user->maildigest;
$userdata['maildisplay'] = $user->maildisplay;
$userdata['htmleditor'] = $user->htmleditor;
$userdata['wwwroot'] = $MNET->wwwroot;
$userdata['session.gc_maxlifetime'] = ini_get('session.gc_maxlifetime');
$userdata['picture'] = $user->picture;
if (!empty($user->picture)) {
$imagefile = "{$CFG->dataroot}/users/{$user->id}/f1.jpg";
if (file_exists($imagefile)) {
$userdata['imagehash'] = sha1(file_get_contents($imagefile));
}
}
$userdata['myhosts'] = array();
if($courses = get_my_courses($user->id, 'id', 'id, visible')) {
$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 count
FROM
{$CFG->prefix}mnet_enrol_course c,
{$CFG->prefix}mnet_enrol_assignments a,
{$CFG->prefix}mnet_host h
WHERE
c.id = a.courseid AND
c.hostid = h.id AND
a.userid = '{$user->id}' AND
c.hostid != '{$MNET_REMOTE_CLIENT->id}'
GROUP BY
h.name,
h.id,
h.wwwroot";
if ($courses = get_records_sql($sql)) {
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.
*/
function start_jump_session($mnethostid, $wantsurl) {
global $CFG;
global $USER;
global $MNET;
require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
// check remote login permissions
if (! has_capability('moodle/site:mnetlogintoremote', get_context_instance(CONTEXT_SYSTEM, SITEID))
or is_mnet_remote_user($USER)
or $USER->username == 'guest'
or empty($USER->id)) {
error(get_string('notpermittedtojump', 'mnet'));
}
// check for SSO publish permission first
if ($this->has_service($mnethostid, 'sso_sp') == false) {
error(get_string('hostnotconfiguredforsso', 'mnet'));
}
// set RPC timeout to 30 seconds if not configured
// TODO: Is this needed/useful/problematic?
if (empty($this->config->rpc_negotiation_timeout)) {
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 = get_record('mnet_session',
'userid', $USER->id,
'mnethostid', $mnethostid,
'useragent', sha1($_SERVER['HTTP_USER_AGENT']));
if ($mnet_session == false) {
$mnet_session = new object();
$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();
if (! $mnet_session->id = insert_record('mnet_session', addslashes_object($mnet_session))) {
error(get_string('databaseerror', 'mnet'));
}
} 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();
if (false == update_record('mnet_session', addslashes_object($mnet_session))) {
error(get_string('databaseerror', 'mnet'));
}
}
// 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={$MNET->wwwroot}&wantsurl={$wantsurl}";
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 string $remotewwwroot The ID provider wwwroot.
* @return array The local user record.
*/
function confirm_mnet_session($token, $remotewwwroot) {
global $CFG, $MNET, $SESSION;
require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
// verify the remote host is configured locally before attempting RPC call
if (! $remotehost = get_record('mnet_host', 'wwwroot', $remotewwwroot)) {
error(get_string('notpermittedtoland', 'mnet'));
}
// get the originating (ID provider) host info
$remotepeer = new mnet_peer();
$remotepeer->set_wwwroot($remotewwwroot);
// 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 response
if ($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();
print_error('mnet_session_prohibited','mnet', $remotewwwroot, format_string($site->fullname));
exit;
}
$message .= "ERROR $code:<br/>$errormessage<br/>";
}
error("RPC auth/mnet/user_authorise:<br/>$message");
}
unset($mnetrequest);
if (empty($remoteuser) or empty($remoteuser->username)) {
print_error('unknownerror', 'mnet');
exit;
}
$firsttime = false;
// get the local record for the remote user
$localuser = get_record('user', 'username', $remoteuser->username, 'mnethostid', $remotehost->id);
// add the remote user to the database if necessary, and if allowed
// TODO: refactor into a separate function
if (! $localuser->id) {
if (empty($this->config->auto_add_remote_users)) {
error(get_string('nolocaluser', 'mnet'));
}
$remoteuser->mnethostid = $remotehost->id;
if (! insert_record('user', addslashes_object($remoteuser))) {
error(get_string('databaseerror', 'mnet'));
}
$firsttime = true;
if (! $localuser = get_record('user', 'username', addslashes($remoteuser->username), 'mnethostid', $remotehost->id)) {
error(get_string('nolocaluser', 'mnet'));
}
}
// check sso access control list for permission first
if (!$this->can_login_remotely($localuser->username, $remotehost->id)) {
print_error('sso_mnet_login_refused', 'mnet', '', array($localuser->username, $remotehost->name));
}
$session_gc_maxlifetime = 1440;
// update the local user record with remote user data
foreach ((array) $remoteuser as $key => $val) {
if ($key == 'session.gc_maxlifetime') {
$session_gc_maxlifetime = $val;
continue;
}
// TODO: fetch image if it has changed
if ($key == 'imagehash') {
$dirname = "{$CFG->dataroot}/users/{$localuser->id}";
$filename = "$dirname/f1.jpg";
$localhash = '';
if (file_exists($filename)) {
$localhash = sha1(file_get_contents($filename));
} elseif (!file_exists($dirname)) {
mkdir($dirname);
}
if ($localhash != $val) {
// fetch image from remote 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) {
$imagecontents = base64_decode($fetchrequest->response['f1']);
file_put_contents($filename, $imagecontents);
$localuser->picture = 1;
}
if (strlen($fetchrequest->response['f2']) > 0) {
$imagecontents = base64_decode($fetchrequest->response['f2']);
file_put_contents($dirname.'/f2.jpg', $imagecontents);
}
}
}
}
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;
$bool = update_record('user', addslashes_object($localuser));
if (!$bool) {
// TODO: Jonathan to clean up mess
// Actually, this should never happen (modulo race conditions) - ML
error("updating user failed in mnet/auth/confirm_mnet_session ");
}
// set up the session
$mnet_session = get_record('mnet_session',
'userid', $localuser->id,
'mnethostid', $remotepeer->id,
'useragent', sha1($_SERVER['HTTP_USER_AGENT']));
if ($mnet_session == false) {
$mnet_session = new object();
$mnet_session->mnethostid = $remotepeer->id;
$mnet_session->userid = $localuser->id;
$mnet_session->username = $localuser->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();
if (! $mnet_session->id = insert_record('mnet_session', addslashes_object($mnet_session))) {
error(get_string('databaseerror', 'mnet'));
}
} else {
$mnet_session->expires = time() + (integer)$session_gc_maxlifetime;
update_record('mnet_session', addslashes_object($mnet_session));
}
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 mnet_enrol_assignments
$mnetrequest->add_param($remoteuser->username);
$fields = 'id, category, sortorder, fullname, shortname, idnumber, summary,
startdate, cost, currency, defaultrole, visible';
$courses = get_my_courses($localuser->id, 'visible DESC,sortorder ASC', $fields);
if (is_array($courses) && !empty($courses)) {
// Second request to do the JOINs that we'd have done
// inside get_my_courses() if we had been allowed
$sql = "SELECT c.id,
cc.name AS cat_name, cc.description AS cat_description,
r.shortname as defaultrolename
FROM {$CFG->prefix}course c
JOIN {$CFG->prefix}course_categories cc ON c.category = cc.id
LEFT OUTER JOIN {$CFG->prefix}role r ON c.defaultrole = r.id
WHERE c.id IN (" . join(',',array_keys($courses)) . ')';
$extra = get_records_sql($sql);
$keys = array_keys($courses);
$defaultrolename = get_field('role', 'shortname', 'id', $CFG->defaultcourseroleid);
foreach ($keys AS $id) {
if ($courses[$id]->visible == 0) {
unset($courses[$id]);
continue;
}
$courses[$id]->cat_id = $courses[$id]->category;
$courses[$id]->defaultroleid = $courses[$id]->defaultrole;
unset($courses[$id]->category);
unset($courses[$id]->defaultrole);
unset($courses[$id]->visible);
$courses[$id]->cat_name = $extra[$id]->cat_name;
$courses[$id]->cat_description = $extra[$id]->cat_description;
if (!empty($extra[$id]->defaultrolename)) {
$courses[$id]->defaultrolename = $extra[$id]->defaultrolename;
} else {
$courses[$id]->defaultrolename = $defaultrolename;
}
// coerce to array
$courses[$id] = (array)$courses[$id];
}
} else {
// if the array is empty, send it anyway
// we may be clearing out stale entries
$courses = array();
}
$mnetrequest->add_param($courses);
// 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;
}
/**
* 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
*
* @param string $username The username
* @param string $courses Assoc array of courses following the structure of mnet_enrol_course
* @return bool
*/
function update_enrolments($username, $courses) {
global $MNET_REMOTE_CLIENT, $CFG;
if (empty($username) || !is_array($courses)) {
return false;
}
// make sure it is a user we have an in active session
// with that host...
$userid = get_field('mnet_session', 'userid',
'username', addslashes($username),
'mnethostid', (int)$MNET_REMOTE_CLIENT->id);
if (!$userid) {
return false;
}
if (empty($courses)) { // no courses? clear out quickly
delete_records('mnet_enrol_assignments',
'hostid', (int)$MNET_REMOTE_CLIENT->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.cat_id,
c.cat_name,
c.cat_description,
c.sortorder,
c.fullname,
c.shortname,
c.idnumber,
c.summary,
c.startdate,
c.cost,
c.currency,
c.defaultroleid,
c.defaultrolename,
a.id as assignmentid
FROM
'.$CFG->prefix.'mnet_enrol_course c
LEFT JOIN
'.$CFG->prefix.'mnet_enrol_assignments a
ON
(a.courseid = c.id AND
a.hostid = c.hostid AND
a.userid = \''.$userid.'\')
WHERE
c.hostid = \''.(int)$MNET_REMOTE_CLIENT->id.'\'';
$currentcourses = get_records_sql($sql);
$local_courseid_array = array();
foreach($courses as $course) {
$course['remoteid'] = $course['id'];
$course['hostid'] = (int)$MNET_REMOTE_CLIENT->id;
$userisregd = false;
// First up - do we have a record for this course?
if (!array_key_exists($course['remoteid'], $currentcourses)) {
// No record - we must create it
$course['id'] = insert_record('mnet_enrol_course', addslashes_object((object)$course));
$currentcourse = (object)$course;
} else {
// Pointer to current course:
$currentcourse =& $currentcourses[$course['remoteid']];
// We have a record - is it up-to-date?
$course['id'] = $currentcourse->id;
$saveflag = false;
foreach($course as $key => $value) {
if ($currentcourse->$key != $value) {
$saveflag = true;
$currentcourse->$key = $value;
}
}
if ($saveflag) {
update_record('mnet_enrol_course', addslashes_object($currentcourse));
}
if (isset($currentcourse->assignmentid) && is_numeric($currentcourse->assignmentid)) {
$userisregd = true;
}
}
// By this point, we should always have a $dataObj->id
$local_courseid_array[] = $course['id'];
// 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
$assignObj = new stdClass();
$assignObj->userid = $userid;
$assignObj->hostid = (int)$MNET_REMOTE_CLIENT->id;
$assignObj->courseid = $course['id'];
$assignObj->rolename = $course['defaultrolename'];
$assignObj->id = insert_record('mnet_enrol_assignments', addslashes_object($assignObj));
}
}
// Clean up courses that the user is no longer enrolled in.
$local_courseid_string = implode(', ', $local_courseid_array);
$whereclause = " userid = '$userid' AND hostid = '{$MNET_REMOTE_CLIENT->id}' AND courseid NOT IN ($local_courseid_string)";
delete_records_select('mnet_enrol_assignments', $whereclause);
}
/**
* 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 string
*/
function change_password_url() {
return '';
}
/**
* Prints a form for configuring this authentication plugin.
*
* This function is called from admin/auth.php, and outputs a full page with
* a form for configuring this plugin.
*
* @param array $page An object containing all the data for this page.
*/
function config_form($config, $err, $user_fields) {
global $CFG;
$query = "
SELECT
h.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 spname
FROM
{$CFG->prefix}mnet_host h
LEFT JOIN
{$CFG->prefix}mnet_host2service h2idp
ON
(h.id = h2idp.hostid AND
(h2idp.publish = 1 OR
h2idp.subscribe = 1))
INNER JOIN
{$CFG->prefix}mnet_service idp
ON
(h2idp.serviceid = idp.id AND
idp.name = 'sso_idp')
LEFT JOIN
{$CFG->prefix}mnet_host2service h2sp
ON
(h.id = h2sp.hostid AND
(h2sp.publish = 1 OR
h2sp.subscribe = 1))
INNER JOIN
{$CFG->prefix}mnet_service sp
ON
(h2sp.serviceid = sp.id AND
sp.name = 'sso_sp')
WHERE
((h2idp.publish = 1 AND h2sp.subscribe = 1) OR
(h2sp.publish = 1 AND h2idp.subscribe = 1)) AND
h.id != {$CFG->mnet_localhost_id}
ORDER BY
h.name ASC";
$id_providers = array();
$service_providers = array();
if ($resultset = get_records_sql($query)) {
foreach($resultset as $hostservice) {
if(!empty($hostservice->idppublish) && !empty($hostservice->spsubscribe)) {
$service_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot);
}
if(!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) {
$id_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot);
}
}
}
include "config.html";
}
/**
* Processes and stores configuration data for this authentication plugin.
*/
function process_config($config) {
// set to defaults if undefined
if (!isset ($config->rpc_negotiation_timeout)) {
$config->rpc_negotiation_timeout = '30';
}
if (!isset ($config->auto_add_remote_users)) {
$config->auto_add_remote_users = '0';
}
// save settings
set_config('rpc_negotiation_timeout', $config->rpc_negotiation_timeout, 'auth/mnet');
set_config('auto_add_remote_users', $config->auto_add_remote_users, 'auth/mnet');
return true;
}
/**
* Poll the IdP server to let it know that a user it has authenticated is still
* online
*
* @return void
*/
function keepalive_client() {
global $CFG, $MNET;
$cutoff = time() - 300; // TODO - find out what the remote server's session
// cutoff is, and preempt that
$sql = "
select
id,
username,
mnethostid
from
{$CFG->prefix}user
where
lastaccess > '$cutoff' AND
mnethostid != '{$CFG->mnet_localhost_id}'
order by
mnethostid";
$immigrants = get_records_sql($sql);
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;
}
$query = "SELECT
l.id as remoteid,
l.time,
l.userid,
l.ip,
l.course,
l.module,
l.cmid,
l.action,
l.url,
l.info,
c.fullname as coursename,
c.modinfo as modinfo,
u.username
FROM
{$CFG->prefix}user u,
{$CFG->prefix}log l,
{$CFG->prefix}course c
WHERE
l.userid = u.id AND
u.mnethostid = '$mnethostid' AND
l.id > '".$mnet_request->response['last log id']."' AND
c.id = l.course
ORDER BY
remoteid ASC";
$results = get_records_sql($query);
if (false == $results) continue;
$param = array();
foreach($results as $result) {
if (!empty($result->modinfo) && !empty($result->cmid)) {
$modinfo = unserialize($result->modinfo);
unset($result->modinfo);
$modulearray = array();
foreach($modinfo as $module) {
$modulearray[$module->cm] = urldecode($module->name);
}
$result->resource_name = $modulearray[$result->cmid];
} else {
$result->resource_name = '';
}
$param[] = array (
'remoteid' => $result->remoteid,
'time' => $result->time,
'userid' => $result->userid,
'ip' => $result->ip,
'course' => $result->course,
'coursename' => $result->coursename,
'module' => $result->module,
'cmid' => $result->cmid,
'action' => $result->action,
'url' => $result->url,
'info' => $result->info,
'resource_name' => $result->resource_name,
'username' => $result->username
);
}
unset($result);
$mnet_request = new mnet_xmlrpc_client();
$mnet_request->set_method('auth/mnet/auth.php/refresh_log');
// set $token and $useragent parameters
$mnet_request->add_param($param);
if ($mnet_request->send($mnet_peer) === true) {
if ($mnet_request->response['code'] > 0) {
debugging($mnet_request->response['message']);
}
} else {
debugging("Server side error has occured on host $mnet_peer->ip: " .join("\n", $mnet_request->error));
}
}
}
/**
* Receives an array of log entries from an SP and adds them to the mnet_log
* table
*
* @param array $array An array of usernames
* @return string "All ok" or an error message
*/
function refresh_log($array) {
global $CFG, $MNET_REMOTE_CLIENT;
// We don't want to output anything to the client machine
$start = ob_start();
$returnString = '';
begin_sql();
$useridarray = array();
foreach($array as $logEntry) {
$logEntryObj = (object)$logEntry;
$logEntryObj->hostid = $MNET_REMOTE_CLIENT->id;
if (isset($useridarray[$logEntryObj->username])) {
$logEntryObj->userid = $useridarray[$logEntryObj->username];
} else {
$logEntryObj->userid = get_field('user','id','username',$logEntryObj->username);
if ($logEntryObj->userid == false) {
$logEntryObj->userid = 0;
}
$useridarray[$logEntryObj->username] = $logEntryObj->userid;
}
unset($logEntryObj->username);
$insertok = insert_record('mnet_log', addslashes_object($logEntryObj), false);
if ($insertok) {
$MNET_REMOTE_CLIENT->last_log_id = $logEntryObj->remoteid;
} else {
$returnString .= 'Record with id '.$logEntryObj->remoteid." failed to insert.\n";
}
}
$MNET_REMOTE_CLIENT->commit();
commit_sql();
$end = ob_end_clean();
if (empty($returnString)) return array('code' => 0, 'message' => 'All ok');
return array('code' => 1, 'message' => $returnString);
}
/**
* 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 $MNET_REMOTE_CLIENT, $CFG;
$CFG->usesid = true;
// Addslashes to all usernames, so we can build the query string real
// simply with 'implode'
$array = array_map('addslashes', $array);
// 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);
$instring = "('".implode("', '",$subArray)."')";
$query = "select id, session_id, username from {$CFG->prefix}mnet_session where username in $instring";
$results = get_records_sql($query);
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 {
// TODO: This process of killing and re-starting the session
// will cause PHP to forget any custom session_set_save_handler
// stuff. Subsequent attempts to prod existing sessions will
// fail, because PHP will look in wherever the default place
// may be (files?) and probably create a new session with the
// right session ID in that location. If it doesn't have write-
// access to that location, then it will fail... not sure how
// apparent that will be.
// There is no way to capture what the custom session handler
// is and then reset it on each pass - I checked that out
// already.
$sesscache = clone($_SESSION);
$sessidcache = session_id();
session_write_close();
unset($_SESSION);
$uc = ini_get('session.use_cookies');
ini_set('session.use_cookies', false);
foreach($results as $emigrant) {
unset($_SESSION);
session_name('MoodleSession'.$CFG->sessioncookie);
session_id($emigrant->session_id);
session_start();
session_write_close();
}
ini_set('session.use_cookies', $uc);
session_name('MoodleSession'.$CFG->sessioncookie);
session_id($sessidcache);
session_start();
$_SESSION = clone($sesscache);
session_write_close();
}
}
$end = ob_end_clean();
if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $MNET_REMOTE_CLIENT->last_log_id);
return array('code' => 1, 'message' => $returnString, 'last log id' => $MNET_REMOTE_CLIENT->last_log_id);
}
/**
* Cron function will be called automatically by cron.php every 5 minutes
*
* @return void
*/
function cron() {
// run the keepalive client
$this->keepalive_client();
// admin/cron.php should have run srand for us
$random100 = rand(0,100);
if ($random100 < 10) { // Approximately 10% of the time.
// nuke olden sessions
$longtime = time() - (1 * 3600 * 24);
delete_records_select('mnet_session', "expires < $longtime");
}
}
/**
* 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 $MNET, $CFG, $USER;
if (!is_enabled_auth('mnet')) {
return;
}
require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
// If the user is local to this Moodle:
if ($USER->mnethostid == $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;
require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
$sql = "
select
*
from
{$CFG->prefix}mnet_session s
where
s.username = '".addslashes($username)."' AND
s.useragent = '$useragent' AND
s.mnethostid = '{$USER->mnethostid}'";
$mnetsessions = get_records_sql($sql);
$ignore = delete_records('mnet_session',
'username', addslashes($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;
}
}
$_SESSION = array();
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, $MNET_REMOTE_CLIENT;
require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
$userid = get_field('user', 'id', 'mnethostid', $CFG->mnet_localhost_id, 'username', addslashes($username));
$returnstring = '';
$sql = "
select
*
from
{$CFG->prefix}mnet_session s
where
s.userid = '{$userid}' AND
s.useragent = '{$useragent}'";
// If we are being executed from a remote machine (client) we don't have
// to kill the moodle session on that machine.
if (isset($MNET_REMOTE_CLIENT) && isset($MNET_REMOTE_CLIENT->id)) {
$excludeid = $MNET_REMOTE_CLIENT->id;
} else {
$excludeid = -1;
}
$mnetsessions = get_records_sql($sql);
if (false == $mnetsessions) {
$returnstring .= "Could find no remote sessions\n$sql\n";
$mnetsessions = array();
}
foreach($mnetsessions as $mnetsession) {
$returnstring .= "Deleting session\n";
if ($mnetsession->mnethostid == $excludeid) continue;
$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 $mnethostid: " .
join("\n", $mnet_request->error));
}
}
$ignore = delete_records('mnet_session',
'useragent', $useragent,
'userid', $userid);
if (isset($MNET_REMOTE_CLIENT) && isset($MNET_REMOTE_CLIENT->id)) {
$start = ob_start();
$uc = ini_get('session.use_cookies');
ini_set('session.use_cookies', false);
$sesscache = clone($_SESSION);
$sessidcache = session_id();
session_write_close();
unset($_SESSION);
session_id($mnetsession->session_id);
session_start();
session_unregister("USER");
session_unregister("SESSION");
unset($_SESSION);
$_SESSION = array();
session_write_close();
ini_set('session.use_cookies', $uc);
session_name('MoodleSession'.$CFG->sessioncookie);
session_id($sessidcache);
session_start();
$_SESSION = clone($sesscache);
session_write_close();
$end = ob_end_clean();
} else {
$_SESSION = array();
}
return $returnstring;
}
/**
* TODO:Untested 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, $MNET_REMOTE_CLIENT;
$session = get_record('mnet_session', 'username', addslashes($username), 'mnethostid', $MNET_REMOTE_CLIENT->id, 'useragent', $useragent);
if (false != $session) {
$start = ob_start();
$uc = ini_get('session.use_cookies');
ini_set('session.use_cookies', false);
$sesscache = clone($_SESSION);
$sessidcache = session_id();
session_write_close();
unset($_SESSION);
session_id($session->session_id);
session_start();
session_unregister("USER");
session_unregister("SESSION");
unset($_SESSION);
$_SESSION = array();
session_write_close();
ini_set('session.use_cookies', $uc);
session_name('MoodleSession'.$CFG->sessioncookie);
session_id($sessidcache);
session_start();
$_SESSION = clone($sesscache);
session_write_close();
$end = ob_end_clean();
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)) {
$start = ob_start();
$uc = ini_get('session.use_cookies');
ini_set('session.use_cookies', false);
$sesscache = clone($_SESSION);
$sessidcache = session_id();
session_write_close();
unset($_SESSION);
while($session = array_pop($sessionArray)) {
session_id($session->session_id);
session_start();
session_unregister("USER");
session_unregister("SESSION");
unset($_SESSION);
$_SESSION = array();
session_write_close();
}
ini_set('session.use_cookies', $uc);
session_name('MoodleSession'.$CFG->sessioncookie);
session_id($sessidcache);
session_start();
$_SESSION = clone($sesscache);
$end = ob_end_clean();
return true;
}
return false;
}
/**
* Returns the user's image as a base64 encoded string.
*
* @param int $userid The id of the user
* @return string The encoded image
*/
function fetch_user_image($username) {
global $CFG;
if ($user = get_record('user', 'username', addslashes($username), 'mnethostid', $CFG->mnet_localhost_id)) {
$filename1 = "{$CFG->dataroot}/users/{$user->id}/f1.jpg";
$filename2 = "{$CFG->dataroot}/users/{$user->id}/f2.jpg";
$return = array();
if (file_exists($filename1)) {
$return['f1'] = base64_encode(file_get_contents($filename1));
}
if (file_exists($filename2)) {
$return['f2'] = base64_encode(file_get_contents($filename2));
}
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;
$sql = "
SELECT
svc.id as serviceid,
svc.name,
svc.description,
svc.offer,
svc.apiversion,
h2s.id as h2s_id
FROM
{$CFG->prefix}mnet_service svc,
{$CFG->prefix}mnet_host2service h2s
WHERE
h2s.hostid = '$mnethostid' AND
h2s.serviceid = svc.id AND
svc.name = '$servicename' AND
h2s.subscribe = '1'";
return get_records_sql($sql);
}
/**
* 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) {
$accessctrl = 'allow';
$aclrecord = get_record('mnet_sso_access_control', 'username', addslashes($username), 'mnet_host_id', $mnethostid);
if (!empty($aclrecord)) {
$accessctrl = $aclrecord->accessctrl;
}
return $accessctrl == 'allow';
}
function logoutpage_hook() {
global $USER, $CFG, $redirect;
if (!empty($USER->mnethostid) and $USER->mnethostid != $CFG->mnet_localhost_id) {
$host = get_record('mnet_host', 'id', $USER->mnethostid);
$redirect = $host->wwwroot.'/';
}
}
}
?>