def validate_lti_launch(consumer, uri, body, headers): verifier = SignatureOnlyEndpoint(LTIOAuthValidator(consumer)) return verifier.validate_request(uri, http_method='POST', body=body, headers=headers)
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
def is_valid_request(self, validator): endpoint = SignatureOnlyEndpoint(validator) return endpoint.validate_request( self.launch_url, 'POST', self.to_params(), self.launch_headers )
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
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
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
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
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
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
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
def __init__(self): super().__init__() self.endpoint = SignatureOnlyEndpoint(self) self.lti_content_source = None self.cache = cache
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
def __init__(self): super(SignatureValidator, self).__init__() self.endpoint = SignatureOnlyEndpoint(self)
def __init__(self): super(SignatureValidator, self).__init__() self.endpoint = SignatureOnlyEndpoint(self) self.lti_consumer = None self.cache = cache
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