Add working microsoft login flow
This commit is contained in:
parent
0ace54a8fd
commit
cc0c271a22
12 changed files with 145 additions and 50 deletions
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
@ -0,0 +1 @@
|
|||
python 3.9.2
|
14
app/app.py
14
app/app.py
|
@ -13,14 +13,13 @@ from flask_bootstrap import Bootstrap, StaticCDN
|
|||
from flask_debugtoolbar import DebugToolbarExtension
|
||||
from flask_login import LoginManager
|
||||
from flask_migrate import Migrate, MigrateCommand
|
||||
from flask_oauthlib.client import OAuth, OAuthException
|
||||
from flask_script import Manager, Server
|
||||
from login import init_login
|
||||
from auth.login import init_login
|
||||
from markupsafe import Markup
|
||||
from models import db
|
||||
from models.anonymous_user import AnonymouseUser
|
||||
from utils import euro_string, price_range_string
|
||||
from zeus import init_oauth
|
||||
from auth.zeus import init_oauth
|
||||
|
||||
|
||||
def register_plugins(app: Flask) -> Manager:
|
||||
|
@ -97,18 +96,21 @@ def add_routes(application: Flask) -> None:
|
|||
# import views # TODO convert to blueprint
|
||||
# import views.stats # TODO convert to blueprint
|
||||
|
||||
from login import auth_bp
|
||||
from auth.login import auth_bp
|
||||
from auth.microsoft import auth_microsoft_bp
|
||||
from auth.zeus import auth_zeus_bp
|
||||
from views.debug import debug_bp
|
||||
from views.general import general_bp
|
||||
from views.order import order_bp
|
||||
from views.stats import stats_blueprint
|
||||
from zeus import oauth_bp
|
||||
|
||||
application.register_blueprint(general_bp, url_prefix="/")
|
||||
application.register_blueprint(order_bp, url_prefix="/order")
|
||||
application.register_blueprint(stats_blueprint, url_prefix="/stats")
|
||||
application.register_blueprint(auth_bp, url_prefix="/")
|
||||
application.register_blueprint(oauth_bp, url_prefix="/")
|
||||
application.register_blueprint(auth_microsoft_bp,
|
||||
url_prefix="/users/auth/microsoft_graph_auth") # "/auth/microsoft")
|
||||
application.register_blueprint(auth_zeus_bp, url_prefix="/auth/zeus")
|
||||
|
||||
if application.debug:
|
||||
application.register_blueprint(debug_bp, url_prefix="/debug")
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
"Script for everything related to logging in and out"
|
||||
"""Script for everything related to logging in and out"""
|
||||
from flask import Blueprint, abort, redirect, session, url_for
|
||||
from flask_login import current_user, logout_user
|
||||
from models import User
|
||||
from werkzeug.wrappers import Response
|
||||
from zeus import zeus_login
|
||||
|
||||
auth_bp = Blueprint("auth_bp", __name__)
|
||||
|
||||
|
||||
def init_login(app) -> None:
|
||||
"Initialize the login"
|
||||
"""Initialize the login"""
|
||||
|
||||
# pylint: disable=W0612
|
||||
@app.login_manager.user_loader
|
||||
def load_user(userid) -> User:
|
||||
"Load the user"
|
||||
"""Load the user"""
|
||||
return User.query.filter_by(id=userid).first()
|
||||
|
||||
|
||||
@auth_bp.route("/login")
|
||||
def login():
|
||||
"Function to handle a user trying to log in"
|
||||
return zeus_login()
|
||||
|
||||
|
||||
@auth_bp.route("/logout")
|
||||
def logout() -> Response:
|
||||
"Function to handle a user trying to log out"
|
||||
"""Function to handle a user trying to log out"""
|
||||
if "zeus_token" in session:
|
||||
session.pop("zeus_token", None)
|
||||
logout_user()
|
||||
|
@ -33,6 +27,6 @@ def logout() -> Response:
|
|||
|
||||
|
||||
def before_request() -> None:
|
||||
"Function for what has to be done before a request"
|
||||
"""Function for what has to be done before a request"""
|
||||
if current_user.is_anonymous() or not current_user.is_allowed():
|
||||
abort(401)
|
77
app/auth/microsoft.py
Normal file
77
app/auth/microsoft.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
import typing
|
||||
|
||||
from flask import Blueprint, url_for, request, redirect, flash, Response
|
||||
from flask_login import login_user
|
||||
from microsoftgraph.client import Client
|
||||
|
||||
from config import Configuration
|
||||
from models import User, db
|
||||
|
||||
auth_microsoft_bp = Blueprint("auth_microsoft_bp", __name__)
|
||||
|
||||
client = Client(Configuration.MICROSOFT_AUTH_ID,
|
||||
Configuration.MICROSOFT_AUTH_SECRET,
|
||||
account_type='common') # by default common, thus account_type is optional parameter.
|
||||
|
||||
|
||||
def microsoft_login():
|
||||
"""Log in using Microsoft"""
|
||||
scope = ["openid", "profile", "User.Read", "User.Read.All"]
|
||||
url = client.authorization_url(url_for("auth_microsoft_bp.authorized", _external=True), scope, state=None)
|
||||
return redirect(url)
|
||||
|
||||
|
||||
@auth_microsoft_bp.route("/login")
|
||||
def login():
|
||||
"""Function to handle a user trying to log in"""
|
||||
return microsoft_login()
|
||||
|
||||
|
||||
@auth_microsoft_bp.route("callback") # "/authorized")
|
||||
def authorized() -> typing.Any:
|
||||
# type is 'typing.Union[str, Response]', but this errors due to
|
||||
# https://github.com/python/mypy/issues/7187
|
||||
"""Check authorized status"""
|
||||
|
||||
oauth_code = request.args['code']
|
||||
|
||||
resp = client.exchange_code(url_for("auth_microsoft_bp.authorized", _external=True), oauth_code)
|
||||
|
||||
# access_token = resp.data['access_token']
|
||||
# id_token = resp.data['id_token']
|
||||
# expires_in = resp.data['expires_in']
|
||||
|
||||
client.set_token(resp.data)
|
||||
|
||||
resp = client.users.get_me()
|
||||
print(resp.data)
|
||||
|
||||
username = resp.data['userPrincipalName']
|
||||
microsoft_uuid = resp.data['id']
|
||||
|
||||
user = User.query.filter_by(username=username).first()
|
||||
|
||||
if username and user:
|
||||
return login_and_redirect_user(user)
|
||||
elif username:
|
||||
# TODO Save 'ugent_username' or something similar
|
||||
user = create_user(username, microsoft_uuid)
|
||||
return login_and_redirect_user(user)
|
||||
|
||||
flash("You're not allowed to enter, please contact a system administrator")
|
||||
return redirect(url_for("general_bp.home"))
|
||||
|
||||
|
||||
def login_and_redirect_user(user) -> Response:
|
||||
"""Log in the user and then redirect them"""
|
||||
login_user(user)
|
||||
return redirect(url_for("general_bp.home"))
|
||||
|
||||
|
||||
def create_user(username, microsoft_uuid) -> User:
|
||||
"""Create a temporary user if it is needed"""
|
||||
user = User()
|
||||
user.configure(username, False, 1, microsoft_uuid)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
|
@ -4,24 +4,30 @@ import typing
|
|||
from flask import (Blueprint, current_app, flash, redirect, request, session,
|
||||
url_for)
|
||||
from flask_login import login_user
|
||||
from flask_oauthlib.client import OAuth, OAuthException
|
||||
from flask_oauthlib.client import OAuth, OAuthException, OAuthRemoteApp
|
||||
from models import User, db
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
oauth_bp = Blueprint("oauth_bp", __name__)
|
||||
auth_zeus_bp = Blueprint("auth_zeus_bp", __name__)
|
||||
|
||||
|
||||
def zeus_login():
|
||||
"Log in using ZeusWPI"
|
||||
"""Log in using ZeusWPI"""
|
||||
return current_app.zeus.authorize(
|
||||
callback=url_for("oauth_bp.authorized", _external=True))
|
||||
callback=url_for("auth_zeus_bp.authorized", _external=True))
|
||||
|
||||
|
||||
@oauth_bp.route("/login/zeus/authorized")
|
||||
@auth_zeus_bp.route("/login")
|
||||
def login():
|
||||
"""Function to handle a user trying to log in"""
|
||||
return zeus_login()
|
||||
|
||||
|
||||
@auth_zeus_bp.route("/authorized")
|
||||
def authorized() -> typing.Any:
|
||||
# type is 'typing.Union[str, Response]', but this errors due to
|
||||
# https://github.com/python/mypy/issues/7187
|
||||
"Check authorized status"
|
||||
"""Check authorized status"""
|
||||
resp = current_app.zeus.authorized_response()
|
||||
if resp is None:
|
||||
# pylint: disable=C0301
|
||||
|
@ -45,8 +51,8 @@ def authorized() -> typing.Any:
|
|||
return redirect(url_for("general_bp.home"))
|
||||
|
||||
|
||||
def init_oauth(app):
|
||||
"Initialize the OAuth for ZeusWPI"
|
||||
def init_oauth(app) -> OAuthRemoteApp:
|
||||
"""Initialize the OAuth for ZeusWPI"""
|
||||
oauth = OAuth(app)
|
||||
|
||||
zeus = oauth.remote_app(
|
||||
|
@ -69,13 +75,13 @@ def init_oauth(app):
|
|||
|
||||
|
||||
def login_and_redirect_user(user) -> Response:
|
||||
"Log in the user and then redirect them"
|
||||
"""Log in the user and then redirect them"""
|
||||
login_user(user)
|
||||
return redirect(url_for("general_bp.home"))
|
||||
|
||||
|
||||
def create_user(username) -> User:
|
||||
"Create a temporary user if it is needed"
|
||||
"""Create a temporary user if it is needed"""
|
||||
user = User()
|
||||
user.configure(username, False, 1)
|
||||
db.session.add(user)
|
|
@ -1,4 +1,4 @@
|
|||
"An example for a Haldis config"
|
||||
"""An example for a Haldis config"""
|
||||
# config
|
||||
|
||||
|
||||
|
@ -14,3 +14,5 @@ class Configuration:
|
|||
LOGFILE = "haldis.log"
|
||||
ZEUS_KEY = "tomtest"
|
||||
ZEUS_SECRET = "blargh"
|
||||
MICROSOFT_AUTH_ID = ""
|
||||
MICROSOFT_AUTH_SECRET = ""
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
import add_admins
|
||||
|
||||
from app import app_manager, db
|
||||
from app import create_app, db
|
||||
|
||||
app_manager = create_app()
|
||||
|
||||
entry_sets = {
|
||||
"admins": add_admins.add,
|
||||
|
|
|
@ -3,11 +3,15 @@ from models import db
|
|||
|
||||
|
||||
class User(db.Model):
|
||||
"Class used for configuring the User model in the database"
|
||||
"""Class used for configuring the User model in the database"""
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
admin = db.Column(db.Boolean)
|
||||
bias = db.Column(db.Integer)
|
||||
# Microsoft OAUTH info
|
||||
microsoft_uuid = db.Column(db.String(120), unique=True)
|
||||
ugent_username = db.Column(db.String(80), unique=True)
|
||||
# Relations
|
||||
runs = db.relation(
|
||||
"Order",
|
||||
backref="courier",
|
||||
|
@ -16,11 +20,12 @@ class User(db.Model):
|
|||
)
|
||||
orderItems = db.relationship("OrderItem", backref="user", lazy="dynamic")
|
||||
|
||||
def configure(self, username: str, admin: bool, bias: int) -> None:
|
||||
"Configure the User"
|
||||
def configure(self, username: str, admin: bool, bias: int, microsoft_uuid: str = None) -> None:
|
||||
"""Configure the User"""
|
||||
self.username = username
|
||||
self.admin = admin
|
||||
self.bias = bias
|
||||
self.microsoft_uuid = microsoft_uuid
|
||||
|
||||
# pylint: disable=C0111, R0201
|
||||
def is_authenticated(self) -> bool:
|
||||
|
|
|
@ -81,7 +81,8 @@
|
|||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if current_user.is_anonymous() %}
|
||||
<li><a href="{{ url_for('auth_bp.login') }}">Login</a></li>
|
||||
<li><a href="{{ url_for('auth_microsoft_bp.login') }}">Login with Microsoft</a></li>
|
||||
<li><a href="{{ url_for('auth_zeus_bp.login') }}">Login with Zeus</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('general_bp.profile') }}">{{ current_user.username }}</a></li>
|
||||
<li><a href="{{ url_for('auth_bp.logout') }}">Logout</a></li>
|
||||
|
|
|
@ -12,7 +12,7 @@ E="${normal}"
|
|||
if [ ! -d "venv" ]; then
|
||||
PYTHON_VERSION=$(cat .python-version)
|
||||
echo -e "${B} No venv found, creating a new one with version ${PYTHON_VERSION} ${E}"
|
||||
python3 -m virtualenv -p $PYTHON_VERSION venv
|
||||
python3 -m virtualenv -p "$PYTHON_VERSION" venv
|
||||
fi
|
||||
source venv/bin/activate
|
||||
|
||||
|
|
|
@ -12,3 +12,4 @@ black
|
|||
pymysql
|
||||
pyyaml
|
||||
tatsu<5.6 # >=5.6 needs Python >=3.8
|
||||
microsoftgraph-python
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile
|
||||
# This file is autogenerated by pip-compile with python 3.9
|
||||
# To update, run:
|
||||
#
|
||||
# pip-compile
|
||||
|
@ -24,6 +24,18 @@ click==7.1.2
|
|||
# flask
|
||||
dominate==2.6.0
|
||||
# via flask-bootstrap
|
||||
flask==1.1.4
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-admin
|
||||
# flask-bootstrap
|
||||
# flask-debugtoolbar
|
||||
# flask-login
|
||||
# flask-migrate
|
||||
# flask-oauthlib
|
||||
# flask-script
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
flask-admin==1.5.8
|
||||
# via -r requirements.in
|
||||
flask-bootstrap==3.3.7.1
|
||||
|
@ -44,18 +56,6 @@ flask-sqlalchemy==2.5.1
|
|||
# flask-migrate
|
||||
flask-wtf==0.15.1
|
||||
# via -r requirements.in
|
||||
flask==1.1.4
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-admin
|
||||
# flask-bootstrap
|
||||
# flask-debugtoolbar
|
||||
# flask-login
|
||||
# flask-migrate
|
||||
# flask-oauthlib
|
||||
# flask-script
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
greenlet==1.1.0
|
||||
# via sqlalchemy
|
||||
idna==2.10
|
||||
|
@ -74,6 +74,8 @@ markupsafe==2.0.1
|
|||
# jinja2
|
||||
# mako
|
||||
# wtforms
|
||||
microsoftgraph-python==1.1.3
|
||||
# via -r requirements.in
|
||||
mypy-extensions==0.4.3
|
||||
# via black
|
||||
oauthlib==2.1.0
|
||||
|
@ -92,10 +94,12 @@ pyyaml==5.4.1
|
|||
# via -r requirements.in
|
||||
regex==2021.4.4
|
||||
# via black
|
||||
requests==2.25.1
|
||||
# via
|
||||
# microsoftgraph-python
|
||||
# requests-oauthlib
|
||||
requests-oauthlib==1.1.0
|
||||
# via flask-oauthlib
|
||||
requests==2.25.1
|
||||
# via requests-oauthlib
|
||||
six==1.16.0
|
||||
# via python-dateutil
|
||||
sqlalchemy==1.4.18
|
||||
|
|
Loading…
Reference in a new issue