Support for OpenID Connect authentication flow
This commit is contained in:
parent
a386a8e0c9
commit
d9bf22c5ff
|
@ -7,6 +7,7 @@ from sqlalchemy.orm import relationship, backref
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
|
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.types import Float
|
from sqlalchemy.types import Float
|
||||||
from sqlalchemy_json import MutableJson
|
from sqlalchemy_json import MutableJson
|
||||||
|
|
||||||
|
@ -26,11 +27,14 @@ class User(db.Model):
|
||||||
admin = db.Column(db.Boolean, default=False)
|
admin = db.Column(db.Boolean, default=False)
|
||||||
proposals = relationship('Proposal', back_populates="user")
|
proposals = relationship('Proposal', back_populates="user")
|
||||||
comments = relationship("Comment", back_populates="user")
|
comments = relationship("Comment", back_populates="user")
|
||||||
|
uuid = db.Column(UUID(as_uuid=True), unique=True)
|
||||||
|
|
||||||
def __init__(self, username, password, email):
|
def __init__(self, username, password, email, uuid=None):
|
||||||
from funding.factory import bcrypt
|
from funding.factory import bcrypt
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = bcrypt.generate_password_hash(password).decode('utf8')
|
if password:
|
||||||
|
self.password = bcrypt.generate_password_hash(password).decode('utf8')
|
||||||
|
self.uuid = uuid
|
||||||
self.email = email
|
self.email = email
|
||||||
self.registered_on = datetime.utcnow()
|
self.registered_on = datetime.utcnow()
|
||||||
|
|
||||||
|
@ -57,16 +61,17 @@ class User(db.Model):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, username, password, email):
|
def add(cls, username, password=None, email=None, uuid=None):
|
||||||
from funding.factory import db
|
from funding.factory import db
|
||||||
from funding.validation import val_username, val_email
|
from funding.validation import val_username, val_email
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# validate incoming username/email
|
# validate incoming username/email
|
||||||
val_username(username)
|
val_username(username)
|
||||||
val_email(email)
|
if email:
|
||||||
|
val_email(email)
|
||||||
|
|
||||||
user = User(username, password, email)
|
user = User(username=username, password=password, email=email, uuid=uuid)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from flask import request, redirect, render_template, url_for, flash, make_response, send_from_directory, jsonify
|
from flask import request, redirect, render_template, url_for, flash, make_response, send_from_directory, jsonify, session
|
||||||
from flask_login import login_user , logout_user , current_user
|
from flask_login import login_user , logout_user , current_user
|
||||||
from dateutil.parser import parse as dateutil_parse
|
from dateutil.parser import parse as dateutil_parse
|
||||||
from flask_yoloapi import endpoint, parameter
|
from flask_yoloapi import endpoint, parameter
|
||||||
|
@ -301,12 +302,93 @@ def register():
|
||||||
return make_response(render_template('register.html'))
|
return make_response(render_template('register.html'))
|
||||||
|
|
||||||
|
|
||||||
|
if settings.OPENID_ENABLED:
|
||||||
|
@app.route("/wow-auth/")
|
||||||
|
def wow_auth():
|
||||||
|
assert "state" in request.args
|
||||||
|
assert "session_state" in request.args
|
||||||
|
assert "code" in request.args
|
||||||
|
|
||||||
|
# verify state
|
||||||
|
if not session.get('auth_state'):
|
||||||
|
return "session error", 500
|
||||||
|
if request.args['state'] != session['auth_state']:
|
||||||
|
return "attack detected :)", 500
|
||||||
|
|
||||||
|
# with this authorization code we can fetch an access token
|
||||||
|
url = f"{settings.OPENID_URL}/token"
|
||||||
|
data = {
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": request.args["code"],
|
||||||
|
"redirect_uri": settings.OPENID_REDIRECT_URI,
|
||||||
|
"client_id": settings.OPENID_CLIENT_ID,
|
||||||
|
"client_secret": settings.OPENID_CLIENT_SECRET,
|
||||||
|
"state": request.args['state']
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
resp = requests.post(url, data=data)
|
||||||
|
resp.raise_for_status()
|
||||||
|
except:
|
||||||
|
return "something went wrong :( #1", 500
|
||||||
|
|
||||||
|
data = resp.json()
|
||||||
|
assert "access_token" in data
|
||||||
|
assert data.get("token_type") == "bearer"
|
||||||
|
access_token = data['access_token']
|
||||||
|
|
||||||
|
# fetch user information with the access token
|
||||||
|
url = f"{settings.OPENID_URL}/userinfo"
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(url, headers={"Authorization": f"Bearer {access_token}"})
|
||||||
|
resp.raise_for_status()
|
||||||
|
user_profile = resp.json()
|
||||||
|
except:
|
||||||
|
return "something went wrong :( #2", 500
|
||||||
|
|
||||||
|
username = user_profile.get("preferred_username")
|
||||||
|
sub = user_profile.get("sub")
|
||||||
|
if not username:
|
||||||
|
return "something went wrong :( #3", 500
|
||||||
|
|
||||||
|
sub_uuid = uuid.UUID(sub)
|
||||||
|
user = User.query.filter_by(username=username).first()
|
||||||
|
if user:
|
||||||
|
if not user.uuid:
|
||||||
|
user.uuid = sub_uuid
|
||||||
|
db.session.commit()
|
||||||
|
db.session.flush()
|
||||||
|
else:
|
||||||
|
user = User.add(username=username,
|
||||||
|
password=None, email=None, uuid=sub_uuid)
|
||||||
|
login_user(user)
|
||||||
|
response = redirect(request.args.get('next') or url_for('index'))
|
||||||
|
response.headers['X-Set-Cookie'] = True
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
@endpoint.api(
|
@endpoint.api(
|
||||||
parameter('username', type=str, location='form'),
|
parameter('username', type=str, location='form', required=False),
|
||||||
parameter('password', type=str, location='form')
|
parameter('password', type=str, location='form', required=False)
|
||||||
)
|
)
|
||||||
def login(username, password):
|
def login(username, password):
|
||||||
|
if settings.OPENID_ENABLED:
|
||||||
|
state = uuid.uuid4().hex
|
||||||
|
session['auth_state'] = state
|
||||||
|
|
||||||
|
url = f"{settings.OPENID_URL}/auth?" \
|
||||||
|
f"client_id={settings.OPENID_CLIENT_ID}&" \
|
||||||
|
f"redirect_uri={settings.OPENID_REDIRECT_URI}&" \
|
||||||
|
f"response_type=code&" \
|
||||||
|
f"state={state}"
|
||||||
|
|
||||||
|
return redirect(url)
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
flash('Enter username/password pl0x')
|
||||||
|
return make_response(render_template('login.html'))
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return make_response(render_template('login.html'))
|
return make_response(render_template('login.html'))
|
||||||
|
|
||||||
|
@ -327,7 +409,6 @@ def logout():
|
||||||
logout_user()
|
logout_user()
|
||||||
response = redirect(request.args.get('next') or url_for('login'))
|
response = redirect(request.args.get('next') or url_for('login'))
|
||||||
response.headers['X-Set-Cookie'] = True
|
response.headers['X-Set-Cookie'] = True
|
||||||
flash('Logout successfully')
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,13 @@ PSQL_DB = ''
|
||||||
PSQL_USER = 'postgres'
|
PSQL_USER = 'postgres'
|
||||||
PSQL_PASS = ''
|
PSQL_PASS = ''
|
||||||
|
|
||||||
|
OPENID_ENABLED = False
|
||||||
|
OPENID_REALM = "master"
|
||||||
|
OPENID_URL = f"https://login.wownero.com/auth/realms/{OPENID_REALM}/protocol/openid-connect"
|
||||||
|
OPENID_CLIENT_ID = ""
|
||||||
|
OPENID_CLIENT_SECRET = ""
|
||||||
|
OPENID_REDIRECT_URI = "http://0.0.0.0:5004/wow-auth/"
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://{user}:{pw}@localhost/{db}').format(user=PSQL_USER, pw=PSQL_PASS, db=PSQL_DB)
|
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://{user}:{pw}@localhost/{db}').format(user=PSQL_USER, pw=PSQL_PASS, db=PSQL_DB)
|
||||||
|
|
||||||
SESSION_COOKIE_NAME = os.environ.get('{coincode}_SESSION_COOKIE_NAME', '{coincode}_id').format(coincode=COINCODE.upper())
|
SESSION_COOKIE_NAME = os.environ.get('{coincode}_SESSION_COOKIE_NAME', '{coincode}_id').format(coincode=COINCODE.upper())
|
||||||
|
|
Loading…
Reference in New Issue