def test_return_url_with_messages(self):
        '''
        Should generate a return url with messages.
        '''
        tp = create_tp()
        self.assertIsNone(tp.build_return_url())

        tp = create_tp(lp={
            'launch_presentation_return_url': 'http://foo.edu/done'
        })
        self.assertEqual(tp.build_return_url(), 'http://foo.edu/done')

        tp = create_tp(lp={
            'launch_presentation_return_url': 'http://foo.edu/done',
            'lti_errormsg': 'user error',
            'lti_errorlog': 'lms error',
            'lti_msg': 'user message',
            'lti_log': 'lms message'
        })
        return_url = tp.build_return_url()
        parsed = urlsplit(return_url)
        self.assertEqual(parsed.hostname, 'foo.edu')
        self.assertEqual(parsed.path, '/done')
        self.assertEqual(parse_qs(parsed.query), {
            'lti_errormsg': 'user error',
            'lti_errorlog': 'lms error',
            'lti_msg': 'user message',
            'lti_log': 'lms message'
        })
Example #2
0
 def test_launch_request_with_qs(self):
     """
     test that qs params in launch url are ok
     """
     launch_params = {
         'lti_version': 'abc',
         'lti_message_type': 'def',
         'resource_link_id': '123'
     }
     tc = ToolConsumer('client_key', 'client_secret',
                       launch_url='http://example.edu/foo?bar=1',
                       params=launch_params)
     launch_req = tc.generate_launch_request(nonce='wxyz7890',
                                             timestamp='2345678901')
     got = parse_qs(unquote(launch_req.body))
     correct = launch_params.copy()
     correct.update({
         'oauth_nonce': 'wxyz7890',
         'oauth_timestamp': '2345678901',
         'oauth_version': '1.0',
         'oauth_signature_method': 'HMAC-SHA1',
         'oauth_consumer_key': 'client_key',
         'oauth_signature': 'UH2l86Wq/g5Mu64GpCRcec6tEYY=',
         })
     self.assertEqual(got, correct)
Example #3
0
    def test_generate_launch_request(self):
        launch_params = {
            'lti_version': 'foo',
            'lti_message_type': 'bar',
            'resource_link_id': 'baz'
        }
        tc = ContentItemResponse('client_key', 'client_secret',
                          launch_url='http://example.edu/',
                          params=launch_params)
        launch_req = tc.generate_launch_request(nonce='abcd1234',
                                                timestamp='1234567890')

        self.assertIsInstance(launch_req, PreparedRequest)

        got = parse_qs(unquote(launch_req.body.decode('utf-8')))
        correct = launch_params.copy()
        correct.update({
            'oauth_nonce': 'abcd1234',
            'oauth_timestamp': '1234567890',
            'oauth_version': '1.0',
            'oauth_signature_method': 'HMAC-SHA1',
            'oauth_consumer_key': 'client_key',
            'oauth_signature': 'u2xlj 1gF4y 6gKHNeiL9cN3tOI=',
        })

        self.assertEqual(got, correct)
Example #4
0
    def test_generate_launch_request(self):
        launch_params = {
            'lti_version': 'foo',
            'lti_message_type': 'bar',
            'resource_link_id': 'baz'
        }
        tc = ToolConsumer('client_key', 'client_secret',
                          launch_url='http://example.edu/',
                          params=launch_params)
        launch_req = tc.generate_launch_request(nonce='abcd1234',
                                                timestamp='1234567890')

        self.assertIsInstance(launch_req, PreparedRequest)

        got = parse_qs(unquote(launch_req.body))
        correct = launch_params.copy()
        correct.update({
            'oauth_nonce': 'abcd1234',
            'oauth_timestamp': '1234567890',
            'oauth_version': '1.0',
            'oauth_signature_method': 'HMAC-SHA1',
            'oauth_consumer_key': 'client_key',
            'oauth_signature': 'u2xlj 1gF4y 6gKHNeiL9cN3tOI=',
        })

        self.assertEqual(got, correct)
Example #5
0
 def test_launch_request_with_qs(self):
     """
     test that qs params in launch url are ok
     """
     launch_params = {
         'lti_version': 'abc',
         'lti_message_type': 'def',
         'resource_link_id': '123'
     }
     tc = ContentItemResponse('client_key', 'client_secret',
                       launch_url='http://example.edu/foo?bar=1',
                       params=launch_params)
     launch_req = tc.generate_launch_request(nonce='wxyz7890',
                                             timestamp='2345678901')
     got = parse_qs(unquote(launch_req.body.decode('utf-8')))
     correct = launch_params.copy()
     correct.update({
         'oauth_nonce': 'wxyz7890',
         'oauth_timestamp': '2345678901',
         'oauth_version': '1.0',
         'oauth_signature_method': 'HMAC-SHA1',
         'oauth_consumer_key': 'client_key',
         'oauth_signature': 'UH2l86Wq/g5Mu64GpCRcec6tEYY=',
         })
     self.assertEqual(got, correct)
Example #6
0
    def lti_launch(self,
                   lti_consumer,
                   lti_resource_link_id,
                   assignment_uuid=None,
                   query_assignment_uuid=None,
                   custom_puid=None,
                   nonce=None,
                   timestamp=None,
                   follow_redirects=True,
                   **kwargs):
        launch_url = "http://localhost/api/lti/auth"
        launch_params = kwargs.copy()
        launch_params['resource_link_id'] = lti_resource_link_id
        if assignment_uuid:
            launch_params['custom_assignment'] = assignment_uuid
        if custom_puid:
            launch_params['custom_puid'] = custom_puid
        if query_assignment_uuid:
            launch_url = launch_url + "?assignment=" + query_assignment_uuid

        tool_consumer = ToolConsumer(lti_consumer.oauth_consumer_key,
                                     lti_consumer.oauth_consumer_secret,
                                     params=launch_params,
                                     launch_url=launch_url)

        launch_request = tool_consumer.generate_launch_request(
            nonce=nonce, timestamp=timestamp)
        launch_data = parse_qs(launch_request.body.decode('utf-8'))
        rv = self.client.post('/api/lti/auth',
                              data=launch_data,
                              follow_redirects=follow_redirects)
        yield rv
        rv.close()
Example #7
0
    def test_return_url_with_messages(self):
        '''
        Should generate a return url with messages.
        '''
        tp = create_tp()
        self.assertIsNone(tp.build_return_url())

        tp = create_tp(
            lp={'launch_presentation_return_url': 'http://foo.edu/done'})
        self.assertEqual(tp.build_return_url(), 'http://foo.edu/done')

        tp = create_tp(
            lp={
                'launch_presentation_return_url': 'http://foo.edu/done',
                'lti_errormsg': 'user error',
                'lti_errorlog': 'lms error',
                'lti_msg': 'user message',
                'lti_log': 'lms message'
            })
        return_url = tp.build_return_url()
        parsed = urlsplit(return_url)
        self.assertEqual(parsed.hostname, 'foo.edu')
        self.assertEqual(parsed.path, '/done')
        self.assertEqual(
            parse_qs(parsed.query), {
                'lti_errormsg': 'user error',
                'lti_errorlog': 'lms error',
                'lti_msg': 'user message',
                'lti_log': 'lms message'
            })
Example #8
0
    def _get_membership_ext(cls, lti_context):
        lti_consumer = lti_context.lti_consumer
        memberships_id = lti_context.ext_ims_lis_memberships_id
        memberships_url = lti_context.ext_ims_lis_memberships_url
        params = {
            'id': memberships_id,
            'lti_message_type':'basic-lis-readmembershipsforcontext',
            'lti_version': 'LTI-1p0',
            'oauth_callback': 'about:blank'
        }
        request = requests.Request('POST', memberships_url, data=params).prepare()
        sign = OAuth1(lti_consumer.oauth_consumer_key, lti_consumer.oauth_consumer_secret,
            signature_type=SIGNATURE_TYPE_BODY, signature_method=SIGNATURE_HMAC)
        signed_request = sign(request)
        params = parse_qs(signed_request.body.decode('utf-8'))

        data = LTIMembership._post_membership_request(memberships_url, params)
        root = ElementTree.fromstring(data.encode('utf-8'))

        codemajor = root.find('statusinfo/codemajor')
        if codemajor is not None and codemajor.text in ['Failure', 'Unsupported']:
            raise MembershipInvalidRequestException

        if root.find('memberships') == None or len(root.findall('memberships/member')) == 0:
            raise MembershipNoResultsException

        members = []
        for record in root.findall('memberships/member'):
            roles_text = record.findtext('roles')

            member = {
                'user_id': record.findtext('user_id'),
                'roles': roles_text.split(",") if roles_text != None else [],
                'global_unique_identifier': None,
                'student_number': None,
                'lis_result_sourcedid': record.findtext('lis_result_sourcedid'),
                'person_contact_email_primary': record.findtext('person_contact_email_primary'),
                'person_name_given': record.findtext('person_name_given'),
                'person_name_family': record.findtext('person_name_family'),
                'person_name_full': record.findtext('person_name_full')
            }

            # find global unique identifier if available
            if lti_consumer.global_unique_identifier_param and record.findtext(lti_consumer.global_unique_identifier_param):
                member['global_unique_identifier'] = record.findtext(lti_consumer.global_unique_identifier_param)

            # find student number if available
            if lti_consumer.student_number_param and record.findtext(lti_consumer.student_number_param):
                member['student_number'] = record.findtext(lti_consumer.student_number_param)

            members.append(member)
        return members
Example #9
0
    def lti_launch(self, lti_consumer, lti_resource_link_id,
                         assignment_uuid=None, query_assignment_uuid=None,
                         nonce=None, timestamp=None, follow_redirects=True,
                         invalid_launch=False, **kwargs):
        launch_url = "http://localhost/api/lti/auth"
        oauth_signature = kwargs.pop('oauth_signature', None)
        launch_params = kwargs.copy()
        launch_params['resource_link_id'] = lti_resource_link_id
        if assignment_uuid:
            launch_params['custom_assignment'] = assignment_uuid
        if query_assignment_uuid:
            launch_url = launch_url+"?assignment="+query_assignment_uuid

        # add basic required launch parameters
        if not 'lti_version' in launch_params:
           launch_params['lti_version'] = "LTI-1p0"

        if not 'lti_message_type' in launch_params:
           launch_params['lti_message_type'] = "basic-lti-launch-request"

        if 'roles' in launch_params and launch_params.get('roles') == None:
            launch_params.pop('roles')

        tool_consumer = ToolConsumer(
            lti_consumer.oauth_consumer_key,
            lti_consumer.oauth_consumer_secret,
            params=launch_params,
            launch_url=launch_url
        )

        # overwrite lti_version and lti_message_type if needed (set by lti.LaunchParams)
        if 'lti_version' in launch_params and launch_params.get('lti_version') == None:
            tool_consumer.launch_params._params.pop('lti_version')

        if 'lti_message_type' in launch_params and launch_params.get('lti_message_type') == None:
            tool_consumer.launch_params._params.pop('lti_message_type')

        if invalid_launch:
            with mock.patch.object(ToolConsumer, 'has_required_params', return_value=True):
                launch_request = tool_consumer.generate_launch_request(nonce=nonce, timestamp=timestamp)
        else:
            launch_request = tool_consumer.generate_launch_request(nonce=nonce, timestamp=timestamp)

        launch_data = parse_qs(launch_request.body.decode('utf-8'))

        # overwrite oauth_signature for tests
        if invalid_launch and oauth_signature:
            launch_data['oauth_signature'] = oauth_signature

        rv = self.client.post('/api/lti/auth', data=launch_data, follow_redirects=follow_redirects)
        yield rv
        rv.close()
Example #10
0
 def test_error_redirect(self):
     from lti.contrib.django import DjangoToolProvider
     tp = create_tp(
         lp={'launch_presentation_return_url': 'http://example.edu/bar'},
         tp_class=DjangoToolProvider)
     redirect_retval = tp.error_redirect(errormsg='abcd', errorlog='efgh')
     self.assertEqual(redirect_retval, 'foo')
     redirect_url, = mock.shortcuts.redirect.call_args[0]
     parsed = urlsplit(redirect_url)
     self.assertEqual(parse_qs(parsed.query), {
         'lti_errormsg': 'abcd',
         'lti_errorlog': 'efgh'
     })
Example #11
0
 def test_success_redirect(self):
     from lti.contrib.django import DjangoToolProvider
     tp = create_tp(
         lp={'launch_presentation_return_url': 'http://example.edu/foo'},
         tp_class=DjangoToolProvider)
     redirect_retval = tp.success_redirect(msg='bar', log='baz')
     self.assertEqual(redirect_retval, 'foo')
     redirect_url, = mock.shortcuts.redirect.call_args[0]
     parsed = urlsplit(redirect_url)
     self.assertEqual(parse_qs(parsed.query), {
         'lti_msg': 'bar',
         'lti_log': 'baz'
     })
Example #12
0
 def test_success_redirect(self):
     from lti.contrib.django import DjangoToolProvider
     tp = create_tp(lp={
         'launch_presentation_return_url': 'http://example.edu/foo'
     }, tp_class=DjangoToolProvider)
     redirect_retval = tp.success_redirect(msg='bar', log='baz')
     self.assertEqual(redirect_retval, 'foo')
     redirect_url, = mock.shortcuts.redirect.call_args[0]
     parsed = urlsplit(redirect_url)
     self.assertEqual(parse_qs(parsed.query), {
         'lti_msg': 'bar',
         'lti_log': 'baz'
     })
Example #13
0
 def test_error_redirect(self):
     from lti.contrib.django import DjangoToolProvider
     tp = create_tp(lp={
         'launch_presentation_return_url': 'http://example.edu/bar'
     }, tp_class=DjangoToolProvider)
     redirect_retval = tp.error_redirect(errormsg='abcd', errorlog='efgh')
     self.assertEqual(redirect_retval, 'foo')
     redirect_url, = mock.shortcuts.redirect.call_args[0]
     parsed = urlsplit(redirect_url)
     self.assertEqual(parse_qs(parsed.query), {
         'lti_errormsg': 'abcd',
         'lti_errorlog': 'efgh'
     })
Example #14
0
    def _get_membership_ext(cls, lti_context):
        lti_consumer = lti_context.lti_consumer
        memberships_id = lti_context.ext_ims_lis_memberships_id
        memberships_url = lti_context.ext_ims_lis_memberships_url
        params = {
            'id': memberships_id,
            'lti_message_type':'basic-lis-readmembershipsforcontext',
            'lti_version': 'LTI-1p0',
            'oauth_callback': 'about:blank'
        }
        request = requests.Request('POST', memberships_url, data=params).prepare()
        sign = OAuth1(lti_consumer.oauth_consumer_key, lti_consumer.oauth_consumer_secret,
            signature_type=SIGNATURE_TYPE_BODY, signature_method=SIGNATURE_HMAC)
        signed_request = sign(request)
        params = parse_qs(signed_request.body.decode('utf-8'))

        data = LTIMembership._post_membership_request(memberships_url, params)
        root = ElementTree.fromstring(data.encode('utf-8'))

        codemajor = root.find('statusinfo/codemajor')
        if codemajor is not None and codemajor.text in ['Failure', 'Unsupported']:
            raise MembershipInvalidRequestException

        if root.find('memberships') == None or len(root.findall('memberships/member')) == 0:
            raise MembershipNoResultsException

        members = []
        for record in root.findall('memberships/member'):
            roles_text = record.findtext('roles')

            member = {
                'user_id': record.findtext('user_id'),
                'roles': roles_text.split(",") if roles_text != None else [],
                'lis_result_sourcedid': record.findtext('lis_result_sourcedid'),
                'person_contact_email_primary': record.findtext('person_contact_email_primary'),
                'person_name_given': record.findtext('person_name_given'),
                'person_name_family': record.findtext('person_name_family'),
                'person_name_full': record.findtext('person_name_full')
            }

            # override user_id with user_id_override if present in membership result
            if lti_consumer.user_id_override and record.findtext(lti_consumer.user_id_override) is not None:
                member['user_id'] = record.findtext(lti_consumer.user_id_override)

            members.append(member)
        return members
Example #15
0
    def lti_launch(self, lti_consumer, lti_resource_link_id,
                         assignment_uuid=None, nonce=None, timestamp=None, follow_redirects=True,
                         **kwargs):
        launch_params = kwargs.copy()
        launch_params['resource_link_id'] = lti_resource_link_id
        if assignment_uuid:
            launch_params['custom_assignment'] = assignment_uuid

        tool_consumer = ToolConsumer(
            lti_consumer.oauth_consumer_key,
            lti_consumer.oauth_consumer_secret,
            params=launch_params,
            #launch_url not actually used. Just needed for validation
            launch_url='http://localhost/api/lti/auth'
        )

        launch_request = tool_consumer.generate_launch_request(nonce=nonce, timestamp=timestamp)
        launch_data = parse_qs(launch_request.body.decode('utf-8'))
        rv = self.client.post('/api/lti/auth', data=launch_data, follow_redirects=follow_redirects)
        yield rv
        rv.close()
Example #16
0
    def _get_membership(cls, lti_consumer, memberships_id, memberships_url):
        params = {
            'id': memberships_id,
            'lti_message_type':'basic-lis-readmembershipsforcontext',
            'lti_version': 'LTI-1p0',
            'oauth_callback': 'about:blank'
        }
        request = requests.Request('POST', memberships_url, data=params).prepare()
        sign = OAuth1(lti_consumer.oauth_consumer_key, lti_consumer.oauth_consumer_secret,
            signature_type=SIGNATURE_TYPE_BODY, signature_method=SIGNATURE_HMAC)
        signed_request = sign(request)
        params = parse_qs(signed_request.body.decode('utf-8'))

        xmlString = LTIMembership._send_membership_request(memberships_url, params)
        root = ElementTree.fromstring(xmlString)

        codemajor = root.find('statusinfo/codemajor')
        if codemajor is not None and codemajor.text in ['Failure', 'Unsupported']:
            raise MembershipInvalidRequestException

        if root.find('memberships') == None or len(root.findall('memberships/member')) == 0:
            raise MembershipNoResultsException

        members = []
        for member in root.findall('memberships/member'):
            roles_text = member.findtext('roles')

            members.append({
                'user_id': member.findtext('user_id'),
                'roles': roles_text.split(",") if roles_text != None else [],
                'person_contact_email_primary': member.findtext('person_contact_email_primary'),
                'person_name_given': member.findtext('person_name_given'),
                'person_name_family': member.findtext('person_name_family'),
                'person_name_full': member.findtext('person_name_full')
            })
        return members
Example #17
0
    def lti_launch(self,
                   lti_consumer,
                   lti_resource_link_id,
                   assignment_uuid=None,
                   query_assignment_uuid=None,
                   nonce=None,
                   timestamp=None,
                   follow_redirects=True,
                   invalid_launch=False,
                   **kwargs):
        launch_url = "http://localhost/api/lti/auth"
        oauth_signature = kwargs.pop('oauth_signature', None)
        launch_params = kwargs.copy()
        launch_params['resource_link_id'] = lti_resource_link_id
        if assignment_uuid:
            launch_params['custom_assignment'] = assignment_uuid
        if query_assignment_uuid:
            launch_url = launch_url + "?assignment=" + query_assignment_uuid

        # add basic required launch parameters
        if not 'lti_version' in launch_params:
            launch_params['lti_version'] = "LTI-1p0"

        if not 'lti_message_type' in launch_params:
            launch_params['lti_message_type'] = "basic-lti-launch-request"

        if 'roles' in launch_params and launch_params.get('roles') == None:
            launch_params.pop('roles')

        tool_consumer = ToolConsumer(lti_consumer.oauth_consumer_key,
                                     lti_consumer.oauth_consumer_secret,
                                     params=launch_params,
                                     launch_url=launch_url)

        # overwrite lti_version and lti_message_type if needed (set by lti.LaunchParams)
        if 'lti_version' in launch_params and launch_params.get(
                'lti_version') == None:
            tool_consumer.launch_params._params.pop('lti_version')

        if 'lti_message_type' in launch_params and launch_params.get(
                'lti_message_type') == None:
            tool_consumer.launch_params._params.pop('lti_message_type')

        if invalid_launch:
            with mock.patch.object(ToolConsumer,
                                   'has_required_params',
                                   return_value=True):
                launch_request = tool_consumer.generate_launch_request(
                    nonce=nonce, timestamp=timestamp)
        else:
            launch_request = tool_consumer.generate_launch_request(
                nonce=nonce, timestamp=timestamp)

        launch_data = parse_qs(launch_request.body.decode('utf-8'))

        # overwrite oauth_signature for tests
        if invalid_launch and oauth_signature:
            launch_data['oauth_signature'] = oauth_signature

        rv = self.client.post('/api/lti/auth',
                              data=launch_data,
                              follow_redirects=follow_redirects)
        yield rv
        rv.close()
Example #18
0
    def _get_membership_ext(cls, lti_context):
        lti_consumer = lti_context.lti_consumer
        memberships_id = lti_context.ext_ims_lis_memberships_id
        memberships_url = lti_context.ext_ims_lis_memberships_url
        params = {
            'id': memberships_id,
            'lti_message_type': 'basic-lis-readmembershipsforcontext',
            'lti_version': 'LTI-1p0',
            'oauth_callback': 'about:blank'
        }
        request = requests.Request('POST', memberships_url,
                                   data=params).prepare()
        sign = OAuth1(lti_consumer.oauth_consumer_key,
                      lti_consumer.oauth_consumer_secret,
                      signature_type=SIGNATURE_TYPE_BODY,
                      signature_method=SIGNATURE_HMAC)
        signed_request = sign(request)
        params = parse_qs(signed_request.body.decode('utf-8'))

        data = LTIMembership._post_membership_request(memberships_url, params)
        root = ElementTree.fromstring(data.encode('utf-8'))

        codemajor = root.find('statusinfo/codemajor')
        if codemajor is not None and codemajor.text in [
                'Failure', 'Unsupported'
        ]:
            raise MembershipInvalidRequestException

        if root.find('memberships') == None or len(
                root.findall('memberships/member')) == 0:
            raise MembershipNoResultsException

        members = []
        for record in root.findall('memberships/member'):
            roles_text = record.findtext('roles')

            member = {
                'user_id':
                record.findtext('user_id'),
                'roles':
                roles_text.split(",") if roles_text != None else [],
                'global_unique_identifier':
                None,
                'student_number':
                None,
                'lis_result_sourcedid':
                record.findtext('lis_result_sourcedid'),
                'person_contact_email_primary':
                record.findtext('person_contact_email_primary'),
                'person_name_given':
                record.findtext('person_name_given'),
                'person_name_family':
                record.findtext('person_name_family'),
                'person_name_full':
                record.findtext('person_name_full')
            }

            # find global unique identifier if available
            if lti_consumer.global_unique_identifier_param and record.findtext(
                    lti_consumer.global_unique_identifier_param):
                member['global_unique_identifier'] = record.findtext(
                    lti_consumer.global_unique_identifier_param)
                if lti_consumer.custom_param_regex_sanitizer and lti_consumer.global_unique_identifier_param.startswith(
                        'custom_'):
                    regex = re.compile(
                        lti_consumer.custom_param_regex_sanitizer)
                    member['global_unique_identifier'] = regex.sub(
                        '', member['global_unique_identifier'])
                    if member['global_unique_identifier'] == '':
                        member['global_unique_identifier'] = None

            # find student number if available
            if lti_consumer.student_number_param and record.findtext(
                    lti_consumer.student_number_param):
                member['student_number'] = record.findtext(
                    lti_consumer.student_number_param)
                if lti_consumer.custom_param_regex_sanitizer and lti_consumer.student_number_param.startswith(
                        'custom_'):
                    regex = re.compile(
                        lti_consumer.custom_param_regex_sanitizer)
                    member['student_number'] = regex.sub(
                        '', member['student_number'])
                    if member['student_number'] == '':
                        member['student_number'] = None

            members.append(member)
        return members