moved everything to a new module called backend
This commit is contained in:
78
backend/auth/__init__.py
Normal file
78
backend/auth/__init__.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2019. Tobias Kurze
|
||||
"""
|
||||
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, jsonify, url_for
|
||||
from flask_login import logout_user, LoginManager
|
||||
from werkzeug.routing import BuildError
|
||||
|
||||
from backend import jwt_extended
|
||||
from backend.models import BlacklistToken, User
|
||||
|
||||
auth_bp = Blueprint('auth', __name__, url_prefix='/auth', template_folder='templates')
|
||||
|
||||
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
|
||||
|
||||
from .basic_auth import *
|
||||
|
||||
|
||||
def auth_decorator(): # custom decorator
|
||||
pass
|
||||
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
try:
|
||||
prov = AUTH_PROVIDERS[DEFAULT_FRONTEND_PROVIDER]
|
||||
except KeyError:
|
||||
return "No known default provider specified!"
|
||||
url = prov["url"]
|
||||
try:
|
||||
url = url_for(prov["url"], next=request.endpoint)
|
||||
except BuildError as e:
|
||||
pass
|
||||
#logger.log("Can't create endpoint for '{}' (specified provider: {}).".format(e.endpoint, DEFAULT_PROVIDER))
|
||||
return redirect(url)
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
|
||||
@jwt_extended.user_claims_loader
|
||||
def add_claims_to_access_token(user):
|
||||
if isinstance(user, str):
|
||||
return {}
|
||||
return {'role': user.role, 'groups': [g.to_dict() for g in user.groups]}
|
||||
|
||||
|
||||
@jwt_extended.user_identity_loader
|
||||
def user_identity_loader(user):
|
||||
return user.email
|
||||
|
||||
|
||||
@jwt_extended.user_loader_callback_loader
|
||||
def user_loader_callback(identity):
|
||||
print("### user_loader_callback_loader")
|
||||
return User.get_by_identifier(identity)
|
||||
|
||||
|
||||
@jwt_extended.token_in_blacklist_loader
|
||||
def check_if_token_in_blacklist(decrypted_token):
|
||||
jti = decrypted_token['jti']
|
||||
return BlacklistToken.get_by_token(jti) is not None
|
||||
22
backend/auth/basic_auth.py
Normal file
22
backend/auth/basic_auth.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Route for handling the login page logic
|
||||
from flask import request, redirect, render_template
|
||||
from flask_login import login_user
|
||||
|
||||
from backend.auth import auth_bp
|
||||
from backend.models.user_model import User
|
||||
|
||||
|
||||
@auth_bp.route('/base_login', methods=['GET', 'POST'])
|
||||
def base_login():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
user = User.authenticate(email=request.form['email'], password=request.form['password'])
|
||||
if user is None:
|
||||
error = 'Invalid Credentials. Please try again.'
|
||||
else:
|
||||
login_user(user)
|
||||
return redirect("/")
|
||||
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
|
||||
29
backend/auth/config.py
Normal file
29
backend/auth/config.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from typing import Dict, List
|
||||
|
||||
AUTH_PROVIDERS: Dict[str, Dict[str, str]] = {
|
||||
"KIT OIDC":
|
||||
{
|
||||
"type": "oidc",
|
||||
"url": "auth_api.oidc"
|
||||
},
|
||||
"Base Login":
|
||||
{
|
||||
"type": "login_form",
|
||||
"url": "auth.base_login"
|
||||
},
|
||||
"KIT OIDC (API)":
|
||||
{
|
||||
"type": "api_oidc",
|
||||
"url": "auth_api.oidc"
|
||||
},
|
||||
"User-Password (API)":
|
||||
{
|
||||
"type": "api_login_form",
|
||||
"url": "auth_api.login"
|
||||
},
|
||||
}
|
||||
|
||||
#DEFAULT_PROVIDER: str = "Base Login"
|
||||
DEFAULT_PROVIDER: str = "KIT OIDC (API)"
|
||||
|
||||
DEFAULT_FRONTEND_PROVIDER: str = "Base Login"
|
||||
77
backend/auth/oidc.py
Normal file
77
backend/auth/oidc.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# Copyright (c) 2019. Tobias Kurze
|
||||
"""
|
||||
OIDC login auth module
|
||||
"""
|
||||
|
||||
import flask
|
||||
from flask import jsonify, redirect, url_for
|
||||
from flask_login import login_user
|
||||
from flask_pyoidc.flask_pyoidc import OIDCAuthentication
|
||||
from flask_pyoidc.user_session import UserSession
|
||||
|
||||
from backend import app, db
|
||||
from backend.models.user_model import User
|
||||
from . import auth_bp
|
||||
from .oidc_config import PROVIDER_NAME, OIDC_PROVIDERS
|
||||
|
||||
|
||||
OIDCAuthentication.oidc_auth_orig = OIDCAuthentication.oidc_auth
|
||||
OIDCAuthentication.oidc_logout_orig = OIDCAuthentication.oidc_logout
|
||||
|
||||
|
||||
def oidc_auth_default_provider(self):
|
||||
"""monkey patch oidc_auth"""
|
||||
return self.oidc_auth_orig(PROVIDER_NAME)
|
||||
|
||||
|
||||
def oidc_logout_default_provider(self):
|
||||
"""monkey patch oidc_logout"""
|
||||
return self.oidc_logout_orig(PROVIDER_NAME)
|
||||
|
||||
|
||||
OIDCAuthentication.oidc_auth = oidc_auth_default_provider
|
||||
OIDCAuthentication.oidc_logout = oidc_logout_default_provider
|
||||
|
||||
oidc_auth = OIDCAuthentication(OIDC_PROVIDERS)
|
||||
|
||||
|
||||
def create_or_retrieve_user_from_userinfo(userinfo):
|
||||
"""Updates and returns ar creates a user from userinfo (part of OIDC token)."""
|
||||
try:
|
||||
email = userinfo["email"]
|
||||
except KeyError:
|
||||
return None
|
||||
user = User.get_by_identifier(email)
|
||||
|
||||
if user is not None:
|
||||
app.logger.info("user found")
|
||||
#TODO: update user!
|
||||
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_bp.route('/oidc', methods=['GET'])
|
||||
@oidc_auth.oidc_auth()
|
||||
def oidc():
|
||||
user_session = UserSession(flask.session)
|
||||
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)
|
||||
|
||||
|
||||
@auth_bp.route('/oidc_logout', methods=['GET'])
|
||||
def oidc_logout():
|
||||
oidc_auth.oidc_logout()
|
||||
return redirect('/')
|
||||
15
backend/auth/oidc_config.py
Normal file
15
backend/auth/oidc_config.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2019. Tobias Kurze
|
||||
from flask_pyoidc.provider_configuration import ClientMetadata, ProviderConfiguration
|
||||
|
||||
REG_RESPONSE_CLIENT_ID = "lrc-test-bibliothek-kit-edu"
|
||||
REG_RESPONSE_CLIENT_SECRET = "d8531b30-0e6b-4280-b611-1e6c8b4911fa"
|
||||
|
||||
CLIENT_METADATA = ClientMetadata(REG_RESPONSE_CLIENT_ID, REG_RESPONSE_CLIENT_SECRET)
|
||||
|
||||
PROVIDER_URL = "https://oidc.scc.kit.edu/auth/realms/kit"
|
||||
PROVIDER_NAME = 'kit_oidc'
|
||||
PROVIDER_CONFIG = ProviderConfiguration(issuer=PROVIDER_URL,
|
||||
client_metadata=CLIENT_METADATA,
|
||||
auth_request_params={'scope': ['openid', 'email', 'profile']})
|
||||
|
||||
OIDC_PROVIDERS = {PROVIDER_NAME: PROVIDER_CONFIG}
|
||||
23
backend/auth/templates/login.html
Normal file
23
backend/auth/templates/login.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Flask Intro - login page</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="static/bootstrap.min.css" rel="stylesheet" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Please login</h1>
|
||||
<br>
|
||||
<form action="" method="post">
|
||||
<input type="text" placeholder="E-Mail" name="email" value="{{
|
||||
request.form.username }}">
|
||||
<input type="password" placeholder="Password" name="password" value="{{
|
||||
request.form.password }}">
|
||||
<input class="btn btn-default" type="submit" value="Login">
|
||||
</form>
|
||||
{% if error %}
|
||||
<p class="error"><strong>Error:</strong> {{ error }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
21
backend/auth/templates/login_select.html
Normal file
21
backend/auth/templates/login_select.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Flask Intro - login page</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="static/bootstrap.min.css" rel="stylesheet" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Please select login method</h1>
|
||||
<br>
|
||||
<ul>
|
||||
{% for provider in providers %}
|
||||
<li><a href="{{url_for(providers[provider].url)}}">{{ provider }} ({{ providers[provider].type }})</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if error %}
|
||||
<p class="error"><strong>Error:</strong> {{ error }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
42
backend/auth/utils.py
Normal file
42
backend/auth/utils.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import flask_jwt_extended
|
||||
from flask_jwt_extended import jwt_optional, get_jwt_identity
|
||||
from functools import wraps
|
||||
|
||||
from backend import jwt_auth
|
||||
from backend.models.user_model import User
|
||||
|
||||
|
||||
def requires_permission_level(permission_level):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if flask_jwt_extended.verify_jwt_in_request():
|
||||
current_user_id = get_jwt_identity()
|
||||
user = User.get_by_identifier(current_user_id)
|
||||
if user is not None:
|
||||
if user.has_permission(permission_level):
|
||||
#for g in user.groups:
|
||||
# if g.permissions
|
||||
#TODO
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
# return FALSE
|
||||
#if not session.get('email'):
|
||||
# return redirect(url_for('users.login'))
|
||||
|
||||
#user = User.find_by_email(session['email'])
|
||||
#elif not user.allowed(access_level):
|
||||
# return redirect(url_for('users.profile', message="You do not have access to that page. Sorry!"))
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
|
||||
def require_jwt():
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
return jwt_auth.login_required(jwt_optional(f(*args, **kwargs)))
|
||||
return decorated_function
|
||||
return decorator
|
||||
Reference in New Issue
Block a user