Example #1
0
 /**
  * Performs the synchronisation of members.
  *
  * @return bool|void
  */
 public function execute()
 {
     global $CFG, $DB;
     require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php';
     require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuthBody.php';
     // Check if the authentication plugin is disabled.
     if (!is_enabled_auth('lti')) {
         mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti')));
         return true;
     }
     // Check if the enrolment plugin is disabled - isn't really necessary as the task should not run if
     // the plugin is disabled, but there is no harm in making sure core hasn't done something wrong.
     if (!enrol_is_enabled('lti')) {
         mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti'));
         return true;
     }
     // Get all the enabled tools.
     if ($tools = \enrol_lti\helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED, 'membersync' => 1))) {
         $ltiplugin = enrol_get_plugin('lti');
         $consumers = array();
         $currentusers = array();
         $userphotos = array();
         foreach ($tools as $tool) {
             mtrace("Starting - Member sync for shared tool '{$tool->id}' for the course '{$tool->courseid}'.");
             // Variables to keep track of information to display later.
             $usercount = 0;
             $enrolcount = 0;
             $unenrolcount = 0;
             // We check for all the users - users can access the same tool from different consumers.
             if ($ltiusers = $DB->get_records('enrol_lti_users', array('toolid' => $tool->id), 'lastaccess DESC')) {
                 foreach ($ltiusers as $ltiuser) {
                     $mtracecontent = "for the user '{$ltiuser->userid}' in the tool '{$tool->id}' for the course " . "'{$tool->courseid}'";
                     $usercount++;
                     // Check if we do not have a membershipsurl - this can happen if the sync process has an unexpected error.
                     if (!$ltiuser->membershipsurl) {
                         mtrace("Skipping - Empty membershipsurl {$mtracecontent}.");
                         continue;
                     }
                     // Check if we do not have a membershipsid - this can happen if the sync process has an unexpected error.
                     if (!$ltiuser->membershipsid) {
                         mtrace("Skipping - Empty membershipsid {$mtracecontent}.");
                         continue;
                     }
                     $consumer = sha1($ltiuser->membershipsurl . ':' . $ltiuser->membershipsid . ':' . $ltiuser->consumerkey . ':' . $ltiuser->consumersecret);
                     if (in_array($consumer, $consumers)) {
                         // We have already synchronised with this consumer.
                         continue;
                     }
                     $consumers[] = $consumer;
                     $params = array('lti_message_type' => self::LTI_MESSAGE_TYPE, 'id' => $ltiuser->membershipsid, 'lti_version' => self::LTI_VERSION);
                     mtrace("Calling memberships url '{$ltiuser->membershipsurl}' with body '" . json_encode($params) . "'");
                     try {
                         $response = sendOAuthParamsPOST('POST', $ltiuser->membershipsurl, $ltiuser->consumerkey, $ltiuser->consumersecret, 'application/x-www-form-urlencoded', $params);
                     } catch (\Exception $e) {
                         mtrace("Skipping - No response received {$mtracecontent} from '{$ltiuser->membershipsurl}'");
                         mtrace($e->getMessage());
                         continue;
                     }
                     // Check the response from the consumer.
                     $data = new \SimpleXMLElement($response);
                     // Check if we did not receive a valid response.
                     if (empty($data->statusinfo)) {
                         mtrace("Skipping - Bad response received {$mtracecontent} from '{$ltiuser->membershipsurl}'");
                         mtrace('Skipping - Error parsing the XML received \'' . substr($response, 0, 125) . '\' ... (Displaying only 125 chars)');
                         continue;
                     }
                     // Check if we did not receive a valid response.
                     if (strpos(strtolower($data->statusinfo->codemajor), 'success') === false) {
                         mtrace('Skipping - Error received from the remote system: ' . $data->statusinfo->codemajor . ' ' . $data->statusinfo->severity . ' ' . $data->statusinfo->codeminor);
                         continue;
                     }
                     $members = $data->memberships->member;
                     mtrace(count($members) . ' members received.');
                     foreach ($members as $member) {
                         // Set the user data.
                         $user = new \stdClass();
                         $user->username = \enrol_lti\helper::create_username($ltiuser->consumerkey, $member->user_id);
                         $user->firstname = \core_user::clean_field($member->person_name_given, 'firstname');
                         $user->lastname = \core_user::clean_field($member->person_name_family, 'lastname');
                         $user->email = \core_user::clean_field($member->person_contact_email_primary, 'email');
                         // Get the user data from the LTI consumer.
                         $user = \enrol_lti\helper::assign_user_tool_data($tool, $user);
                         if (!($dbuser = $DB->get_record('user', array('username' => $user->username, 'deleted' => 0)))) {
                             if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL || $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_NEW) {
                                 // If the email was stripped/not set then fill it with a default one. This
                                 // stops the user from being redirected to edit their profile page.
                                 if (empty($user->email)) {
                                     $user->email = $user->username . "@example.com";
                                 }
                                 $user->auth = 'lti';
                                 $user->id = user_create_user($user);
                                 // Add the information to the necessary arrays.
                                 $currentusers[] = $user->id;
                                 $userphotos[$user->id] = $member->user_image;
                             }
                         } else {
                             // If email is empty remove it, so we don't update the user with an empty email.
                             if (empty($user->email)) {
                                 unset($user->email);
                             }
                             $user->id = $dbuser->id;
                             user_update_user($user);
                             // Add the information to the necessary arrays.
                             $currentusers[] = $user->id;
                             $userphotos[$user->id] = $member->user_image;
                         }
                         if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL || $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_NEW) {
                             // Enrol the user in the course.
                             \enrol_lti\helper::enrol_user($tool, $user->id);
                         }
                     }
                 }
                 // Now we check if we have to unenrol users who were not listed.
                 if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL || $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_UNENROL_MISSING) {
                     // Go through the users and check if any were never listed, if so, remove them.
                     foreach ($ltiusers as $ltiuser) {
                         if (!in_array($ltiuser->userid, $currentusers)) {
                             $instance = new \stdClass();
                             $instance->id = $tool->enrolid;
                             $instance->courseid = $tool->courseid;
                             $instance->enrol = 'lti';
                             $ltiplugin->unenrol_user($instance, $ltiuser->id);
                         }
                     }
                 }
             }
             mtrace("Completed - Synced members for tool '{$tool->id}' in the course '{$tool->courseid}'. " . "Processed {$usercount} users; enrolled {$enrolcount} members; unenrolled {$unenrolcount} members.");
             mtrace("");
         }
         // Sync the user profile photos.
         mtrace("Started - Syncing user profile images.");
         $counter = 0;
         if (!empty($userphotos)) {
             foreach ($userphotos as $userid => $url) {
                 if ($url) {
                     $result = \enrol_lti\helper::update_user_profile_image($userid, $url);
                     if ($result === \enrol_lti\helper::PROFILE_IMAGE_UPDATE_SUCCESSFUL) {
                         $counter++;
                         mtrace("Profile image succesfully downloaded and created for user '{$userid}' from {$url}.");
                     } else {
                         mtrace($result);
                     }
                 }
             }
         }
         mtrace("Completed - Synced {$counter} profile images.");
     }
 }
Example #2
0
 /**
  * Performs synchronisation of member information and enrolments.
  *
  * @param stdClass $tool
  * @param ToolConsumer $consumer
  * @param User[] $members
  * @return array An array containing the number of members that were processed and the number of members that were enrolled.
  */
 protected function sync_member_information(stdClass $tool, ToolConsumer $consumer, $members)
 {
     global $DB;
     $usercount = 0;
     $enrolcount = 0;
     // Process member information.
     foreach ($members as $member) {
         $usercount++;
         // Set the user data.
         $user = new stdClass();
         $user->username = helper::create_username($consumer->getKey(), $member->ltiUserId);
         $user->firstname = core_user::clean_field($member->firstname, 'firstname');
         $user->lastname = core_user::clean_field($member->lastname, 'lastname');
         $user->email = core_user::clean_field($member->email, 'email');
         // Get the user data from the LTI consumer.
         $user = helper::assign_user_tool_data($tool, $user);
         $dbuser = core_user::get_user_by_username($user->username, 'id');
         if ($dbuser) {
             // If email is empty remove it, so we don't update the user with an empty email.
             if (empty($user->email)) {
                 unset($user->email);
             }
             $user->id = $dbuser->id;
             user_update_user($user);
             // Add the information to the necessary arrays.
             if (!in_array($user->id, $this->currentusers)) {
                 $this->currentusers[] = $user->id;
             }
             $this->userphotos[$user->id] = $member->image;
         } else {
             if ($this->should_sync_enrol($tool->membersyncmode)) {
                 // If the email was stripped/not set then fill it with a default one. This
                 // stops the user from being redirected to edit their profile page.
                 if (empty($user->email)) {
                     $user->email = $user->username . "@example.com";
                 }
                 $user->auth = 'lti';
                 $user->id = user_create_user($user);
                 // Add the information to the necessary arrays.
                 $this->currentusers[] = $user->id;
                 $this->userphotos[$user->id] = $member->image;
             }
         }
         // Sync enrolments.
         if ($this->should_sync_enrol($tool->membersyncmode)) {
             // Enrol the user in the course.
             if (helper::enrol_user($tool, $user->id) === helper::ENROLMENT_SUCCESSFUL) {
                 // Increment enrol count.
                 $enrolcount++;
             }
             // Check if this user has already been registered in the enrol_lti_users table.
             if (!$DB->record_exists('enrol_lti_users', ['toolid' => $tool->id, 'userid' => $user->id])) {
                 // Create an initial enrol_lti_user record that we can use later when syncing grades and members.
                 $userlog = new stdClass();
                 $userlog->userid = $user->id;
                 $userlog->toolid = $tool->id;
                 $userlog->consumerkey = $consumer->getKey();
                 $DB->insert_record('enrol_lti_users', $userlog);
             }
         }
     }
     return [$usercount, $enrolcount];
 }
Example #3
0
 // Set the user data.
 $user = new stdClass();
 $user->username = \enrol_lti\helper::create_username($ltirequest->info['oauth_consumer_key'], $ltirequest->info['user_id']);
 if (!empty($ltirequest->info['lis_person_name_given'])) {
     $user->firstname = $ltirequest->info['lis_person_name_given'];
 } else {
     $user->firstname = $ltirequest->info['user_id'];
 }
 if (!empty($ltirequest->info['lis_person_name_family'])) {
     $user->lastname = $ltirequest->info['lis_person_name_family'];
 } else {
     $user->lastname = $ltirequest->info['context_id'];
 }
 $user->email = \core_user::clean_field($ltirequest->getUserEmail(), 'email');
 // Get the user data from the LTI consumer.
 $user = \enrol_lti\helper::assign_user_tool_data($tool, $user);
 // Check if the user exists.
 if (!($dbuser = $DB->get_record('user', array('username' => $user->username, 'deleted' => 0)))) {
     // If the email was stripped/not set then fill it with a default one. This
     // stops the user from being redirected to edit their profile page.
     if (empty($user->email)) {
         $user->email = $user->username . "@example.com";
     }
     $user->auth = 'lti';
     $user->id = user_create_user($user);
     // Get the updated user record.
     $user = $DB->get_record('user', array('id' => $user->id));
 } else {
     if (\enrol_lti\helper::user_match($user, $dbuser)) {
         $user = $dbuser;
     } else {
Example #4
0
 /**
  * Override onLaunch with tool logic.
  * @return void
  */
 protected function onLaunch()
 {
     global $DB, $SESSION, $CFG;
     // Check for valid consumer.
     if (empty($this->consumer) || $this->dataConnector->loadToolConsumer($this->consumer) === false) {
         $this->ok = false;
         $this->message = get_string('invalidtoolconsumer', 'enrol_lti');
         return;
     }
     $url = helper::get_launch_url($this->tool->id);
     // If a tool proxy has been stored for the current consumer trying to access a tool,
     // check that the tool is being launched from the correct url.
     $correctlaunchurl = false;
     if (!empty($this->consumer->toolProxy)) {
         $proxy = json_decode($this->consumer->toolProxy);
         $handlers = $proxy->tool_profile->resource_handler;
         foreach ($handlers as $handler) {
             foreach ($handler->message as $message) {
                 $handlerurl = new moodle_url($message->path);
                 $fullpath = $handlerurl->out(false);
                 if ($message->message_type == "basic-lti-launch-request" && $fullpath == $url) {
                     $correctlaunchurl = true;
                     break 2;
                 }
             }
         }
     } else {
         if ($this->tool->secret == $this->consumer->secret) {
             // Test if the LTI1 secret for this tool is being used. Then we know the correct tool is being launched.
             $correctlaunchurl = true;
         }
     }
     if (!$correctlaunchurl) {
         $this->ok = false;
         $this->message = get_string('invalidrequest', 'enrol_lti');
         return;
     }
     // Before we do anything check that the context is valid.
     $tool = $this->tool;
     $context = context::instance_by_id($tool->contextid);
     // Set the user data.
     $user = new stdClass();
     $user->username = helper::create_username($this->consumer->getKey(), $this->user->ltiUserId);
     if (!empty($this->user->firstname)) {
         $user->firstname = $this->user->firstname;
     } else {
         $user->firstname = $this->user->getRecordId();
     }
     if (!empty($this->user->lastname)) {
         $user->lastname = $this->user->lastname;
     } else {
         $user->lastname = $this->tool->contextid;
     }
     $user->email = core_user::clean_field($this->user->email, 'email');
     // Get the user data from the LTI consumer.
     $user = helper::assign_user_tool_data($tool, $user);
     // Check if the user exists.
     if (!($dbuser = $DB->get_record('user', ['username' => $user->username, 'deleted' => 0]))) {
         // If the email was stripped/not set then fill it with a default one. This
         // stops the user from being redirected to edit their profile page.
         if (empty($user->email)) {
             $user->email = $user->username . "@example.com";
         }
         $user->auth = 'lti';
         $user->id = \user_create_user($user);
         // Get the updated user record.
         $user = $DB->get_record('user', ['id' => $user->id]);
     } else {
         if (helper::user_match($user, $dbuser)) {
             $user = $dbuser;
         } else {
             // If email is empty remove it, so we don't update the user with an empty email.
             if (empty($user->email)) {
                 unset($user->email);
             }
             $user->id = $dbuser->id;
             \user_update_user($user);
             // Get the updated user record.
             $user = $DB->get_record('user', ['id' => $user->id]);
         }
     }
     // Update user image.
     if (isset($this->user) && isset($this->user->image) && !empty($this->user->image)) {
         $image = $this->user->image;
     } else {
         // Use custom_user_image parameter as a fallback.
         $image = $this->resourceLink->getSetting('custom_user_image');
     }
     // Check if there is an image to process.
     if ($image) {
         helper::update_user_profile_image($user->id, $image);
     }
     // Check if we need to force the page layout to embedded.
     $isforceembed = $this->resourceLink->getSetting('custom_force_embed') == 1;
     // Check if we are an instructor.
     $isinstructor = $this->user->isStaff() || $this->user->isAdmin();
     if ($context->contextlevel == CONTEXT_COURSE) {
         $courseid = $context->instanceid;
         $urltogo = new moodle_url('/course/view.php', ['id' => $courseid]);
     } else {
         if ($context->contextlevel == CONTEXT_MODULE) {
             $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST);
             $urltogo = new moodle_url('/mod/' . $cm->modname . '/view.php', ['id' => $cm->id]);
             // If we are a student in the course module context we do not want to display blocks.
             if (!$isforceembed && !$isinstructor) {
                 $isforceembed = true;
             }
         } else {
             print_error('invalidcontext');
             exit;
         }
     }
     // Force page layout to embedded if necessary.
     if ($isforceembed) {
         $SESSION->forcepagelayout = 'embedded';
     } else {
         // May still be set from previous session, so unset it.
         unset($SESSION->forcepagelayout);
     }
     // Enrol the user in the course with no role.
     $result = helper::enrol_user($tool, $user->id);
     // Display an error, if there is one.
     if ($result !== helper::ENROLMENT_SUCCESSFUL) {
         print_error($result, 'enrol_lti');
         exit;
     }
     // Give the user the role in the given context.
     $roleid = $isinstructor ? $tool->roleinstructor : $tool->rolelearner;
     role_assign($roleid, $user->id, $tool->contextid);
     // Login user.
     $sourceid = $this->user->ltiResultSourcedId;
     $serviceurl = $this->resourceLink->getSetting('lis_outcome_service_url');
     // Check if we have recorded this user before.
     if ($userlog = $DB->get_record('enrol_lti_users', ['toolid' => $tool->id, 'userid' => $user->id])) {
         if ($userlog->sourceid != $sourceid) {
             $userlog->sourceid = $sourceid;
         }
         if ($userlog->serviceurl != $serviceurl) {
             $userlog->serviceurl = $serviceurl;
         }
         $userlog->lastaccess = time();
         $DB->update_record('enrol_lti_users', $userlog);
     } else {
         // Add the user details so we can use it later when syncing grades and members.
         $userlog = new stdClass();
         $userlog->userid = $user->id;
         $userlog->toolid = $tool->id;
         $userlog->serviceurl = $serviceurl;
         $userlog->sourceid = $sourceid;
         $userlog->consumerkey = $this->consumer->getKey();
         $userlog->consumersecret = $tool->secret;
         $userlog->lastgrade = 0;
         $userlog->lastaccess = time();
         $userlog->timecreated = time();
         $userlog->membershipsurl = $this->resourceLink->getSetting('ext_ims_lis_memberships_url');
         $userlog->membershipsid = $this->resourceLink->getSetting('ext_ims_lis_memberships_id');
         $DB->insert_record('enrol_lti_users', $userlog);
     }
     // Finalise the user log in.
     complete_user_login($user);
     // Everything's good. Set appropriate OK flag and message values.
     $this->ok = true;
     $this->message = get_string('success');
     if (empty($CFG->allowframembedding)) {
         // Provide an alternative link.
         $stropentool = get_string('opentool', 'enrol_lti');
         echo html_writer::tag('p', get_string('frameembeddingnotenabled', 'enrol_lti'));
         echo html_writer::link($urltogo, $stropentool, ['target' => '_blank']);
     } else {
         // All done, redirect the user to where they want to go.
         redirect($urltogo);
     }
 }