From 024f063bead32ebd817be3f34582be6b96fe4ac2 Mon Sep 17 00:00:00 2001 From: Tobias Date: Tue, 2 Apr 2019 10:47:53 +0200 Subject: [PATCH] better separation between api and frontend login --- __init__.py | 5 +++++ api/auth_api.py | 45 ++++++++++++++++++++++++++++++++++++++------- auth/__init__.py | 14 +++++++++++--- auth/basic_auth.py | 6 ++++++ auth/config.py | 16 ++++++++++++++-- auth/oidc.py | 6 ++++-- config.py | 2 ++ 7 files changed, 80 insertions(+), 14 deletions(-) diff --git a/__init__.py b/__init__.py index 7826906..0383e90 100644 --- a/__init__.py +++ b/__init__.py @@ -5,12 +5,17 @@ Backend base module from flask import Flask from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth +from flask_login import LoginManager from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config.from_object('backend.config.Config') db = SQLAlchemy(app) + +login_manager = LoginManager() +login_manager.init_app(app) + jwt_auth = HTTPTokenAuth() basic_auth = HTTPBasicAuth() multi_auth = MultiAuth(basic_auth, jwt_auth) diff --git a/api/auth_api.py b/api/auth_api.py index efc5b7d..a3dff3d 100644 --- a/api/auth_api.py +++ b/api/auth_api.py @@ -2,7 +2,10 @@ """ This module provides functions related to authentication through the API. For example: listing of available auth providers or registration of users. + +Login through API does not start a new session, but instead returns JWT. """ +import flask from datetime import datetime, timedelta import jwt from flask import request, jsonify, current_app, url_for @@ -12,9 +15,9 @@ from random import randint from flask_login import logout_user, login_user from werkzeug.routing import BuildError -from backend import db +from backend import db, app from backend.api import auth_api_bp -from backend.auth import AUTH_PROVIDERS +from backend.auth import AUTH_PROVIDERS, oidc_auth from backend.models.user_model import User @@ -62,11 +65,39 @@ def login(): return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401 token = create_jwt(user) - #login_user(user) return jsonify({'token': token.decode('UTF-8')}) -@auth_api_bp.route('/logout', methods=('GET', )) -def logout(): - return jsonify({'message': 'Not yet implemented!', 'authenticated': False}), 401 - #logout_user() + +def create_or_retrieve_user_from_userinfo(userinfo): + try: + email = userinfo["email"] + except KeyError: + return None + user = User.get_by_identifier(email) + + if user is not None: + app.logger.info("user found") + return user + + user = User(email=email, first_name=userinfo.get("given_name", ""), + last_name=userinfo.get("family_name", "")) + + app.logger.info("creating new user") + + db.session.add(user) + db.session.commit() + return user + + +@auth_api_bp.route('/oidc', methods=['GET']) +@oidc_auth.oidc_auth() +def oidc(): + user = create_or_retrieve_user_from_userinfo(flask.session['userinfo']) + if user is None: + return "Could not authenticate: could not find or create user.", 401 + if current_app.config.get("AUTH_RETURN_EXTERNAL_JWT", False): + token = jwt.encode(flask.session['id_token'], current_app.config['SECRET_KEY']) + else: + token = create_jwt(user) + return token diff --git a/auth/__init__.py b/auth/__init__.py index 93fa83f..1fb0e67 100644 --- a/auth/__init__.py +++ b/auth/__init__.py @@ -5,13 +5,16 @@ Base module for auth aspects. Also this module contains mainly code for login through HTML pages served by the backend. If frontend pages are build by frontend code (JS, etc.) authentication should consider using api functions. (For more info, see api.auth_api.py.) + +This code uses login_user and logout user (to start and end sessions) ... API code returns JWTs. """ -from flask import Blueprint +from flask import Blueprint, jsonify +from flask_login import logout_user, LoginManager from werkzeug.routing import BuildError auth_bp = Blueprint('auth', __name__, url_prefix='/auth', template_folder='templates') -from backend.auth.config import AUTH_PROVIDERS, DEFAULT_PROVIDER +from backend.auth.config import AUTH_PROVIDERS, DEFAULT_FRONTEND_PROVIDER from backend.auth.oidc_config import OIDC_PROVIDERS from backend.auth.oidc import oidc_auth @@ -26,7 +29,7 @@ def auth_decorator(): # custom decorator @auth_bp.route('/login', methods=['GET', 'POST']) def login(): try: - prov = AUTH_PROVIDERS[DEFAULT_PROVIDER] + prov = AUTH_PROVIDERS[DEFAULT_FRONTEND_PROVIDER] except KeyError: return "No known default provider specified!" url = prov["url"] @@ -41,3 +44,8 @@ def login(): @auth_bp.route('/login_select', methods=['GET']) def login_select(): return render_template('login_select.html', providers=AUTH_PROVIDERS) + + +@auth_bp.route('/logout', methods=('GET', )) +def logout(): + logout_user() diff --git a/auth/basic_auth.py b/auth/basic_auth.py index e91fa04..9490cca 100644 --- a/auth/basic_auth.py +++ b/auth/basic_auth.py @@ -1,5 +1,7 @@ # Route for handling the login page logic from flask import request, redirect, render_template, url_for +from flask_login import login_user + from backend.auth import auth_bp @@ -10,5 +12,9 @@ def base_login(): if request.form['username'] != 'admin' or request.form['password'] != 'admin': error = 'Invalid Credentials. Please try again.' else: + login_user() return redirect("/") + return render_template('login.html', error=error) + + diff --git a/auth/config.py b/auth/config.py index 471b8b0..e6c099d 100644 --- a/auth/config.py +++ b/auth/config.py @@ -11,7 +11,19 @@ AUTH_PROVIDERS: Dict[str, Dict[str, str]] = { "type": "login_form", "url": "auth.base_login" }, + "KIT OIDC (API)": + { + "type": "api_oidc", + "url": "auth_api_bp.oidc" + }, + "User-Password (API)": + { + "type": "api_login_form", + "url": "auth_api_bp.base_login" + }, } -DEFAULT_PROVIDER: str = "Base Login" -#DEFAULT_PROVIDER: str = "KIT OIDC" +#DEFAULT_PROVIDER: str = "Base Login" +DEFAULT_PROVIDER: str = "KIT OIDC (API)" + +DEFAULT_FRONTEND_PROVIDER: str = "Base Login" diff --git a/auth/oidc.py b/auth/oidc.py index 908a059..f69fd10 100644 --- a/auth/oidc.py +++ b/auth/oidc.py @@ -5,6 +5,7 @@ OIDC login auth module import flask from flask import jsonify +from flask_login import login_user from flask_pyoidc.flask_pyoidc import OIDCAuthentication from flask_pyoidc.user_session import UserSession @@ -50,8 +51,9 @@ def create_or_retrieve_user_from_userinfo(userinfo): @oidc_auth.oidc_auth() def oidc(): user_session = UserSession(flask.session) - create_or_retrieve_user_from_userinfo(user_session.userinfo) - #login_user(user) + app.logger.info(user_session.userinfo) + user = create_or_retrieve_user_from_userinfo(user_session.userinfo) + login_user(user) return jsonify(id_token=user_session.id_token, access_token=flask.session['access_token'], userinfo=user_session.userinfo) diff --git a/config.py b/config.py index 1646ee1..28b41ec 100644 --- a/config.py +++ b/config.py @@ -68,6 +68,8 @@ class Config(): JWT_ALGORITHM = "HS256" JWT_EXP_DELTA_SECONDS = 5 * 60 + AUTH_RETURN_EXTERNAL_JWT = False + INDEX_TEMPLATE = "index.html"