Exemplo n.º 1
0
    def _get_lti_1p3_consumer(self):
        """
        Return a class of LTI 1.3 consumer.

        Uses the `config_store` variable to determine where to
        look for the configuration and instance the class.
        """
        # If LTI configuration is stored in the XBlock.
        if self.config_store == self.CONFIG_ON_XBLOCK:
            consumer = LtiAdvantageConsumer(
                iss=get_lms_base(),
                lti_oidc_url=self.block.lti_1p3_oidc_url,
                lti_launch_url=self.block.lti_1p3_launch_url,
                client_id=self.lti_1p3_client_id,
                # Deployment ID hardcoded to 1 since
                # we're not using multi-tenancy.
                deployment_id="1",
                # XBlock Private RSA Key
                rsa_key=self.lti_1p3_private_key,
                rsa_key_id=self.lti_1p3_private_key_id,
                # LTI 1.3 Tool key/keyset url
                tool_key=self.block.lti_1p3_tool_public_key,
                tool_keyset_url=None,
            )

            # Check if enabled and setup LTI-AGS
            if self.block.has_score:

                default_values = {
                    'resource_id': self.block.location,
                    'score_maximum': self.block.weight,
                    'label': self.block.display_name,
                }

                if hasattr(self.block, 'start'):
                    default_values['start_date_time'] = self.block.start

                if hasattr(self.block, 'due'):
                    default_values['end_date_time'] = self.block.due

                # create LineItem if there is none for current lti configuration
                lineitem, _ = LtiAgsLineItem.objects.get_or_create(
                    lti_configuration=self,
                    resource_link_id=self.block.location,
                    defaults=default_values)

                consumer.enable_ags(
                    lineitems_url=get_lti_ags_lineitems_url(self.id),
                    lineitem_url=get_lti_ags_lineitems_url(
                        self.id, lineitem.id),
                )

            return consumer

        # There's no configuration stored locally, so throw
        # NotImplemented.
        raise NotImplementedError
Exemplo n.º 2
0
class TestLtiAdvantageConsumer(TestCase):
    """
    Unit tests for LtiAdvantageConsumer
    """
    def setUp(self):
        super(TestLtiAdvantageConsumer, self).setUp()

        # Set up consumer
        self.lti_consumer = LtiAdvantageConsumer(
            iss=ISS,
            lti_oidc_url=OIDC_URL,
            lti_launch_url=LAUNCH_URL,
            client_id=CLIENT_ID,
            deployment_id=DEPLOYMENT_ID,
            rsa_key=RSA_KEY,
            rsa_key_id=RSA_KEY_ID,
            # Use the same key for testing purposes
            tool_key=RSA_KEY)

    def test_no_ags_returns_failure(self):
        """
        Test that when LTI-AGS isn't configured, the class yields an error.
        """
        with self.assertRaises(exceptions.LtiAdvantageServiceNotSetUp):
            self.lti_consumer.lti_ags  # pylint: disable=pointless-statement

    def test_enable_ags(self):
        """
        Test enabling LTI AGS and checking that required parameters are set.
        """
        self.lti_consumer.enable_ags("http://example.com/lineitems")

        # Check that the AGS class was properly instanced and set
        self.assertEqual(type(self.lti_consumer.ags), LtiAgs)

        # Check retrieving class works
        lti_ags_class = self.lti_consumer.lti_ags
        self.assertEqual(self.lti_consumer.ags, lti_ags_class)

        # Check that enabling the AGS adds the LTI AGS claim
        # in the launch message
        self.assertEqual(
            self.lti_consumer.extra_claims, {
                '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/result.readonly',
                        'https://purl.imsglobal.org/spec/lti-ags/scope/score'
                    ],
                    'lineitems':
                    'http://example.com/lineitems'
                }
            })
Exemplo n.º 3
0
    def setUp(self):
        super(TestLtiAdvantageConsumer, self).setUp()

        # Set up consumer
        self.lti_consumer = LtiAdvantageConsumer(
            iss=ISS,
            lti_oidc_url=OIDC_URL,
            lti_launch_url=LAUNCH_URL,
            client_id=CLIENT_ID,
            deployment_id=DEPLOYMENT_ID,
            rsa_key=RSA_KEY,
            rsa_key_id=RSA_KEY_ID,
            # Use the same key for testing purposes
            tool_key=RSA_KEY)
Exemplo n.º 4
0
    def _get_lti_1p3_consumer(self):
        """
        Return a class of LTI 1.3 consumer.

        Uses the `config_store` variable to determine where to
        look for the configuration and instance the class.
        """
        # If LTI configuration is stored in the XBlock.
        if self.config_store == self.CONFIG_ON_XBLOCK:
            consumer = LtiAdvantageConsumer(
                iss=get_lms_base(),
                lti_oidc_url=self.block.lti_1p3_oidc_url,
                lti_launch_url=self.block.lti_1p3_launch_url,
                client_id=self.lti_1p3_client_id,
                # Deployment ID hardcoded to 1 since
                # we're not using multi-tenancy.
                deployment_id="1",
                # XBlock Private RSA Key
                rsa_key=self.lti_1p3_private_key,
                rsa_key_id=self.lti_1p3_private_key_id,
                # LTI 1.3 Tool key/keyset url
                tool_key=self.block.lti_1p3_tool_public_key,
                tool_keyset_url=None,
            )

            # Check if enabled and setup LTI-AGS
            if self.block.has_score:
                consumer.enable_ags(
                    lineitems_url=get_lti_ags_lineitems_url(self.id)
                )

            return consumer

        # There's no configuration stored locally, so throw
        # NotImplemented.
        raise NotImplementedError
Exemplo n.º 5
0
class TestLtiAdvantageConsumer(TestCase):
    """
    Unit tests for LtiAdvantageConsumer
    """
    def setUp(self):
        super().setUp()

        # Set up consumer
        self.lti_consumer = LtiAdvantageConsumer(
            iss=ISS,
            lti_oidc_url=OIDC_URL,
            lti_launch_url=LAUNCH_URL,
            client_id=CLIENT_ID,
            deployment_id=DEPLOYMENT_ID,
            rsa_key=RSA_KEY,
            rsa_key_id=RSA_KEY_ID,
            # Use the same key for testing purposes
            tool_key=RSA_KEY)

        self.preflight_response = {}

    def _setup_deep_linking(self):
        """
        Set's up deep linking class in LTI consumer.
        """
        self.lti_consumer.enable_deep_linking("launch-url", "return-url")

        # Set LTI Consumer parameters
        self.preflight_response = {
            "client_id": CLIENT_ID,
            "redirect_uri": LAUNCH_URL,
            "nonce": NONCE,
            "state": STATE,
            "lti_message_hint": "deep_linking_launch",
        }
        self.lti_consumer.set_user_data("1", "student")

    def test_no_ags_returns_failure(self):
        """
        Test that when LTI-AGS isn't configured, the class yields an error.
        """
        with self.assertRaises(exceptions.LtiAdvantageServiceNotSetUp):
            self.lti_consumer.lti_ags  # pylint: disable=pointless-statement

    def test_enable_ags(self):
        """
        Test enabling LTI AGS and checking that required parameters are set.
        """
        self.lti_consumer.enable_ags("http://example.com/lineitems")

        # Check that the AGS class was properly instanced and set
        self.assertEqual(type(self.lti_consumer.ags), LtiAgs)

        # Check retrieving class works
        lti_ags_class = self.lti_consumer.lti_ags
        self.assertEqual(self.lti_consumer.ags, lti_ags_class)

        # Check that enabling the AGS adds the LTI AGS claim
        # in the launch message
        self.assertEqual(
            self.lti_consumer.extra_claims, {
                'https://purl.imsglobal.org/spec/lti-ags/claim/endpoint': {
                    'scope': [
                        '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':
                    'http://example.com/lineitems',
                }
            })

    def test_deep_linking_enabled_launch_request(self):
        """
        Test that the `generate_launch_request` returns a deep linking launch message
        when the preflight request indicates it.
        """
        self._setup_deep_linking()

        # Retrieve LTI Deep Link Launch Message
        token = self.lti_consumer.generate_launch_request(
            self.preflight_response, "resourceLink")['id_token']

        # Decode and check
        decoded_token = self.lti_consumer.key_handler.validate_and_decode(
            token)
        self.assertEqual(
            decoded_token[
                'https://purl.imsglobal.org/spec/lti/claim/message_type'],
            "LtiDeepLinkingRequest",
        )
        self.assertEqual(
            decoded_token[
                'https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings']
            ['deep_link_return_url'], "return-url")

    def test_deep_linking_token_decode_no_dl(self):
        """
        Check that trying to run the Deep Linking decoding fails if service is not set up.
        """
        with self.assertRaises(exceptions.LtiAdvantageServiceNotSetUp):
            self.lti_consumer.check_and_decode_deep_linking_token("token")

    def test_deep_linking_token_invalid_content_type(self):
        """
        Check that trying to run the Deep Linking decoding fails if an invalid content type is passed.
        """
        self._setup_deep_linking()

        # Dummy Deep linking response
        lti_reponse = {
            "https://purl.imsglobal.org/spec/lti/claim/message_type":
            "LtiDeepLinkingResponse",
            "https://purl.imsglobal.org/spec/lti-dl/claim/content_items": [
                {
                    "type": "wrongContentType",
                    "url": "https://something.example.com/page.html",
                },
            ]
        }

        with self.assertRaises(
                exceptions.LtiDeepLinkingContentTypeNotSupported):
            self.lti_consumer.check_and_decode_deep_linking_token(
                self.lti_consumer.key_handler.encode_and_sign(lti_reponse))

    def test_deep_linking_token_wrong_message(self):
        """
        Check that trying to run the Deep Linking decoding fails if a message with the wrong type is passed.
        """
        self._setup_deep_linking()

        # Dummy Deep linking response
        lti_reponse = {
            "https://purl.imsglobal.org/spec/lti/claim/message_type":
            "WrongType"
        }

        with self.assertRaises(exceptions.InvalidClaimValue):
            self.lti_consumer.check_and_decode_deep_linking_token(
                self.lti_consumer.key_handler.encode_and_sign(lti_reponse))

    def test_deep_linking_token_returned(self):
        """
        Check corect token decoding and retrieval of content_items.
        """
        self._setup_deep_linking()

        # Dummy Deep linking response
        lti_reponse = {
            "https://purl.imsglobal.org/spec/lti/claim/message_type":
            "LtiDeepLinkingResponse",
            "https://purl.imsglobal.org/spec/lti-dl/claim/content_items": []
        }

        content_items = self.lti_consumer.check_and_decode_deep_linking_token(
            self.lti_consumer.key_handler.encode_and_sign(lti_reponse))

        self.assertEqual(content_items, [])

    def test_set_dl_content_launch_parameters(self):
        """
        Check that the DL overrides replace LTI launch parameters
        """
        self._setup_deep_linking()

        # Set DL variables and return LTI message
        self.lti_consumer.set_dl_content_launch_parameters(
            url="example.com",
            custom={"test": "test"},
        )
        message = self.lti_consumer.get_lti_launch_message("test_link")

        # Check if custom item was set
        self.assertEqual(
            message['https://purl.imsglobal.org/spec/lti/claim/custom'],
            {"test": "test"})
        self.assertEqual(self.lti_consumer.launch_url, "example.com")

    def test_no_nrps_returns_failure(self):
        """
        Test that when LTI NRPS isn't configured, the class yields an error.
        """
        with self.assertRaises(exceptions.LtiNrpsServiceNotSetUp):
            self.lti_consumer.lti_nrps  # pylint: disable=pointless-statement

    def test_enable_nrps(self):
        """
        Test enabling LTI NRPS and checking that required parameters are set.
        """
        self.lti_consumer.enable_nrps("http://example.com/20/membership")

        # Check that the NRPS class was properly instanced and set
        self.assertIsInstance(self.lti_consumer.nrps, LtiNrps)

        # Check retrieving class works
        lti_nrps_class = self.lti_consumer.lti_nrps
        self.assertEqual(self.lti_consumer.nrps, lti_nrps_class)

        # Check that enabling the NRPS adds the LTI NRPS claim
        # in the launch message
        self.assertEqual(
            self.lti_consumer.extra_claims, {
                "https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice":
                {
                    "context_memberships_url":
                    "http://example.com/20/membership",
                    "service_versions": ["2.0"]
                }
            })
Exemplo n.º 6
0
    def _get_lti_1p3_consumer(self):
        """
        Return a class of LTI 1.3 consumer.

        Uses the `config_store` variable to determine where to
        look for the configuration and instance the class.
        """
        # If LTI configuration is stored in the XBlock.
        if self.config_store == self.CONFIG_ON_XBLOCK:
            consumer = LtiAdvantageConsumer(
                iss=get_lms_base(),
                lti_oidc_url=self.block.lti_1p3_oidc_url,
                lti_launch_url=self.block.lti_1p3_launch_url,
                client_id=self.lti_1p3_client_id,
                # Deployment ID hardcoded to 1 since
                # we're not using multi-tenancy.
                deployment_id="1",
                # XBlock Private RSA Key
                rsa_key=self.lti_1p3_private_key,
                rsa_key_id=self.lti_1p3_private_key_id,
                # LTI 1.3 Tool key/keyset url
                tool_key=self.block.lti_1p3_tool_public_key,
                tool_keyset_url=self.block.lti_1p3_tool_keyset_url,
            )

            # Check if enabled and setup LTI-AGS
            if self.block.lti_advantage_ags_mode != 'disabled':
                lineitem = None
                # If using the declarative approach, we should create a LineItem if it
                # doesn't exist. This is because on this mode the tool is not able to create
                # and manage lineitems using the AGS endpoints.
                if self.block.lti_advantage_ags_mode == 'declarative':
                    # Set grade attributes
                    default_values = {
                        'resource_id': self.block.location,
                        'score_maximum': self.block.weight,
                        'label': self.block.display_name,
                    }

                    if hasattr(self.block, 'start'):
                        default_values['start_date_time'] = self.block.start

                    if hasattr(self.block, 'due'):
                        default_values['end_date_time'] = self.block.due

                    # create LineItem if there is none for current lti configuration
                    lineitem, _ = LtiAgsLineItem.objects.get_or_create(
                        lti_configuration=self,
                        resource_link_id=self.block.location,
                        defaults=default_values)

                consumer.enable_ags(
                    lineitems_url=get_lti_ags_lineitems_url(self.id),
                    lineitem_url=get_lti_ags_lineitems_url(
                        self.id, lineitem.id) if lineitem else None,
                    allow_programmatic_grade_interaction=(
                        self.block.lti_advantage_ags_mode == 'programmatic'))

            # Check if enabled and setup LTI-DL
            if self.block.lti_advantage_deep_linking_enabled:
                consumer.enable_deep_linking(
                    self.block.lti_advantage_deep_linking_launch_url,
                    get_lti_deeplinking_response_url(self.id),
                )

            # Check if enabled and setup LTI-NRPS
            if self.block.lti_1p3_enable_nrps:
                consumer.enable_nrps(
                    get_lti_nrps_context_membership_url(self.id))

            return consumer

        # There's no configuration stored locally, so throw
        # NotImplemented.
        raise NotImplementedError