def launch(): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() launch_data_storage = get_launch_data_storage() message_launch = ExtendedFlaskMessageLaunch( flask_request, tool_conf, launch_data_storage=launch_data_storage) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) tpl_kwargs = { 'page_title': PAGE_TITLE, 'is_deep_link_launch': message_launch.is_deep_link_launch(), 'launch_data': message_launch.get_launch_data(), 'launch_id': message_launch.get_launch_id(), 'curr_user_name': message_launch_data.get('name', '') } learn_url = message_launch_data[ 'https://purl.imsglobal.org/spec/lti/claim/tool_platform'][ 'url'].rstrip('/') params = { 'redirect_uri': Config.config['app_url'] + '/authcode/', 'response_type': 'code', 'client_id': Config.config['learn_rest_key'], 'scope': '*', 'state': str(uuid.uuid4()) } encodedParams = urllib.parse.urlencode(params) get_authcode_url = learn_url + '/learn/api/public/v1/oauth2/authorizationcode?' + encodedParams print("authcode_URL: " + get_authcode_url) return (redirect(get_authcode_url))
def login(request): cache = caches['default'] cookies_allowed = str(request.GET.get('cookies_allowed', 0)) if cookies_allowed == '1': login_unique_id = str(request.GET.get('login_unique_id', '')) if not login_unique_id: raise Exception('Missing "login_unique_id" param') login_data = cache.get(login_unique_id) if not login_data: raise Exception("Can't restore login data from cache") tool_conf = ToolConfJsonFile(get_lti_config_path()) request_like_obj = DjangoFakeRequest( login_data['GET'], login_data['POST'], login_data['COOKIES'], request.session, request.is_secure()) oidc_login = DjangoOIDCLogin(request_like_obj, tool_conf) target_link_uri = get_launch_url(request_like_obj) return oidc_login.redirect(target_link_uri) else: login_unique_id = str(uuid.uuid4()) cache.set(login_unique_id, { 'GET': {k: v for k, v in request.GET.items()}, 'POST': {k: v for k, v in request.POST.items()}, 'COOKIES': {k: v for k, v in request.COOKIES.items()} }, 3600) return render(request, 'check_cookie.html', { 'login_unique_id': login_unique_id, 'same_site': getattr(settings, 'SESSION_COOKIE_SAMESITE'), 'page_title': PAGE_TITLE })
def launch(request): cache = caches['default'] launch_unique_id = str(request.GET.get('launch_id', '')) # reload page in case if session cookie is unavailable (chrome samesite issue): # https://chromestatus.com/feature/5088147346030592 # to share GET/POST data between requests we save them into cache if not request.session.session_key and not launch_unique_id: launch_unique_id = str(uuid.uuid4()) cache.set( launch_unique_id, { 'GET': {k: v for k, v in request.GET.items()}, 'POST': {k: v for k, v in request.POST.items()} }, 3600) current_url = request.build_absolute_uri() if '?' in current_url: current_url += '&' else: current_url += '?' current_url = current_url + 'launch_id=' + launch_unique_id return HttpResponse( '<script type="text/javascript">window.location="%s";</script>' % current_url) request_like_obj = None if request.method == "GET": launch_data = cache.get(launch_unique_id) if not launch_data: raise Exception("Can't restore launch data from cache") request_like_obj = DjangoFakeRequest(launch_data['GET'], launch_data['POST'], request.COOKIES, request.session, request.is_secure()) tool_conf = ToolConfJsonFile(get_lti_config_path()) message_launch = ExtendedDjangoMessageLaunch( request_like_obj if request_like_obj else request, tool_conf) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) return render( request, 'game.html', { 'page_title': PAGE_TITLE, 'is_deep_link_launch': message_launch.is_deep_link_launch(), 'launch_data': message_launch.get_launch_data(), 'launch_id': message_launch.get_launch_id(), 'curr_user_name': message_launch_data.get('name', ''), 'curr_diff': message_launch_data.get( 'https://purl.imsglobal.org/spec/lti/claim/custom', {}).get( 'difficulty', 'normal') })
def __init__(self): for setting in dir(app_config): setattr(self, setting, getattr(app_config, setting)) path = Path(__file__).absolute().parent setattr(self, "LTI_TOOL_CONFIG", ToolConfJsonFile(path / "tool_config.json"))
def score(launch_id, earned_score, time_spent): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() launch_data_storage = get_launch_data_storage() message_launch = ExtendedFlaskMessageLaunch.from_cache( launch_id, flask_request, tool_conf, launch_data_storage=launch_data_storage) resource_link_id = message_launch.get_launch_data() \ .get('https://purl.imsglobal.org/spec/lti/claim/resource_link', {}).get('id') if not message_launch.has_ags(): raise Forbidden("Don't have grades!") sub = message_launch.get_launch_data().get('sub') timestamp = datetime.datetime.utcnow().isoformat() + 'Z' earned_score = int(earned_score) time_spent = int(time_spent) grades = message_launch.get_ags() sc = Grade() sc.set_score_given(earned_score) \ .set_score_maximum(100) \ .set_timestamp(timestamp) \ .set_activity_progress('Completed') \ .set_grading_progress('FullyGraded') \ .set_user_id(sub) sc_line_item = LineItem() sc_line_item.set_tag('score') \ .set_score_maximum(100) \ .set_label('Score') if resource_link_id: sc_line_item.set_resource_id(resource_link_id) grades.put_grade(sc, sc_line_item) tm = Grade() tm.set_score_given(time_spent) \ .set_score_maximum(999) \ .set_timestamp(timestamp) \ .set_activity_progress('Completed') \ .set_grading_progress('FullyGraded') \ .set_user_id(sub) tm_line_item = LineItem() tm_line_item.set_tag('time') \ .set_score_maximum(999) \ .set_label('Time Taken') if resource_link_id: tm_line_item.set_resource_id(resource_link_id) result = grades.put_grade(tm, tm_line_item) return jsonify({'success': True, 'result': result.get('body')})
def launch(): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() launch_data_storage = get_launch_data_storage() message_launch = ExtendedFlaskMessageLaunch( flask_request, tool_conf, launch_data_storage=launch_data_storage) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) tpl_kwargs = { 'page_title': PAGE_TITLE, 'is_deep_link_launch': message_launch.is_deep_link_launch(), 'launch_data': message_launch.get_launch_data(), 'launch_id': message_launch.get_launch_id(), 'curr_user_name': message_launch_data.get('name', '') } """ We could do the launch to the external page here. The following which does the 3LO with REST APIs back to the Learn system is not necessary. It's an artifact of project this one was leveraged from. We left it here for the most part to demonstrate how one can pass data through the 3LO process using the state parameter. The state is an opaque value that doesn't get modified by the developer portal or by Learn. We take the external URL that will be launched to and include it as a portion of the state to be pulled out on the other side of 3LO. It's the only way across. Attempts to pass the data by adding an additional parameter to the request for a authroization code will fail because those will be dropped. I.E setting your redirect_uri to .../authcode/?launch_url=URL does not work. https://stackabuse.com/encoding-and-decoding-base64-strings-in-python/ """ learn_url = message_launch_data[ 'https://purl.imsglobal.org/spec/lti/claim/tool_platform'][ 'url'].rstrip('/') # MUST include a custom parameter like 'external_url=https://www.foodies.com' in the custom params external_url = message_launch_data[ 'https://purl.imsglobal.org/spec/lti/claim/custom'][ 'external_url'].rstrip('/') state = str(uuid.uuid4()) + f'&launch_url={external_url}' message_bytes = state.encode('ascii') base64_bytes = base64.b64encode(message_bytes) base64_message = base64_bytes.decode('ascii') params = { 'redirect_uri': Config.config['app_url'] + '/authcode/', 'response_type': 'code', 'client_id': Config.config['learn_rest_key'], 'scope': '*', 'state': base64_message } encodedParams = urllib.parse.urlencode(params) get_authcode_url = learn_url + '/learn/api/public/v1/oauth2/authorizationcode?' + encodedParams print("authcode_URL: " + get_authcode_url, flush=True) return (redirect(get_authcode_url))
def scoreboard(launch_id): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() launch_data_storage = get_launch_data_storage() message_launch = ExtendedFlaskMessageLaunch.from_cache( launch_id, flask_request, tool_conf, launch_data_storage=launch_data_storage) resource_link_id = message_launch.get_launch_data() \ .get('https://purl.imsglobal.org/spec/lti/claim/resource_link', {}).get('id') if not message_launch.has_nrps(): raise Forbidden("Don't have names and roles!") if not message_launch.has_ags(): raise Forbidden("Don't have grades!") ags = message_launch.get_ags() score_line_item = LineItem() score_line_item.set_tag('score') \ .set_score_maximum(100) \ .set_label('Score') if resource_link_id: score_line_item.set_resource_id(resource_link_id) scores = ags.get_grades(score_line_item) time_line_item = LineItem() time_line_item.set_tag('time') \ .set_score_maximum(999) \ .set_label('Time Taken') if resource_link_id: time_line_item.set_resource_id(resource_link_id) times = ags.get_grades(time_line_item) members = message_launch.get_nrps().get_members() scoreboard_result = [] for sc in scores: result = {'score': sc['resultScore']} for tm in times: if tm['userId'] == sc['userId']: result['time'] = tm['resultScore'] break for member in members: if member['user_id'] == sc['userId']: result['name'] = member.get('name', 'Unknown') break scoreboard_result.append(result) return jsonify(scoreboard_result)
def launch(): launch_unique_id = str(request.args.get('launch_id', '')) # reload page in case if session cookie is unavailable (chrome samesite issue): # https://chromestatus.com/feature/5088147346030592 # to share GET/POST data between requests we save them into cache session_key = request.cookies.get(app.config['SESSION_COOKIE_NAME'], None) if not session_key and not launch_unique_id: launch_unique_id = str(uuid.uuid4()) cache.set(launch_unique_id, { 'GET': request.args.to_dict(), 'POST': request.form.to_dict() }, 3600) current_url = request.base_url if '?' in current_url: current_url += '&' else: current_url += '?' current_url = current_url + 'launch_id=' + launch_unique_id return '<script type="text/javascript">window.location="%s";</script>' % current_url launch_request = FlaskRequest() if request.method == "GET": launch_data = cache.get(launch_unique_id) if not launch_data: raise Exception("Can't restore launch data from cache") request_params_dict = {} request_params_dict.update(launch_data['GET']) request_params_dict.update(launch_data['POST']) launch_request = FlaskRequest(request_data=request_params_dict) tool_conf = ToolConfJsonFile(get_lti_config_path()) message_launch = ExtendedFlaskMessageLaunch(launch_request, tool_conf) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) tpl_kwargs = { 'page_title': PAGE_TITLE, 'is_deep_link_launch': message_launch.is_deep_link_launch(), 'launch_data': message_launch.get_launch_data(), 'launch_id': message_launch.get_launch_id(), 'curr_user_name': message_launch_data.get('name', ''), 'curr_diff': message_launch_data.get( 'https://purl.imsglobal.org/spec/lti/claim/custom', {}).get('difficulty', 'normal') } return render_template('game.html', **tpl_kwargs)
def launch(request): tool_conf = ToolConfJsonFile(get_lti_config_path()) message_launch = ExtendedDjangoMessageLaunch(request, tool_conf) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) return render(request, 'game.html', { 'page_title': PAGE_TITLE, 'is_deep_link_launch': message_launch.is_deep_link_launch(), 'launch_data': message_launch.get_launch_data(), 'launch_id': message_launch.get_launch_id(), 'curr_user_name': message_launch_data.get('name', ''), 'curr_diff': message_launch_data.get('https://purl.imsglobal.org/spec/lti/claim/custom', {}) .get('difficulty', 'normal')})
def login(): tool_conf = ToolConfJsonFile(get_lti_config_path()) launch_data_storage = get_launch_data_storage() flask_request = FlaskRequest() target_link_uri = flask_request.get_param('target_link_uri') if not target_link_uri: raise Exception('Missing "target_link_uri" param') oidc_login = FlaskOIDCLogin(flask_request, tool_conf, launch_data_storage=launch_data_storage) return oidc_login\ .enable_check_cookies()\ .redirect(target_link_uri)
def launch(): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() launch_data_storage = get_launch_data_storage() message_launch = ExtendedFlaskMessageLaunch( flask_request, tool_conf, launch_data_storage=launch_data_storage) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) jsonData = dict(message_launch_data) app.logger.info(jsonData) #return redirect('/predict') return render_template( 'camera.html', jsonData=jsonData[ 'https://purl.imsglobal.org/spec/lti/claim/tool_platform']['name'])
def configure(request, launch_id, difficulty): tool_conf = ToolConfJsonFile(get_lti_config_path()) message_launch = ExtendedDjangoMessageLaunch.from_cache(launch_id, request, tool_conf) if not message_launch.is_deep_link_launch(): return HttpResponseForbidden('Must be a deep link!') launch_url = request.build_absolute_uri(reverse('game-launch')) resource = DeepLinkResource() resource.set_url(launch_url)\ .set_custom_params({'difficulty': difficulty})\ .set_title('Breakout ' + difficulty + ' mode!') html = message_launch.get_deep_link().output_response_form([resource]) return HttpResponse(html)
def score(request, launch_id, earned_score, time_spent): tool_conf = ToolConfJsonFile(get_lti_config_path()) message_launch = ExtendedDjangoMessageLaunch.from_cache( launch_id, request, tool_conf) if not message_launch.has_ags(): return HttpResponseForbidden("Don't have grades!") sub = message_launch.get_launch_data().get('sub') timestamp = datetime.datetime.utcnow().isoformat() earned_score = int(earned_score) time_spent = int(time_spent) grades = message_launch.get_ags() sc = Grade() sc.set_score_given(earned_score)\ .set_score_maximum(100)\ .set_timestamp(timestamp)\ .set_activity_progress('Completed')\ .set_grading_progress('FullyGraded')\ .set_user_id(sub) sc_line_item = LineItem() sc_line_item.set_tag('score')\ .set_score_maximum(100)\ .set_label('Score') grades.put_grade(sc, sc_line_item) tm = Grade() tm.set_score_given(time_spent)\ .set_score_maximum(999)\ .set_timestamp(timestamp)\ .set_activity_progress('Completed')\ .set_grading_progress('FullyGraded')\ .set_user_id(sub) tm_line_item = LineItem() tm_line_item.set_tag('time')\ .set_score_maximum(999)\ .set_label('Time Taken') result = grades.put_grade(tm, tm_line_item) return JsonResponse({'success': True, 'result': result.get('body')})
def launch(): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() launch_data_storage = get_launch_data_storage() message_launch = ExtendedFlaskMessageLaunch( flask_request, tool_conf, launch_data_storage=launch_data_storage) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) tpl_kwargs = { 'page_title': PAGE_TITLE, 'is_deep_link_launch': message_launch.is_deep_link_launch(), 'launch_data': message_launch.get_launch_data(), 'launch_id': message_launch.get_launch_id(), 'curr_user_name': message_launch_data.get('name', '') } learn_url = message_launch_data[ 'https://purl.imsglobal.org/spec/lti/claim/tool_platform'][ 'url'].rstrip('/') # Rererence: https://docs.blackboard.com/blog/2021/05/10/use-one-time-session-tokens-instead-of-cookies-for-UEF-authentication.html # Get the value of the one time session token from the LTI claim one_time_session_token = message_launch_data[ 'https://blackboard.com/lti/claim/one_time_session_token'] # If there is no comma in the value, we've hit the bug. Add it and the user's UUID if "," not in one_time_session_token: one_time_session_token += "," + message_launch_data['sub'] params = { 'redirect_uri': Config.config['app_url'] + '/authcode/', 'response_type': 'code', 'client_id': Config.config['learn_rest_key'], 'scope': '*', 'state': str(uuid.uuid4()), 'one_time_session_token': one_time_session_token } encodedParams = urllib.parse.urlencode(params) get_authcode_url = learn_url + '/learn/api/public/v1/oauth2/authorizationcode?' + encodedParams print("authcode_URL: " + get_authcode_url, flush=True) return (redirect(get_authcode_url))
def configure(launch_id, difficulty): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() message_launch = ExtendedFlaskMessageLaunch.from_cache( launch_id, flask_request, tool_conf) if not message_launch.is_deep_link_launch(): raise Forbidden('Must be a deep link!') launch_url = url_for('launch', _external=True) resource = DeepLinkResource() resource.set_url(launch_url) \ .set_custom_params({'difficulty': difficulty}) \ .set_title('Breakout ' + difficulty + ' mode!') html = message_launch.get_deep_link().output_response_form([resource]) return html
def deepLink(): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() launch_data_storage = get_launch_data_storage() message_launch = ExtendedFlaskMessageLaunch( flask_request, tool_conf, launch_data_storage=launch_data_storage) message_launch_data = message_launch.get_launch_data() resource = DeepLinkResource() resource.set_url('https://inviguluscanvas.online/launch/') \ .set_custom_params({'text': 'Invigulus'}) \ .set_title('LTI Launch Invigulus') html = message_launch.get_deep_link().output_response_form([resource]) app.logger.info(html) return html pprint.pprint(message_launch_data)
def scoreboard(request, launch_id): tool_conf = ToolConfJsonFile(get_lti_config_path()) message_launch = ExtendedDjangoMessageLaunch.from_cache( launch_id, request, tool_conf) if not message_launch.has_nrps(): return HttpResponseForbidden("Don't have names and roles!") if not message_launch.has_ags(): return HttpResponseForbidden("Don't have grades!") ags = message_launch.get_ags() score_line_item = LineItem() score_line_item.set_tag('score') \ .set_score_maximum(100) \ .set_label('Score') scores = ags.get_grades(score_line_item) time_line_item = LineItem() time_line_item.set_tag('time') \ .set_score_maximum(999) \ .set_label('Time Taken') times = ags.get_grades(time_line_item) members = message_launch.get_nrps().get_members() scoreboard_result = [] for sc in scores: result = {'score': sc['resultScore']} for tm in times: if tm['userId'] == sc['userId']: result['time'] = tm['resultScore'] break for member in members: if member['user_id'] == sc['userId']: result['name'] = member.get('name', 'Unknown') break scoreboard_result.append(result) return JsonResponse(scoreboard_result, safe=False)
def login(request): cache = caches['default'] cookies_allowed = str(request.GET.get('cookies_allowed', '')) # check cookies and ask to open page in the new window in case if cookies are not allowed # https://chromestatus.com/feature/5088147346030592 # to share GET/POST data between requests we save them into cache if cookies_allowed: login_unique_id = str(request.GET.get('login_unique_id', '')) if not login_unique_id: raise Exception('Missing "login_unique_id" param') login_data = cache.get(login_unique_id) if not login_data: raise Exception("Can't restore login data from cache") tool_conf = ToolConfJsonFile(get_lti_config_path()) request_like_obj = DjangoFakeRequest(login_data['GET'], login_data['POST'], request.COOKIES, request.session, request.is_secure()) oidc_login = DjangoOIDCLogin(request_like_obj, tool_conf) target_link_uri = get_launch_url(request_like_obj) return oidc_login.redirect(target_link_uri) else: login_unique_id = str(uuid.uuid4()) cache.set( login_unique_id, { 'GET': {k: v for k, v in request.GET.items()}, 'POST': {k: v for k, v in request.POST.items()} }, 3600) return render( request, 'check_cookie.html', { 'login_unique_id': login_unique_id, 'same_site': getattr(settings, 'SESSION_COOKIE_SAMESITE'), 'site_protocol': 'https' if request.is_secure() else 'http', 'page_title': PAGE_TITLE })
def launch(): tool_conf = ToolConfJsonFile(get_lti_config_path()) flask_request = FlaskRequest() launch_data_storage = get_launch_data_storage() message_launch = ExtendedFlaskMessageLaunch( flask_request, tool_conf, launch_data_storage=launch_data_storage) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) difficulty = message_launch_data.get('https://purl.imsglobal.org/spec/lti/claim/custom', {}) \ .get('difficulty', None) if not difficulty: difficulty = request.args.get('difficulty', 'normal') tpl_kwargs = { 'page_title': PAGE_TITLE, 'is_deep_link_launch': message_launch.is_deep_link_launch(), 'launch_data': message_launch.get_launch_data(), 'launch_id': message_launch.get_launch_id(), 'curr_user_name': message_launch_data.get('name', ''), 'curr_diff': difficulty } return render_template('game.html', **tpl_kwargs)
def login(): cookies_allowed = str(request.args.get('cookies_allowed', '')) # check cookies and ask to open page in the new window in case if cookies are not allowed # https://chromestatus.com/feature/5088147346030592 # to share GET/POST data between requests we save them into cache if cookies_allowed: login_unique_id = str(request.args.get('login_unique_id', '')) if not login_unique_id: raise Exception('Missing "login_unique_id" param') login_data = cache.get(login_unique_id) if not login_data: raise Exception("Can't restore login data from cache") tool_conf = ToolConfJsonFile(get_lti_config_path()) request_params_dict = {} request_params_dict.update(login_data['GET']) request_params_dict.update(login_data['POST']) oidc_request = FlaskRequest(request_data=request_params_dict) oidc_login = FlaskOIDCLogin(oidc_request, tool_conf) target_link_uri = request_params_dict.get('target_link_uri') return oidc_login.redirect(target_link_uri) else: login_unique_id = str(uuid.uuid4()) cache.set(login_unique_id, { 'GET': request.args.to_dict(), 'POST': request.form.to_dict() }, 3600) tpl_kwargs = { 'login_unique_id': login_unique_id, 'same_site': app.config['SESSION_COOKIE_SAMESITE'], 'site_protocol': 'https' if request.is_secure else 'http', 'page_title': PAGE_TITLE } return render_template('check_cookie.html', **tpl_kwargs)
def get_jwks(): tool_conf = ToolConfJsonFile(get_lti_config_path()) return jsonify({'keys': tool_conf.get_jwks()})
def get_tool_conf(): tool_conf = ToolConfJsonFile(get_lti_config_path()) return tool_conf
def login(request): tool_conf = ToolConfJsonFile(get_lti_config_path()) oidc_login = DjangoOIDCLogin(request, tool_conf) target_link_uri = get_launch_url(request) return oidc_login.redirect(target_link_uri)
def launch(): launch_unique_id = str(request.args.get('launch_id', '')) print("launch_unique_id: " + launch_unique_id) # reload page in case if session cookie is unavailable (chrome samesite issue): # https://chromestatus.com/feature/5088147346030592 # to share GET/POST data between requests we save them into cache session_key = request.cookies.get(app.config['SESSION_COOKIE_NAME'], None) if not session_key: print("session_key: None") else: print("session_key: " + session_key) if not session_key and not launch_unique_id: launch_unique_id = str(uuid.uuid4()) cache.set(launch_unique_id, { 'GET': request.args.to_dict(), 'POST': request.form.to_dict() }, 3600) current_url = request.base_url parsed_url = urlparse(current_url) parsed_url = parsed_url._replace(scheme='https') current_url = parsed_url.geturl() if '?' in current_url: current_url += '&' else: current_url += '?' current_url = current_url + 'launch_id=' + launch_unique_id return '<script type="text/javascript">window.location="%s";</script>' % current_url launch_request = FlaskRequest() if request.method == "GET": launch_data = cache.get(launch_unique_id) print("launch_data: " + str(launch_data)) if not launch_data: raise Exception("Can't restore launch data from cache") request_params_dict = {} request_params_dict.update(launch_data['GET']) request_params_dict.update(launch_data['POST']) print("request_params_dict: " + str(request_params_dict)) launch_request = FlaskRequest(request_data=request_params_dict) print("launch_request: " + str(launch_request)) tool_conf = ToolConfJsonFile(get_lti_config_path()) print("tool_conf: " + str(tool_conf)) message_launch = ExtendedFlaskMessageLaunch(launch_request, tool_conf) print("message_launch: " + str(message_launch)) message_launch_data = message_launch.get_launch_data() pprint.pprint(message_launch_data) learn_url = message_launch_data['https://purl.imsglobal.org/spec/lti/claim/tool_platform']['url'].rstrip('/') tpl_kwargs = { 'page_title': PAGE_TITLE, 'is_deep_link_launch': message_launch.is_deep_link_launch(), 'launch_data': message_launch.get_launch_data(), 'launch_id': message_launch.get_launch_id(), 'family_name': message_launch_data.get('family_name', ''), 'given_name': message_launch_data.get('given_name', ''), 'user_email': message_launch_data.get('email', ''), 'user_uuid': message_launch_data.get('sub', ''), 'learn_url': learn_url } print("tpl_kwargs: " + str(tpl_kwargs)) params = { 'redirect_uri' : 'https://ask-an-expert.herokuapp.com/authcode/', 'response_type' : 'code', 'client_id' : Config.config['learn_rest_key'], 'scope' : '*', 'state' : str(uuid.uuid4()) } encodedParams = urllib.parse.urlencode(params) get_authcode_url = learn_url + '/learn/api/public/v1/oauth2/authorizationcode?' + encodedParams return(redirect(get_authcode_url))