def get_input_fields(self): # LTI provides a list of default parameters that might be passed as # part of the POST data. These parameters should not be prefixed. # Likewise, The creator of an LTI link can add custom key/value parameters # to a launch which are to be included with the launch of the LTI link. # In this case, we will automatically add `custom_` prefix before this parameters. # See http://www.imsglobal.org/LTI/v1p1p1/ltiIMGv1p1p1.html#_Toc316828520 PARAMETERS = [ "lti_message_type", "lti_version", "resource_link_title", "resource_link_description", "user_image", "lis_person_name_given", "lis_person_name_family", "lis_person_name_full", "lis_person_contact_email_primary", "lis_person_sourcedid", "role_scope_mentor", "context_type", "context_title", "context_label", "launch_presentation_locale", "launch_presentation_document_target", "launch_presentation_css_url", "launch_presentation_width", "launch_presentation_height", "launch_presentation_return_url", "tool_consumer_info_product_family_code", "tool_consumer_info_version", "tool_consumer_instance_guid", "tool_consumer_instance_name", "tool_consumer_instance_description", "tool_consumer_instance_url", "tool_consumer_instance_contact_email", ] client_key, client_secret = self.get_client_key_secret() # parsing custom parameters to dict custom_parameters = {} for custom_parameter in self.custom_parameters: try: param_name, param_value = [ p.strip() for p in custom_parameter.split('=', 1) ] except ValueError: _ = self.runtime.service(self, "i18n").ugettext msg = _( 'Could not parse custom parameter: {custom_parameter}. Should be "x=y" string.' ).format(custom_parameter="{0!r}".format(custom_parameter)) raise LTIError(msg) # LTI specs: 'custom_' should be prepended before each custom parameter, as pointed in link above. if param_name not in PARAMETERS: param_name = 'custom_' + param_name custom_parameters[unicode(param_name)] = unicode(param_value) return self.oauth_params( custom_parameters, client_key, client_secret, )
def verify_oauth_body_sign(self, request, content_type='application/x-www-form-urlencoded'): """ Verify grade request from LTI provider using OAuth body signing. Uses http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html:: This specification extends the OAuth signature to include integrity checks on HTTP request bodies with content types other than application/x-www-form-urlencoded. Arguments: request: DjangoWebobRequest. Raises: LTIError if request is incorrect. """ client_key, client_secret = self.get_client_key_secret() headers = { 'Authorization': six.text_type(request.headers.get('Authorization')), 'Content-Type': content_type, } sha1 = hashlib.sha1() sha1.update(request.body) oauth_body_hash = base64.b64encode(sha1.digest()).decode('utf-8') oauth_params = signature.collect_parameters(headers=headers, exclude_oauth_signature=False) oauth_headers = dict(oauth_params) oauth_signature = oauth_headers.pop('oauth_signature') mock_request_lti_1 = mock.Mock( uri=six.text_type(six.moves.urllib.parse.unquote(self.get_outcome_service_url())), http_method=six.text_type(request.method), params=list(oauth_headers.items()), signature=oauth_signature ) mock_request_lti_2 = mock.Mock( uri=six.text_type(six.moves.urllib.parse.unquote(request.url)), http_method=six.text_type(request.method), params=list(oauth_headers.items()), signature=oauth_signature ) if oauth_body_hash != oauth_headers.get('oauth_body_hash'): log.error( "OAuth body hash verification failed, provided: {}, " "calculated: {}, for url: {}, body is: {}".format( oauth_headers.get('oauth_body_hash'), oauth_body_hash, self.get_outcome_service_url(), request.body ) ) raise LTIError("OAuth body hash verification is failed.") if (not signature.verify_hmac_sha1(mock_request_lti_1, client_secret) and not signature.verify_hmac_sha1(mock_request_lti_2, client_secret)): log.error("OAuth signature verification failed, for " "headers:{} url:{} method:{}".format( oauth_headers, self.get_outcome_service_url(), six.text_type(request.method) )) raise LTIError("OAuth signature verification has failed.")