Example #1
0
 /**
  * Perform a Memberships service request.
  *
  * The user table is updated with the new list of user objects.
  *
  * @param boolean $withGroups True is group information is to be requested as well
  *
  * @return mixed Array of User objects or False if the request was not successful
  */
 public function doMembershipsService($withGroups = false)
 {
     $users = [];
     $old_users = $this->getUserResultSourcedIDs(true, ToolProvider::ID_SCOPE_RESOURCE);
     $url = $this->getSetting('ext_ims_lis_memberships_url');
     $params = [];
     $params['id'] = $this->getSetting('ext_ims_lis_memberships_id');
     $ok = false;
     if ($withGroups) {
         $ok = $this->doService('basic-lis-readmembershipsforcontextwithgroups', $url, $params);
     }
     if ($ok) {
         $this->groupSets = [];
         $this->groups = [];
     } else {
         $ok = $this->doService('basic-lis-readmembershipsforcontext', $url, $params);
     }
     if ($ok) {
         if (!isset($this->extNodes['memberships']['member'])) {
             $members = [];
         } else {
             if (!isset($this->extNodes['memberships']['member'][0])) {
                 $members = [];
                 $members[0] = $this->extNodes['memberships']['member'];
             } else {
                 $members = $this->extNodes['memberships']['member'];
             }
         }
         for ($i = 0; $i < count($members); $i++) {
             $user = new User($this, $members[$i]['user_id']);
             // Set the user name
             $firstname = isset($members[$i]['person_name_given']) ? $members[$i]['person_name_given'] : '';
             $lastname = isset($members[$i]['person_name_family']) ? $members[$i]['person_name_family'] : '';
             $fullname = isset($members[$i]['person_name_full']) ? $members[$i]['person_name_full'] : '';
             $user->setNames($firstname, $lastname, $fullname);
             // Set the user email
             $email = isset($members[$i]['person_contact_email_primary']) ? $members[$i]['person_contact_email_primary'] : '';
             $user->setEmail($email, $this->consumer->defaultEmail);
             // Set the user roles
             if (isset($members[$i]['roles'])) {
                 $user->roles = ToolProvider::parseRoles($members[$i]['roles']);
             }
             // Set the user groups
             if (!isset($members[$i]['groups']['group'])) {
                 $groups = [];
             } else {
                 if (!isset($members[$i]['groups']['group'][0])) {
                     $groups = [];
                     $groups[0] = $members[$i]['groups']['group'];
                 } else {
                     $groups = $members[$i]['groups']['group'];
                 }
             }
             for ($j = 0; $j < count($groups); $j++) {
                 $group = $groups[$j];
                 if (isset($group['set'])) {
                     $set_id = $group['set']['id'];
                     if (!isset($this->groupSets[$set_id])) {
                         $this->groupSets[$set_id] = ['title' => $group['set']['title'], 'groups' => [], 'num_members' => 0, 'num_staff' => 0, 'num_learners' => 0];
                     }
                     $this->groupSets[$set_id]['num_members']++;
                     if ($user->isStaff()) {
                         $this->groupSets[$set_id]['num_staff']++;
                     }
                     if ($user->isLearner()) {
                         $this->groupSets[$set_id]['num_learners']++;
                     }
                     if (!in_array($group['id'], $this->groupSets[$set_id]['groups'])) {
                         $this->groupSets[$set_id]['groups'][] = $group['id'];
                     }
                     $this->groups[$group['id']] = ['title' => $group['title'], 'set' => $set_id];
                 } else {
                     $this->groups[$group['id']] = ['title' => $group['title']];
                 }
                 $user->groups[] = $group['id'];
             }
             // If a result sourcedid is provided save the user
             if (isset($members[$i]['lis_result_sourcedid'])) {
                 $user->ltiResultSourcedId = $members[$i]['lis_result_sourcedid'];
                 $user->save();
             }
             $users[] = $user;
             // Remove old user (if it exists)
             unset($old_users[$user->getId(ToolProvider::ID_SCOPE_RESOURCE)]);
         }
         // Delete any old users which were not in the latest list from the tool consumer
         foreach ($old_users as $id => $user) {
             $user->delete();
         }
     } else {
         $users = false;
     }
     return $users;
 }
Example #2
0
 /**
  * Check the authenticity of the LTI launch request.
  *
  * The consumer, resource link and user objects will be initialised if the request is valid.
  *
  * @param ServerRequestInterface $request
  * @return bool True if the request has been successfully validated.
  * @throws Exception
  * @internal param array $requestBody
  */
 private function authenticate(ServerRequestInterface $request)
 {
     $requestBody = (array) $request->getParsedBody();
     // Get the consumer
     $doSaveConsumer = false;
     // Check all required launch parameters
     $version = isset($requestBody['lti_version']) ? $requestBody['lti_version'] : '';
     $messageType = isset($requestBody['lti_message_type']) ? $requestBody['lti_message_type'] : '';
     if (!in_array($version, $this->LTI_VERSIONS)) {
         throw new Exception('Invalid or missing lti_version parameter');
     }
     switch ($messageType) {
         case 'basic-lti-launch-request':
         case 'DashboardRequest':
             if (!isset($requestBody['resource_link_id']) || strlen(trim($requestBody['resource_link_id'])) == 0) {
                 throw new Exception('Missing resource link ID');
             }
             break;
         case 'ContentItemSelectionRequest':
             $acceptMediaTypes = isset($requestBody['accept_media_types']) ? trim($requestBody['accept_media_types']) : '';
             if (strlen($acceptMediaTypes) == 0) {
                 throw new Exception('No accept_media_types found');
             }
             $mediaTypes = array_filter(explode(',', str_replace(' ', '', $acceptMediaTypes)), 'strlen');
             $mediaTypes = array_unique($mediaTypes);
             if (count($mediaTypes) == 0) {
                 throw new Exception('No valid accept_media_types found');
             }
             $this->mediaTypes = $mediaTypes;
             $acceptDocumentTargets = isset($requestBody['accept_presentation_document_targets']) ? trim($requestBody['accept_presentation_document_targets']) : '';
             if (strlen($acceptDocumentTargets) == 0) {
                 throw new Exception('No accept_presentation_document_targets found');
             }
             $documentTargets = array_filter(explode(',', str_replace(' ', '', $acceptDocumentTargets), 'strlen'));
             $documentTargets = array_unique($documentTargets);
             if (count($documentTargets) == 0) {
                 throw new Exception('No valid accept_presentation_document_targets found');
             }
             foreach ($documentTargets as $documentTarget) {
                 $this->checkValue($documentTarget, ['embed', 'frame', 'iframe', 'window', 'popup', 'overlay', 'none'], 'Invalid value in accept_presentation_document_targets parameter: %s.');
             }
             $this->documentTargets = $documentTargets;
             $returnUrl = isset($requestBody['content_item_return_url']) ? trim($requestBody['content_item_return_url']) : '';
             if (strlen($returnUrl) == 0) {
                 throw new Exception('Missing content_item_return_url parameter');
             }
             break;
         default:
             throw new Exception('Invalid or missing lti_message_type parameter');
     }
     // Check consumer key
     if (!isset($requestBody['oauth_consumer_key'])) {
         throw new Exception('Missing consumer key');
     }
     $this->consumer = new ToolConsumer($requestBody['oauth_consumer_key'], $this->storage);
     if (is_null($this->consumer->created)) {
         throw new Exception('Invalid consumer key');
     }
     $now = time();
     $today = date('Y-m-d', $now);
     if (is_null($this->consumer->lastAccess)) {
         $doSaveConsumer = true;
     } else {
         $last = date('Y-m-d', $this->consumer->lastAccess);
         $doSaveConsumer = $doSaveConsumer || $last != $today;
     }
     $this->consumer->lastAccess = $now;
     $store = new DataStore($this);
     $server = new Server($store);
     $method = new SignatureMethodHmacSha1();
     $server->addSignatureMethod($method);
     $request = Request::fromPsrRequest($request);
     $server->verifyRequest($request);
     if ($this->consumer->protected) {
         $consumerGuid = isset($requestBody['tool_consumer_instance_guid']) ? $requestBody['tool_consumer_instance_guid'] : '';
         if (empty($consumerGuid)) {
             throw new Exception('A tool consumer GUID must be included in the launch request');
         }
         if ($this->consumer->consumerGuid !== $consumerGuid) {
             throw new Exception('Request is from an invalid tool consumer');
         }
     }
     if (!$this->consumer->enabled) {
         throw new Exception('Tool consumer has not been enabled by the tool provider');
     }
     if (!is_null($this->consumer->enableFrom) && $this->consumer->enableFrom > $now) {
         throw new Exception('Tool consumer access is not yet available');
     }
     if (!is_null($this->consumer->enableUntil) && $this->consumer->enableUntil <= $now) {
         throw new Exception('Tool consumer access has expired');
     }
     // Validate other message parameter values
     if ($requestBody['lti_message_type'] != 'ContentItemSelectionRequest') {
         if (isset($requestBody['launch_presentation_document_target'])) {
             $this->checkValue($requestBody['launch_presentation_document_target'], ['embed', 'frame', 'iframe', 'window', 'popup', 'overlay'], 'Invalid value for launch_presentation_document_target parameter: %s.');
         }
     } else {
         if (isset($requestBody['accept_unsigned'])) {
             $this->checkValue($requestBody['accept_unsigned'], ['true', 'false'], 'Invalid value for accept_unsigned parameter: %s.');
         }
         if (isset($requestBody['accept_multiple'])) {
             $this->checkValue($requestBody['accept_multiple'], ['true', 'false'], 'Invalid value for accept_multiple parameter: %s.');
         }
         if (isset($requestBody['accept_copy_advice'])) {
             $this->checkValue($requestBody['accept_copy_advice'], ['true', 'false'], 'Invalid value for accept_copy_advice parameter: %s.');
         }
         if (isset($requestBody['auto_create'])) {
             $this->checkValue($requestBody['auto_create'], ['true', 'false'], 'Invalid value for auto_create parameter: %s.');
         }
         if (isset($requestBody['can_confirm'])) {
             $this->checkValue($requestBody['can_confirm'], ['true', 'false'], 'Invalid value for can_confirm parameter: %s.');
         }
     }
     // Validate message parameter constraints
     $invalid_parameters = [];
     foreach ($this->constraints as $name => $constraint) {
         if (empty($constraint['messages']) || in_array($messageType, $constraint['messages'])) {
             $ok = true;
             if ($constraint['required']) {
                 if (!isset($requestBody[$name]) || strlen(trim($requestBody[$name])) <= 0) {
                     $invalid_parameters[] = "{$name} (missing)";
                     $ok = false;
                 }
             }
             if ($ok && !is_null($constraint['max_length']) && isset($requestBody[$name])) {
                 if (strlen(trim($requestBody[$name])) > $constraint['max_length']) {
                     $invalid_parameters[] = "{$name} (too long)";
                 }
             }
         }
     }
     if (count($invalid_parameters) > 0) {
         throw new Exception('Invalid parameter(s): ' . implode(', ', $invalid_parameters));
     }
     // Set the request context/resource link
     if (isset($requestBody['resource_link_id'])) {
         $content_item_id = '';
         if (isset($requestBody['custom_content_item_id'])) {
             $content_item_id = $requestBody['custom_content_item_id'];
         }
         $this->resourceLink = new ResourceLink($this->consumer, trim($requestBody['resource_link_id']), $content_item_id);
         if (isset($requestBody['context_id'])) {
             $this->resourceLink->lti_context_id = trim($requestBody['context_id']);
         }
         $this->resourceLink->lti_resource_id = trim($requestBody['resource_link_id']);
         $title = '';
         if (isset($requestBody['context_title'])) {
             $title = trim($requestBody['context_title']);
         }
         if (isset($requestBody['resource_link_title']) && strlen(trim($requestBody['resource_link_title'])) > 0) {
             if (!empty($title)) {
                 $title .= ': ';
             }
             $title .= trim($requestBody['resource_link_title']);
         }
         if (empty($title)) {
             $title = "Course {$this->resourceLink->getId()}";
         }
         $this->resourceLink->title = $title;
         // Save LTI parameters
         foreach ($this->ltiSettingsNames as $name) {
             if (isset($requestBody[$name])) {
                 $this->resourceLink->setSetting($name, $requestBody[$name]);
             } else {
                 $this->resourceLink->setSetting($name, null);
             }
         }
         // Delete any existing custom parameters
         foreach ($this->resourceLink->getSettings() as $name => $value) {
             if (strpos($name, 'custom_') === 0) {
                 $this->resourceLink->setSetting($name);
             }
         }
         // Save custom parameters
         foreach ($requestBody as $name => $value) {
             if (strpos($name, 'custom_') === 0) {
                 $this->resourceLink->setSetting($name, $value);
             }
         }
     }
     // Set the user instance
     $user_id = '';
     if (isset($requestBody['user_id'])) {
         $user_id = trim($requestBody['user_id']);
     }
     $this->user = new User($this->resourceLink, $user_id);
     // Set the user name
     if (isset($requestBody['lis_person_name_given'])) {
         $firstname = $requestBody['lis_person_name_given'];
     } elseif (isset($requestBody['custom_lis_person_given_name'])) {
         $firstname = $requestBody['custom_lis_person_given_name'];
     } else {
         $firstname = '';
     }
     if (isset($requestBody['lis_person_name_family'])) {
         $lastname = $requestBody['lis_person_name_family'];
     } elseif (isset($requestBody['custom_lis_person_name_family'])) {
         $lastname = $requestBody['custom_lis_person_name_family'];
     } else {
         $lastname = '';
     }
     if (isset($requestBody['lis_person_name_full'])) {
         $fullname = $requestBody['lis_person_name_full'];
     } elseif (isset($requestBody['custom_lis_person_name_full'])) {
         $fullname = $requestBody['custom_lis_person_name_full'];
     } else {
         $fullname = '';
     }
     $this->user->setNames($firstname, $lastname, $fullname);
     // Set the user email
     if (isset($requestBody['lis_person_contact_email_primary'])) {
         $email = $requestBody['lis_person_contact_email_primary'];
     } elseif (isset($requestBody['custom_lis_person_contact_email_primary'])) {
         $email = $requestBody['custom_lis_person_contact_email_primary'];
     } else {
         $email = '';
     }
     $this->user->setEmail($email, $this->defaultEmail);
     // Set the user roles
     if (isset($requestBody['roles'])) {
         $this->user->roles = ToolProvider::parseRoles($requestBody['roles']);
     }
     // Save the user instance
     if (isset($requestBody['lis_result_sourcedid'])) {
         if ($this->user->ltiResultSourcedId != $requestBody['lis_result_sourcedid']) {
             $this->user->ltiResultSourcedId = $requestBody['lis_result_sourcedid'];
             $this->user->save();
         }
     } else {
         if (!empty($this->user->ltiResultSourcedId)) {
             $this->user->delete();
         }
     }
     // Initialise the consumer and check for changes
     $this->consumer->defaultEmail = $this->defaultEmail;
     if ($this->consumer->ltiVersion != $requestBody['lti_version']) {
         $this->consumer->ltiVersion = $requestBody['lti_version'];
         $doSaveConsumer = true;
     }
     if (isset($requestBody['tool_consumer_instance_name'])) {
         if ($this->consumer->consumerName != $requestBody['tool_consumer_instance_name']) {
             $this->consumer->consumerName = $requestBody['tool_consumer_instance_name'];
             $doSaveConsumer = true;
         }
     }
     if (isset($requestBody['tool_consumer_info_product_family_code'])) {
         $version = $requestBody['tool_consumer_info_product_family_code'];
         if (isset($requestBody['tool_consumer_info_version'])) {
             $version .= "-{$requestBody['tool_consumer_info_version']}";
         }
         // do not delete any existing consumer version if none is passed
         if ($this->consumer->consumerVersion != $version) {
             $this->consumer->consumerVersion = $version;
             $doSaveConsumer = true;
         }
     } else {
         if (isset($requestBody['ext_lms']) && $this->consumer->consumerName != $requestBody['ext_lms']) {
             $this->consumer->consumerVersion = $requestBody['ext_lms'];
             $doSaveConsumer = true;
         }
     }
     if (isset($requestBody['tool_consumer_instance_guid'])) {
         if (is_null($this->consumer->consumerGuid)) {
             $this->consumer->consumerGuid = $requestBody['tool_consumer_instance_guid'];
             $doSaveConsumer = true;
         } else {
             if (!$this->consumer->protected) {
                 $doSaveConsumer = $this->consumer->consumerGuid != $requestBody['tool_consumer_instance_guid'];
                 if ($doSaveConsumer) {
                     $this->consumer->consumerGuid = $requestBody['tool_consumer_instance_guid'];
                 }
             }
         }
     }
     if (isset($requestBody['launch_presentation_css_url'])) {
         if ($this->consumer->cssPath != $requestBody['launch_presentation_css_url']) {
             $this->consumer->cssPath = $requestBody['launch_presentation_css_url'];
             $doSaveConsumer = true;
         }
     } else {
         if (isset($requestBody['ext_launch_presentation_css_url']) && $this->consumer->cssPath != $requestBody['ext_launch_presentation_css_url']) {
             $this->consumer->cssPath = $requestBody['ext_launch_presentation_css_url'];
             $doSaveConsumer = true;
         } else {
             if (!empty($this->consumer->cssPath)) {
                 $this->consumer->cssPath = null;
                 $doSaveConsumer = true;
             }
         }
     }
     // Persist changes to consumer
     if ($doSaveConsumer) {
         $this->consumer->save();
     }
     if (isset($this->resourceLink)) {
         // Check if a share arrangement is in place for this resource link
         $this->checkForShare($requestBody);
         // Persist changes to resource link
         $this->resourceLink->save();
     }
     return true;
 }