Skip to content

Commit

Permalink
Support for authentication using external proxy (#33)
Browse files Browse the repository at this point in the history
* add options for HTTP header authentication to config

* add template for handling error 401: Unauthorized

* support external authentication

Expects authentication to be done using an external tool (such as
Apache), that fills the users UUID to a HTTP header and acts as a
proxy.
  • Loading branch information
jakubman1 committed Nov 3, 2023
1 parent 9af2ec9 commit 6913689
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 16 deletions.
6 changes: 6 additions & 0 deletions config.example.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ class Config():
TESTING = False
# SSO auth enabled
SSO_AUTH = False
# Authentication is done outside the app, use HTTP header to get the user uuid.
# If SSO_AUTH is set to True, this option is ignored and SSO auth is used.
HEADER_AUTH = True
# Name of HTTP header containing the UUID of authenticated user.
# Only used when HEADER_AUTH is set to True
AUTH_HEADER_NAME = 'X-Authenticated-User'
# SSO LOGOUT
LOGOUT_URL = "https://flowspec.example.com/Shibboleth.sso/Logout"
# SQL Alchemy config
Expand Down
47 changes: 32 additions & 15 deletions flowapp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import babel

from flask import Flask, redirect, render_template, session, url_for
from flask import Flask, redirect, render_template, session, url_for, request
from flask_sso import SSO
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
Expand Down Expand Up @@ -72,21 +72,9 @@ def login(user_info):
else:
user = db.session.query(models.User).filter_by(uuid=uuid).first()
try:
session["user_uuid"] = user.uuid
session["user_email"] = user.uuid
session["user_name"] = user.name
session["user_id"] = user.id
session["user_roles"] = [role.name for role in user.role.all()]
session["user_orgs"] = ", ".join(
org.name for org in user.organization.all()
)
session["user_role_ids"] = [role.id for role in user.role.all()]
session["user_org_ids"] = [org.id for org in user.organization.all()]
roles = [i > 1 for i in session["user_role_ids"]]
session["can_edit"] = True if all(roles) and roles else []
_register_user_to_session(uuid)
except AttributeError:
return redirect("/")

pass
return redirect("/")

@app.route("/logout")
Expand All @@ -96,6 +84,19 @@ def logout():
session.clear()
return redirect(app.config.get("LOGOUT_URL"))

@app.route("/ext-login")
def ext_login():
header_name = app.config.get("AUTH_HEADER_NAME", 'X-Authenticated-User')
if header_name not in request.headers:
return render_template("errors/401.j2")
uuid = request.headers.get(header_name)
if uuid:
try:
_register_user_to_session(uuid)
except AttributeError:
return render_template("errors/401.j2")
return redirect("/")

@app.route("/")
@auth_required
def index():
Expand Down Expand Up @@ -177,4 +178,20 @@ def format_datetime(value):

return babel.dates.format_datetime(value, format)

def _register_user_to_session(uuid: str):
user = db.session.query(models.User).filter_by(uuid=uuid).first()
session["user_uuid"] = user.uuid
session["user_email"] = user.uuid
session["user_name"] = user.name
session["user_id"] = user.id
session["user_roles"] = [role.name for role in user.role.all()]
session["user_orgs"] = ", ".join(
org.name for org in user.organization.all()
)
session["user_role_ids"] = [role.id for role in user.role.all()]
session["user_org_ids"] = [org.id for org in user.organization.all()]
roles = [i > 1 for i in session["user_role_ids"]]
session["can_edit"] = True if all(roles) and roles else []

return app

11 changes: 10 additions & 1 deletion flowapp/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ def auth_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if not check_auth(get_user()):
return redirect("/login")
if current_app.config.get("SSO_AUTH"):
return redirect("/login")
elif current_app.config.get("HEADER_AUTH", False):
return redirect("/ext-login")
return f(*args, **kwargs)

return decorated
Expand Down Expand Up @@ -99,6 +102,12 @@ def check_auth(uuid):
if uuid:
exist = db.session.query(User).filter_by(uuid=uuid).first()
return exist
elif current_app.config.get("HEADER_AUTH", False):
# External auth (for example apache)
header_name = current_app.config.get("AUTH_HEADER_NAME", 'X-Authenticated-User')
if header_name not in request.headers or not session.get("user_uuid"):
return False
return db.session.query(User).filter_by(uuid=request.headers.get(header_name))
else:
# Localhost login / no check
session["user_email"] = current_app.config["LOCAL_USER_UUID"]
Expand Down
7 changes: 7 additions & 0 deletions flowapp/templates/errors/401.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% extends 'layouts/default.j2' %}
{% block content %}
<h1>Could not log you in.</h1>
<p class="form-text">401: Unauthorized</p>
<p>Please log out and try logging in again.</p>
<p><a href="{{url_for('logout')}}">Log out</a></p>
{% endblock %}

0 comments on commit 6913689

Please sign in to comment.