示例#1
0
def validate_lti_launch(consumer, uri, body, headers):
    verifier = SignatureOnlyEndpoint(LTIOAuthValidator(consumer))

    return verifier.validate_request(uri,
                                     http_method='POST',
                                     body=body,
                                     headers=headers)
示例#2
0
    def verify(self) -> bool:
        """Verify the LTI request.

        Returns:
            bool: True if the LTI launch request is valid.

        Raises:
            LTIException: Raised if request validation fails
        """

        try:
            launch_params = LaunchParams(self.request.POST)
        except LaunchParamException as error:
            raise LTIException(
                "Exception while processing launch parameters : {}".format(
                    error))

        validator = LTIRequestValidator()
        oauth_endpoint = SignatureOnlyEndpoint(validator)

        self._valid, _ = oauth_endpoint.validate_request(
            uri=self.request.build_absolute_uri(),
            http_method=self.request.method,
            body=launch_params.urlencoded,
            headers=self.request.headers,
        )

        if self._valid is not True:
            raise LTIException("LTI verification failed")

        self._params = launch_params
        self._verified = True

        return self._valid
示例#3
0
 def is_valid_request(self, validator):
     endpoint = SignatureOnlyEndpoint(validator)
     return endpoint.validate_request(
         self.launch_url,
         'POST',
         self.to_params(),
         self.launch_headers
     )
示例#4
0
def validate_lti_launch(consumer, uri, body, headers):
    verifier = SignatureOnlyEndpoint(LTIOAuthValidator(consumer))

    #@@@ spoof the request results for dev purposes
    #return verifier.validate_request(
    ok, other = verifier.validate_request(uri,
                                          http_method='POST',
                                          body=body,
                                          headers=headers)

    return True, other
示例#5
0
    def is_valid_request(self, validator):
        validator = ProxyValidator(validator)
        endpoint = SignatureOnlyEndpoint(validator)

        valid, request = endpoint.validate_request(self.launch_url, 'POST',
                                                   self.to_params(),
                                                   self.launch_headers)

        if valid and not self.consumer_key and not self.consumer_secret:
            # Gather the key and secret
            self.consumer_key = self.launch_params['oauth_consumer_key']
            self.consumer_secret = validator.secret

        return valid
示例#6
0
    def is_valid_request(self, validator):
        validator = ProxyValidator(validator)
        endpoint = SignatureOnlyEndpoint(validator)

        # hack to fix lti launch to assignment not working
        # as far as I can figure, looks like Canvas doesn't use the modified
        # url with the assignment query as part of the signature, so we need
        # to use the url without the assignment query for signature validation
        launchUrlParts = urlsplit(self.launch_url)
        validateUrl = launchUrlParts.scheme + '://' + launchUrlParts.netloc + \
            launchUrlParts.path

        valid, request = endpoint.validate_request(validateUrl, 'POST',
                                                   self.to_params(),
                                                   self.launch_headers)

        if valid and not self.consumer_key and not self.consumer_secret:
            # Gather the key and secret
            self.consumer_key = self.launch_params['oauth_consumer_key']
            self.consumer_secret = validator.secret

        return valid
示例#7
0
def lti_login():
    uri = request.base_url
    headers = dict(request.headers)
    method = request.method
    body = request.form
    endpoint = SignatureOnlyEndpoint(LTIRequestValidator())
    is_valid, oauth_request = endpoint.validate_request(
        uri, method, body, headers)
    if not is_valid:
        logger.warning(
            'An invalid LTI login request. Are the tokens configured correctly?'
        )
        raise PermissionDenied(
            'An invalid LTI login request. Are the tokens configured correctly?'
        )

    user = load_user_from_request(oauth_request)
    if not user:
        raise PermissionDenied(
            'Authentication of a LTI request did not yield an user')
    # if not user.is_active:
    #     logger.warning('A LTI login attempt by inactive user: %s', user)
    #     raise PermissionDenied('An authenticated user is not active')

    # Set vars for listenters
    # request.oauth = oauth_request
    oauth_request.redirect_url = current_app.config['LOGIN_REDIRECT_URL']
    oauth_request.set_cookies = []

    # send signal
    lti_login_authenticated.send(**dict(user._asdict()))
    response = redirect(current_app.config['LOGIN_REDIRECT_URL'])

    for args, kwargs in oauth_request.set_cookies:
        response.set_cookie(*args, **kwargs)

    logger.debug('Login completed for a LTI authenticated user: %s', user)
    return response
示例#8
0
    def _authenticate(self, handler, data=None):
        self.log.debug("calling authenticate in LTIAuthenticator\n")
        validator = LTIValidator()
        signature_authenticate = SignatureOnlyEndpoint(validator)

        request = handler.request
        body = request.body.decode('utf-8')
        for p in body.split('&'):
            self.log.debug('%s' % p)
        self.log.debug('\nFinished self.log.debugging body.split\n')

        # Since we're behind a proxy we need to hardcode the URL here for the signature
        url = '%s://%s/hub/login' % (os.environ.get('PROTO', 'http'), os.environ.get('DOMAIN', 'localhost'))
        self.log.info('url: %s' % url)
        x = signature_authenticate.validate_request(url, request.method, request.body.decode('utf-8'), request.headers)
        self.log.debug("Authenticated? %s\n\n" % str(x[0]))

        if x[0]:
            user = db.get_user(handler.get_argument("user_id"))
            if not user:
                user = db.add_user(handler.get_argument('user_id'))
            return user

        return None
 def __init__(self, lti_consumer):
     super(SignatureValidator, self).__init__()
     self.endpoint = SignatureOnlyEndpoint(self)
     self.lti_consumer = lti_consumer
示例#10
0
    async def authenticate(self, handler, data=None):
        self.log.debug("calling authenticate in LTIAuthenticator\n")
        validator = LTIValidator()
        signature_authenticate = SignatureOnlyEndpoint(validator)

        request = handler.request

        self.log.debug('self.enable_auth_state: %s' % self.enable_auth_state)
        self.log.debug('self: %s' % self)
        self.log.debug('%s://%s%s\n' %
                       (request.protocol, request.host, request.uri))
        self.log.debug('%s\n\n' % str(dict(request.headers)))
        # self.log.debug('%s\n\n' % request.body.decode('utf-8'))
        body = request.body.decode('utf-8')
        for p in body.split('&'):
            self.log.debug('%s' % p)
        self.log.debug('\nFinished self.log.debugging body.split\n')

        # Since we're behind a proxy we need to hardcode the URL here for the signature
        url = '%s://%s/hub/login' % (os.environ.get(
            'PROTO', 'http'), os.environ.get('DOMAIN', 'localhost'))
        self.log.info('url: %s' % url)
        x = signature_authenticate.validate_request(
            url, request.method, request.body.decode('utf-8'), request.headers)
        self.log.debug("Authenticated? %s\n\n" % str(x[0]))

        if x[0]:

            user = db.get_user(handler.get_argument("user_id"))

            if not user:
                user = db.add_user(handler.get_argument('user_id'))

        else:
            return None

        username = user.unix_name
        role = handler.get_argument('roles')

        upper_r = role.upper()
        self.log.debug('upper_r: %s', upper_r)
        allow_admin = 'TeachingAssistant'.upper(
        ) in upper_r or 'Instructor'.upper(
        ) in upper_r or 'ContentDeveloper'.upper() in upper_r

        is_admin = bool(allow_admin
                        and handler.get_argument('custom_admin', ''))
        if is_admin:
            username = '******'

        course_name = handler.get_argument('custom_course', '')
        if course_name not in user.courses:
            user.courses.append(
                LtiUserCourse(user_id=user.user_id, course=course_name))
            db.db.commit()

        firstname = handler.get_argument('lis_person_name_given', '')
        surname = handler.get_argument('lis_person_name_family', '')
        env = os.environ.copy()

        auth_state = {'course': course_name}
        for var in [
                'JUPYTERHUB_API_URL', 'JUPYTERHUB_API_TOKEN', 'GRADEBOOK_DB',
                'MONGO_PW'
        ]:
            auth_state[var] = env[var]

        auth_state['first_name'] = firstname
        auth_state['surname'] = surname

        self.log.debug('auth_state: %s' % auth_state)
        ret = {'name': username, 'auth_state': auth_state, 'admin': is_admin}

        # self.log.debug(ret)

        return ret
示例#11
0
def lti_login(request):
    """
    Accepts LTI launch requests
    """
    # Extract request data for oauthlib.
    uri = request.build_absolute_uri()
    method = request.method
    body = urlencode(request.POST.items())
    headers = {
        k: v
        for k, v in request.META.items() if not k.startswith('wsgi.')
    }
    if 'HTTP_AUTHORIZATION' in headers:
        headers['Authorization'] = headers['HTTP_AUTHORIZATION']
    if 'CONTENT_TYPE' in headers:
        headers['Content-Type'] = headers['CONTENT_TYPE']

    # create oauth endpoint and validate request
    endpoint = SignatureOnlyEndpoint(LTIRequestValidator())
    is_valid, oauth_request = endpoint.validate_request(
        uri, method, body, headers)

    if not is_valid:
        logger.warning(
            'An invalid LTI login request. Are the tokens configured correctly?'
        )
        raise PermissionDenied(
            'An invalid LTI login request. Are the tokens configured correctly?'
        )

    if (oauth_request.lti_version != 'LTI-1p0'
            or oauth_request.lti_message_type != 'basic-lti-launch-request'):
        logger.warning(
            'A LTI login request is not LTI-1p0 or basic-lti-launch-request.')
        raise PermissionDenied(
            'Version is not LTI-1p0 or type is not basic-lti-launch-request for a LTI login request.'
        )

    # authenticate user
    user = authenticate(request, oauth_request=oauth_request)
    if not user:
        raise PermissionDenied(
            'Authentication of a LTI request did not yield an user')
    if not user.is_active:
        logger.warning('A LTI login attempt by inactive user: %s', user)
        raise PermissionDenied('An authenticated user is not active')

    # Set vars for listenters
    request.oauth = oauth_request
    oauth_request.redirect_url = None
    oauth_request.set_cookies = []

    # signal that authentication step has been done
    lti_login_authenticated.send(sender=user.__class__,
                                 request=request,
                                 user=user)

    # login the user (sends signal user_logged_in)
    login(request, user)

    # Create redirect response
    redirect_to = oauth_request.redirect_url
    if redirect_to and not is_safe_url(
            url=redirect_to,
            allowed_hosts={request.get_host()},
            require_https=request.is_secure(),
    ):
        redirect_to = None
    if redirect_to is None:
        redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
    response = HttpResponseRedirect(redirect_to)

    # set possible cookies
    for args, kwargs in oauth_request.set_cookies:
        response.set_cookie(*args, **kwargs)

    logger.debug('Login completed for a LTI authenticated user: %s', user)
    return response
示例#12
0
 def __init__(self):
     super().__init__()
     self.endpoint = SignatureOnlyEndpoint(self)
     self.lti_content_source = None
     self.cache = cache
示例#13
0
    def __call__(self, request):

        request.LTI_MODE = False

        uri = urlparse(request.build_absolute_uri())
        method = request.method

        query_dict = {}
        if request.POST:
            query_dict.update(request.POST.items())
        if request.GET:
            query_dict.update(request.GET.items())

        if 'lti_message_type' in query_dict and \
           'lti_version' in query_dict and \
           'oauth_consumer_key' in query_dict:

            uri = uri._replace(query=urlencode(query_dict.items())).geturl()
            headers = {
                k: v
                for k, v in request.META.items() if not k.startswith('wsgi.')
            }

            if 'HTTP_AUTHORIZATION' in headers:
                headers['Authorization'] = headers['HTTP_AUTHORIZATION']
            if 'CONTENT_TYPE' in headers:
                headers['Content-Type'] = headers['CONTENT_TYPE']

            endpoint = SignatureOnlyEndpoint(LTIRequestValidator())
            is_valid, oauth_request = endpoint.validate_request(
                uri, method, '', headers)

            if not is_valid:
                # normally an exception would be raised in a case like this.
                # here, instead of raising an exception and causing an HTTP 403 or 500,
                # return a 200 OK with information what happened since the consuming
                # end (A+) who sent the request swallows errors.
                return HttpResponse(('LTI login/authentication failed. <br> \
                                     Contact admin and ask to check LTI key and secret.'
                                     ))

            # this tells the view classes they should provide the LTI version of the page
            request.LTI_MODE = True

            # since the HTTP POSTs are coming through a proxy(ish) server that drops
            # cookies but keeps CSRF tokens, the CSRF check always fails
            # in this case we can disable the checks altogether
            setattr(request, '_dont_enforce_csrf_checks', True)

            # if the same person has previously accessed the system through LTI interop,
            # then there already exists a user account. in that case, login that user account,
            # otherwise create a new one.
            # it is CRITICAL to pay attention to UserModel.lti boolean since accounts of
            # Shibboleth and LTI login protocols should not be mixed.

            try:
                user = User.objects.get(
                    email=oauth_request.lis_person_contact_email_primary,
                    lti=True)
                logger.info(f"Previous user found! Logging {user} in")
            except Exception:
                logger.info("Previous user NOT FOUND --> create a new one")
                user = LTIAuthBackend().authenticate(
                    oauth_request=oauth_request)
                user.lti = True
                user.save()
                logger.info(f"Created user {user}")

            resolved_view = resolve(request.path)
            course = Course.objects.get(
                base_course__url_slug=resolved_view.kwargs['base_url_slug'],
                url_slug=resolved_view.kwargs['url_slug'])

            course.enroll(user)
            request.user = user

        response = self.get_response(request)

        return response
示例#14
0
 def __init__(self):
     super(SignatureValidator, self).__init__()
     self.endpoint = SignatureOnlyEndpoint(self)
示例#15
0
 def __init__(self):
     super(SignatureValidator, self).__init__()
     self.endpoint = SignatureOnlyEndpoint(self)
     self.lti_consumer = None
     self.cache = cache
示例#16
0
 def __init__(self, lti_consumer):
     super(SignatureValidator, self).__init__()  # lint-amnesty, pylint: disable=super-with-arguments
     self.endpoint = SignatureOnlyEndpoint(self)
     self.lti_consumer = lti_consumer
def authenticator(validator, monkeypatch):
    signature_authenticate = SignatureOnlyEndpoint(validator)
    monkeypatch.setattr(SignatureOnlyEndpoint, 'validate_request', True)
    return signature_authenticate