parent
c1482c2bd0
commit
d3e1a4412c
|
@ -4,3 +4,5 @@ Flask-SpaceAuth
|
||||||
Simple wrapper around Flask-OAuthlib & Flask-Login for quick and dirty
|
Simple wrapper around Flask-OAuthlib & Flask-Login for quick and dirty
|
||||||
integration of random services with [Warsaw Hackerspace Single
|
integration of random services with [Warsaw Hackerspace Single
|
||||||
Sign-On](https://sso.hackerspace.pl).
|
Sign-On](https://sso.hackerspace.pl).
|
||||||
|
|
||||||
|
See example in `example.py`.
|
||||||
|
|
|
@ -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
|
||||||
|
];
|
||||||
|
}
|
40
example.py
40
example.py
|
@ -1,26 +1,36 @@
|
||||||
from flask import Flask, request, url_for, Markup
|
from flask import Flask, request, url_for, Markup, get_flashed_messages
|
||||||
from spaceauth import SpaceAuth, login_required, cap_required
|
from spaceauth import SpaceAuth, login_required, cap_required, current_user
|
||||||
|
|
||||||
app = Flask('spaceauth-example')
|
app = Flask("spaceauth-example")
|
||||||
app.config['SECRET_KEY'] = 'testing'
|
app.config["SECRET_KEY"] = "testing"
|
||||||
app.config['SPACEAUTH_CONSUMER_KEY'] = 'testing'
|
app.config["SPACEAUTH_CONSUMER_KEY"] = "17817145-de34-4547-b067-64632f04156a"
|
||||||
app.config['SPACEAUTH_CONSUMER_SECRET'] = 'asdTasdfhwqweryrewegfdsfJIxkGc'
|
app.config["SPACEAUTH_CONSUMER_SECRET"] = "SbZGSw8UgV9uWXvQzxn10czBBTLpE7"
|
||||||
auth = SpaceAuth(app)
|
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
|
@login_required
|
||||||
def profile():
|
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():
|
def staff_only():
|
||||||
return 'This is staff-only zone!'
|
return "This is staff-only zone!"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
54
setup.py
54
setup.py
|
@ -1,36 +1,42 @@
|
||||||
'''
|
"""
|
||||||
Flask-SpaceAuth
|
Flask-SpaceAuth
|
||||||
---------------
|
---------------
|
||||||
Simple generic user authentication module using `Warsaw Hackerspace SSO Service
|
Simple generic user authentication module using `Warsaw Hackerspace SSO Service
|
||||||
<https://code.hackerspace.pl/q3k/sso>`_.
|
<https://code.hackerspace.pl/q3k/sso>`_.
|
||||||
Integrates with Flask-Login and Flask-OAuthlib
|
Integrates with Flask-Login and authlib
|
||||||
'''
|
"""
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Flask-SpaceAuth',
|
name="Flask-SpaceAuth",
|
||||||
version='0.2.0',
|
version="0.3.0",
|
||||||
description='Warsaw Hackerspace SSO Flask module',
|
description="Warsaw Hackerspace SSO Flask module",
|
||||||
long_description=__doc__,
|
long_description=__doc__,
|
||||||
url='https://code.hackerspace.pl/informatic/flask-spaceauth',
|
url="https://code.hackerspace.pl/informatic/flask-spaceauth",
|
||||||
author='Piotr Dobrowolski',
|
author="Piotr Dobrowolski",
|
||||||
author_email='informatic@hackerspace.pl',
|
author_email="informatic@hackerspace.pl",
|
||||||
license='MIT',
|
license="MIT",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
"Development Status :: 4 - Beta",
|
||||||
'Environment :: Web Environment',
|
"Environment :: Web Environment",
|
||||||
'Intended Audience :: Developers',
|
"Intended Audience :: Developers",
|
||||||
'License :: OSI Approved :: MIT License',
|
"License :: OSI Approved :: MIT License",
|
||||||
'Operating System :: OS Independent',
|
"Operating System :: OS Independent",
|
||||||
'Programming Language :: Python',
|
"Programming Language :: Python",
|
||||||
'Programming Language :: Python :: 2',
|
"Programming Language :: Python :: 2",
|
||||||
'Programming Language :: Python :: 2.7',
|
"Programming Language :: Python :: 2.7",
|
||||||
'Programming Language :: Python :: 3',
|
"Programming Language :: Python :: 3",
|
||||||
'Programming Language :: Python :: 3.4',
|
"Programming Language :: Python :: 3.4",
|
||||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
"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'],
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,73 +1,91 @@
|
||||||
from flask import Blueprint, request, url_for, session, redirect, abort, flash
|
from flask import Blueprint, request, url_for, session, redirect, flash
|
||||||
from flask_oauthlib.client import OAuth, OAuthException
|
from authlib.common.errors import AuthlibBaseError
|
||||||
from flask_login import LoginManager, login_user, logout_user, current_user, login_required, UserMixin
|
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 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):
|
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.oauth = OAuth()
|
||||||
self.remote = self.oauth.remote_app(
|
self.domain = domain
|
||||||
'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)
|
|
||||||
|
|
||||||
bp = Blueprint('spaceauth', __name__)
|
bp = Blueprint("spaceauth", __name__)
|
||||||
bp.add_url_rule('/login', 'login', self.login_view_handler)
|
bp.add_url_rule("/login", "login", self.login_view_handler)
|
||||||
bp.add_url_rule('/logout', 'logout', self.logout_view_handler)
|
bp.add_url_rule("/logout", "logout", self.logout_view_handler)
|
||||||
bp.add_url_rule('/callback', 'callback', self.callback_view_handler)
|
bp.add_url_rule("/callback", "callback", self.callback_view_handler)
|
||||||
self.blueprint = bp
|
self.blueprint = bp
|
||||||
|
|
||||||
super(SpaceAuth, self).__init__()
|
super(SpaceAuth, self).__init__()
|
||||||
self.refresh_view = 'spaceauth.login'
|
self.refresh_view = "spaceauth.login"
|
||||||
self.login_view = 'spaceauth.login'
|
self.login_view = "spaceauth.login"
|
||||||
self.user_loader(self.user_loader_handler)
|
self.user_loader(self.user_loader_handler)
|
||||||
|
|
||||||
if app:
|
if app:
|
||||||
self.init_app(app, *args, **kwargs)
|
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)
|
self.oauth.init_app(app)
|
||||||
super(SpaceAuth, self).init_app(app)
|
super(SpaceAuth, self).init_app(app)
|
||||||
app.register_blueprint(self.blueprint, url_prefix=url_prefix)
|
app.register_blueprint(self.blueprint, url_prefix=url_prefix)
|
||||||
|
|
||||||
@app.errorhandler(OAuthException)
|
@app.errorhandler(AuthlibBaseError)
|
||||||
def errorhandler(err):
|
def errorhandler(err):
|
||||||
flash('OAuth error occured', 'error')
|
flash("OAuth error occured ({})".format(err), "error")
|
||||||
return redirect('/')
|
return redirect("/")
|
||||||
|
|
||||||
def login_view_handler(self):
|
def login_view_handler(self):
|
||||||
session['spaceauth_next'] = request.args.get('next') or request.referrer
|
session["spaceauth_next"] = request.args.get("next") or request.referrer
|
||||||
return self.remote.authorize(
|
return self.remote.authorize_redirect(
|
||||||
callback=url_for('spaceauth.callback', _external=True)
|
redirect_uri=url_for("spaceauth.callback", _external=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
def logout_view_handler(self):
|
def logout_view_handler(self):
|
||||||
# TODO revoke token
|
# TODO revoke token
|
||||||
session.pop('spaceauth_token', None)
|
session.pop("spaceauth_token", None)
|
||||||
session.pop('spaceauth_next', None)
|
session.pop("spaceauth_next", None)
|
||||||
logout_user()
|
logout_user()
|
||||||
return redirect('/')
|
return redirect("/")
|
||||||
|
|
||||||
def callback_view_handler(self):
|
def callback_view_handler(self):
|
||||||
resp = self.remote.authorized_response()
|
try:
|
||||||
if resp is None:
|
resp = self.remote.authorize_access_token()
|
||||||
raise OAuthException(
|
self.update_token(resp)
|
||||||
'Access denied', type=request.args.get('error'))
|
return redirect(session.pop("spaceauth_next", None) or "/")
|
||||||
|
except Exception as exc:
|
||||||
# TODO encrypt token...?
|
raise OAuthError(
|
||||||
session['spaceauth_token'] = resp['access_token']
|
error="Unable to authorize access token", description=str(exc)
|
||||||
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'), '')
|
|
||||||
|
|
||||||
def user_loader_handler(self, uid, profile=None):
|
def user_loader_handler(self, uid, profile=None):
|
||||||
"""
|
"""
|
||||||
|
@ -75,9 +93,18 @@ class SpaceAuth(LoginManager):
|
||||||
anonymous.
|
anonymous.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = UserMixin()
|
return SSOUser(self, uid)
|
||||||
user.id = uid
|
|
||||||
return user
|
|
||||||
|
|
||||||
def user_profile(self):
|
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"]),
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue