authlib rewrite

authlib-rewrite v0.3.0
informatic 2021-09-15 22:43:18 +02:00
parent c1482c2bd0
commit d3e1a4412c
5 changed files with 144 additions and 86 deletions

View File

@ -4,3 +4,5 @@ Flask-SpaceAuth
Simple wrapper around Flask-OAuthlib & Flask-Login for quick and dirty
integration of random services with [Warsaw Hackerspace Single
Sign-On](https://sso.hackerspace.pl).
See example in `example.py`.

13
default.nix Normal file
View File

@ -0,0 +1,13 @@
{ pkgs ? import <nixpkgs> {} }:
with pkgs.python3Packages;
buildPythonPackage rec {
pname = "flask-spaceauth-${version}";
version = "0.3";
src = ./.;
propagatedBuildInputs = [
flask
flask_login
blinker
authlib
];
}

View File

@ -1,26 +1,36 @@
from flask import Flask, request, url_for, Markup
from spaceauth import SpaceAuth, login_required, cap_required
from flask import Flask, request, url_for, Markup, get_flashed_messages
from spaceauth import SpaceAuth, login_required, cap_required, current_user
app = Flask('spaceauth-example')
app.config['SECRET_KEY'] = 'testing'
app.config['SPACEAUTH_CONSUMER_KEY'] = 'testing'
app.config['SPACEAUTH_CONSUMER_SECRET'] = 'asdTasdfhwqweryrewegfdsfJIxkGc'
app = Flask("spaceauth-example")
app.config["SECRET_KEY"] = "testing"
app.config["SPACEAUTH_CONSUMER_KEY"] = "17817145-de34-4547-b067-64632f04156a"
app.config["SPACEAUTH_CONSUMER_SECRET"] = "SbZGSw8UgV9uWXvQzxn10czBBTLpE7"
auth = SpaceAuth(app)
@app.route('/')
def index():
return Markup('Hey! <a href="%s">Login with spaceauth</a> / %r') % (
url_for('spaceauth.login'), spaceauth.current_user)
@app.route('/profile')
@app.route("/")
def index():
return Markup(
'<pre>%r</pre>Hey! <a href="%s">Login with spaceauth</a> / <a href="%s">Logout</a> / %r / <a href="%s">Members only space</a>'
) % (
get_flashed_messages(),
url_for("spaceauth.login"),
url_for("spaceauth.logout"),
current_user.get_id(),
url_for("profile"),
)
@app.route("/profile")
@login_required
def profile():
return Markup('Hey {}!').format(spaceauth.current_user)
return Markup("Hey {}!").format(current_user.get_id())
@app.route('/staff')
@cap_required('staff')
@app.route("/staff")
@cap_required("staff")
def staff_only():
return 'This is staff-only zone!'
return "This is staff-only zone!"
if __name__ == "__main__":

View File

@ -1,36 +1,42 @@
'''
"""
Flask-SpaceAuth
---------------
Simple generic user authentication module using `Warsaw Hackerspace SSO Service
<https://code.hackerspace.pl/q3k/sso>`_.
Integrates with Flask-Login and Flask-OAuthlib
'''
Integrates with Flask-Login and authlib
"""
from setuptools import setup
setup(
name='Flask-SpaceAuth',
version='0.2.0',
description='Warsaw Hackerspace SSO Flask module',
name="Flask-SpaceAuth",
version="0.3.0",
description="Warsaw Hackerspace SSO Flask module",
long_description=__doc__,
url='https://code.hackerspace.pl/informatic/flask-spaceauth',
author='Piotr Dobrowolski',
author_email='informatic@hackerspace.pl',
license='MIT',
url="https://code.hackerspace.pl/informatic/flask-spaceauth",
author="Piotr Dobrowolski",
author_email="informatic@hackerspace.pl",
license="MIT",
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries :: Python Modules'
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
],
packages=["spaceauth"],
install_requires=[
"Flask",
"authlib>=0.14",
"Flask-Login>=0.4",
"requests>=2.0",
"blinker",
],
packages=['spaceauth'],
install_requires=['Flask-OAuthlib>=0.9.4', 'Flask-Login>=0.4', 'requests>=2.0', 'blinker'],
)

View File

@ -1,73 +1,91 @@
from flask import Blueprint, request, url_for, session, redirect, abort, flash
from flask_oauthlib.client import OAuth, OAuthException
from flask_login import LoginManager, login_user, logout_user, current_user, login_required, UserMixin
from flask import Blueprint, request, url_for, session, redirect, flash
from authlib.common.errors import AuthlibBaseError
from authlib.integrations.flask_client import OAuth, OAuthError
from flask_login import (
LoginManager,
login_user,
logout_user,
current_user,
login_required,
UserMixin,
)
from spaceauth.caps import cap_required
from datetime import datetime, timedelta
class SSOUser(UserMixin):
def __init__(self, parent, id):
self.id = id
def __str__(self):
return self.id
class SpaceAuth(LoginManager):
def __init__(self, app=None, *args, **kwargs):
def __init__(self, app=None, domain="https://sso.hackerspace.pl", *args, **kwargs):
self.oauth = OAuth()
self.remote = self.oauth.remote_app(
'spaceauth',
base_url='https://sso.hackerspace.pl/api/',
access_token_url='https://sso.hackerspace.pl/oauth/token',
authorize_url='https://sso.hackerspace.pl/oauth/authorize',
request_token_params={'scope': 'profile:read'},
app_key='SPACEAUTH')
self.remote.tokengetter(self.tokengetter)
self.domain = domain
bp = Blueprint('spaceauth', __name__)
bp.add_url_rule('/login', 'login', self.login_view_handler)
bp.add_url_rule('/logout', 'logout', self.logout_view_handler)
bp.add_url_rule('/callback', 'callback', self.callback_view_handler)
bp = Blueprint("spaceauth", __name__)
bp.add_url_rule("/login", "login", self.login_view_handler)
bp.add_url_rule("/logout", "logout", self.logout_view_handler)
bp.add_url_rule("/callback", "callback", self.callback_view_handler)
self.blueprint = bp
super(SpaceAuth, self).__init__()
self.refresh_view = 'spaceauth.login'
self.login_view = 'spaceauth.login'
self.refresh_view = "spaceauth.login"
self.login_view = "spaceauth.login"
self.user_loader(self.user_loader_handler)
if app:
self.init_app(app, *args, **kwargs)
def init_app(self, app, url_prefix='/oauth'):
def init_app(self, app, url_prefix="/oauth"):
app.config.get("SPACEAUTH_CONSUMER_KEY")
self.remote = self.oauth.register(
"spaceauth",
client_id=app.config["SPACEAUTH_CONSUMER_KEY"],
client_secret=app.config["SPACEAUTH_CONSUMER_SECRET"],
api_base_url=self.domain + "/api/",
access_token_url=self.domain + "/oauth/token",
authorize_url=self.domain + "/oauth/authorize",
token_endpoint_auth_method="client_secret_post",
# server_metadata_url=self.domain + "/.well-known/openid-configuration",
scope="profile:read",
fetch_token=self.fetch_token,
update_token=self.update_token,
)
self.oauth.init_app(app)
super(SpaceAuth, self).init_app(app)
app.register_blueprint(self.blueprint, url_prefix=url_prefix)
@app.errorhandler(OAuthException)
@app.errorhandler(AuthlibBaseError)
def errorhandler(err):
flash('OAuth error occured', 'error')
return redirect('/')
flash("OAuth error occured ({})".format(err), "error")
return redirect("/")
def login_view_handler(self):
session['spaceauth_next'] = request.args.get('next') or request.referrer
return self.remote.authorize(
callback=url_for('spaceauth.callback', _external=True)
)
session["spaceauth_next"] = request.args.get("next") or request.referrer
return self.remote.authorize_redirect(
redirect_uri=url_for("spaceauth.callback", _external=True)
)
def logout_view_handler(self):
# TODO revoke token
session.pop('spaceauth_token', None)
session.pop('spaceauth_next', None)
session.pop("spaceauth_token", None)
session.pop("spaceauth_next", None)
logout_user()
return redirect('/')
return redirect("/")
def callback_view_handler(self):
resp = self.remote.authorized_response()
if resp is None:
raise OAuthException(
'Access denied', type=request.args.get('error'))
# TODO encrypt token...?
session['spaceauth_token'] = resp['access_token']
profile = self.remote.get('profile').data
login_user(self.user_loader_handler(profile['username'], profile))
return redirect(session.pop('spaceauth_next', None) or '/')
def tokengetter(self):
return (session.get('spaceauth_token'), '')
try:
resp = self.remote.authorize_access_token()
self.update_token(resp)
return redirect(session.pop("spaceauth_next", None) or "/")
except Exception as exc:
raise OAuthError(
error="Unable to authorize access token", description=str(exc)
)
def user_loader_handler(self, uid, profile=None):
"""
@ -75,9 +93,18 @@ class SpaceAuth(LoginManager):
anonymous.
"""
user = UserMixin()
user.id = uid
return user
return SSOUser(self, uid)
def user_profile(self):
return self.remote.get('profile').data
return self.remote.get("profile").data
def fetch_token(self):
return session.get("spaceauth_token")
def update_token(self, token, refresh_token=None, access_token=None):
session["spaceauth_token"] = token
profile = self.remote.get("1/userinfo").json()
login_user(
self.user_loader_handler(profile["sub"], profile),
duration=timedelta(seconds=token["expires_in"]),
)