149 lines
4.8 KiB
Python
149 lines
4.8 KiB
Python
# 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 base64
|
|
import json
|
|
|
|
import flask
|
|
from datetime import datetime, timedelta
|
|
import jwt
|
|
from flask import request, jsonify, current_app, url_for, Response, session, redirect, make_response
|
|
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_refresh_token_required, get_jwt_identity
|
|
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
|
|
|
|
|
|
@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 = {
|
|
'access_token': create_access_token(identity=user.email, fresh=True),
|
|
'refresh_token': create_refresh_token(identity=user.email)
|
|
}
|
|
return jsonify(token), 200
|
|
|
|
|
|
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'])
|
|
@auth_api_bp.route('/oidc/<redirect_url>', methods=['GET'])
|
|
@oidc_auth.oidc_auth()
|
|
def oidc(redirect_url=None):
|
|
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 = json.dumps({
|
|
'access_token': create_access_token(identity=user.email, fresh=True),
|
|
'refresh_token': create_refresh_token(identity=user.email)
|
|
})
|
|
if redirect_url is None:
|
|
redirect_url = request.headers.get("Referer")
|
|
if redirect_url is None:
|
|
redirect_url = request.args.get('redirect_url')
|
|
if redirect_url is None:
|
|
redirect_url = "/"
|
|
app.logger.info("Token: {}".format(token))
|
|
response = make_response(redirect(redirect_url))
|
|
response.set_cookie('tokens', base64.b64encode(token.encode('utf-8')))
|
|
return response
|
|
|
|
|
|
@app.route('/refresh', methods=['POST'])
|
|
@jwt_refresh_token_required
|
|
def refresh():
|
|
"""Refresh token endpoint. This will generate a new access token from
|
|
the refresh token, but will mark that access token as non-fresh,
|
|
as we do not actually verify a password in this endpoint."""
|
|
current_user = get_jwt_identity()
|
|
new_token = create_access_token(identity=current_user, fresh=False)
|
|
ret = {'access_token': new_token}
|
|
return jsonify(ret), 200
|