Skip to content
Snippets Groups Projects
Commit 0697923d authored by michaelroytman's avatar michaelroytman
Browse files

fix: X-Frame-Options DENY response header prevents LTI 1.3 launch

This commit fixes a bug caused by the X-Frame-Options response header. The X-Frame-Options response header indicates to the browser whether a site's content can be loaded within certain tags, including the <iframe> tag. This is a form of clickjacking protection.

In Django, this response header is set by the django.middleware.clickjacking.XFrameOptionsMiddleware middleware. In the edx-platform, by default, X-Frame-Options is set to DENY (see the X_FRAME_OPTIONS Django setting), which means that the response content returned by Django views cannot be loaded within certain tags. However, this behavior can be disabled by decorating views with the django.views.decorators.clickjacking.xframe_options_exempt view decorator.

This creates a problem for LTI 1.3 lauches in the edx-platform. When an LTI component is loaded, the LtiConsumerXBlock is loaded via the lms.djangoapps.courseware.views.views.render_xblock_view view. This view is called an <iframe> tag, but the view is decorated by the xfame_options_exempt decorator, which disables clickjacking protection and communicates to the browser that the content can be loaded in the <iframe> tag.

Once the third-party login request of the LTI 1.3 launch is completed, the LTI tool directs the browser to make a request to the launch_gate_endpoint. This endpoint returns a response, which is an auto-submitting form that makes a POST request - the LTI launch request - to the tool. This view has clickjacking enabled, so the browser blocks the requests, which prevents the launch from occurring.

This commit adds the xframe_options_exempt view decorator to the launch_gate_endpoint view.

Note that LTI 1.1 does not have this bug, because the LTI launch request is handled via the lti_launch_handler. The XBlock runtime handles requests to the LTI handlers via the openedx.core.djangoapps.xblock.rest_api.views.xblock_handler view, which is also decorated by the xframe_options_exempt view decorator.
parent 52dfb3ea
No related branches found
No related tags found
No related merge requests found
...@@ -10,7 +10,7 @@ from django.http import JsonResponse, Http404 ...@@ -10,7 +10,7 @@ from django.http import JsonResponse, Http404
from django.db import transaction from django.db import transaction
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django.views.decorators.clickjacking import xframe_options_sameorigin from django.views.decorators.clickjacking import xframe_options_exempt, xframe_options_sameorigin
from django.shortcuts import render from django.shortcuts import render
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
...@@ -138,6 +138,7 @@ def public_keyset_endpoint(request, usage_id=None, lti_config_id=None): ...@@ -138,6 +138,7 @@ def public_keyset_endpoint(request, usage_id=None, lti_config_id=None):
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
@xframe_options_exempt
def launch_gate_endpoint(request, suffix=None): # pylint: disable=unused-argument def launch_gate_endpoint(request, suffix=None): # pylint: disable=unused-argument
""" """
Gate endpoint that triggers LTI launch endpoint XBlock handler Gate endpoint that triggers LTI launch endpoint XBlock handler
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment