added a lot of auth code
This commit is contained in:
17
__init__.py
17
__init__.py
@@ -7,20 +7,23 @@ from flask import Flask
|
||||
from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
jwt_auth = HTTPTokenAuth()
|
||||
basic_auth = HTTPBasicAuth()
|
||||
multi_auth = MultiAuth(basic_auth, jwt_auth)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('backend.config.Config')
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
jwt_auth = HTTPTokenAuth()
|
||||
basic_auth = HTTPBasicAuth()
|
||||
multi_auth = MultiAuth(basic_auth, jwt_auth)
|
||||
from backend.auth import oidc_auth, auth_bp
|
||||
|
||||
oidc_auth.init_app(app)
|
||||
|
||||
# oidc_multi_auth = MultiAuth(oidc_auth, jwt_auth) <- can't work as OIDCAuthentication not implementing HTTPAuth
|
||||
|
||||
from .serve_frontend import fe_bp
|
||||
from .api import auth_api_bp, api_bp
|
||||
|
||||
from backend.auth import oidc_auth
|
||||
oidc_auth.init_app(app)
|
||||
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(auth_api_bp)
|
||||
app.register_blueprint(api_bp)
|
||||
app.register_blueprint(fe_bp)
|
||||
|
||||
@@ -1,15 +1,39 @@
|
||||
# Copyright (c) 2019. Tobias Kurze
|
||||
import datetime
|
||||
"""
|
||||
This module provides functions related to authentication through the API.
|
||||
For example: listing of available auth providers or registration of users.
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
import jwt
|
||||
from flask import request, jsonify, current_app
|
||||
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 backend import db
|
||||
from backend.api import auth_api_bp
|
||||
from backend.auth import AUTH_PROVIDERS
|
||||
from backend.models.user_model import User
|
||||
|
||||
|
||||
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 = list()
|
||||
for p in AUTH_PROVIDERS:
|
||||
provider = dict(p)
|
||||
provider["url"] = url_for(p["url"])
|
||||
return jsonify(providers)
|
||||
|
||||
|
||||
@auth_api_bp.route('/register', methods=('POST',))
|
||||
def register():
|
||||
data = request.get_json()
|
||||
@@ -21,15 +45,21 @@ def register():
|
||||
|
||||
@auth_api_bp.route('/login', methods=('GET', 'POST',))
|
||||
def login():
|
||||
print("login")
|
||||
print(request)
|
||||
data = request.get_json()
|
||||
print(data)
|
||||
user = User.authenticate(**data)
|
||||
|
||||
if not user:
|
||||
return jsonify({ 'message': 'Invalid credentials', 'authenticated': False }), 401
|
||||
return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401
|
||||
|
||||
token = jwt.encode({
|
||||
'sub': user.email,
|
||||
'iat':datetime.utcnow(),
|
||||
'exp': datetime.utcnow() + datetime.timedelta(minutes=30)},
|
||||
current_app.config['SECRET_KEY'])
|
||||
token = create_jwt(user)
|
||||
#login_user(user)
|
||||
return jsonify({'token': token.decode('UTF-8')})
|
||||
|
||||
|
||||
@auth_api_bp.route('/logout', methods=('GET', ))
|
||||
def logout():
|
||||
pass
|
||||
#logout_user()
|
||||
|
||||
@@ -6,7 +6,7 @@ from random import *
|
||||
from flask import jsonify, Blueprint, request
|
||||
from flask_restplus import Resource, reqparse
|
||||
|
||||
from backend import basic_auth, multi_auth, db
|
||||
from backend import basic_auth, multi_auth, db, jwt_auth
|
||||
from backend.api import api_v1, api_bp
|
||||
|
||||
|
||||
@@ -21,6 +21,18 @@ def random_number():
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
@api_bp.route('/test_jwt')
|
||||
@jwt_auth.login_required
|
||||
def random_number_jwt():
|
||||
"""
|
||||
:return: a random number
|
||||
"""
|
||||
response = {
|
||||
'randomNumber': randint(1, 100)
|
||||
}
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
class HelloWorld(Resource):
|
||||
"""
|
||||
This is a test class.
|
||||
|
||||
@@ -1,6 +1,44 @@
|
||||
# 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.)
|
||||
"""
|
||||
from flask import Blueprint
|
||||
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.oidc import OIDCAuthentication
|
||||
from backend.auth.oidc_config import OIDC_PROVIDERS
|
||||
|
||||
from backend.auth.oidc_config import PROVIDERS
|
||||
oidc_auth = OIDCAuthentication(OIDC_PROVIDERS)
|
||||
|
||||
oidc_auth = OIDCAuthentication(PROVIDERS)
|
||||
from .basic_auth import *
|
||||
|
||||
|
||||
def auth_decorator(): # custom decorator
|
||||
pass
|
||||
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
try:
|
||||
prov = AUTH_PROVIDERS[DEFAULT_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)
|
||||
|
||||
14
auth/basic_auth.py
Normal file
14
auth/basic_auth.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Route for handling the login page logic
|
||||
from flask import request, redirect, render_template, url_for
|
||||
from backend.auth import auth_bp
|
||||
|
||||
|
||||
@auth_bp.route('/base_login', methods=['GET', 'POST'])
|
||||
def base_login():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if request.form['username'] != 'admin' or request.form['password'] != 'admin':
|
||||
error = 'Invalid Credentials. Please try again.'
|
||||
else:
|
||||
return redirect("/")
|
||||
return render_template('login.html', error=error)
|
||||
17
auth/config.py
Normal file
17
auth/config.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from typing import Dict, List
|
||||
|
||||
AUTH_PROVIDERS: Dict[str, Dict[str, str]] = {
|
||||
"KIT OIDC":
|
||||
{
|
||||
"type": "oidc",
|
||||
"url": "auth.oidc"
|
||||
},
|
||||
"Base Login":
|
||||
{
|
||||
"type": "login_form",
|
||||
"url": "auth.base_login"
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_PROVIDER: str = "Base Login"
|
||||
#DEFAULT_PROVIDER: str = "KIT OIDC"
|
||||
23
auth/oidc.py
23
auth/oidc.py
@@ -1,6 +1,15 @@
|
||||
# Copyright (c) 2019. Tobias Kurze
|
||||
"""
|
||||
OIDC login auth module
|
||||
"""
|
||||
|
||||
import flask
|
||||
from flask import jsonify
|
||||
from flask_pyoidc.flask_pyoidc import OIDCAuthentication
|
||||
from backend.auth.oidc_config import PROVIDER_NAME
|
||||
from flask_pyoidc.user_session import UserSession
|
||||
|
||||
from .import auth_bp
|
||||
from .oidc_config import PROVIDER_NAME, OIDC_PROVIDERS
|
||||
|
||||
|
||||
def oidc_auth_default_provider(self):
|
||||
@@ -9,3 +18,15 @@ def oidc_auth_default_provider(self):
|
||||
|
||||
OIDCAuthentication.oidc_auth_orig = OIDCAuthentication.oidc_auth
|
||||
OIDCAuthentication.oidc_auth = oidc_auth_default_provider
|
||||
|
||||
oidc_auth = OIDCAuthentication(OIDC_PROVIDERS)
|
||||
|
||||
@auth_bp.route('/oidc', methods=['GET', 'POST'])
|
||||
@oidc_auth.oidc_auth()
|
||||
def oidc():
|
||||
pass
|
||||
user_session = UserSession(flask.session)
|
||||
access_token = user_session.access_token
|
||||
|
||||
#login_user(user)
|
||||
return jsonify(id_token=flask.session['id_token'], access_token=flask.session['access_token'])
|
||||
@@ -11,4 +11,4 @@ PROVIDER_NAME = 'kit_oidc'
|
||||
PROVIDER_CONFIG = ProviderConfiguration(issuer=PROVIDER_URL,
|
||||
client_metadata=CLIENT_METADATA)
|
||||
|
||||
PROVIDERS = {PROVIDER_NAME: PROVIDER_CONFIG}
|
||||
OIDC_PROVIDERS = {PROVIDER_NAME: PROVIDER_CONFIG}
|
||||
|
||||
23
auth/templates/login.html
Normal file
23
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="Username" name="username" 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
auth/templates/login_select.html
Normal file
21
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>
|
||||
@@ -6,10 +6,13 @@ basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
class Config():
|
||||
SERVER_NAME = "ubkaps154.ubka.uni-karlsruhe.de:5443"
|
||||
#SERVER_NAME = "ubkaps154.ubka.uni-karlsruhe.de:5443"
|
||||
#SERVER_NAME = "localhost.dev"
|
||||
#SERVER_NAME = "localhost:5443"
|
||||
PREFERRED_URL_SCHEME = 'https'
|
||||
SERVER_NAME = "localhost:5443"
|
||||
#SERVER_NAME = "localhost"
|
||||
#SERVER_NAME = "localhost.localdomain"
|
||||
#PORT = 5443
|
||||
#PREFERRED_URL_SCHEME = 'https'
|
||||
|
||||
TEMPLATE_AUTO_RELOAD = True
|
||||
|
||||
|
||||
@@ -102,6 +102,20 @@ class User(UserMixin, db.Model):
|
||||
"""
|
||||
return re.sub('[^a-zA-Z0-9_.]', '', nickname)
|
||||
|
||||
@classmethod
|
||||
def authenticate(cls, **kwargs):
|
||||
email = kwargs.get('email')
|
||||
password = kwargs.get('password')
|
||||
|
||||
if not email or not password:
|
||||
return None
|
||||
|
||||
user = cls.query.filter_by(email=email).first()
|
||||
if not user or not user.verify_password(password):
|
||||
return None
|
||||
|
||||
return user
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
"""
|
||||
|
||||
@@ -43,6 +43,7 @@ def test_oidc():
|
||||
token_header=token_header)
|
||||
|
||||
|
||||
|
||||
def has_no_empty_params(rule):
|
||||
defaults = rule.defaults if rule.defaults is not None else ()
|
||||
arguments = rule.arguments if rule.arguments is not None else ()
|
||||
|
||||
Reference in New Issue
Block a user