Fix authentication on Keycloak

This commit is contained in:
Andrea Mistrali 2025-01-17 10:24:21 +01:00
parent 9fcae05d20
commit 33c0e603f8
Signed by: andre
SSH Key Fingerprint: SHA256:/D780pZnuHMQ8xFII5lAtXWy8zdowtBhgWjwi88p+lI
8 changed files with 532 additions and 388 deletions

1
.gitignore vendored
View File

@ -266,3 +266,4 @@ tags
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
.flaskenv .flaskenv
docker.env docker.env
flask_session/

View File

@ -2,6 +2,7 @@ from flask import Flask, render_template, g
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from flask_mobility import Mobility from flask_mobility import Mobility
from flask_session import Session
from . import filters from . import filters
from .lib import OIDCAuthentication from .lib import OIDCAuthentication
@ -10,6 +11,8 @@ import os
mobility = Mobility() mobility = Mobility()
auth = OIDCAuthentication() auth = OIDCAuthentication()
# SESSION_TYPE = 'filesystem'
sess = Session()
def create_app(environment='development'): def create_app(environment='development'):
@ -50,6 +53,8 @@ def create_app(environment='development'):
app.logger.info("jinja2 custom filters loaded") app.logger.info("jinja2 custom filters loaded")
filters.init_app(app) filters.init_app(app)
sess.init_app(app)
# Error handlers. # Error handlers.
@app.errorhandler(HTTPException) @app.errorhandler(HTTPException)
def handle_http_error(exc): def handle_http_error(exc):

View File

@ -5,7 +5,7 @@ from flask import request, abort, current_app
from flask import session as flask_session, jsonify from flask import session as flask_session, jsonify
from flask_pyoidc import OIDCAuthentication as _OIDCAuth from flask_pyoidc import OIDCAuthentication as _OIDCAuth
from flask_pyoidc.user_session import UserSession from flask_pyoidc.user_session import UserSession
from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata, ProviderMetadata
from typing import Callable, List from typing import Callable, List
@ -51,35 +51,33 @@ class OIDCAuthentication(_OIDCAuth):
super().init_app(app) super().init_app(app)
app.auth = self app.auth = self
@property
def userinfo(self) -> dict:
return flask_session.get('userinfo', {})
@property @property
def username(self) -> str: def username(self) -> str:
userinfo = flask_session['userinfo'] return self.userinfo.get('preferred_username', 'unknown')
return userinfo['email'].split('@')[0]
@property @property
def email(self) -> str: def email(self) -> str:
userinfo = flask_session['userinfo'] return self.userinfo.get('email', 'unknown')
return userinfo['email']
@property @property
def login_name(self) -> str: def login_name(self) -> str:
userinfo = flask_session['userinfo'] return self.userinfo.get('preferred_username', self.username)
return userinfo.get('preferred_username', self.username)
@property @property
def full_name(self) -> str: def full_name(self) -> str:
userinfo = flask_session['userinfo'] return self.userinfo.get('name', self.username)
return userinfo.get('name')
@property @property
def groups(self) -> list: def groups(self) -> list:
userinfo = flask_session['userinfo'] return self.userinfo.get('groups', [])
return userinfo.get('groups') or []
@property @property
def isAdmin(self) -> bool: def isAdmin(self) -> bool:
userinfo = flask_session['userinfo'] user_groups = self.userinfo.get('groups', [])
user_groups = userinfo.get('groups', [])
with current_app.app_context(): with current_app.app_context():
admin_groups = current_app.config.get('ADMIN_GROUPS', []) admin_groups = current_app.config.get('ADMIN_GROUPS', [])
admin_users = current_app.config.get('ADMIN_USERS', []) admin_users = current_app.config.get('ADMIN_USERS', [])

View File

@ -6,8 +6,6 @@ from flask import render_template, Blueprint
from flask import redirect, session, url_for from flask import redirect, session, url_for
from app import auth from app import auth
# from ..lib import username
from flask import jsonify, make_response from flask import jsonify, make_response
from flask_pyoidc.user_session import UserSession from flask_pyoidc.user_session import UserSession
@ -47,6 +45,8 @@ def index():
@main_blueprint.route('/logout') @main_blueprint.route('/logout')
@auth.oidc_logout @auth.oidc_logout
def logout(): def logout():
# UserSession(session).clear()
session.clear()
return redirect(url_for('main.index')) return redirect(url_for('main.index'))

View File

@ -13,8 +13,13 @@ class BaseConfig(object):
APP_PREFIX = os.getenv('APP_PREFIX', '') APP_PREFIX = os.getenv('APP_PREFIX', '')
DEBUG_TB_ENABLED = False DEBUG_TB_ENABLED = False
WTF_CSRF_ENABLED = False WTF_CSRF_ENABLED = False
# Session
# We store sessions in filesystem, max 100 files, expire in 2 hours
SESSION_TYPE = 'filesystem'
SESSION_FILE_THRESHOLD = 100
PERMANENT_SESSION_LIFETIME = 7200
# All the followinf vars can be overriden # All the following vars can be overriden
# in the environment, using `HSMAN_` prefix # in the environment, using `HSMAN_` prefix
SECRET_KEY = "secreto" SECRET_KEY = "secreto"
ADMIN_GROUPS = "adminGroup" ADMIN_GROUPS = "adminGroup"
@ -22,6 +27,7 @@ class BaseConfig(object):
OIDC_CLIENT_SECRET = 'client-secreto' OIDC_CLIENT_SECRET = 'client-secreto'
OIDC_URL = "https://myidp.example.com/auth" OIDC_URL = "https://myidp.example.com/auth"
OIDC_REDIRECT_URI = 'http://localhost:5000/auth' OIDC_REDIRECT_URI = 'http://localhost:5000/auth'
OIDC_CLOCK_SKEW = 30
# These are required by hsapi, should not be defined here # These are required by hsapi, should not be defined here
# HSAPI_SERVER = "https://headscale.example.com" # HSAPI_SERVER = "https://headscale.example.com"

View File

@ -17,7 +17,7 @@ RUN apk --update --no-cache add \
libffi-dev \ libffi-dev \
curl && \ curl && \
chmod g+w /run && \ chmod g+w /run && \
pip install poetry gunicorn pip install poetry gunicorn poetry-plugin-export
COPY . /hsman COPY . /hsman

872
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,11 @@
[tool.poetry] [tool.poetry]
name = "hsman" name = "hsman"
version = "0.9.21" version = "0.9.22"
description = "Flask Admin webui for Headscale" description = "Flask Admin webui for Headscale"
authors = ["Andrea Mistrali <andrea@mistrali.pw>"] authors = ["Andrea Mistrali <andrea@mistrali.pw>"]
license = "BSD" license = "BSD"
readme = "README.md" readme = "README.md"
package-mode = false
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.11,<4.0" python = ">=3.11,<4.0"
@ -17,6 +18,7 @@ flask-pydantic = "^0.12.0"
uvicorn = "^0.30.1" uvicorn = "^0.30.1"
hsapi-client = "^0.9.9" hsapi-client = "^0.9.9"
# hsapi_client = { path = "../hsapi-client", develop = true } # hsapi_client = { path = "../hsapi-client", develop = true }
flask-session = "^0.8.0"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]