Skip to content
Snippets Groups Projects
Commit 8d815e5e authored by Bjarke Madsen's avatar Bjarke Madsen
Browse files

Add login management with OAuth2 and flask-login

parent 5327cca1
No related branches found
No related tags found
1 merge request!44Feature/comp 208 google o auth poc
......@@ -12,9 +12,14 @@ from flask import Flask
from flask_cors import CORS # for debugging
# the currently available stubs for flask_migrate are old (they depend on sqlalchemy 1.4 types)
from flask_migrate import Migrate, upgrade # type: ignore
from flask_login import LoginManager # type: ignore
from compendium_v2 import config, environment
from compendium_v2.db import db
from compendium_v2.auth import setup_oauth
from compendium_v2.auth.session_management import setup_login_manager
sentry_dsn = os.getenv('SENTRY_DSN')
if sentry_dsn:
sentry_sdk.init(
......@@ -24,6 +29,8 @@ if sentry_dsn:
environment.setup_logging()
logger = logging.getLogger(__name__)
def _create_app(app_config) -> Flask:
# used by sphinx to create documentation without config and db migrations
......@@ -31,10 +38,19 @@ def _create_app(app_config) -> Flask:
CORS(app)
app.config['CONFIG_PARAMS'] = app_config
app.config['SECRET_KEY'] = app_config['SECRET_KEY']
if 'oidc' not in app_config:
app.config['LOGIN_DISABLED'] = True
logger.info('No OIDC configuration found, authentication disabled')
else:
logger.info('OIDC configuration found, authentication will be enabled')
from compendium_v2.routes import default
app.register_blueprint(default.routes, url_prefix='/')
from compendium_v2.routes import authentication
app.register_blueprint(authentication.routes, url_prefix='/')
from compendium_v2.routes import api
app.register_blueprint(api.routes, url_prefix='/api')
......@@ -72,8 +88,11 @@ def create_app() -> Flask:
Migrate(app, db, directory=os.path.join(os.path.dirname(__file__), 'migrations'))
logging.info('Flask app initialized')
environment.setup_logging()
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'authentication.login'
setup_login_manager(login_manager)
setup_oauth(app, app_config.get('oidc'))
# run migrations on startup
with app.app_context():
......
from flask import Flask
from authlib.integrations.flask_client import OAuth, FlaskOAuth2App # type: ignore
oauth = None
def setup_oauth(app: Flask, oidc_config: dict):
global oauth
if oauth is not None:
return
oauth = OAuth(app)
if oidc_config is None:
return
oauth.register(
name='provider',
client_id=oidc_config['client_id'],
client_secret=oidc_config['client_secret'],
server_metadata_url=oidc_config['server_metadata_url'],
client_kwargs={
'scope': ' '.join([
'email',
'profile'
])},
)
def get_client() -> FlaskOAuth2App:
return oauth.create_client('provider') # type: ignore
from flask_login import LoginManager, UserMixin # type: ignore
# TODO: implement user model as SQLAlchemy model
class User(UserMixin):
pass
def fetch_user(email: str):
"""
Function used to retrieve the internal user model for the user attempting login.
:param profile: The email of the user attempting login.
:return: User object if the user exists, None otherwise.
"""
# TODO: fetch user from database instead of just creating a user object
user = User()
user.id = email
return user
def setup_login_manager(login_manager: LoginManager):
login_manager.user_loader(fetch_user)
from flask_login import login_required, login_user, logout_user # type: ignore
from flask import Blueprint, url_for, redirect
from compendium_v2.auth import get_client
from compendium_v2.auth.session_management import fetch_user
routes = Blueprint('authentication', __name__)
@routes.route('/login')
def login():
client = get_client()
# _external uses headers to determine the full URL when behind a reverse proxy
redirect_uri = url_for('authentication.authorize', _external=True)
return client.authorize_redirect(redirect_uri)
@routes.route('/authorize')
def authorize():
client = get_client()
token = client.authorize_access_token()
profile = client.userinfo(token=token)
if 'email' not in profile or not profile['email']:
return '<h3>Authentication failed: Invalid user response from provider</h3>', 400
user = fetch_user(profile['email'])
login_user(user)
# redirect to /survey since we only require login for this part of the app
return redirect(url_for('compendium-v2-default.survey_index'))
@routes.route("/logout")
@login_required
def logout():
# The user will be logged out of the application, but not the IDP.
# If they visit again before their oauth token expires, they are immediately logged in.
logout_user()
return redirect('/')
import pkg_resources
from flask import Blueprint, jsonify, render_template, Response
from flask_login import login_required # type: ignore
from compendium_v2.routes import common
routes = Blueprint('compendium-v2-default', __name__)
......@@ -45,7 +45,9 @@ def index(path):
@routes.route('/survey', defaults={'path': ''}, methods=['GET'])
@routes.route('/survey/', defaults={'path': ''}, methods=['GET'])
@routes.route('/survey/<path:path>', methods=['GET'])
@login_required
def survey_index(path):
is_api = path.startswith('api')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment