示例#1
0
文件: lti.py 项目: ccnmtl/mediathread
    def _verify_request(self, request):
        """
        Verify LTI request

        :raises: LTIException is request validation failed
        """
        if request.method == 'POST':
            params = dict(request.POST.iteritems())
        else:
            params = dict(request.GET.iteritems())

        try:
            verify_request_common(self._consumers(),
                                  request.build_absolute_uri(),
                                  request.method, request.META,
                                  params)

            self._validate_role()

            self.lti_params = params
            request.session[LTI_SESSION_KEY] = True
            return True
        except LTIException:
            self.lti_params = {}
            request.session[LTI_SESSION_KEY] = False
            raise
示例#2
0
 def test_verify_request_common_no_oauth_fields(self):
     """
     verify_request_common fails on missing authentication
     """
     headers = dict()
     consumers, method, url, _, params = (self.generate_oauth_request())
     with self.assertRaises(LTIException):
         verify_request_common(consumers, url, method, headers, params)
示例#3
0
def lti(request):
    if not get_timeslot():
        return HttpResponse(
            'Login is not available. The system is currently closed.',
            status=403)
    config = getattr(settings, 'PYLTI_CONFIG', dict())
    consumers = config.get('consumers', dict())
    params = dict(request.POST.items())
    headers = request.META
    headers['X-Forwarded-Proto'] = headers['HTTP_X_FORWARDED_PROTO']
    try:
        verify_request_common(consumers, request.build_absolute_uri(),
                              request.method, headers, params)
    except LTIException as e:
        logger.error('LTI exception from canvas; {}'.format(e))
        return HttpResponse("Signature Validation failed!", status=403)

    data = request.POST
    try:
        username = data['lis_person_sourcedid']
        email = data['lis_person_contact_email_primary']
        studentnumber = data['custom_canvas_user_login_id']
        coursecode = data['context_label']
    except KeyError as e:
        logger.error('Invalid post data from canvas; {}; {}'.format(data, e))
        return HttpResponse("Missing data in POST", status=400)

    user = get_user(email, username)
    if user is None:
        user = User(email=email, username=username)
        user.save()
    try:
        meta = user.usermeta
    except UserMeta.DoesNotExist:
        meta = UserMeta(User=user)

    meta.Studentnumber = studentnumber
    if not meta.Overruled:
        if settings.COURSE_CODE_BEP in coursecode:
            meta.EnrolledBEP = True
        elif settings.COURSE_CODE_EXT in coursecode:
            meta.EnrolledBEP = True
            meta.EnrolledExt = True
        else:
            logger.warning(
                'Course code not matched on BEP or EXT for user {}. Code was: {}'
                .format(user, coursecode))

    meta.save()
    meta.TimeSlot.add(get_timeslot())
    meta.save()
    user.save()

    log = CanvasLogin()
    log.Subject = user
    log.save()

    return redirect("{}/login/".format(settings.DOMAIN))
示例#4
0
 def test_verify_request_common_no_oauth_fields(self):
     """
     verify_request_common fails on missing authentication
     """
     headers = dict()
     consumers, method, url, _, params = (
         self.generate_oauth_request()
     )
     with self.assertRaises(LTIException):
         verify_request_common(consumers, url, method, headers, params)
示例#5
0
 def test_verify_request_common_no_params(self):
     """
     verify_request_common fails on missing parameters
     """
     consumers = {"__consumer_key__": {"secret": "__lti_secret__"}}
     url = 'http://localhost:5000/?'
     method = 'GET'
     headers = dict()
     params = dict()
     with self.assertRaises(LTIException):
         verify_request_common(consumers, url, method, headers, params)
示例#6
0
 def post(self, request, save_id):
     params = {key: request.data[key] for key in request.data}
     consumers_dict = consumers()
     url = request.build_absolute_uri()
     headers = request.META
     # Define the redirect url
     host = request.get_host()
     _ = headers.pop('HTTP_COOKIE', None)
     if 'HTTP_SEC_FETCH_DEST' not in headers:
         headers['HTTP_SEC_FETCH_DEST'] = 'iframe'
     if 'HTTP_SEC_FETCH_MODE' not in headers:
         headers['HTTP_SEC_FETCH_MODE'] = 'navigate'
     if 'HTTP_SEC_FETCH_SITE' not in headers:
         headers['HTTP_SEC_FETCH_SITE'] = 'cross-site'
     print("params:", params)
     print("headers:", headers)
     print("host:", host)
     print("url:", url)
     ltikeys = ['user_id', 'lis_result_sourcedid',
                'lis_outcome_service_url', 'oauth_nonce',
                'oauth_timestamp', 'oauth_consumer_key',
                'oauth_signature_method',
                'oauth_version', 'oauth_signature']
     ltidata = {key: params.get(key) for key in ltikeys}
     lti_session = ltiSession.objects.create(**ltidata)
     print("Got POST for validating LTI consumer")
     try:
         i = lticonsumer.objects.get(consumer_key=request.data.get(
             'oauth_consumer_key'), initial_schematic__save_id=save_id
         )
         lti_session.lti_consumer = i
         lti_session.save()
     except lticonsumer.DoesNotExist:
         print("Consumer does not exist on backend")
         return HttpResponseRedirect(get_reverse('ltiAPI:denied'))
     print(i.initial_schematic.save_id)
     next_url = "http://" + host + "/eda/#editor?id=" + \
                str(i.initial_schematic.save_id) + "&branch=" \
                + str(i.initial_schematic.branch) + "&version=" \
                + str(i.initial_schematic.version) \
                + "&lti_id=" + str(lti_session.id) + "&lti_user_id=" + \
                lti_session.user_id \
                + "&lti_nonce=" + lti_session.oauth_nonce
     try:
         print("Got verification request")
         verify_request_common(consumers_dict, url,
                               request.method, headers, params)
         print("Verified consumer")
         # grade = LTIPostGrade(params, request)
         return HttpResponseRedirect(next_url)
     except LTIException:
         traceback.print_exc()
         return HttpResponseRedirect(get_reverse('ltiAPI:denied'))
示例#7
0
    def wrapper(request):
        consumer_key = request.params["oauth_consumer_key"]
        instance = request.db.query(ai.ApplicationInstance).filter(
            ai.ApplicationInstance.consumer_key == consumer_key).one()

        consumers = {}

        consumers[consumer_key] = {"secret": instance.shared_secret}

        # TODO rescue from an invalid lti launch
        verify_request_common(consumers, request.url, request.method,
                              dict(request.headers), dict(request.params))
        return view_function(request)
示例#8
0
 def test_verify_request_common_no_params(self):
     """
     verify_request_common fails on missing parameters
     """
     consumers = {
         "__consumer_key__": {"secret": "__lti_secret__"}
     }
     url = 'http://localhost:5000/?'
     method = 'GET'
     headers = dict()
     params = dict()
     with self.assertRaises(LTIException):
         verify_request_common(consumers, url, method, headers, params)
示例#9
0
def lti(request):
    config = getattr(settings, 'PYLTI_CONFIG', dict())
    consumers = config.get('consumers', dict())
    params = dict(request.POST.items())
    headers = request.META
    headers['X-Forwarded-Proto'] = headers['HTTP_X_FORWARDED_PROTO']
    try:
        verify_request_common(consumers, request.build_absolute_uri(),
                              request.method, headers, params)
    except LTIException:
        return HttpResponse("Signature Validation failed!", status=403)

    data = request.POST
    try:
        username = data['lis_person_sourcedid']
        email = data['lis_person_contact_email_primary']
        studentnumber = data['custom_canvas_user_login_id']
        coursecode = data['context_label']
    except KeyError:
        return HttpResponse("Missing data in POST", status=400)

    user = get_user(email, username)
    if user is None:
        user = User(email=email, username=username)
        user.save()
    try:
        meta = user.usermeta
    except UserMeta.DoesNotExist:
        meta = UserMeta(User=user)

    meta.Studentnumber = studentnumber
    if not meta.Overruled:
        if coursecode == '5XEC0':
            meta.EnrolledBEP = True
        elif coursecode == '5XED0':
            meta.EnrolledBEP = True
            meta.EnrolledExt = True

    meta.save()
    meta.TimeSlot.add(get_timeslot())
    meta.save()
    user.save()

    log = CanvasLogin()
    log.Subject = user
    log.save()

    return redirect("https://bep.ele.tue.nl/login")
示例#10
0
    def _parse_lti_data(self, courseid, taskid):
        """ Verify and parse the data for the LTI basic launch """
        post_input = web.webapi.rawinput("POST")
        try:
            verified = verify_request_common(self.consumers, web.ctx.home + web.ctx.fullpath, "POST", {}, post_input)
        except:
            raise Exception("Cannot authentify request (1)")

        if verified:
            user_id = post_input["user_id"]
            roles = post_input.get("roles", "Student").split(",")
            realname = self._find_realname(post_input)
            email = post_input.get("lis_person_contact_email_primary", "")
            lis_outcome_service_url = post_input.get("lis_outcome_service_url", None)
            outcome_result_id = post_input.get("lis_result_sourcedid", None)
            consumer_key = post_input["oauth_consumer_key"]

            if lis_outcome_service_url is None:
                raise Exception("INGInious needs the parameter lis_outcome_service_url in the LTI basic-launch-request")
            if outcome_result_id is None:
                raise Exception("INGInious needs the parameter lis_result_sourcedid in the LTI basic-launch-request")

            self.user_manager.lti_auth(user_id, roles, realname, email, courseid, taskid, consumer_key, lis_outcome_service_url, outcome_result_id)
        else:
            raise Exception("Cannot authentify request (2)")
示例#11
0
    def _parse_lti_data(self, courseid, taskid):
        """ Verify and parse the data for the LTI basic launch """
        post_input = web.webapi.rawinput("POST")
        try:
            verified = verify_request_common(self.consumers,
                                             web.ctx.home + web.ctx.fullpath,
                                             "POST", {}, post_input)
        except:
            raise Exception("Cannot authentify request (1)")

        if verified:
            user_id = post_input["user_id"]
            roles = post_input.get("roles", "Student").split(",")
            realname = self._find_realname(post_input)
            email = post_input.get("lis_person_contact_email_primary", "")
            lis_outcome_service_url = post_input.get("lis_outcome_service_url",
                                                     None)
            outcome_result_id = post_input.get("lis_result_sourcedid", None)
            consumer_key = post_input["oauth_consumer_key"]

            if lis_outcome_service_url is None:
                raise Exception(
                    "INGInious needs the parameter lis_outcome_service_url in the LTI basic-launch-request"
                )
            if outcome_result_id is None:
                raise Exception(
                    "INGInious needs the parameter lis_result_sourcedid in the LTI basic-launch-request"
                )

            self.user_manager.lti_auth(user_id, roles, realname, email,
                                       courseid, taskid, consumer_key,
                                       lis_outcome_service_url,
                                       outcome_result_id)
        else:
            raise Exception("Cannot authentify request (2)")
示例#12
0
def auth(request):
    '''POST handler for the LTI login POST back call'''
    if request.method == 'POST':
        # Extracts the LTI payload information
        params = {key: request.POST[key] for key in request.POST}
        # Maps the settings defined for the LTI consumer
        consumers = settings.PYLTI_CONFIG['consumers']
        # Builds the tool URL from the request
        url = request.build_absolute_uri()
        # Extracts the request headers from the request
        headers = request.META
        # Get the default next URL from the LTI config
        next_template = settings.PYLTI_CONFIG.get('next_url')
        try:
            # Validate the incoming LTI
            verify_request_common(consumers, url,\
                                  request.method, headers,\
                                  params)
            # Map and call the login method hook if defined in the settings
            login_method_hook = settings.PYLTI_CONFIG.get(
                'method_hooks', {}).get('valid_lti_request', None)
            if (login_method_hook):
                # If there is a return URL from the configured call the redirect URL
                # is updated with the one that is returned. This is to enable redirecting to
                # constructed URLs
                ############ K3ru: updated lines ################################
                update_template = import_string(login_method_hook)(params,
                                                                   request)
                print("Update URL: ", update_template)
                if update_template:
                    next_template = update_template
                    print("USER post_update: ", request.user.username)
            return render(request, next_template)
            ################### End updated lines ###############################
        except LTIException:
            # Map and call the invalid login method hook if defined in the settings
            invalid_login_method_hook = settings.PYLTI_CONFIG.get(
                'method_hooks', {}).get('invalid_lti_request', None)
            if (invalid_login_method_hook):
                import_string(invalid_login_method_hook)(params)
            return HttpResponseRedirect(get_reverse('django_lti_auth:denied'))
    else:
        return HttpResponseRedirect(get_reverse('django_lti_auth:denied'))
示例#13
0
 def test_verify_request_common(self):
     """
     verify_request_common succeeds on valid request
     """
     headers = dict()
     consumers, method, url, verify_params, params = \
         self.generate_oauth_request()
     ret = verify_request_common(consumers, url, method,
                                 headers, verify_params)
     self.assertTrue(ret)
示例#14
0
 def test_verify_request_common(self):
     """
     verify_request_common succeeds on valid request
     """
     headers = dict()
     consumers, method, url, verify_params, params = \
         self.generate_oauth_request()
     ret = verify_request_common(consumers, url, method, headers,
                                 verify_params)
     self.assertTrue(ret)
示例#15
0
 def test_verify_request_common_via_proxy(self):
     """
     verify_request_common succeeds on valid request via proxy
     """
     headers = dict()
     headers['X-Forwarded-Proto'] = 'https'
     orig_url = 'https://localhost:5000/?'
     consumers, method, url, verify_params, params = \
         self.generate_oauth_request(url_to_sign=orig_url)
     ret = verify_request_common(consumers, url, method, headers,
                                 verify_params)
     self.assertTrue(ret)
示例#16
0
 def test_verify_request_common_via_proxy(self):
     """
     verify_request_common succeeds on valid request via proxy
     """
     headers = dict()
     headers['X-Forwarded-Proto'] = 'https'
     orig_url = 'https://localhost:5000/?'
     consumers, method, url, verify_params, params = \
         self.generate_oauth_request(url_to_sign=orig_url)
     ret = verify_request_common(consumers, url, method,
                                 headers, verify_params)
     self.assertTrue(ret)
示例#17
0
    def verify_request(self):
        """
        Verify LTI request
        :raises: LTIException is request validation failed
        """
        if flask_request.method == 'POST':
            params = flask_request.form.to_dict()
        else:
            params = flask_request.args.to_dict()
        log.debug(params)

        self.lti_session.delete()
        self.lti_session = self.get_ltisession(params=params)

        log.debug('verify_request?')
        try:
            verify_request_common(self._consumers(), flask_request.url,
                                  flask_request.method, flask_request.headers,
                                  params)
            log.debug('verify_request success')

            # All good to go, store all of the LTI params into a
            # session dict for use in views
            for prop in LTI_PROPERTIES:
                if params.get(prop, None):
                    log.debug("params %s=%s", prop, params[prop])
                    setattr(self.lti_session, prop, params[prop])

            session['user_id'] = params['user_id']

            # Set logged in session key
            setattr(self.lti_session, LTI_SESSION_KEY, True)
            self.lti_session.save()

            return True
        except LTIException:
            log.debug('verify_request failed')
            self.close_session()
            raise
示例#18
0
    def _verify_request(self, request):
        """
        Verify LTI request

        :raises: LTIException is request validation failed
        """
        try:
            params = self._params(request)
            verify_request_common(self.consumers(),
                                  request.build_absolute_uri(), request.method,
                                  request.META, params)

            self._validate_role()

            self.clear_session(request)
            self.initialize_session(request, params)
            request.session[LTI_SESSION_KEY] = True
            return True
        except LTIException:
            self.clear_session(request)
            request.session[LTI_SESSION_KEY] = False
            raise
示例#19
0
    def test_verify_request_common_via_proxy_wsgi_syntax(self):
        """
        verify_request_common succeeds on valid request via proxy with
        wsgi syntax for headers
        """
        headers = dict()
        headers['HTTP_X_FORWARDED_PROTO'] = 'https'
        orig_url = 'https://localhost:5000/?'
        consumers, method, url, verify_params, _ = (
            self.generate_oauth_request(url_to_sign=orig_url))

        ret = verify_request_common(consumers, url, method, headers,
                                    verify_params)
        self.assertTrue(ret)
示例#20
0
 def test_verify_request_common_no_auth_fields(self):
     """
     verify_request_common fails on missing authentication
     """
     headers = dict()
     consumers, method, url, verify_params, params = \
         self.generate_oauth_request()
     ret = False
     try:
         ret = verify_request_common(consumers, url, method, headers,
                                     params)
     except LTIException:
         self.assertTrue(True)
     self.assertFalse(ret)
示例#21
0
    def _verify_request(self, request):
        """
        Verify LTI request

        :raises: LTIException is request validation failed
        """
        try:
            params = self._params(request)
            verify_request_common(self.consumers(),
                                  request.build_absolute_uri(),
                                  request.method, request.META,
                                  params)

            self._validate_role()

            self.clear_session(request)
            self.initialize_session(request, params)
            request.session[LTI_SESSION_KEY] = True
            return True
        except LTIException:
            self.clear_session(request)
            request.session[LTI_SESSION_KEY] = False
            raise
示例#22
0
 def test_verify_request_common_no_auth_fields(self):
     """
     verify_request_common fails on missing authentication
     """
     headers = dict()
     consumers, method, url, verify_params, params = \
         self.generate_oauth_request()
     ret = False
     try:
         ret = verify_request_common(consumers, url, method,
                                     headers, params)
     except LTIException:
         self.assertTrue(True)
     self.assertFalse(ret)
示例#23
0
def is_valid_request(consumer_key: str, consumer_secret: str,
                     request: Request):
    consumers = {
        consumer_key: {
            "secret": consumer_secret,
        }
    }

    try:
        return verify_request_common(consumers, request.build_absolute_uri(),
                                     request.method, request.META,
                                     request.POST.dict())
    except Exception as e:
        logger.error(e)
        return False
示例#24
0
 def test_verify_request_common_no_params(self):
     """
     verify_request_common fails on missing parameters
     """
     consumers = {"__consumer_key__": {"secret": "__lti_secret__"}}
     url = 'http://localhost:5000/?'
     method = 'GET'
     headers = dict()
     params = dict()
     ret = False
     try:
         ret = verify_request_common(consumers, url, method, headers,
                                     params)
     except LTIException:
         self.assertTrue(True)
     self.assertFalse(ret)
示例#25
0
 def test_verify_request_common_no_params(self):
     """
     verify_request_common fails on missing parameters
     """
     consumers = {
         "__consumer_key__": {"secret": "__lti_secret__"}
     }
     url = 'http://localhost:5000/?'
     method = 'GET'
     headers = dict()
     params = dict()
     ret = False
     try:
         ret = verify_request_common(consumers, url, method,
                                     headers, params)
     except LTIException:
         self.assertTrue(True)
     self.assertFalse(ret)
示例#26
0
文件: lti.py 项目: sampaccoud/marsha
    def verify(self):
        """Verify the LTI request.

        Raises
        ------
        LTIException
            Raised if request validation fails
        ImproperlyConfigured
            Raised if BYPASS_LTI_VERIFICATION is True but we are not in DEBUG mode

        Returns
        -------
        string
            It returns the consumer site related to the passport used in the LTI launch
            request if it is valid.
            If the BYPASS_LTI_VERIFICATION and DEBUG settings are True, it creates and return a
            consumer site with the consumer site domain passed in the LTI request.

        """
        if settings.BYPASS_LTI_VERIFICATION:
            if not settings.DEBUG:
                raise ImproperlyConfigured(
                    "Bypassing LTI verification only works in DEBUG mode.")
            return ConsumerSite.objects.get_or_create(
                domain=self.consumer_site_domain,
                defaults={"name": self.consumer_site_domain},
            )[0]

        passport = self.get_passport()
        consumers = {
            str(passport.oauth_consumer_key): {
                "secret": str(passport.shared_secret)
            }
        }

        # The LTI signature is computed using the url of the LTI launch request. But when Marsha
        # is behind a TLS termination proxy, the url as seen by Django is changed and starts with
        # "http". We need to revert this so that the signature we calculate matches the one
        # calculated by our LTI consumer.
        # Note that this is normally done in pylti's "verify_request_common" method but it does
        # not support WSGI normalized headers so let's do it ourselves.
        url = self.request.build_absolute_uri()
        if self.request.META.get("HTTP_X_FORWARDED_PROTO", "http") == "https":
            url = url.replace("http:", "https:", 1)

        # A call to the verification function should raise an LTIException but
        # we can further check that it returns True.
        if (verify_request_common(
                consumers,
                url,
                self.request.method,
                self.request.META,
                dict(self.request.POST.items()),
        ) is not True):
            raise LTIException()

        consumer_site = passport.consumer_site or passport.playlist.consumer_site

        # Make sure we only accept requests from domains in which the "top parts" match
        # the URL for the consumer_site associated with the passport.
        # eg. sub.example.com & example.com for an example.com consumer site.
        domain_check = urlparse(self.request.META.get("HTTP_REFERER")).hostname
        if domain_check != consumer_site.domain and not domain_check.endswith(
                ".{:s}".format(consumer_site.domain)):
            raise LTIException(
                "Host domain does not match registered passport.")

        return consumer_site
示例#27
0
    def post(self, request, format=None):
        url = f"{BACKEND_DOMAIN}{reverse('lti')}"
        consumers = LTI_CONFIG["consumers"]
        method = request.method
        headers = request.META
        payload = request.POST.dict()

        # Verify that the payload received from LTI is valid
        try:
            verify_request_common(consumers, url, method, headers, payload)
        # If not, redirect to the error page
        # This page would be displayed in an iframe on Moodle
        except LTIException:
            # TODO: Implement logging of this error
            return redirect(FRONTEND_DOMAIN + "/forbidden")

        current_user_email = payload["lis_person_contact_email_primary"]
        data = {
            "email": current_user_email,
            "first_name": payload["lis_person_name_given"],
            "last_name": payload["lis_person_name_family"],
        }
        user = get_or_create_user(data)

        # Elevate the user to instructor group if they have a staff role in LTI
        # If they are already instructor or admin, then do nothing
        user_groups = [group.name for group in user.groups.all()]
        is_lti_instructor = (LTI_CONFIG["staff_role"]
                             in payload["roles"].split(",")
                             if LTI_CONFIG.get("staff_role") else False)
        if "user" in user_groups and is_lti_instructor:
            user.groups.set([Group.objects.get(name="instructor")])

        token = generate_one_time_token(user)

        user.last_login = dt.now(timezone.utc)
        user.save()

        logger.info("authentication.login",
                    extra={
                        "user": user.email,
                        "type": "LTI"
                    })

        # Store the important LTI fields for this user
        # These fields be used to grant permissions in containers
        lti_payload = {
            "lti_id": payload["user_id"],
            "lti_email": current_user_email,
            "user_id": payload.get(LTI_CONFIG.get("username_field")),
        }
        lti.objects(user=user.id).update_one(payload=lti_payload, upsert=True)

        # If any containers have been bound to this LTI resource, then
        # redirect to that container
        # Otherwise, prompt the user to choose a container for binding
        lti_resource_id = payload["resource_link_id"]
        try:
            container = Container.objects.get(lti_resource=lti_resource_id)

            if LTI_CONFIG.get("auto_create_share_containers"):
                if container.owner != current_user_email and current_user_email not in container.sharing:
                    container.sharing.append(current_user_email)
                    container.save()

            return redirect(FRONTEND_DOMAIN + "?tkn=" + token + "&container=" +
                            str(container.id))
        except:
            if LTI_CONFIG.get("auto_create_share_containers"):
                container = Container(owner=current_user_email,
                                      code=payload["context_title"],
                                      lti_resource=payload["resource_link_id"],
                                      lti_context=payload["context_id"])
                container.save()

            return redirect(FRONTEND_DOMAIN + "?tkn=" + token + "&lti=" +
                            lti_resource_id)
示例#28
0
    def verify(self):
        """Verify the LTI request.

        Raises
        ------
        LTIException
            Raised if request validation fails
        ImproperlyConfigured
            Raised if BYPASS_LTI_VERIFICATION is True but we are not in DEBUG mode

        Returns
        -------
        string
            It returns the consumer site related to the passport used in the LTI launch
            request if it is valid.
            If the BYPASS_LTI_VERIFICATION and DEBUG settings are True, it creates and return a
            consumer site with the consumer site domain passed in the LTI request.

        """
        if self._consumer_site:
            return True

        if not self.context_id:
            raise LTIException("A context ID is required.")

        request_domain = self.request_domain

        if settings.BYPASS_LTI_VERIFICATION:
            if not settings.DEBUG:
                raise ImproperlyConfigured(
                    "Bypassing LTI verification only works in DEBUG mode."
                )
            if not request_domain:
                raise LTIException(
                    "You must provide an http referer in your LTI launch request."
                )
            self._consumer_site, _created = ConsumerSite.objects.get_or_create(
                domain=request_domain, defaults={"name": request_domain}
            )
            return True

        passport = self.get_passport()
        consumers = {
            str(passport.oauth_consumer_key): {"secret": str(passport.shared_secret)}
        }

        # The LTI signature is computed using the url of the LTI launch request. But when Marsha
        # is behind a TLS termination proxy, the url as seen by Django is changed and starts with
        # "http". We need to revert this so that the signature we calculate matches the one
        # calculated by our LTI consumer.
        # Note that this is normally done in pylti's "verify_request_common" method but it does
        # not support WSGI normalized headers so let's do it ourselves.
        url = build_absolute_uri_behind_proxy(self.request)

        # A call to the verification function should raise an LTIException but
        # we can further check that it returns True.
        if (
            verify_request_common(
                consumers,
                url,
                self.request.method,
                self.request.META,
                dict(self.request.POST.items()),
            )
            is not True
        ):
            raise LTIException("LTI verification failed.")

        consumer_site = passport.consumer_site or passport.playlist.consumer_site

        # Make sure we only accept requests from domains in which the "top parts" match
        # the URL for the consumer_site associated with the passport.
        # eg. sub.example.com & example.com for an example.com consumer site.
        # Also referer matching ALLOWED_HOSTS are accepted
        if (
            request_domain
            and request_domain != consumer_site.domain
            and not (
                request_domain.endswith(f".{consumer_site.domain}")
                or validate_host(request_domain, settings.ALLOWED_HOSTS)
            )
        ):
            raise LTIException(
                (
                    f"Host domain ({request_domain}) does not match registered passport "
                    f"({consumer_site.domain})."
                )
            )

        self._consumer_site = consumer_site
        return True
示例#29
0
文件: lti.py 项目: kylincode/marsha
    def verify(self):
        """Verify the LTI request.

        Raises
        ------
        LTIException
            Exception raised if request validation fails

        Returns
        -------
        boolean
            True if the request is a valid LTI launch request

        """
        consumer_key = self.request.POST.get("oauth_consumer_key", None)
        consumer_site_name = self.consumer_site_name

        try:
            assert consumer_key
        except AssertionError:
            raise LTIException("An oauth consumer key is required.")

        try:
            assert self.context_id
        except AssertionError:
            raise LTIException("A context ID is required.")

        try:
            assert consumer_site_name
        except AssertionError:
            raise LTIException("A consumer site name is required.")

        # find a passport related to either the consumer site or the playlist
        try:
            lti_passport = LTIPassport.objects.get(
                Q(
                    oauth_consumer_key=consumer_key,
                    is_enabled=True,
                    consumer_site__name=consumer_site_name,
                )
                | Q(
                    oauth_consumer_key=consumer_key,
                    is_enabled=True,
                    playlist__consumer_site__name=consumer_site_name,
                ))
        except LTIPassport.DoesNotExist:
            raise LTIException(
                "Could not find a valid passport for this consumer site and this "
                "oauth consumer key: {:s}/{:s}.".format(
                    consumer_site_name, consumer_key))

        consumers = {
            str(lti_passport.oauth_consumer_key): {
                "secret": str(lti_passport.shared_secret)
            }
        }
        # A call to the verification function should raise an LTIException but
        # we can further check that it returns True.
        if (verify_request_common(
                consumers,
                self.request.build_absolute_uri(),
                self.request.method,
                self.request.META,
                dict(self.request.POST.items()),
        ) is not True):
            raise LTIException()

        self._is_verified = True
        return True