parent
c1482c2bd0
commit
d3e1a4412c
|
@ -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`.
|
||||
|
|
|
@ -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 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__":
|
||||
|
|
54
setup.py
54
setup.py
|
@ -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'],
|
||||
)
|
||||
|
|
|
@ -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"]),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue