使用flask.create_app时如何在flask.blueprint中使用oidc(OpenIDConnect对象)?

时间:2019-06-03 21:53:40

标签: flask okta oidc

我想使用@oidc.require_login将登录请求重定向到okta。我收到无法解决的AttributeError: '_AppCtxGlobals' object has no attribute 'oidc_id_token'错误

1)我基于Flaskr教程构建了一个应用,该应用程序演示了烧瓶工厂方法create_app的使用。

2)我创建了一个okta.py类,如下所示:

from oauth2client.client import OAuth2Credentials
from flask_oidc import OpenIDConnect
from okta import UsersClient

import click
from flask import current_app, g, session
from flask.cli import with_appcontext

oidc = OpenIDConnect()


def get_oidc():
    """
    Connect to okta
    """
    if 'oidc' not in g:
        print('okta: get_oidc call')
        g.oidc = OpenIDConnect(current_app)
        g.okta_client = UsersClient("<okta-server-url>", "<secret>")
        # fixing global oidc problem for decorator in rooms
        oidc = g.oidc
    return g.oidc


def close_oidc(app):
    """ Release okta connection
    """
    oidc = g.pop('oidc',None)
    if oidc is not None:
        oidc.logout()

    # with app.app_context():
    #     session.clear()


def init_okta():
    """Connect to existing table"""
    oidc = get_oidc()
    """ Can do additional initialization if required """


@click.command('init-okta')
@with_appcontext
def init_okta_command():
    """Connect to existing oidc"""
    init_okta()
    click.echo (get_oidc())
    click.echo('Initialized Okta.')
    print('Initialized Okta.')


def init_app(app):
    """Register okta functions with the Flask app. This is called by
    the application factory.
    """
    app.teardown_appcontext(close_oidc)
    app.cli.add_command(init_okta_command)

3)我的目标是使用okta登录浏览房间列表

from flask import (
    Blueprint, flash, g, redirect, render_template, 
    request, url_for, current_app, session, jsonify
)
from werkzeug.exceptions import abort

...
from hotel.okta import oidc, get_oidc, init_app

bp = Blueprint('rooms', __name__)
...
@bp.route('/login', methods=['GET', 'POST'])
@oidc.require_login
def login():
    """
    Force the user to login, then redirect them to the get_books.
    Currently this code DOES NOT work
    Problem:
        * oidc global object is not available to pass request to okta
    Resolution:
        * redirecting to rooms.calendar
    """
    # info = oidc.user_getinfo(['preferred_username', 'email', 'sub'])
    # id_token = OAuth2Credentials.from_json(oidc.credentials_store[info.get('sub')]).token_response['id_token']

    return redirect(url_for("rooms.calendar"))

4)我的__init__.py看起来像这样

import os

from flask import Flask
from flask_oidc import OpenIDConnect
from okta import UsersClient

# This is a factory method for productive deployment
# Use app specific configuration
# For any app local files, use /instnce Folder
def create_app(test_config=None):
    """Create and configure an instance of the Flask application."""
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        # a default secret that should be overridden by instance config
        SECRET_KEY='dev',
        # store the database in the instance folder
        DATABASE=os.path.join(app.instance_path, 'hotel.sqlite'),
        OIDC_CLIENT_SECRETS=os.path.join(app.instance_path, 'client_secrets.json'),
        OIDC_COOKIE_SECURE=False,
        OIDC_CALLBACK_ROUTE= '/oidc/callback',
        OIDC_SCOPES=["openid", "email", "profile"],
        OIDC_ID_TOKEN_COOKIE_NAME = 'oidc_token',
    )

    if test_config is None:
        # load the instance config, if it exists, when not testing
        app.config.from_pyfile('config.py', silent=True)
    else:
        # load the test config if passed in
        app.config.update(test_config)

    # ensure the instance folder exists
    try:
        os.makedirs(app.instance_path)
    except OSError:
        pass

    # # register the database commands
    from hotel import db
    db.init_app(app)

    # apply the blueprints to the app
    from hotel import rooms
    app.register_blueprint(rooms.bp)

    # for Okta
    # Ref: https://www.fullstackpython.com/blog/add-user-authentication-flask-apps-okta.html

    from hotel import okta
    with app.app_context():
        okta.init_app(app)



    @app.route('/hello') # For testing factory method
    def hello():
        return 'Hello, World!'


    # make url_for('index') == url_for('blog.index')
    # in another app, you might define a separate main index here with
    # app.route, while giving the blog blueprint a url_prefix, but for
    # the tutorial the blog will be the main index
    app.add_url_rule('/', endpoint='index')

    return app

5)这是rooms.before_request

的代码段
@bp.before_request
def before_request():
    print ('rooms.before_request call reached')
    with current_app.app_context():
        print ('rooms.before_request in app_context',g)
        oidc = g.pop('oidc',None)
        okta_client = g.pop('okta_client',None)
        if oidc is not None and okta_client is not None:
            print ('rooms.before_request g.oidc and g.okta_client available')
            if oidc.user_loggedin:
                # OpenID Token as 
                g.user = okta_client.get_user(oidc.user_getfield("sub"))
                g.oidc_id_token = OAuth2Credentials.from_json(g.oidc.credentials_store[info.get('sub')]).token_response['id_token']
            else:
                g.user = None
        else:
            print('rooms.beforerequest No user logged in')
            g.user = None

我的分析:

  • okta.init_app(app)应该使用client_secrets.json文件夹中的/instance连接到okta。
  • 为了使@oidc.require_login工作,我在oidc中暴露了okta.get_oidc并将其导入到Rooms.py代码中
  • 但是,这不起作用:(!堆栈跟踪为:
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2328, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2314, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1760, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/_compat.py", line 36, in reraise
    raise value
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2311, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1834, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1737, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/_compat.py", line 36, in reraise
    raise value
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1832, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1818, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask_oidc/__init__.py", line 485, in decorated
    if g.oidc_id_token is None:
  File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/werkzeug/local.py", line 348, in __getattr__
    return getattr(self._get_current_object(), name)
AttributeError: '_AppCtxGlobals' object has no attribute 'oidc_id_token'

我的问题:

  1. 如何在烧瓶应用程序工厂中调用@oidc.require_login 方法?
  2. 初始化okta连接的方法正确吗?
  3. 是否有无需装饰方法即可手动调用外部授权服务器的方法?怎么样?
  4. 设置烧瓶堆栈变量是前进的更好方法吗?如果可以,怎么办?
  5. 尝试在g中设置before_request似乎无效。 Flask的App Factory方法中还有哪些其他选项可以使用?

任何见解将不胜感激!

谢谢! 尤瓦

0 个答案:

没有答案