]> git.mjollnir.org Git - moodle.git/commitdiff
mnet: new auth plugin
authormartinlanghoff <martinlanghoff>
Thu, 4 Jan 2007 03:26:04 +0000 (03:26 +0000)
committermartinlanghoff <martinlanghoff>
Thu, 4 Jan 2007 03:26:04 +0000 (03:26 +0000)
auth/mnet/auth.php [new file with mode: 0644]
auth/mnet/config.html [new file with mode: 0644]
auth/mnet/jump.php [new file with mode: 0644]
auth/mnet/land.php [new file with mode: 0644]

diff --git a/auth/mnet/auth.php b/auth/mnet/auth.php
new file mode 100644 (file)
index 0000000..305466f
--- /dev/null
@@ -0,0 +1,1014 @@
+<?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.
+ */
+
+// This page cannot be called directly
+if (!isset($CFG)) {
+    exit;
+}
+
+/**
+ * Moodle Network authentication plugin.
+ */
+class auth_plugin_mnet
+{
+
+    /**
+     * The configuration details for the plugin.
+     */
+    var $config;
+
+    /**
+     * Constructor.
+     */
+    function auth_plugin_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');
+
+        $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
+     * @returns 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;
+        global $MNET;
+        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));
+            }
+        }
+        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', $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', $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}/auth/mnet/land.php?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.
+     *   @returns array The local user record.
+     */
+    function confirm_mnet_session($token, $remotewwwroot) {
+        global $CFG, $MNET;
+        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 $code => $errormessage) {
+                $message .= "ERROR $code:<br>$errormessage<br>";
+            }
+            error("RPC auth/mnet/user_authorise:<br>$message");
+        }
+
+        if (empty($remoteuser) or empty($remoteuser->username)) {
+            error(get_string('unknownerror', 'mnet'));
+        }
+
+        // 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', $remoteuser)) {
+                error(get_string('databaseerror', 'mnet'));
+            }
+            if (! $localuser = get_record('user', 'username', $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)) {
+            error("Username '$localuser->username' is not permitted to login from '$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);
+                        }
+                        if (strlen($fetchrequest->response['f2']) > 0) {
+                            $imagecontents = base64_decode($fetchrequest->response['f2']);
+                            file_put_contents($dirname.'/f2.jpg', $imagecontents);
+                        }
+                    }
+                }
+            }
+
+            $localuser->{$key} = $val;
+        }
+
+        $localuser->mnethostid = $remotepeer->id;
+
+        $bool = update_record('user', $localuser);
+        if (!$bool) {
+            //TODO: Jonathan to clean up mess:
+            exit("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 = ''; //Not needed on the SP side
+            $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', $mnet_session)) {
+                error(get_string('databaseerror', 'mnet'));
+            }
+        } else {
+            $mnet_session->expires = time() + (integer)$session_gc_maxlifetime;
+            update_record('mnet_session', $mnet_session);
+        }
+
+        return $localuser;
+    }
+
+    /**
+     * Returns true if this authentication plugin is 'internal'.
+     *
+     * @returns bool
+     */
+    function is_internal() {
+        return false;
+    }
+
+    /**
+     * Returns true if this authentication plugin can change the user's
+     * password.
+     *
+     * @returns bool
+     */
+    function can_change_password() {
+        return false;
+    }
+
+    /**
+     * Returns the URL for changing the user's pw, or false if the default can
+     * be used.
+     *
+     * @returns bool
+     */
+    function change_password_url() {
+        return false;
+    }
+
+    /**
+     * 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) {
+        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->host = '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;
+        $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));
+            }
+
+            $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', $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() {
+        $this->keepalive_client();
+    }
+
+    /**
+     * Cleanup any remote mnet_sessions, kill the local mnet_session data
+     *
+     * This is called by require_logout in moodlelib
+     *
+     * @return   void
+     */
+    function logout() {
+        global $MNET, $CFG, $USER;
+        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   = '$username' AND
+                s.useragent  = '$useragent' AND
+                s.mnethostid = '{$USER->mnethostid}'";
+
+        $mnetsessions = get_records_sql($sql);
+
+        $ignore = delete_records('mnet_session', '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; 
+            }
+        }
+
+        $_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', $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', $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', $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) {
+        $access = 'allow';
+        $aclrecord = get_record('mnet_sso_access_control', 'username', $username, 'mnet_host_id', $mnethostid);
+        if (!empty($aclrecord)) {
+            $access = $aclrecord->access;
+        }
+        return $access == 'allow';
+    }
+}
+
+?>
diff --git a/auth/mnet/config.html b/auth/mnet/config.html
new file mode 100644 (file)
index 0000000..b6f55a1
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+// set to defaults if undefined
+if (!isset($config->rpc_negotiation_timeout)) {
+    $config->host = '30';
+}
+if (!isset ($config->auto_add_remote_users)) {
+    $config->auto_add_remote_users = '0';
+}
+
+$yesno = array(get_string('no'), get_string('yes'));
+
+?>
+<table cellspacing="0" cellpadding="5" border="0" align="center">
+
+<tr valign="top" class="required">
+    <td align="right"> rpc_negotiation_timeout: </td>
+    <td>
+        <input name="rpc_negotiation_timeout" type="text" size="5" value="<?php echo $config->rpc_negotiation_timeout ?>" />
+        <?php
+
+        if (isset($err['rpc_negotiation_timeout'])) {
+            formerr($err['rpc_negotiation_timeout']);
+        }
+
+        ?>
+    </td>
+    <td>
+        <?php
+
+        print_string('auth_mnet_rpc_negotiation_timeout', 'auth');
+
+        ?>
+    </td>
+</tr>
+
+<tr valign="top" class="required">
+    <td align="right"> auto_add_remote_users: </td>
+    <td>
+        <?php
+
+        choose_from_menu($yesno, 'auto_add_remote_users', $config->auto_add_remote_users, '');
+
+        ?>
+    </td>
+    <td>
+        <?php
+
+        print_string('auth_mnet_auto_add_remote_users', 'auth');
+
+        ?>
+    </td>
+</tr>
+
+<?php 
+
+// global $user_fields;
+// print_auth_lock_options('mnet', $user_fields, get_string('auth_fieldlocks_help', 'auth'), false, false);
+
+?>
+</table>
diff --git a/auth/mnet/jump.php b/auth/mnet/jump.php
new file mode 100644 (file)
index 0000000..99a73fb
--- /dev/null
@@ -0,0 +1,31 @@
+<?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.
+ */
+
+require_once dirname(dirname(dirname(__FILE__))) . '/config.php';
+
+// grab the GET params - wantsurl could be anything - take it
+// with PARAM_RAW
+$hostid   = required_param('hostid',        PARAM_INT);
+$wantsurl = optional_param('wantsurl', '/', PARAM_RAW); 
+
+// start the mnet session and redirect browser to remote URL
+$mnetauth = get_auth_plugin('mnet');
+$url      = $mnetauth->start_jump_session($hostid, $wantsurl);
+
+if (empty($url)) {
+    error('DEBUG: Jump session was not started correctly or blank URL returned.'); // TODO: errors
+}
+redirect($url);
+
+?>
diff --git a/auth/mnet/land.php b/auth/mnet/land.php
new file mode 100644 (file)
index 0000000..51106c8
--- /dev/null
@@ -0,0 +1,35 @@
+<?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.
+ */
+
+require_once dirname(dirname(dirname(__FILE__))) . '/config.php';
+require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+
+// grab the GET params
+$token         = required_param('token',    PARAM_BASE64);
+$remotewwwroot = required_param('idp',      PARAM_URL);
+$wantsurl      = required_param('wantsurl', PARAM_LOCALURL);
+
+// confirm the MNET session
+$mnetauth = get_auth_plugin('mnet');
+$localuser = $mnetauth->confirm_mnet_session($token, $remotewwwroot);
+
+// log in
+$CFG->auth = 'mnet';
+$USER = get_complete_user_data('id', $localuser->id);
+load_all_capabilities();
+
+// redirect
+redirect($CFG->wwwroot . $wantsurl);
+
+?>