Example #1
0
 /**
  * pretty much a helper function to set up the request
  */
 public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters = NULL)
 {
     $parameters = $parameters ? $parameters : array();
     $defaults = array("oauth_version" => OAuthRequest::$version, "oauth_nonce" => OAuthRequest::generate_nonce(), "oauth_timestamp" => OAuthRequest::generate_timestamp(), "oauth_consumer_key" => $consumer->key);
     if ($token) {
         $defaults['oauth_token'] = $token->key;
     }
     $parameters = array_merge($defaults, $parameters);
     return new OAuthRequest($http_method, $http_url, $parameters);
 }
Example #2
0
 /**
  * Add the OAuth signature to an LTI message.
  *
  * @param string  $url         URL for message request
  * @param string  $type        LTI message type
  * @param string  $version     LTI version
  * @param array   $params      Message parameters
  *
  * @return array Array of signed message parameters
  */
 public function signParameters($url, $type, $version, $params)
 {
     if (!empty($url)) {
         // Check for query parameters which need to be included in the signature
         $query_params = array();
         $query_string = parse_url($url, PHP_URL_QUERY);
         if (!is_null($query_string)) {
             $query_items = explode('&', $query_string);
             foreach ($query_items as $item) {
                 if (strpos($item, '=') !== FALSE) {
                     list($name, $value) = explode('=', $item);
                     $query_params[urldecode($name)] = urldecode($value);
                 } else {
                     $query_params[urldecode($item)] = '';
                 }
             }
         }
         $params = $params + $query_params;
         // Add standard parameters
         $params['lti_version'] = $version;
         $params['lti_message_type'] = $type;
         $params['oauth_callback'] = 'about:blank';
         // Add OAuth signature
         $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
         $consumer = new OAuthConsumer($this->getKey(), $this->secret, NULL);
         $req = OAuthRequest::from_consumer_and_token($consumer, NULL, 'POST', $url, $params);
         $req->sign_request($hmac_method, $consumer, NULL);
         $params = $req->get_parameters();
         // Remove parameters being passed on the query string
         foreach (array_keys($query_params) as $name) {
             unset($params[$name]);
         }
     }
     return $params;
 }
Example #3
0
 /**
  * Check the authenticity of the LTI launch request.
  *
  * The consumer, resource link and user objects will be initialised if the request is valid.
  *
  * @return boolean True if the request has been successfully validated.
  */
 private function authenticate()
 {
     #
     ### Get the consumer
     #
     $doSaveConsumer = FALSE;
     // Check all required launch parameters
     $this->isOK = isset($_POST['lti_message_type']) && array_key_exists($_POST['lti_message_type'], $this->messageTypes);
     if (!$this->isOK) {
         $this->reason = 'Invalid or missing lti_message_type parameter.';
     }
     if ($this->isOK) {
         $this->isOK = isset($_POST['lti_version']) && in_array($_POST['lti_version'], $this->LTI_VERSIONS);
         if (!$this->isOK) {
             $this->reason = 'Invalid or missing lti_version parameter.';
         }
     }
     if ($this->isOK) {
         if ($_POST['lti_message_type'] == 'basic-lti-launch-request' || $_POST['lti_message_type'] == 'DashboardRequest') {
             $this->isOK = isset($_POST['resource_link_id']) && strlen(trim($_POST['resource_link_id'])) > 0;
             if (!$this->isOK) {
                 $this->reason = 'Missing resource link ID.';
             }
         } else {
             if ($_POST['lti_message_type'] == 'ContentItemSelectionRequest') {
                 if (isset($_POST['accept_media_types']) && strlen(trim($_POST['accept_media_types'])) > 0) {
                     $mediaTypes = array_filter(explode(',', str_replace(' ', '', $_POST['accept_media_types'])), 'strlen');
                     $mediaTypes = array_unique($mediaTypes);
                     $this->isOK = count($mediaTypes) > 0;
                     if (!$this->isOK) {
                         $this->reason = 'No accept_media_types found.';
                     } else {
                         $this->mediaTypes = $mediaTypes;
                     }
                 } else {
                     $this->isOK = FALSE;
                 }
                 if ($this->isOK && isset($_POST['accept_presentation_document_targets']) && strlen(trim($_POST['accept_presentation_document_targets'])) > 0) {
                     $documentTargets = array_filter(explode(',', str_replace(' ', '', $_POST['accept_presentation_document_targets'])), 'strlen');
                     $documentTargets = array_unique($documentTargets);
                     $this->isOK = count($documentTargets) > 0;
                     if (!$this->isOK) {
                         $this->reason = 'Missing or empty accept_presentation_document_targets parameter.';
                     } else {
                         foreach ($documentTargets as $documentTarget) {
                             $this->isOK = $this->checkValue($documentTarget, array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay', 'none'), 'Invalid value in accept_presentation_document_targets parameter: %s.');
                             if (!$this->isOK) {
                                 break;
                             }
                         }
                         if ($this->isOK) {
                             $this->documentTargets = $documentTargets;
                         }
                     }
                 } else {
                     $this->isOK = FALSE;
                 }
                 if ($this->isOK) {
                     $this->isOK = isset($_POST['content_item_return_url']) && strlen(trim($_POST['content_item_return_url'])) > 0;
                     if (!$this->isOK) {
                         $this->reason = 'Missing content_item_return_url parameter.';
                     }
                 }
             }
         }
     }
     // Check consumer key
     if ($this->isOK) {
         $this->isOK = isset($_POST['oauth_consumer_key']);
         if (!$this->isOK) {
             $this->reason = 'Missing consumer key.';
         }
     }
     if ($this->isOK) {
         $this->consumer = new LTI_Tool_Consumer($_POST['oauth_consumer_key'], $this->data_connector);
         $this->isOK = !is_null($this->consumer->created);
         if (!$this->isOK) {
             $this->reason = 'Invalid consumer key.';
         }
     }
     $now = time();
     if ($this->isOK) {
         $today = date('Y-m-d', $now);
         if (is_null($this->consumer->last_access)) {
             $doSaveConsumer = TRUE;
         } else {
             $last = date('Y-m-d', $this->consumer->last_access);
             $doSaveConsumer = $doSaveConsumer || $last != $today;
         }
         $this->consumer->last_access = $now;
         try {
             $store = new LTI_OAuthDataStore($this);
             $server = new OAuthServer($store);
             $method = new OAuthSignatureMethod_HMAC_SHA1();
             $server->add_signature_method($method);
             $request = OAuthRequest::from_request();
             $res = $server->verify_request($request);
         } catch (Exception $e) {
             $this->isOK = FALSE;
             if (empty($this->reason)) {
                 if ($this->debugMode) {
                     $consumer = new OAuthConsumer($this->consumer->getKey(), $this->consumer->secret);
                     $signature = $request->build_signature($method, $consumer, FALSE);
                     $this->reason = $e->getMessage();
                     if (empty($this->reason)) {
                         $this->reason = 'OAuth exception';
                     }
                     $this->details[] = 'Timestamp: ' . time();
                     $this->details[] = "Signature: {$signature}";
                     $this->details[] = "Base string: {$request->base_string}]";
                 } else {
                     $this->reason = 'OAuth signature check failed - perhaps an incorrect secret or timestamp.';
                 }
             }
         }
     }
     if ($this->isOK && $this->consumer->protected) {
         if (!is_null($this->consumer->consumer_guid)) {
             $this->isOK = isset($_POST['tool_consumer_instance_guid']) && !empty($_POST['tool_consumer_instance_guid']) && $this->consumer->consumer_guid == $_POST['tool_consumer_instance_guid'];
             if (!$this->isOK) {
                 $this->reason = 'Request is from an invalid tool consumer.';
             }
         } else {
             $this->isOK = isset($_POST['tool_consumer_instance_guid']);
             if (!$this->isOK) {
                 $this->reason = 'A tool consumer GUID must be included in the launch request.';
             }
         }
     }
     if ($this->isOK) {
         $this->isOK = $this->consumer->enabled;
         if (!$this->isOK) {
             $this->reason = 'Tool consumer has not been enabled by the tool provider.';
         }
     }
     if ($this->isOK) {
         $this->isOK = is_null($this->consumer->enable_from) || $this->consumer->enable_from <= $now;
         if ($this->isOK) {
             $this->isOK = is_null($this->consumer->enable_until) || $this->consumer->enable_until > $now;
             if (!$this->isOK) {
                 $this->reason = 'Tool consumer access has expired.';
             }
         } else {
             $this->reason = 'Tool consumer access is not yet available.';
         }
     }
     #
     ### Validate other message parameter values
     #
     if ($this->isOK) {
         if ($_POST['lti_message_type'] != 'ContentItemSelectionRequest') {
             if (isset($_POST['launch_presentation_document_target'])) {
                 $this->isOK = $this->checkValue($_POST['launch_presentation_document_target'], array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay'), 'Invalid value for launch_presentation_document_target parameter: %s.');
             }
         } else {
             if (isset($_POST['accept_unsigned'])) {
                 $this->isOK = $this->checkValue($_POST['accept_unsigned'], array('true', 'false'), 'Invalid value for accept_unsigned parameter: %s.');
             }
             if ($this->isOK && isset($_POST['accept_multiple'])) {
                 $this->isOK = $this->checkValue($_POST['accept_multiple'], array('true', 'false'), 'Invalid value for accept_multiple parameter: %s.');
             }
             if ($this->isOK && isset($_POST['accept_copy_advice'])) {
                 $this->isOK = $this->checkValue($_POST['accept_copy_advice'], array('true', 'false'), 'Invalid value for accept_copy_advice parameter: %s.');
             }
             if ($this->isOK && isset($_POST['auto_create'])) {
                 $this->isOK = $this->checkValue($_POST['auto_create'], array('true', 'false'), 'Invalid value for auto_create parameter: %s.');
             }
             if ($this->isOK && isset($_POST['can_confirm'])) {
                 $this->isOK = $this->checkValue($_POST['can_confirm'], array('true', 'false'), 'Invalid value for can_confirm parameter: %s.');
             }
         }
     }
     #
     ### Validate message parameter constraints
     #
     if ($this->isOK) {
         $invalid_parameters = array();
         foreach ($this->constraints as $name => $constraint) {
             if (empty($constraint['messages']) || in_array($_POST['lti_message_type'], $constraint['messages'])) {
                 $ok = TRUE;
                 if ($constraint['required']) {
                     if (!isset($_POST[$name]) || strlen(trim($_POST[$name])) <= 0) {
                         $invalid_parameters[] = "{$name} (missing)";
                         $ok = FALSE;
                     }
                 }
                 if ($ok && !is_null($constraint['max_length']) && isset($_POST[$name])) {
                     if (strlen(trim($_POST[$name])) > $constraint['max_length']) {
                         $invalid_parameters[] = "{$name} (too long)";
                     }
                 }
             }
         }
         if (count($invalid_parameters) > 0) {
             $this->isOK = FALSE;
             if (empty($this->reason)) {
                 $this->reason = 'Invalid parameter(s): ' . implode(', ', $invalid_parameters) . '.';
             }
         }
     }
     if ($this->isOK) {
         #
         ### Set the request context/resource link
         #
         if (isset($_POST['resource_link_id'])) {
             $content_item_id = '';
             if (isset($_POST['custom_content_item_id'])) {
                 $content_item_id = $_POST['custom_content_item_id'];
             }
             $this->resource_link = new LTI_Resource_Link($this->consumer, trim($_POST['resource_link_id']), $content_item_id);
             if (isset($_POST['context_id'])) {
                 $this->resource_link->lti_context_id = trim($_POST['context_id']);
             }
             $this->resource_link->lti_resource_id = trim($_POST['resource_link_id']);
             $title = '';
             if (isset($_POST['context_title'])) {
                 $title = trim($_POST['context_title']);
             }
             if (isset($_POST['resource_link_title']) && strlen(trim($_POST['resource_link_title'])) > 0) {
                 if (!empty($title)) {
                     $title .= ': ';
                 }
                 $title .= trim($_POST['resource_link_title']);
             }
             if (empty($title)) {
                 $title = "Course {$this->resource_link->getId()}";
             }
             $this->resource_link->title = $title;
             // Save LTI parameters
             foreach ($this->lti_settings_names as $name) {
                 if (isset($_POST[$name])) {
                     $this->resource_link->setSetting($name, $_POST[$name]);
                 } else {
                     $this->resource_link->setSetting($name, NULL);
                 }
             }
             // Delete any existing custom parameters
             foreach ($this->resource_link->getSettings() as $name => $value) {
                 if (strpos($name, 'custom_') === 0) {
                     $this->resource_link->setSetting($name);
                 }
             }
             // Save custom parameters
             foreach ($_POST as $name => $value) {
                 if (strpos($name, 'custom_') === 0) {
                     $this->resource_link->setSetting($name, $value);
                 }
             }
         }
         #
         ### Set the user instance
         #
         $user_id = '';
         if (isset($_POST['user_id'])) {
             $user_id = trim($_POST['user_id']);
         }
         $this->user = new LTI_User($this->resource_link, $user_id);
         #
         ### Set the user name
         #
         $firstname = isset($_POST['lis_person_name_given']) ? $_POST['lis_person_name_given'] : '';
         $lastname = isset($_POST['lis_person_name_family']) ? $_POST['lis_person_name_family'] : '';
         $fullname = isset($_POST['lis_person_name_full']) ? $_POST['lis_person_name_full'] : '';
         $this->user->setNames($firstname, $lastname, $fullname);
         #
         ### Set the user email
         #
         $email = isset($_POST['lis_person_contact_email_primary']) ? $_POST['lis_person_contact_email_primary'] : '';
         $this->user->setEmail($email, $this->defaultEmail);
         #
         ### Set the user roles
         #
         if (isset($_POST['roles'])) {
             $this->user->roles = LTI_Tool_Provider::parseRoles($_POST['roles']);
         }
         #
         ### Save the user instance
         #
         if (isset($_POST['lis_result_sourcedid'])) {
             if ($this->user->lti_result_sourcedid != $_POST['lis_result_sourcedid']) {
                 $this->user->lti_result_sourcedid = $_POST['lis_result_sourcedid'];
                 $this->user->save();
             }
         } else {
             if (!empty($this->user->lti_result_sourcedid)) {
                 $this->user->delete();
             }
         }
         #
         ### Initialise the consumer and check for changes
         #
         $this->consumer->defaultEmail = $this->defaultEmail;
         if ($this->consumer->lti_version != $_POST['lti_version']) {
             $this->consumer->lti_version = $_POST['lti_version'];
             $doSaveConsumer = TRUE;
         }
         if (isset($_POST['tool_consumer_instance_name'])) {
             if ($this->consumer->consumer_name != $_POST['tool_consumer_instance_name']) {
                 $this->consumer->consumer_name = $_POST['tool_consumer_instance_name'];
                 $doSaveConsumer = TRUE;
             }
         }
         if (isset($_POST['tool_consumer_info_product_family_code'])) {
             $version = $_POST['tool_consumer_info_product_family_code'];
             if (isset($_POST['tool_consumer_info_version'])) {
                 $version .= "-{$_POST['tool_consumer_info_version']}";
             }
             // do not delete any existing consumer version if none is passed
             if ($this->consumer->consumer_version != $version) {
                 $this->consumer->consumer_version = $version;
                 $doSaveConsumer = TRUE;
             }
         } else {
             if (isset($_POST['ext_lms']) && $this->consumer->consumer_name != $_POST['ext_lms']) {
                 $this->consumer->consumer_version = $_POST['ext_lms'];
                 $doSaveConsumer = TRUE;
             }
         }
         if (isset($_POST['tool_consumer_instance_guid'])) {
             if (is_null($this->consumer->consumer_guid)) {
                 $this->consumer->consumer_guid = $_POST['tool_consumer_instance_guid'];
                 $doSaveConsumer = TRUE;
             } else {
                 if (!$this->consumer->protected) {
                     $doSaveConsumer = $this->consumer->consumer_guid != $_POST['tool_consumer_instance_guid'];
                     if ($doSaveConsumer) {
                         $this->consumer->consumer_guid = $_POST['tool_consumer_instance_guid'];
                     }
                 }
             }
         }
         if (isset($_POST['launch_presentation_css_url'])) {
             if ($this->consumer->css_path != $_POST['launch_presentation_css_url']) {
                 $this->consumer->css_path = $_POST['launch_presentation_css_url'];
                 $doSaveConsumer = TRUE;
             }
         } else {
             if (isset($_POST['ext_launch_presentation_css_url']) && $this->consumer->css_path != $_POST['ext_launch_presentation_css_url']) {
                 $this->consumer->css_path = $_POST['ext_launch_presentation_css_url'];
                 $doSaveConsumer = TRUE;
             } else {
                 if (!empty($this->consumer->css_path)) {
                     $this->consumer->css_path = NULL;
                     $doSaveConsumer = TRUE;
                 }
             }
         }
     }
     #
     ### Persist changes to consumer
     #
     if ($doSaveConsumer) {
         $this->consumer->save();
     }
     if ($this->isOK && isset($this->resource_link)) {
         #
         ### Check if a share arrangement is in place for this resource link
         #
         $this->isOK = $this->checkForShare();
         #
         ### Persist changes to resource link
         #
         $this->resource_link->save();
     }
     return $this->isOK;
 }