diff --git a/README.rst b/README.rst index 92a28b85db5125ed921c45cc14d346e9b1cedf0e..2ec4f4a73fdeae24a079dc1ed166950d33b7d755 100644 --- a/README.rst +++ b/README.rst @@ -184,39 +184,36 @@ needs to know the LMS's one. Instructions: -1. Set up a local tunnel tunneling the LMS (using `ngrok` or a similar tool) to get a URL accessible from the internet. -2. Create a new course, and add the `lti_consumer` block to the advanced modules list. -3. In the course, create a new unit and add the LTI block. +#. Set up a local tunnel (using `ngrok` or a similar tool) to get a URL accessible from the internet. +#. Add the following settings to `edx-platform/lms/envs/private.py` and `edx-platform/cms/envs/private.py`: + + * LTI_BASE="http://localhost:18000" + * LTI_API_BASE="http://<your_ngrok>.ngrok.io" + +#. Create a new course, and add the `lti_consumer` block to the advanced modules list. +#. In the course, create a new unit and add the LTI block. * Set ``LTI Version`` to ``LTI 1.3``. * Set the ``Tool Launch URL`` to ``https://lti-ri.imsglobal.org/lti/tools/`` -4. In Studio, you'll see a few parameters being displayed in the preview: +#. In Studio, you'll see a few parameters being displayed in the preview: .. code:: Client ID: f0532860-cb34-47a9-b16c-53deb077d4de Deployment ID: 1 # Note that these are LMS URLS - Keyset URL: http://localhost:18000/api/lti_consumer/v1/public_keysets/block-v1:OpenCraft+LTI101+2020_T2+type@lti_consumer+block@efc55c7abb87430883433bfafb83f054 - Access Token URL: http://localhost:18000/api/lti_consumer/v1/token/block-v1:OpenCraft+LTI101+2020_T2+type@lti_consumer+block@efc55c7abb87430883433bfafb83f054 + Keyset URL: http://1234.ngrok.io/api/lti_consumer/v1/public_keysets/88e45ecbd-7cce-4fa0-9537-23e9f7288ad9 + Access Token URL: http://1234.ngrok.io/api/lti_consumer/v1/token/8e45ecbd-7cce-4fa0-9537-23e9f7288ad9 OIDC Callback URL: http://localhost:18000/api/lti_consumer/v1/launch/ -5. Add the tunnel URL to the each of these URLs as it'll need to be accessed by the tool (hosted externally). - -.. code:: - - # This is <LMS_URL>/api/lti_consumer/v1/public_keysets/<BLOCK_LOCATION> - https://647dd2e1.ngrok.io/api/lti_consumer/v1/public_keysets/block-v1:OpenCraft+LTI101+2020_T2+type@lti_consumer+block@996c72b16070434098bc598bd7d6dbde - - -6. Set up a tool in the IMS Global reference implementation (https://lti-ri.imsglobal.org/lti/tools/). +#. Set up a tool in the IMS Global reference implementation (https://lti-ri.imsglobal.org/lti/tools/). * Click on ``Add tool`` at the top of the page (https://lti-ri.imsglobal.org/lti/tools). * Add the parameters and URLs provided by the block, and generate a private key on https://lti-ri.imsglobal.org/keygen/index and paste it there (don't close the tab, you'll need the public key later). -7. Go back to Studio, and edit the block adding its settings (you'll find them by scrolling down https://lti-ri.imsglobal.org/lti/tools/ until you find the tool you just created): +#. Go back to Studio, and edit the block adding its settings (you'll find them by scrolling down https://lti-ri.imsglobal.org/lti/tools/ until you find the tool you just created): .. code:: @@ -224,15 +221,8 @@ Instructions: Tool Initiate Login URL: https://lti-ri.imsglobal.org/lti/tools/[tool_id]/login_initiations Tool Public key: Public key from key page. -8. Publish block, log into LMS and navigate to the LTI block page. -9. Click ``Send Request`` and verify that the LTI launch was successful. - -.. admonition:: Testing using ``ngrok`` - - When launching LTI 1.3 requests through ``ngrok``, make sure your LMS is serving session cookies marked as - ``Secure`` and with the ``SameSite`` attribute set to ``None``. You can do this by changing ``SESSION_COOKIE_SECURE: true`` - and ``DCS_SESSION_COOKIE_SAMESITE: None`` in your ``lms.yml`` configuration files. Note that this will break logins - for locally accessed URLs in the devstack. +#. Publish block, log into LMS and navigate to the LTI block page. +#. Click ``Send Request`` and verify that the LTI launch was successful. LTI Advantage Features diff --git a/lti_consumer/lti_1p3/README.md b/lti_consumer/lti_1p3/README.md index 06577f3db0b3f5775d61158d03810d5a61b8e95f..90b6e27f95118e706e815ecada7222866053694e 100644 --- a/lti_consumer/lti_1p3/README.md +++ b/lti_consumer/lti_1p3/README.md @@ -54,7 +54,7 @@ def _get_lti1p3_consumer(): lti_oidc_url=lti_1p3_oidc_url, lti_launch_url=lti_1p3_launch_url, # Platform and deployment configuration - iss=get_lms_base(), + iss=get_lti_api_base(), client_id=lti_1p3_client_id, deployment_id="1", # Platform key diff --git a/lti_consumer/models.py b/lti_consumer/models.py index 4c6ebc42b4661f26a631a422608b3d3d539706fe..dce26c07e9564929379c775b1ff9da3e6b8d3366 100644 --- a/lti_consumer/models.py +++ b/lti_consumer/models.py @@ -24,7 +24,7 @@ from lti_consumer.lti_1p3.consumer import LtiAdvantageConsumer, LtiProctoringCon from lti_consumer.lti_1p3.key_handlers import PlatformKeyHandler from lti_consumer.plugin import compat from lti_consumer.utils import ( - get_lms_base, + get_lti_api_base, get_lti_ags_lineitems_url, get_lti_deeplinking_response_url, get_lti_nrps_context_membership_url, @@ -488,7 +488,7 @@ class LtiConfiguration(models.Model): block = compat.load_enough_xblock(self.location) consumer = consumer_class( - iss=get_lms_base(), + iss=get_lti_api_base(), lti_oidc_url=block.lti_1p3_oidc_url, lti_launch_url=block.lti_1p3_launch_url, client_id=self.lti_1p3_client_id, @@ -504,7 +504,7 @@ class LtiConfiguration(models.Model): ) elif self.config_store == self.CONFIG_ON_DB: consumer = consumer_class( - iss=get_lms_base(), + iss=get_lti_api_base(), lti_oidc_url=self.lti_1p3_oidc_url, lti_launch_url=self.lti_1p3_launch_url, client_id=self.lti_1p3_client_id, diff --git a/lti_consumer/utils.py b/lti_consumer/utils.py index 350e51c232011902ccd56fa3cec10773edb637d0..e7a3e75db94acaf3f7efd7014549bb40967228fd 100644 --- a/lti_consumer/utils.py +++ b/lti_consumer/utils.py @@ -26,22 +26,37 @@ def _(text): return text -def get_lms_base(): +def get_lti_api_base(): """ - Returns LMS base url to be used as issuer on OAuth2 flows - and in various LTI URLs. For local testing it is often necessary - to override the normal LMS base with a proxy such as ngrok, use - the setting LTI_LMS_BASE_URL_OVERRIDE in your LMS settings if - necessary. + Returns base url to be used as issuer on OAuth2 flows + and in various LTI API calls. If LTI_API_BASE is set this will + override the default LTI_BASE url for these URLs only. TODO: This needs to be improved and account for Open edX sites and organizations. One possible improvement is to use `contentstore.get_lms_link_for_item` and strip the base domain name. """ - if hasattr(settings, 'LTI_LMS_BASE_URL_OVERRIDE'): - return settings.LTI_LMS_BASE_URL_OVERRIDE + if hasattr(settings, 'LTI_API_BASE'): + return settings.LTI_API_BASE + elif hasattr(settings, 'LTI_BASE'): + return settings.LTI_BASE else: + # Eventually we should move away from supporting this setting as it is incorrect + # in applications that are not the LMS. Keeping this around for backward support. + return settings.LMS_ROOT_URL + + +def get_lti_view_base(): + """ + Returns base url to be used when generating view and redirect urls + as part of the LTI launch flow. + """ + if hasattr(settings, 'LTI_BASE'): + return settings.LTI_BASE + else: + # Eventually we should move away from supporting this setting as it is incorrect + # in applications that are not the LMS. Keeping this around for backward support. return settings.LMS_ROOT_URL @@ -52,7 +67,7 @@ def get_lms_lti_keyset_link(config_id): :param config_id: the config_id of the LtiConfiguration object """ return "{lms_base}/api/lti_consumer/v1/public_keysets/{config_id}".format( - lms_base=get_lms_base(), + lms_base=get_lti_api_base(), config_id=str(config_id), ) @@ -64,7 +79,7 @@ def get_lms_lti_launch_link(): :param location: the location of the block """ return "{lms_base}/api/lti_consumer/v1/launch/".format( - lms_base=get_lms_base(), + lms_base=get_lti_view_base(), ) @@ -75,7 +90,7 @@ def get_lms_lti_access_token_link(config_id): :param config_id: the config_id of the LtiConfiguration object """ return "{lms_base}/api/lti_consumer/v1/token/{config_id}".format( - lms_base=get_lms_base(), + lms_base=get_lti_api_base(), config_id=str(config_id), ) @@ -90,7 +105,7 @@ def get_lti_ags_lineitems_url(lti_config_id, lineitem_id=None): """ url = "{lms_base}/api/lti_consumer/v1/lti/{lti_config_id}/lti-ags".format( - lms_base=get_lms_base(), + lms_base=get_lti_api_base(), lti_config_id=str(lti_config_id), ) @@ -107,7 +122,7 @@ def get_lti_deeplinking_response_url(lti_config_id): :param lti_config_id: LTI configuration id """ return "{lms_base}/api/lti_consumer/v1/lti/{lti_config_id}/lti-dl/response".format( - lms_base=get_lms_base(), + lms_base=get_lti_api_base(), lti_config_id=str(lti_config_id), ) @@ -120,7 +135,7 @@ def get_lti_deeplinking_content_url(lti_config_id, launch_data): :param launch_data: (lti_consumer.data.Lti1p3LaunchData): a class containing data necessary for an LTI 1.3 launch """ url = "{lms_base}/api/lti_consumer/v1/lti/{lti_config_id}/lti-dl/content".format( - lms_base=get_lms_base(), + lms_base=get_lti_api_base(), lti_config_id=str(lti_config_id), ) url += "?" @@ -142,7 +157,7 @@ def get_lti_nrps_context_membership_url(lti_config_id): """ return "{lms_base}/api/lti_consumer/v1/lti/{lti_config_id}/memberships".format( - lms_base=get_lms_base(), + lms_base=get_lti_api_base(), lti_config_id=str(lti_config_id), )