Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to test a launch function #76

Open
perllaghu opened this issue Apr 7, 2022 · 0 comments
Open

how to test a launch function #76

perllaghu opened this issue Apr 7, 2022 · 0 comments
Labels
question Further information is requested

Comments

@perllaghu
Copy link

perllaghu commented Apr 7, 2022

I've a working system - I can connect from a test moodle to my tool - and now I want to add a test for the code.

My test code starts:

Overall imports

from rest_framework_jwt.settings import api_settings as jwt_settings
from mock import patch
from unittest.mock import Mock
from urllib import parse
from django.test import Client
from myApp.lti1p3_backend import ToolConfMyApp # My version of DjangoDbToolConf
from myApp_utils.test_utils import TestCreators  # some helper methods for creating test objects in the db

class TestToolConfMyApp(TestCreators):

There are a whole suite of tests for the ToolConfMyApp functions: check_iss_has_one_client, check_iss_has_many_clients, get_public_key, get_iss_config, etc...

I've even got POST and GET tests for login - and test the parameters in the response.url from those calls

For the specific launch test - this is what I start with:


    @patch('pylti1p3.message_launch.MessageLaunch.fetch_public_key')
    def test_launch(self, mock_fpk: Mock):

        # Set up the environment, and the initial login
        key = self.create_lti1p3_tool_key(name="test", private_key="foo", public_key=pub_key)
        self.create_lti1p3_tool(
            title="test",
            tool_key=key,
            issuer="example_dot_com",
            client_id="foo",
            auth_login_url="http://example.com/a",
            auth_token_url="http://example.com/b",
            deployment_ids='["test-id-1", "test-id-2"]',
            key_set_url="http://example.com/c",
        )
        c = Client()
        response = c.get("/lti1p3/login/?lti1p3_new_window=1&iss=example_dot_com&login_hint=5&target_link_uri=http%3A%2F%2Fexample.com%2Flti1p3%2Flaunch%2F&lti_message_hint=5&lti_deployment_id=3&client_id=foo")

        # Cookies should now exist, test launch

Then we want to set up the token for actual call:

        query_string = parse.urlparse(response.url)[4]
        query_dict = parse.parse_qs(query_string)
        tool_conf = ToolConfNoteable()
        mock_fpk.return_value = tool_conf.get_jwks("example_dot_com")

        # create id_token that VLE creates - needs nonce to match login nonce!
        source_token = {
            "nonce": query_dict["nonce"][0],
            "iat": 1649251764,
            "exp": 1649251824,
            "iss": "example_dot_com",
            "aud": "foo",
            "https://purl.imsglobal.org/spec/lti/claim/deployment_id": "test-id-1",
            "https://purl.imsglobal.org/spec/lti/claim/target_link_uri": "http://vle.example.com/lti1p3/launch/",
            "sub": "5",
            "https://purl.imsglobal.org/spec/lti/claim/lis": {
                "person_sourcedid": "",
                "course_section_sourcedid": ""
            },
            "https://purl.imsglobal.org/spec/lti/claim/roles": [
                "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator",
                "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor",
                "http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator"
            ],
            "https://purl.imsglobal.org/spec/lti/claim/context": {
                "id": "3",
                "label": "sample_course",
                "title": "An Example of a Course Title",
                "type": [
                "CourseSection"
                ]
            },
            "https://purl.imsglobal.org/spec/lti/claim/resource_link": {
                "title": "some link text",
                "id": "3"
            },
            "https://purl.imsglobal.org/spec/lti-bo/claim/basicoutcome": {
                "lis_result_sourcedid": "{\"data\":{\"instanceid\":\"3\",\"userid\":\"5\",\"typeid\":\"3\",\"launchid\":1293835966},\"hash\":\"f85b809cb4b10a0bb65ce2824e8c892a2d08f6a2015edd68d3ccaf014486cf81\"}",
                "lis_outcome_service_url": "https://vle.example.com/lti/service.php"
            },
            "given_name": "Jo",
            "family_name": "Doe",
            "name": "Jo Doe",
            "https://purl.imsglobal.org/spec/lti/claim/ext": {
                "user_username": "student1",
                "lms": "moodle-2"
            },
            "https://purl.imsglobal.org/spec/lti/claim/launch_presentation": {
                "locale": "en",
                "document_target": "window",
                "return_url": "https://vle.example.com/lti/return.php?course=3&launch_container=4&instanceid=3&sesskey=BKH3oeWS3f"
            },
            "https://purl.imsglobal.org/spec/lti/claim/tool_platform": {
                "product_family_code": "moodle",
                "version": "2020061512",
                "guid": "vle.example.com",
                "name": "vle.example.com",
                "description": "VLE for example.com"
            },
            "https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",
            "https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiResourceLinkRequest",
            "https://purl.imsglobal.org/spec/lti/claim/custom": {
                "course_suffix": "foo",
                "system_setting_url": "https://vle.example.com/lti/services.php/tool/3/custom",
                "context_setting_url": "https://vle.example.com/lti/services.php/CourseSection/3/bindings/tool/3/custom",
                "link_setting_url": "https://vle.example.com/lti/services.php/links/{link_id}/custom"
            },
            "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint": {
                "scope": [
                "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
                "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly",
                "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly",
                "https://purl.imsglobal.org/spec/lti-ags/scope/score"
                ],
                "lineitems": "https://vle.example.com/lti/services.php/3/lineitems?type_id=3",
                "lineitem": "https://vle.example.com/lti/services.php/3/lineitems/5/lineitem?type_id=3"
            }
        }
        jwt_encode_handler = jwt_settings.JWT_ENCODE_HANDLER
        token = jwt_encode_handler(source_token)

        response = c.post("/lti1p3/launch/?", {"state": query_dict["state"][0], "id_token": token})

.... and it fails with a pylti1p3.exception.LtiException: JWT KID not found error.

The full error reads:

Traceback (most recent call last):
  File \"/path/to/lib/python3.7/site-packages/django/core/handlers/exception.py\", line 47, in inner
    response = get_response(request)
  File \"/path/to/lib/python3.7/site-packages/django/core/handlers/base.py\", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File \"/path/to/lib/python3.7/site-packages/sentry_sdk/integrations/django/views.py\", line 36, in sentry_wrapped_callback
    return callback(request, *args, **kwargs)
  File \"/path/to/lib/python3.7/site-packages/django/views/generic/base.py\", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File \"/different/path/to/mycomponent/api/views.py\", line 554, in dispatch
    return super().dispatch(*args, **kwargs)
  File \"/different/path/to/mycomponent/api/views.py\", line 494, in dispatch
    payload = self.add_extra_jwt_claims(payload)
  File \"/different/path/to/mycomponent/api/views.py\", line 564, in add_extra_jwt_claims
    message_launch_data = message_launch.get_launch_data()
  File \"/path/to/lib/python3.7/site-packages/pylti1p3/message_launch.py\", line 494, in get_launch_data
    return self._get_jwt_body()
  File \"/path/to/lib/python3.7/site-packages/pylti1p3/message_launch.py\", line 299, in _get_jwt_body
    self.validate()
  File \"/path/to/lib/python3.7/site-packages/pylti1p3/message_launch.py\", line 284, in validate
    return self.validate_state()\\
  File \"/path/to/lib/python3.7/site-packages/pylti1p3/message_launch.py\", line 671, in validate_jwt_signature
    public_key = self.get_public_key()
  File \"/path/to/lib/python3.7/site-packages/pylti1p3/message_launch.py\", line 569, in get_public_key
    raise LtiException(\"JWT KID not found\")
pylti1p3.exception.LtiException: JWT KID not found

Digging into the code, the problem seems to be the contents of self._jwt in the MessageLaunch.get_public_key method has the id_token rather than the jwks

Any hints as to where I'm going wrong?

EDIT:
I can confirm that tool_conf.get_jwks("example_dot_com") returns an appropriate token:

{
    "alg": "RS256",
    "e": "AQAB",
    "kid": "9uXgneuBIDrQQprq9DVtQ0Za-elqN5wHsviNOtLmQdY",
    "kty": "RSA",
    "n": "u9omeEgZmWHV0448LhGFFUzpGynHhLN88sdbbHP7tEd67zcqfbSoeWiIWbVCwXFVrkTZNjXb5YANNcBrj7bOj6nGNvevYMM5YgxhfjvJ2rbAfPNtjeUzaj95la2wO14rc9b32FmMSzLE5xn5I8HqXZUHPH0W5VS3avBp_v-dyLTacXmlvzvDgJ2gNT9vPiUWLXSS9SajIYr7Sj5IILjSlHod5UkHGSyi7sK5vSpArOh3LbzM0fPPCqupIb6bouDgYRxjUqSphvY891nRMCWtQKXO4aOk65AqqQsRnQovLiyDxPYMesc3kZcNsuPlWyF5YNE81EcmdXRnrkBKqowdZQ",
    "use": "sig",
}
@dmitry-viskov dmitry-viskov added the question Further information is requested label Sep 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants