diff --git a/lenticular_cloud/app.py b/lenticular_cloud/app.py index 70e0f89..7028463 100644 --- a/lenticular_cloud/app.py +++ b/lenticular_cloud/app.py @@ -144,9 +144,9 @@ def oidc_provider_init_app(name=None): app.babel = Babel(app) app.login_manager = LoginManager(app) - init_login_manager(app) - from .views import oidc_provider_views, auth_views, frontend_views + from .views import oidc_provider_views, auth_views, frontend_views, init_login_manager + init_login_manager(app) app.register_blueprint(oidc_provider_views) app.register_blueprint(auth_views) app.register_blueprint(frontend_views) @@ -171,16 +171,3 @@ def oidc_provider_init_app(name=None): return app - -def init_login_manager(app): - @app.login_manager.user_loader - def user_loader(username): - return model.User.query().by_username(username) - - @app.login_manager.request_loader - def request_loader(request): - pass - - @app.login_manager.unauthorized_handler - def unauthorized(): - return redirect(url_for('auth.login')) diff --git a/lenticular_cloud/form/frontend.py b/lenticular_cloud/form/frontend.py index fcc05c9..e116383 100644 --- a/lenticular_cloud/form/frontend.py +++ b/lenticular_cloud/form/frontend.py @@ -32,3 +32,7 @@ class TOTPForm(FlaskForm): class TOTPDeleteForm(FlaskForm): submit = SubmitField(gettext('Delete')) + + +class OidcAuthenticationConfirm(FlaskForm): + submit = SubmitField(gettext('Continue')) diff --git a/lenticular_cloud/views/__init__.py b/lenticular_cloud/views/__init__.py index 61ae5fd..51b33e1 100644 --- a/lenticular_cloud/views/__init__.py +++ b/lenticular_cloud/views/__init__.py @@ -1,5 +1,5 @@ # pylint: disable=unused-import from .oidc import oidc_provider_views -from .auth import auth_views +from .auth import auth_views, init_login_manager from .frontend import frontend_views diff --git a/lenticular_cloud/views/auth.py b/lenticular_cloud/views/auth.py index 57b2c6d..b9326be 100644 --- a/lenticular_cloud/views/auth.py +++ b/lenticular_cloud/views/auth.py @@ -18,7 +18,8 @@ from flask import Blueprint, render_template, request, url_for from flask_login import login_required, login_user, logout_user from werkzeug.utils import redirect import logging - +from urllib.parse import urlparse +from base64 import b64decode, b64encode from ..model import User, SecurityUser from ..form.login import LoginForm @@ -29,6 +30,19 @@ from .oidc import do_logout auth_views = Blueprint('auth', __name__, url_prefix='') +def init_login_manager(app): + @app.login_manager.user_loader + def user_loader(username): + return User.query().by_username(username) + + @app.login_manager.request_loader + def request_loader(request): + pass + + @app.login_manager.unauthorized_handler + def unauthorized(): + return redirect(url_for('auth.login', next=b64encode(request.url.encode()))) + @auth_views.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() @@ -36,7 +50,7 @@ def login(): user = User.query().by_username(form.data['name']) session['username'] = str(user.username) session['auth_providers'] = [] - return redirect(url_for('auth.login_auth')) + return redirect(url_for('auth.login_auth', next=flask.request.args.get('next'))) return render_template('frontend/login.html.j2', form=form) @@ -44,7 +58,7 @@ def login(): def login_auth(): if 'username' not in session: return redirect(url_for('auth.login')) - auth_forms = [] + auth_forms = {} user = User.query().by_username(session['username']) for auth_provider in AUTH_PROVIDER_LIST: form = auth_provider.get_form() @@ -53,14 +67,20 @@ def login_auth(): session['auth_providers'].append(auth_provider.get_name()) if auth_provider.get_name() not in session['auth_providers']: - auth_forms.append(form) + auth_forms[auth_provider.get_name()]=form if len(session['auth_providers']) >= 2: login_user(SecurityUser(session['username'])) # TODO use this var - _next = request.args.get('next') - return redirect(url_for('frontend.index')) - print(auth_forms) + _next = None + try: + _next_url = urlparse(b64decode(request.args.get('next')).decode()) + _host_url = urlparse(request.url) + if _next_url.scheme == _host_url.scheme and _next_url.netloc == _host_url.netloc : + _next = _next_url.geturl() + except Exception as e: + raise e + return redirect(_next or url_for('frontend.index')) return render_template('frontend/login_auth.html.j2', forms=auth_forms) diff --git a/lenticular_cloud/views/oidc.py b/lenticular_cloud/views/oidc.py index 3a7eb59..4bc97d4 100644 --- a/lenticular_cloud/views/oidc.py +++ b/lenticular_cloud/views/oidc.py @@ -1,7 +1,7 @@ from urllib.parse import urlencode, parse_qs import flask -from flask import Blueprint, redirect +from flask import Blueprint, redirect, request from flask import current_app, session from flask import jsonify from flask.helpers import make_response @@ -14,6 +14,8 @@ from pyop.exceptions import InvalidAuthenticationRequest, InvalidAccessToken, In InvalidSubjectIdentifier, InvalidClientRegistrationRequest from pyop.util import should_fragment_encode +from ..form.frontend import OidcAuthenticationConfirm + oidc_provider_views = Blueprint('oidc_provider', __name__, url_prefix='') @@ -29,10 +31,17 @@ def registration_endpoint(): @oidc_provider_views.route('/authentication', methods=['GET']) @login_required +def authentication_endpoint_confirm(): + form = OidcAuthenticationConfirm() + return render_template('frontend/oidc_authentication.html.j2', form=form) + + +@oidc_provider_views.route('/authentication', methods=['POST']) +@login_required def authentication_endpoint(): # parse authentication request - print(flask.request) - print(flask.request.headers) + print(request) + print(request.headers) try: auth_req = current_app.provider.parse_authentication_request(urlencode(flask.request.args), flask.request.args) @@ -45,7 +54,7 @@ def authentication_endpoint(): # show error to user return make_response('Something went wrong: {}'.format(str(e)), 400) - # automagic authentication + # automatically authentication authn_response = current_app.provider.authorize(auth_req, str(current_user.username)) response_url = authn_response.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) return redirect(response_url, 303) diff --git a/templates/frontend/login.html.j2 b/templates/frontend/login.html.j2 index 80b01c6..c93aa15 100644 --- a/templates/frontend/login.html.j2 +++ b/templates/frontend/login.html.j2 @@ -4,15 +4,8 @@ {% block content %} -
- {{ form.csrf_token }} -
- -
- {{ form.name(size=20) }} -
-
- {{ form.submit() }} -
+ +{{ render_form(form) }} + {% endblock %} diff --git a/templates/frontend/login_auth.html.j2 b/templates/frontend/login_auth.html.j2 index 7523f59..ff60a6b 100644 --- a/templates/frontend/login_auth.html.j2 +++ b/templates/frontend/login_auth.html.j2 @@ -4,9 +4,21 @@ {% block content %} -{% for form in forms %} - {{ render_form(form) }} -{% endfor %} + +
+ {% for auth_name, form in forms.items() %} +
+ + {{ render_form(form) }} +
+ {% endfor %} +
{% endblock %} diff --git a/templates/frontend/oidc_authentication.html.j2 b/templates/frontend/oidc_authentication.html.j2 new file mode 100644 index 0000000..c2853be --- /dev/null +++ b/templates/frontend/oidc_authentication.html.j2 @@ -0,0 +1,17 @@ +{% extends 'frontend/base.html.j2' %} + +{% block title %}{{ gettext('Oauth2 Request') }}{% endblock %} + +{% block content %} + +
+

+ A service ask for access to your account data.
+ + You can confirm this pressing "Continue" +

+ + {{ render_form(form) }} +
+ +{% endblock %}