2020-05-09 18:00:07 +00:00
|
|
|
|
2022-02-19 22:16:13 +00:00
|
|
|
from authlib.integrations.flask_client import OAuth
|
2020-05-09 18:00:07 +00:00
|
|
|
from urllib.parse import urlencode, parse_qs
|
|
|
|
|
|
|
|
import flask
|
2022-02-19 22:16:13 +00:00
|
|
|
from flask import Blueprint, redirect, flash, current_app, session
|
2020-05-09 18:00:07 +00:00
|
|
|
from flask.templating import render_template
|
2020-05-26 20:55:37 +00:00
|
|
|
from flask_babel import gettext
|
2022-02-19 22:16:13 +00:00
|
|
|
from flask.typing import ResponseReturnValue
|
2020-05-09 18:00:07 +00:00
|
|
|
|
2020-06-01 21:43:10 +00:00
|
|
|
from flask import request, url_for, jsonify
|
2020-05-30 21:33:59 +00:00
|
|
|
from flask_login import login_required, login_user, logout_user, current_user
|
2020-05-09 18:00:07 +00:00
|
|
|
import logging
|
2020-05-13 15:04:22 +00:00
|
|
|
from urllib.parse import urlparse
|
|
|
|
from base64 import b64decode, b64encode
|
2020-05-21 11:20:27 +00:00
|
|
|
import http
|
2020-05-27 19:16:14 +00:00
|
|
|
import crypt
|
2020-06-01 21:43:10 +00:00
|
|
|
from datetime import datetime
|
2020-06-21 09:52:37 +00:00
|
|
|
import logging
|
2022-02-11 15:09:40 +00:00
|
|
|
import json
|
2022-02-19 22:16:13 +00:00
|
|
|
from ory_hydra_client.api.admin import get_consent_request, accept_consent_request, accept_login_request, get_login_request, accept_login_request, accept_logout_request, get_login_request
|
|
|
|
from ory_hydra_client.models import AcceptLoginRequest, AcceptConsentRequest, ConsentRequestSession, GenericError, ConsentRequestSessionAccessToken, ConsentRequestSessionIdToken
|
|
|
|
from typing import Optional
|
2020-05-09 18:00:07 +00:00
|
|
|
|
2020-05-27 19:16:14 +00:00
|
|
|
from ..model import db, User, SecurityUser, UserSignUp
|
|
|
|
from ..form.auth import ConsentForm, LoginForm, RegistrationForm
|
2020-05-09 18:00:07 +00:00
|
|
|
from ..auth_providers import AUTH_PROVIDER_LIST
|
2022-02-19 22:16:13 +00:00
|
|
|
from ..hydra import hydra_service
|
2020-05-09 18:00:07 +00:00
|
|
|
|
|
|
|
|
2020-06-21 09:52:37 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2020-05-21 11:20:27 +00:00
|
|
|
auth_views = Blueprint('auth', __name__, url_prefix='/auth')
|
2020-05-09 18:00:07 +00:00
|
|
|
|
2020-05-26 20:55:37 +00:00
|
|
|
|
2022-02-19 22:16:13 +00:00
|
|
|
|
2020-05-21 11:20:27 +00:00
|
|
|
@auth_views.route('/consent', methods=['GET', 'POST'])
|
2022-02-20 15:53:24 +00:00
|
|
|
async def consent() -> ResponseReturnValue:
|
2020-05-21 11:20:27 +00:00
|
|
|
"""Always grant consent."""
|
|
|
|
# DUMMPY ONLY
|
2020-05-09 18:00:07 +00:00
|
|
|
|
2020-05-26 20:55:37 +00:00
|
|
|
form = ConsentForm()
|
2022-02-11 15:09:40 +00:00
|
|
|
remember_for = 60*60*24*30 # remember for 30 days
|
2020-05-13 15:04:22 +00:00
|
|
|
|
2022-02-19 22:16:13 +00:00
|
|
|
#try:
|
2022-02-20 15:53:24 +00:00
|
|
|
consent_request = await get_consent_request.asyncio(consent_challenge=request.args['consent_challenge'],_client=hydra_service.hydra_client)
|
2022-02-19 22:16:13 +00:00
|
|
|
|
|
|
|
if consent_request is None or isinstance( consent_request, GenericError):
|
|
|
|
return redirect(url_for('frontend.index'))
|
2020-05-30 17:00:08 +00:00
|
|
|
|
2022-02-19 22:16:13 +00:00
|
|
|
|
|
|
|
# except ory_hydra_client.exceptions.ApiValueError:
|
|
|
|
# logger.info('ory exception - could not fetch user data ApiValueError')
|
|
|
|
# return redirect(url_for('frontend.index'))
|
|
|
|
# except ory_hydra_client.exceptions.ApiException:
|
|
|
|
# logger.exception('ory exception - could not fetch user data')
|
|
|
|
# return redirect(url_for('frontend.index'))
|
|
|
|
|
|
|
|
requested_scope = consent_request.requested_scope
|
|
|
|
requested_audiences = consent_request.requested_access_token_audience
|
2020-05-26 20:55:37 +00:00
|
|
|
|
|
|
|
if form.validate_on_submit() or consent_request.skip:
|
2020-05-30 21:43:55 +00:00
|
|
|
user = User.query.get(consent_request.subject)
|
2020-05-30 21:33:59 +00:00
|
|
|
token_data = {
|
2020-06-01 21:43:10 +00:00
|
|
|
'name': str(user.username),
|
2020-05-30 21:43:55 +00:00
|
|
|
'preferred_username': str(user.username),
|
|
|
|
'email': str(user.email),
|
2020-05-30 21:33:59 +00:00
|
|
|
'email_verified': True,
|
2020-06-01 21:43:10 +00:00
|
|
|
'groups': [group.name for group in user.groups]
|
2020-05-30 21:33:59 +00:00
|
|
|
}
|
|
|
|
id_token_data = {}
|
2022-02-19 22:16:13 +00:00
|
|
|
if isinstance(requested_scope, list) and 'openid' in requested_scope:
|
2020-05-30 21:33:59 +00:00
|
|
|
id_token_data = token_data
|
2022-02-19 22:16:13 +00:00
|
|
|
access_token=ConsentRequestSessionAccessToken.from_dict(token_data)
|
|
|
|
id_token=ConsentRequestSessionIdToken.from_dict(id_token_data)
|
|
|
|
body = AcceptConsentRequest(
|
|
|
|
grant_scope= requested_scope,
|
|
|
|
grant_access_token_audience= requested_audiences,
|
|
|
|
remember= form.data['remember'],
|
|
|
|
remember_for= remember_for,
|
|
|
|
session= ConsentRequestSession(
|
|
|
|
access_token= access_token,
|
|
|
|
id_token= id_token
|
|
|
|
)
|
|
|
|
)
|
2022-02-20 15:53:24 +00:00
|
|
|
resp = await accept_consent_request.asyncio(_client=hydra_service.hydra_client,
|
2022-02-19 22:16:13 +00:00
|
|
|
json_body=body,
|
|
|
|
consent_challenge=consent_request.challenge)
|
|
|
|
if resp is None or isinstance( resp, GenericError):
|
|
|
|
return 'internal error, could not forward request', 503
|
2020-05-26 20:55:37 +00:00
|
|
|
return redirect(resp.redirect_to)
|
|
|
|
return render_template(
|
|
|
|
'auth/consent.html.j2',
|
|
|
|
form=form,
|
|
|
|
client=consent_request.client,
|
|
|
|
requested_scope=requested_scope,
|
|
|
|
requested_audiences=requested_audiences)
|
|
|
|
|
2020-05-13 15:04:22 +00:00
|
|
|
|
2020-05-09 18:00:07 +00:00
|
|
|
@auth_views.route('/login', methods=['GET', 'POST'])
|
2022-02-20 15:53:24 +00:00
|
|
|
async def login() -> ResponseReturnValue:
|
2020-05-21 11:20:27 +00:00
|
|
|
login_challenge = request.args.get('login_challenge')
|
2022-02-19 22:16:13 +00:00
|
|
|
if login_challenge is None:
|
|
|
|
return 'login_challenge missing', 400
|
2022-02-20 15:53:24 +00:00
|
|
|
login_request = await get_login_request.asyncio(_client=hydra_service.hydra_client, login_challenge=login_challenge)
|
2022-02-19 22:16:13 +00:00
|
|
|
if login_request is None or isinstance( login_request, GenericError):
|
2022-02-06 22:57:01 +00:00
|
|
|
logger.exception("could not fetch login request")
|
2020-06-01 21:43:10 +00:00
|
|
|
return redirect(url_for('frontend.index'))
|
2020-05-21 11:20:27 +00:00
|
|
|
|
|
|
|
if login_request.skip:
|
2022-02-20 15:53:24 +00:00
|
|
|
resp = await accept_login_request.asyncio(_client=hydra_service.hydra_client,
|
2022-02-19 22:16:13 +00:00
|
|
|
login_challenge=login_challenge,
|
|
|
|
json_body=AcceptLoginRequest(subject=login_request.subject))
|
|
|
|
if resp is None or isinstance( resp, GenericError):
|
|
|
|
return 'internal error, could not forward request', 503
|
|
|
|
|
2020-05-21 11:20:27 +00:00
|
|
|
return redirect(resp.redirect_to)
|
2020-05-09 18:00:07 +00:00
|
|
|
form = LoginForm()
|
|
|
|
if form.validate_on_submit():
|
2020-05-27 15:56:10 +00:00
|
|
|
user = User.query_().by_username(form.data['name'])
|
2020-05-13 18:08:28 +00:00
|
|
|
if user:
|
|
|
|
session['username'] = str(user.username)
|
|
|
|
else:
|
|
|
|
session['user'] = None
|
2020-05-09 18:00:07 +00:00
|
|
|
session['auth_providers'] = []
|
2020-05-26 20:55:37 +00:00
|
|
|
return redirect(
|
|
|
|
url_for('auth.login_auth', login_challenge=login_challenge))
|
|
|
|
return render_template('auth/login.html.j2', form=form)
|
2020-05-09 18:00:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
@auth_views.route('/login/auth', methods=['GET', 'POST'])
|
2022-02-20 15:53:24 +00:00
|
|
|
async def login_auth() -> ResponseReturnValue:
|
2020-05-21 11:20:27 +00:00
|
|
|
login_challenge = request.args.get('login_challenge')
|
2022-02-19 22:16:13 +00:00
|
|
|
if login_challenge is None:
|
|
|
|
return 'missing login_challenge, bad request', 400
|
2022-02-20 15:53:24 +00:00
|
|
|
login_request = await get_login_request.asyncio(_client=hydra_service.hydra_client, login_challenge=login_challenge)
|
2022-02-19 22:16:13 +00:00
|
|
|
if login_request is None:
|
2020-06-01 21:43:10 +00:00
|
|
|
return redirect(url_for('frontend.index'))
|
|
|
|
|
2020-05-09 18:00:07 +00:00
|
|
|
if 'username' not in session:
|
|
|
|
return redirect(url_for('auth.login'))
|
2020-05-13 15:04:22 +00:00
|
|
|
auth_forms = {}
|
2020-05-27 15:56:10 +00:00
|
|
|
user = User.query_().by_username(session['username'])
|
2020-05-09 18:00:07 +00:00
|
|
|
for auth_provider in AUTH_PROVIDER_LIST:
|
|
|
|
form = auth_provider.get_form()
|
|
|
|
if auth_provider.get_name() not in session['auth_providers'] and\
|
|
|
|
auth_provider.check_auth(user, form):
|
|
|
|
session['auth_providers'].append(auth_provider.get_name())
|
|
|
|
|
|
|
|
if auth_provider.get_name() not in session['auth_providers']:
|
2020-05-13 15:04:22 +00:00
|
|
|
auth_forms[auth_provider.get_name()]=form
|
2020-05-09 18:00:07 +00:00
|
|
|
|
|
|
|
if len(session['auth_providers']) >= 2:
|
2020-05-21 11:20:27 +00:00
|
|
|
remember_me = True
|
2020-05-27 15:56:10 +00:00
|
|
|
# if db_user is None:
|
|
|
|
# db_user = User(username=session['username'])
|
|
|
|
# db.session.add(db_user)
|
|
|
|
# db.session.commit()
|
2020-05-21 11:20:27 +00:00
|
|
|
|
2020-05-27 15:56:10 +00:00
|
|
|
subject = user.id
|
2020-06-01 21:43:10 +00:00
|
|
|
user.last_login = datetime.now()
|
|
|
|
db.session.commit()
|
2022-02-20 15:53:24 +00:00
|
|
|
resp = await accept_login_request.asyncio(_client=hydra_service.hydra_client,
|
2022-02-19 22:16:13 +00:00
|
|
|
login_challenge=login_challenge, json_body=AcceptLoginRequest(
|
|
|
|
subject=subject,
|
|
|
|
remember=remember_me,
|
|
|
|
))
|
|
|
|
if resp is None or isinstance( resp, GenericError):
|
|
|
|
return 'internal error, could not forward request', 503
|
2020-05-21 11:20:27 +00:00
|
|
|
return redirect(resp.redirect_to)
|
2020-05-26 20:55:37 +00:00
|
|
|
return render_template('auth/login_auth.html.j2', forms=auth_forms)
|
2020-05-09 18:00:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
@auth_views.route("/logout")
|
2022-02-20 15:53:24 +00:00
|
|
|
async def logout() -> ResponseReturnValue:
|
2020-05-21 11:20:27 +00:00
|
|
|
logout_challenge = request.args.get('logout_challenge')
|
2022-02-19 22:16:13 +00:00
|
|
|
if logout_challenge is None:
|
|
|
|
return 'invalid request, logout_challenge not set', 400
|
2020-05-27 15:56:10 +00:00
|
|
|
# TODO confirm
|
2022-02-20 15:53:24 +00:00
|
|
|
resp = await accept_logout_request.asyncio(_client=hydra_service.hydra_client, logout_challenge=logout_challenge)
|
2022-02-19 22:16:13 +00:00
|
|
|
if resp is None or isinstance( resp, GenericError):
|
|
|
|
return 'internal error, could not forward request', 503
|
2020-05-21 11:20:27 +00:00
|
|
|
return redirect(resp.redirect_to)
|
|
|
|
|
2020-05-09 18:00:07 +00:00
|
|
|
|
2022-02-11 15:09:40 +00:00
|
|
|
@auth_views.route("/error", methods=["GET"])
|
2022-02-19 22:16:13 +00:00
|
|
|
def auth_error() -> ResponseReturnValue:
|
2022-02-11 15:09:40 +00:00
|
|
|
error = request.args.get('error')
|
|
|
|
error_description = request.args.get('error_description')
|
|
|
|
|
|
|
|
return render_template('auth/error.html.j2', error=error, error_description=error_description)
|
|
|
|
|
2020-05-27 19:16:14 +00:00
|
|
|
|
2020-06-01 21:43:10 +00:00
|
|
|
@auth_views.route("/sign_up", methods=["GET"])
|
2020-05-27 19:16:14 +00:00
|
|
|
def sign_up():
|
2020-06-01 21:43:10 +00:00
|
|
|
form = RegistrationForm()
|
|
|
|
return render_template('auth/sign_up.html.j2', form=form)
|
|
|
|
|
|
|
|
@auth_views.route("/sign_up", methods=["POST"])
|
|
|
|
def sign_up_submit():
|
2020-05-27 19:16:14 +00:00
|
|
|
form = RegistrationForm()
|
|
|
|
if form.validate_on_submit():
|
|
|
|
user = UserSignUp()
|
|
|
|
user.username = form.data['username']
|
|
|
|
user.password = crypt.crypt(form.data['password'])
|
|
|
|
user.alternative_email = form.data['alternative_email']
|
|
|
|
db.session.add(user)
|
|
|
|
db.session.commit()
|
2020-06-01 21:43:10 +00:00
|
|
|
return jsonify({})
|
|
|
|
return jsonify({
|
|
|
|
'status': 'error',
|
|
|
|
'errors': form.errors
|
|
|
|
})
|