/** * Send request to enrol our user to the remote course * * Updates our remote enrolments cache if the enrolment was successful. * * @uses mnet_xmlrpc_client Invokes XML-RPC request * @param object $user our user * @param object $remotecourse record from mnetservice_enrol_courses table * @return true|string true if success, error message from the remote host otherwise */ public function req_enrol_user(stdclass $user, stdclass $remotecourse) { global $CFG, $DB; require_once $CFG->dirroot . '/mnet/xmlrpc/client.php'; $peer = new mnet_peer(); $peer->set_id($remotecourse->hostid); $request = new mnet_xmlrpc_client(); $request->set_method('enrol/mnet/enrol.php/enrol_user'); $request->add_param(mnet_strip_user((array) $user, mnet_fields_to_send($peer))); $request->add_param($remotecourse->remoteid); if ($request->send($peer) === true) { if ($request->response === true) { // cache the enrolment information in our table $enrolment = new stdclass(); $enrolment->hostid = $peer->id; $enrolment->userid = $user->id; $enrolment->remotecourseid = $remotecourse->remoteid; $enrolment->enroltype = 'mnet'; // $enrolment->rolename not known now, must be re-fetched // $enrolment->enroltime not known now, must be re-fetched $DB->insert_record('mnetservice_enrol_enrolments', $enrolment); return true; } else { return serialize(array('invalid response: ' . print_r($request->response, true))); } } else { return serialize($request->error); } }
/** * Test that {@link user_not_fully_set_up()} takes required custom fields into account. */ public function test_profile_has_required_custom_fields_set() { global $CFG, $DB; require_once $CFG->dirroot . '/mnet/lib.php'; $this->resetAfterTest(); // Add a required, visible, unlocked custom field. $DB->insert_record('user_info_field', ['shortname' => 'house', 'name' => 'House', 'required' => 1, 'visible' => 1, 'locked' => 0, 'categoryid' => 1, 'datatype' => 'text']); // Add an optional, visible, unlocked custom field. $DB->insert_record('user_info_field', ['shortname' => 'pet', 'name' => 'Pet', 'required' => 0, 'visible' => 1, 'locked' => 0, 'categoryid' => 1, 'datatype' => 'text']); // Add required but invisible custom field. $DB->insert_record('user_info_field', ['shortname' => 'secretid', 'name' => 'Secret ID', 'required' => 1, 'visible' => 0, 'locked' => 0, 'categoryid' => 1, 'datatype' => 'text']); // Add required but locked custom field. $DB->insert_record('user_info_field', ['shortname' => 'muggleborn', 'name' => 'Muggle-born', 'required' => 1, 'visible' => 1, 'locked' => 1, 'categoryid' => 1, 'datatype' => 'checkbox']); // Create some student accounts. $hermione = $this->getDataGenerator()->create_user(); $harry = $this->getDataGenerator()->create_user(); $ron = $this->getDataGenerator()->create_user(); $draco = $this->getDataGenerator()->create_user(); // Hermione has all available custom fields filled (of course she has). profile_save_data((object) ['id' => $hermione->id, 'profile_field_house' => 'Gryffindor']); profile_save_data((object) ['id' => $hermione->id, 'profile_field_pet' => 'Crookshanks']); // Harry has only the optional field filled. profile_save_data((object) ['id' => $harry->id, 'profile_field_pet' => 'Hedwig']); // Draco has only the required field filled. profile_save_data((object) ['id' => $draco->id, 'profile_field_house' => 'Slytherin']); // Only students with required fields filled should be considered as fully set up in the default (strict) mode. $this->assertFalse(user_not_fully_set_up($hermione)); $this->assertFalse(user_not_fully_set_up($draco)); $this->assertTrue(user_not_fully_set_up($harry)); $this->assertTrue(user_not_fully_set_up($ron)); // In the lax mode, students do not need to have required fields filled. $this->assertFalse(user_not_fully_set_up($hermione, false)); $this->assertFalse(user_not_fully_set_up($draco, false)); $this->assertFalse(user_not_fully_set_up($harry, false)); $this->assertFalse(user_not_fully_set_up($ron, false)); // Lack of required core field is seen as a problem in either mode. unset($hermione->email); $this->assertTrue(user_not_fully_set_up($hermione, true)); $this->assertTrue(user_not_fully_set_up($hermione, false)); // When confirming remote MNet users, we do not have custom fields available. $roamingharry = mnet_strip_user($harry, ['firstname', 'lastname', 'email']); $roaminghermione = mnet_strip_user($hermione, ['firstname', 'lastname', 'email']); $this->assertTrue(user_not_fully_set_up($roamingharry, true)); $this->assertFalse(user_not_fully_set_up($roamingharry, false)); $this->assertTrue(user_not_fully_set_up($roaminghermione, true)); $this->assertTrue(user_not_fully_set_up($roaminghermione, false)); }
/** * 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'; // verify the remote host is configured locally before attempting RPC call if (!($remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotepeer->wwwroot, 'deleted' => 0)))) { print_error('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 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', $remotepeer->wwwroot, format_string($site->fullname)); exit; } $message .= "ERROR {$code}:<br/>{$errormessage}<br/>"; } print_error("rpcerror", '', '', $message); } unset($mnetrequest); if (empty($remoteuser) or empty($remoteuser->username)) { print_error('unknownerror', 'mnet'); exit; } if (user_not_fully_set_up($remoteuser)) { print_error('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 default if (isset($remoteuser->lang)) { $remoteuser->lang = clean_param(str_replace('_utf8', '', $remoteuser->lang), PARAM_LANG); } if (empty($remoteuser->lang)) { if (!empty($CFG->lang)) { $remoteuser->lang = $CFG->lang; } else { $remoteuser->lang = 'en'; } } $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 function if (empty($localuser) || !$localuser->id) { /* if (empty($this->config->auto_add_remote_users)) { print_error('nolocaluser', 'mnet'); } See MDL-21327 for why this is commented out */ $remoteuser->mnethostid = $remotehost->id; $remoteuser->firstaccess = time(); // First time user in this server, grab it here $remoteuser->id = $DB->insert_record('user', $remoteuser); $firsttime = true; $localuser = $remoteuser; } // 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('user' => $localuser->username, 'host' => $remotehost->name)); } $fs = get_file_storage(); // update the local user record with remote user data foreach ((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 = get_context_instance(CONTEXT_USER, $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->dataroot . '/temp/mnet-usericon-' . $localuser->id; $imagecontents = base64_decode($fetchrequest->response['f1']); file_put_contents($imagefilename, $imagecontents); if (process_new_icon($usercontext, 'user', 'icon', 0, $imagefilename)) { $localuser->picture = 1; } 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; if (empty($localuser->firstaccess)) { // Now firstaccess, grab it here $localuser->firstaccess = time(); } $DB->update_record('user', $localuser); 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, 'visible DESC,sortorder ASC'); 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,\n cc.name AS cat_name, cc.description AS cat_description\n FROM {course} c\n JOIN {course_categories} cc ON c.category = cc.id\n WHERE c.id IN (" . join(',', array_keys($courses)) . ')'; $extra = $DB->get_records_sql($sql); $keys = array_keys($courses); $defaultrole = reset(get_archetype_roles('student')); //$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 { // 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; }
/** * Enrol remote user to our course * * If we do not have local record for the remote user in our database, * it gets created here. * * @uses mnet_remote_client Callable via XML-RPC only * @param array $userdata user details {@see mnet_fields_to_import()} * @param int $courseid our local course id * @return bool true if the enrolment has been successful, throws exception otherwise */ public function enrol_user(array $userdata, $courseid) { global $CFG, $DB; require_once dirname(__FILE__) . '/lib.php'; if (!($client = get_mnet_remote_client())) { die('Callable via XML-RPC only'); } if (empty($userdata['username'])) { throw new mnet_server_exception(5021, 'emptyusername', 'enrol_mnet'); } // do we know the remote user? $user = $DB->get_record('user', array('username' => $userdata['username'], 'mnethostid' => $client->id)); if ($user === false) { // here we could check the setting if the enrol_mnet is allowed to auto-register // users {@link http://tracker.moodle.org/browse/MDL-21327} $user = mnet_strip_user((object) $userdata, mnet_fields_to_import($client)); $user->mnethostid = $client->id; try { $user->id = $DB->insert_record('user', $user); } catch (Exception $e) { throw new mnet_server_exception(5011, 'couldnotcreateuser', 'enrol_mnet'); } } if (!($course = $DB->get_record('course', array('id' => $courseid)))) { throw new mnet_server_exception(5012, 'coursenotfound', 'enrol_mnet'); } $courses = $this->available_courses(); $isavailable = false; foreach ($courses as $available) { if ($available->remoteid == $course->id) { $isavailable = true; break; } } if (!$isavailable) { throw new mnet_server_exception(5013, 'courseunavailable', 'enrol_mnet'); } // try to load host specific enrol_mnet instance first $instance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'mnet', 'customint1' => $client->id), '*', IGNORE_MISSING); if ($instance === false) { // if not found, try to load instance for all hosts $instance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'mnet', 'customint1' => 0), '*', IGNORE_MISSING); } if ($instance === false) { // this should not happen as the course was returned by {@see self::available_courses()} throw new mnet_server_exception(5017, 'noenrolinstance', 'enrol_mnet'); } if (!($enrol = enrol_get_plugin('mnet'))) { throw new mnet_server_exception(5018, 'couldnotinstantiate', 'enrol_mnet'); } try { $enrol->enrol_user($instance, $user->id, $instance->roleid, time()); } catch (Exception $e) { throw new mnet_server_exception(5019, 'couldnotenrol', 'enrol_mnet', $e->getMessage()); } return true; }