# Copyright (c) 2019. Tobias Kurze """ 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 from functools import wraps from random import randint from flask_login import logout_user, login_user from typing import Iterable from werkzeug.routing import BuildError from backend import db, app from backend.api import auth_api_bp from backend.auth import AUTH_PROVIDERS, oidc_auth from backend.models.user_model import User, Group def create_jwt(user: User, validity_min=30): return jwt.encode({ 'sub': user.email, 'iat': datetime.utcnow(), 'exp': datetime.utcnow() + timedelta(minutes=validity_min)}, current_app.config['SECRET_KEY']) @auth_api_bp.route('/providers', methods=('GET',)) def get_auth_providers(): providers = dict() for p in AUTH_PROVIDERS: provider = dict(AUTH_PROVIDERS[p]) try: provider["url"] = url_for(AUTH_PROVIDERS[p]["url"]) except BuildError: provider["url"] = AUTH_PROVIDERS[p]["url"] providers[p] = provider return jsonify(providers) @auth_api_bp.route('/register', methods=('POST',)) def register(): data = request.get_json() user = User(**data) db.session.add(user) db.session.commit() return jsonify(user.to_dict()), 201 @auth_api_bp.route('/login', methods=('GET', 'POST',)) def login(): print("login") print(request) data = request.get_json() if not data: return jsonify({'message': 'Invalid request data', 'authenticated': False}), 401 print(data) user = User.authenticate(**data) if not user: return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401 token = create_jwt(user) return jsonify({'token': token.decode('UTF-8')}) def check_and_create_groups(groups: Iterable[str]): user_groups = [] for g in groups: group = Group.get_by_name(g) if group is None: group = Group(name=g) db.session.add(group) user_groups.append(group) db.session.commit() return user_groups def create_or_retrieve_user_from_userinfo(userinfo): try: email = userinfo["email"] except KeyError: return None user_groups = check_and_create_groups(groups=userinfo.get("memberOf", [])) user = User.get_by_identifier(email) if user is not None: app.logger.info("user found -> update user") user.first_name = userinfo.get("given_name", "") user.last_name = userinfo.get("family_name", "") for g in user_groups: user.groups.append(g) db.session.commit() return user user = User(email=email, first_name=userinfo.get("given_name", ""), last_name=userinfo.get("family_name", ""), external_user=True, groups=userinfo.get("memberOf", [])) 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']) return jsonify(user.to_dict()) 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