def post_replace_result(self, score, result_data=None): ''' POSTs the given score to the Tool Consumer with a replaceResult. OPTIONAL: result_data must be a dictionary Note: ONLY ONE of these values can be in the dict at a time, due to the Canvas specification. 'text' : str text 'url' : str url ''' self.operation = REPLACE_REQUEST self.score = score self.result_data = result_data if result_data is not None: if len(result_data) > 1: error_msg = ('Dictionary result_data can only have one entry. ' '{0} entries were found.'.format( len(result_data))) raise InvalidLTIConfigError(error_msg) elif 'text' not in result_data and 'url' not in result_data: error_msg = ('Dictionary result_data can only have the key ' '"text" or the key "url".') raise InvalidLTIConfigError(error_msg) else: return self.post_outcome_request() else: return self.post_outcome_request()
def __init__(self, **kwargs): # Initialize all class accessors to None for attr in VALID_ATTRIBUTES: setattr(self, attr, None) # Store specified options in our options member for (key, val) in kwargs.iteritems(): if key in VALID_ATTRIBUTES: setattr(self, key, val) else: raise InvalidLTIConfigError( "Invalid outcome response option: {}".format(key))
def __init__(self, opts=defaultdict(lambda: None)): # Initialize all our accessors to None for attr in VALID_ATTRIBUTES: setattr(self, attr, None) # Store specified options in our accessors for (key, val) in opts.iteritems(): if key in VALID_ATTRIBUTES: setattr(self, key, val) else: raise InvalidLTIConfigError( "Invalid outcome request option: {}".format(key))
def generate_launch_data(self): # Validate params if not self.has_required_params(): raise InvalidLTIConfigError( 'ToolConsumer does not have all required attributes: consumer_key = %s, consumer_secret = %s, resource_link_id = %s, launch_url = %s' % (self.consumer_key, self.consumer_secret, self.resource_link_id, self.launch_url)) params = self.to_params() if not params.get('lit_version', None): params['lti_version'] = 'LTI-1.0' params['lti_message_type'] = 'basic-lti-launch-request' # Get new OAuth consumer consumer = oauth2.Consumer(key = self.consumer_key,\ secret = self.consumer_secret) params.update({ 'oauth_nonce': str(generate_identifier()), 'oauth_timestamp': str(int(time.time())), 'oauth_scheme': 'body', 'oauth_consumer_key': consumer.key }) uri = urlparse.urlparse(self.launch_url) if uri.query != '': for param in uri.query.split('&'): key, val = param.split('=') if params.get(key) == None: params[key] = str(val) request = oauth2.Request(method='POST', url=self.launch_url, parameters=params) signature_method = oauth2.SignatureMethod_HMAC_SHA1() request.sign_request(signature_method, consumer, None) # Request was made by an HTML form in the user's browser. # Return the dict of post parameters ready for embedding # in an html view. return_params = {} for key in request: if request[key] == None: return_params[key] = None elif isinstance(request[key], list): return_params[key] = request.get_parameter(key) else: return_params[key] = unquote(request.get_parameter(key)) return return_params
def post_outcome_request(self): ''' POST an OAuth signed request to the Tool Consumer. ''' if not self.has_required_attributes(): raise InvalidLTIConfigError( 'OutcomeRequest does not have all required attributes') consumer = oauth2.Consumer(key=self.consumer_key, secret=self.consumer_secret) client = oauth2.Client(consumer) # monkey_patch_headers ensures that Authorization # header is NOT lower cased monkey_patch_headers = True monkey_patch_function = None if monkey_patch_headers: import httplib2 http = httplib2.Http normalize = http._normalize_headers def my_normalize(self, headers): print("My Normalize", headers) ret = normalize(self, headers) if 'authorization' in ret: ret['Authorization'] = ret.pop('authorization') print("My Normalize", ret) return ret http._normalize_headers = my_normalize monkey_patch_function = normalize response, content = client.request( self.lis_outcome_service_url, 'POST', body=self.generate_request_xml(), headers={'Content-Type': 'application/xml'}) if monkey_patch_headers and monkey_patch_function: import httplib2 http = httplib2.Http http._normalize_headers = monkey_patch_function self.outcome_response = OutcomeResponse.from_post_response( response, content) return self.outcome_response
def generate_launch_request(self, **kwargs): """ returns a Oauth v1 "signed" requests.PreparedRequest instance """ if not self.has_required_params(): raise InvalidLTIConfigError( 'Consumer\'s launch params missing one of ' \ + str(LAUNCH_PARAMS_REQUIRED) ) # if 'oauth_consumer_key' not in self.launch_params: # self.launch_params['oauth_consumer_key'] = self.consumer_key params = self.to_params() r = Request('POST', self.launch_url, data=params).prepare() sign = OAuth1(self.consumer_key, self.consumer_secret, signature_type=SIGNATURE_TYPE_BODY, **kwargs) return sign(r)
def __init__(self, consumer_key, consumer_secret, params=None, launch_url=None): ''' Create new ToolConsumer. ''' # allow launch_url to be specified in launch_params for # backwards compatibility if launch_url is None: if 'launch_url' not in params: raise InvalidLTIConfigError('missing \'launch_url\' arg!') else: launch_url = params['launch_url'] del params['launch_url'] self.launch_url = launch_url super(ToolConsumer, self).__init__(consumer_key, consumer_secret, params=params)
def post_outcome_request(self): ''' POST an OAuth signed request to the Tool Consumer. ''' if not self.has_required_attributes(): raise InvalidLTIConfigError( 'OutcomeRequest does not have all required attributes') consumer = oauth2.Consumer(key=self.consumer_key, secret=self.consumer_secret) client = oauth2.Client(consumer) response, content = client.request( self.lis_outcome_service_url, 'POST', body=self.generate_request_xml(), headers={'Content-Type': 'application/xml'}) self.outcome_response = OutcomeResponse.from_post_response( response, content) return self.outcome_response
def post_outcome_request(self, **kwargs): ''' POST an OAuth signed request to the Tool Consumer. ''' if not self.has_required_attributes(): raise InvalidLTIConfigError( 'OutcomeRequest does not have all required attributes') header_oauth = OAuth1(self.consumer_key, self.consumer_secret, signature_type=SIGNATURE_TYPE_AUTH_HEADER, force_include_body=True, **kwargs) headers = {'Content-type': 'application/xml'} resp = requests.post(self.lis_outcome_service_url, auth=header_oauth, data=self.generate_request_xml(), headers=headers) outcome_resp = OutcomeResponse.from_post_response(resp, resp.content) self.outcome_response = outcome_resp return self.outcome_response
def __init__(self, **kwargs): ''' Create a new ToolConfig with the given options. ''' # Initialize all class accessors to None for attr in VALID_ATTRIBUTES: setattr(self, attr, None) for attr in ['custom_params', 'extensions']: if attr in kwargs: attr_val = kwargs.pop(attr) else: attr_val = defaultdict(lambda: None) setattr(self, attr, attr_val) # Iterate over all provided options and save to class instance members for (key, val) in kwargs.iteritems(): if key in VALID_ATTRIBUTES: setattr(self, key, val) else: raise InvalidLTIConfigError( "Invalid outcome request option: {}".format(key) )
def to_xml(self, opts=defaultdict(lambda: None)): ''' Generate XML from the current settings. ''' if not self.launch_url or not self.secure_launch_url: raise InvalidLTIConfigError('Invalid LTI configuration') NSMAP = { 'blti': 'http://www.imsglobal.org/xsd/imsbasiclti_v1p0', 'xsi': "http://www.w3.org/2001/XMLSchema-instance", 'lticp': 'http://www.imsglobal.org/xsd/imslticp_v1p0', 'lticm': 'http://www.imsglobal.org/xsd/imslticm_v1p0', } root = etree.Element( 'cartridge_basiclti_link', attrib={ '{%s}%s' % (NSMAP['xsi'], 'schemaLocation'): 'http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0p1.xsd http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd', 'xmlns': 'http://www.imsglobal.org/xsd/imslticc_v1p0' }, nsmap=NSMAP) for key in ['title', 'description', 'launch_url', 'secure_launch_url']: option = etree.SubElement(root, '{%s}%s' % (NSMAP['blti'], key)) option.text = getattr(self, key) vendor_keys = ['name', 'code', 'description', 'url'] if any('vendor_' + key for key in vendor_keys) or\ self.vendor_contact_email: vendor_node = etree.SubElement( root, '{%s}%s' % (NSMAP['blti'], 'vendor')) for key in vendor_keys: if getattr(self, 'vendor_' + key) != None: v_node = etree.SubElement(vendor_node, '{%s}%s' % (NSMAP['lticp'], key)) v_node.text = getattr(self, 'vendor_' + key) if getattr(self, 'vendor_contact_email'): v_node = etree.SubElement( vendor_node, '{%s}%s' % (NSMAP['lticp'], 'contact')) c_name = etree.SubElement(v_node, '{%s}%s' % (NSMAP['lticp'], 'name')) c_name.text = self.vendor_contact_name c_email = etree.SubElement( v_node, '{%s}%s' % (NSMAP['lticp'], 'email')) c_email.text = self.vendor_contact_email # Custom params if len(self.custom_params) != 0: custom_node = etree.SubElement( root, '{%s}%s' % (NSMAP['blti'], 'custom')) for (key, val) in sorted(self.custom_params.items()): c_node = etree.SubElement( custom_node, '{%s}%s' % (NSMAP['lticm'], 'property')) c_node.set('name', key) c_node.text = val # Extension params if len(self.extensions) != 0: for (key, params) in sorted(self.extensions.items()): extension_node = etree.SubElement( root, '{%s}%s' % (NSMAP['blti'], 'extensions'), platform=key) for key, val in params.iteritems(): if isinstance(val, dict): options_node = etree.SubElement( extension_node, '{%s}%s' % (NSMAP['lticm'], 'options'), name=key) for key, val in val.iteritems(): property_node = etree.SubElement( options_node, '{%s}%s' % (NSMAP['lticm'], 'property'), name=key) property_node.text = val else: param_node = etree.SubElement( extension_node, '{%s}%s' % (NSMAP['lticm'], 'property'), name=key) param_node.text = val if getattr(self, 'cartridge_bundle'): identifierref = etree.SubElement( root, 'cartridge_bundle', identifierref=self.cartridge_bundle) if getattr(self, 'cartridge_icon'): identifierref = etree.SubElement(root, 'cartridge_icon', identifierref=self.cartridge_icon) return '<?xml version="1.0" encoding="UTF-8"?>' + etree.tostring(root)