def get(self):
        """

        :return:
        """
        tool_conf = settings.LTI_TOOL_CONFIG
        launch_data_storage = FlaskCacheDataStorage(cache)

        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')

        logs.api_logger.info("LTI Login",
                             extra={
                                 "clientip": request.remote_addr,
                                 "path": request.path,
                                 "user": request.remote_user
                             })

        oidc_login = FlaskOIDCLogin(flask_request,
                                    tool_conf,
                                    launch_data_storage=launch_data_storage)
        return oidc_login.enable_check_cookies().redirect(target_link_uri)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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 post(self):
        """
        Post method
        :return:
        """

        flask_request = FlaskRequest()
        launch_data_storage = FlaskCacheDataStorage(cache)
        message_launch = FlaskMessageLaunch(
            flask_request,
            current_app.config['LTI_TOOL_CONFIG'],
            launch_data_storage=launch_data_storage)
        message_launch_data = message_launch.get_launch_data()
        pprint.pprint(message_launch_data)
        token = message_launch.get_launch_id()
        cache.set(token, message_launch_data)

        redirection = redirect('http://localhost:3000/deepLinkContent/' +
                               token)

        return redirection

        redirection = redirect('http://localhost:3000/deepLinkContent/' +
                               token)

        return redirection
Exemplo n.º 5
0
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))
Exemplo n.º 6
0
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')})
Exemplo n.º 7
0
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))
Exemplo n.º 8
0
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)
Exemplo n.º 9
0
 def _get_request(self,
                  login_request,
                  login_response,
                  request_is_secure=False,
                  post_data=None,
                  empty_session=False,
                  empty_cookies=False):
     session = {} if empty_session else login_request.session
     cookies = {} if empty_cookies else self.get_cookies_dict_from_response(
         login_response)
     post_launch_data = post_data if post_data else self.post_launch_data
     return FlaskRequest(request_data=post_launch_data,
                         cookies=cookies,
                         session=session,
                         request_is_secure=request_is_secure)
Exemplo n.º 10
0
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'])
Exemplo n.º 11
0
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))
Exemplo n.º 12
0
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
Exemplo n.º 13
0
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)
Exemplo n.º 14
0
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)
Exemplo n.º 15
0
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)
Exemplo n.º 16
0
    def _make_oidc_login(self,
                         uuid_val=None,
                         tool_conf_cls=None,
                         secure=False):
        tool_conf = get_test_tool_conf(tool_conf_cls)
        if not uuid_val:
            uuid_val = 'test-uuid-1234'

        login_data = {
            'iss':
            'https://canvas.instructure.com',
            'login_hint':
            '86157096483e6b3a50bfedc6bac902c0b20a824f',
            'target_link_uri':
            'http://lti.django.test/launch/',
            'lti_message_hint':
            'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ2ZXJpZmllciI6Ijg0NjMxZjc1Z'
            'GYxNmNiZjNmYTM5YzEwMzk4YTg0M2U1NTAwZTc5MTU2OTBhN2RjYTJhNGMzMTJjYjR'
            'jOWU0YWY5NzE2MWVhYjg4ODhmOWJlNDc2MmViNzUzZDE5ZmI3YWU5N2I2MjAxZWZjM'
            'jRmODY4NWE3NjJmY2U0ZWU4MDk4IiwiY2FudmFzX2RvbWFpbiI6ImNhbnZhcy5kb2N'
            'rZXIiLCJjb250ZXh0X3R5cGUiOiJDb3Vyc2UiLCJjb250ZXh0X2lkIjoxMDAwMDAwM'
            'DAwMDAwMSwiZXhwIjoxNTY1NDQyMzcwfQ.B1Lddgthaa-YBT4-Lkm3OM_noETl3dIz'
            '5E14YWJ8m_Q'
        }

        request = FlaskRequest(request_data=login_data,
                               cookies={},
                               session={},
                               request_is_secure=secure)

        with patch('flask.redirect') as mock_redirect:
            from pylti1p3.contrib.flask import FlaskOIDCLogin
            with patch.object(FlaskOIDCLogin, "_get_uuid",
                              autospec=True) as get_uuid:
                get_uuid.side_effect = lambda x: uuid_val  # pylint: disable=unnecessary-lambda
                oidc_login = FlaskOIDCLogin(
                    request,
                    tool_conf,
                    cookie_service=FlaskCookieService(request),
                    session_service=FlaskSessionService(request))
                mock_redirect.side_effect = lambda x: FakeResponse(x)  # pylint: disable=unnecessary-lambda
                launch_url = 'http://lti.django.test/launch/'
                response = oidc_login.redirect(launch_url)

                # check cookie data
                self.assertTrue('Set-Cookie' in response.headers)
                set_cookie_header = response.headers['Set-Cookie']
                expected_cookie = 'lti1p3-state-' + uuid_val + '=state-' + uuid_val
                self.assertTrue(expected_cookie in set_cookie_header)

                if secure:
                    self.assertTrue('Secure' in set_cookie_header)
                    self.assertTrue('SameSite=None' in set_cookie_header)
                else:
                    self.assertFalse('Secure' in set_cookie_header)
                    self.assertFalse('SameSite' in set_cookie_header)

                # check session data
                self.assertEqual(len(request.session), 1)
                self.assertEqual(request.session['lti1p3-nonce-' + uuid_val],
                                 True)

                # check redirect_url
                redirect_url = response.location
                self.assertTrue(
                    redirect_url.startswith(
                        TOOL_CONFIG[login_data['iss']]['auth_login_url']))
                url_params = redirect_url.split('?')[1].split('&')
                self.assertTrue(('nonce=' + uuid_val) in url_params)
                self.assertTrue(('state=state-' + uuid_val) in url_params)
                self.assertTrue(('state=state-' + uuid_val) in url_params)
                self.assertTrue('prompt=none' in url_params)
                self.assertTrue('response_type=id_token' in url_params)
                self.assertTrue((
                    'client_id=' +
                    TOOL_CONFIG[login_data['iss']]['client_id']) in url_params)
                self.assertTrue(('login_hint=' +
                                 login_data['login_hint']) in url_params)
                self.assertTrue(('lti_message_hint=' +
                                 login_data['lti_message_hint']) in url_params)
                self.assertTrue('scope=openid' in url_params)
                self.assertTrue('response_mode=form_post' in url_params)
                self.assertTrue(('redirect_uri=' +
                                 quote(launch_url, '')) in url_params)

        return tool_conf, request, response
Exemplo n.º 17
0
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))