diff --git a/CHANGES.rst b/CHANGES.rst index 3480b8e7..7efef4fb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,14 +3,21 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog `_, and this project adheres to `Semantic Versioning `_. -Fixed +Added ***** -- OIDC jwks endpoint do not return empty kid claim +- If users register or authenticate during a OAuth Authorization + phase, they get redirected back to that page afterwards. + :issue:`168` :pr:`151` [0.0.33] - 2023-08-26 ===================== +Fixed +***** + +- OIDC jwks endpoint do not return empty kid claim + Added ***** diff --git a/canaille/core/account.py b/canaille/core/account.py index 44b19bf9..e3cbb726 100644 --- a/canaille/core/account.py +++ b/canaille/core/account.py @@ -34,6 +34,7 @@ from flask import redirect from flask import request from flask import send_file +from flask import session from flask import url_for from flask_babel import gettext as _ from flask_babel import refresh @@ -339,7 +340,12 @@ def registration(data=None, hash=None): user = profile_create(current_app, form) login_user(user) flash(_("Your account has been created successfully."), "success") - return redirect(url_for("core.account.profile_edition", edited_user=user)) + return redirect( + session.pop( + "redirect-after-login", + url_for("core.account.profile_edition", edited_user=user), + ) + ) @bp.route("/email-confirmation//") diff --git a/canaille/core/auth.py b/canaille/core/auth.py index 8be350cf..2e12d8eb 100644 --- a/canaille/core/auth.py +++ b/canaille/core/auth.py @@ -92,7 +92,7 @@ def password(): _("Connection successful. Welcome %(user)s", user=user.formatted_name[0]), "success", ) - return redirect(url_for("core.account.index")) + return redirect(session.pop("redirect-after-login", url_for("core.account.index"))) @bp.route("/logout") @@ -214,6 +214,9 @@ def reset(user, hash): login_user(user) flash(_("Your password has been updated successfully"), "success") - return redirect(url_for("core.account.profile_edition", edited_user=user)) + return session.pop( + "redirect-after-login", + url_for("core.account.profile_edition", edited_user=user), + ) return render_template("reset-password.html", form=form, user=user, hash=hash) diff --git a/canaille/core/forms.py b/canaille/core/forms.py index 6bc21b42..6e76c623 100644 --- a/canaille/core/forms.py +++ b/canaille/core/forms.py @@ -74,10 +74,6 @@ class PasswordForm(Form): ) -class FullLoginForm(LoginForm, PasswordForm): - pass - - class ForgottenPasswordForm(Form): login = wtforms.StringField( _("Login"), diff --git a/canaille/core/templates/login.html b/canaille/core/templates/login.html index 4406a2ed..a2799e84 100644 --- a/canaille/core/templates/login.html +++ b/canaille/core/templates/login.html @@ -27,10 +27,6 @@

{% call fui.render_form(form) %} {% block login_field scoped %}{{ login_field.render_field(form.login, class="autofocus") }}{% endblock %} - {% if "password" in form %} - {% block password_field scoped %}{{ login_field.render_field(form.password) }}{% endblock %} - {% endif %} -
{% if has_registration %} diff --git a/canaille/oidc/endpoints.py b/canaille/oidc/endpoints.py index 940ef377..13fe31a3 100644 --- a/canaille/oidc/endpoints.py +++ b/canaille/oidc/endpoints.py @@ -8,11 +8,9 @@ from canaille import csrf from canaille.app import models from canaille.app.flask import current_user -from canaille.app.flask import login_user from canaille.app.flask import logout_user from canaille.app.flask import set_parameter_in_url_query from canaille.app.themes import render_template -from canaille.core.forms import FullLoginForm from flask import abort from flask import Blueprint from flask import current_app @@ -69,26 +67,8 @@ def authorize(): if request.args.get("prompt") == "none": return jsonify({"error": "login_required"}) - form = FullLoginForm(request.form or None) - if request.method == "GET": - return render_template("login.html", form=form, menu=False) - - user = models.User.get_from_login(form.login.data) - if not form.validate() or not user: - flash(_("Login failed, please check your information"), "error") - return render_template("login.html", form=form, menu=False) - - success, message = user.check_password(form.password.data) - if not success: - flash( - _(message or "Login failed, please check your information"), - "error", - ) - return render_template("login.html", form=form, menu=False) - - login_user(user) - - return redirect(request.url) + session["redirect-after-login"] = request.url + return redirect(url_for("core.auth.login")) if not user.can_use_oidc: abort(400) @@ -133,11 +113,9 @@ def authorize(): form=form, ) - # request.method == "POST" if request.form["answer"] == "logout": - del session["user_id"] - flash(_("You have been successfully logged out."), "success") - return redirect(request.url) + session["redirect-after-login"] = request.url + return redirect(url_for("core.auth.logout")) if request.form["answer"] == "deny": grant_user = None diff --git a/canaille/oidc/templates/login.html b/canaille/oidc/templates/login.html deleted file mode 100644 index 3cce23f0..00000000 --- a/canaille/oidc/templates/login.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends theme('base.html') %} -{% import 'macro/flask.html' as flask %} -{% import 'macro/form.html' as fui %} -{% import 'partial/login_field.html' as login_field %} - -{% block container %} -
-
-
- {% if logo_url %} - - {{ website_name }} - - {% else %} - - {% endif %} - -

-
- {{ _("Sign in at %(website)s", website=website_name) }} -
-
{% trans %}Manage your information and your authorizations{% endtrans %}
-

- - {{ flask.messages() }} - - {% call fui.render_form(form) %} - {% block login_field scoped %}{{ login_field.render_field(form.login, class="autofocus") }}{% endblock %} - - {% if "password" in form %} - {% block password_field scoped %}{{ login_field.render_field(form.password) }}{% endblock %} - {% endif %} - -
-
- {% if has_smtp and has_password_recovery %} - {{ _("Forgotten password") }} - {% endif %} - -
-
- {% endcall %} -
-
-
-{% endblock %} diff --git a/canaille/oidc/templates/partial/login_field.html b/canaille/oidc/templates/partial/login_field.html deleted file mode 100644 index 09cf4fe3..00000000 --- a/canaille/oidc/templates/partial/login_field.html +++ /dev/null @@ -1,9 +0,0 @@ -{% import 'macro/form.html' as fui %} -{% macro render_field(field) %} - {% if field.name == "login" %} - {{ fui.render_field(field, icon="user", noindicator=true, class="autofocus", **kwargs) }} - {% endif %} - {% if field.name == "password" %} - {{ fui.render_field(field, icon="key", noindicator=true, **kwargs) }} - {% endif %} -{% endmacro %} diff --git a/tests/oidc/test_authorization_code_flow.py b/tests/oidc/test_authorization_code_flow.py index f1ed8ab9..846616e0 100644 --- a/tests/oidc/test_authorization_code_flow.py +++ b/tests/oidc/test_authorization_code_flow.py @@ -234,16 +234,20 @@ def test_logout_login(testclient, logged_user, client): status=200, ) - res = res.form.submit(name="answer", value="logout", status=302) + res = res.form.submit(name="answer", value="logout") + res = res.follow() g.user = None - res = res.follow(status=200) + res = res.follow() + res = res.follow() res.form["login"] = logged_user.user_name[0] + res = res.form.submit() + res = res.follow() + res.form["password"] = "wrong password" res = res.form.submit(status=200) assert ("error", "Login failed, please check your information") in res.flashes - res.form["login"] = logged_user.user_name[0] res.form["password"] = "correct horse battery staple" res = res.form.submit(status=302) res = res.follow(status=200) @@ -322,14 +326,16 @@ def test_refresh_token(testclient, user, client): scope="openid profile", nonce="somenonce", ), - status=200, ) + res = res.follow() res.form["login"] = "user" + res = res.form.submit(name="answer", value="accept") + res = res.follow() res.form["password"] = "correct horse battery staple" - res = res.form.submit(name="answer", value="accept", status=302) + res = res.form.submit(name="answer", value="accept") res = res.follow() - res = res.form.submit(name="answer", value="accept", status=302) + res = res.form.submit(name="answer", value="accept") assert res.location.startswith(client.redirect_uris[0]) params = parse_qs(urlsplit(res.location).query) @@ -591,11 +597,12 @@ def test_authorization_code_flow_but_user_cannot_use_oidc( scope="openid profile", nonce="somenonce", ), - status=200, ) + res = res.follow() res.form["login"] = "user" - res = res.form.submit(status=200) + res = res.form.submit() + res = res.follow() res.form["password"] = "correct horse battery staple" res = res.form.submit(status=302) @@ -784,9 +791,12 @@ def test_authorization_code_expired(testclient, user, client): scope="openid profile email groups address phone", nonce="somenonce", ), - status=200, ) + res = res.follow() + res.form["login"] = "user" + res = res.form.submit(name="answer", value="accept").follow() + res.form["password"] = "correct horse battery staple" res = res.form.submit(name="answer", value="accept").follow() res = res.form.submit(name="answer", value="accept", status=302) @@ -829,9 +839,11 @@ def test_code_with_invalid_user(testclient, admin, client): scope="openid profile email groups address phone", nonce="somenonce", ), - status=200, - ) + ).follow() + res.form["login"] = "temp" + res = res.form.submit(name="answer", value="accept", status=302).follow() + res.form["password"] = "correct horse battery staple" res = res.form.submit(name="answer", value="accept", status=302).follow() res = res.form.submit(name="answer", value="accept", status=302) @@ -877,10 +889,11 @@ def test_refresh_token_with_invalid_user(testclient, client): scope="openid profile", nonce="somenonce", ), - status=200, - ) + ).follow() res.form["login"] = "temp" + res = res.form.submit(name="answer", value="accept", status=302).follow() + res.form["password"] = "correct horse battery staple" res = res.form.submit(name="answer", value="accept", status=302).follow() res = res.form.submit(name="answer", value="accept", status=302) diff --git a/tests/oidc/test_hybrid_flow.py b/tests/oidc/test_hybrid_flow.py index 23858d28..dfc971c0 100644 --- a/tests/oidc/test_hybrid_flow.py +++ b/tests/oidc/test_hybrid_flow.py @@ -14,15 +14,15 @@ def test_oauth_hybrid(testclient, backend, user, client): scope="openid profile", nonce="somenonce", ), - status=200, - ) + ).follow() assert "text/html" == res.content_type, res.json res.form["login"] = user.user_name[0] + res = res.form.submit(status=302).follow() + res.form["password"] = "correct horse battery staple" - res = res.form.submit(status=302) + res = res.form.submit(status=302).follow() - res = res.follow(status=200) assert "text/html" == res.content_type, res.json res = res.form.submit(name="answer", value="accept", status=302) diff --git a/tests/oidc/test_implicit_flow.py b/tests/oidc/test_implicit_flow.py index 61b3d279..321a4027 100644 --- a/tests/oidc/test_implicit_flow.py +++ b/tests/oidc/test_implicit_flow.py @@ -19,14 +19,15 @@ def test_oauth_implicit(testclient, user, client): scope="profile", nonce="somenonce", ), - ) + ).follow() assert "text/html" == res.content_type res.form["login"] = "user" + res = res.form.submit(status=302).follow() + res.form["password"] = "correct horse battery staple" - res = res.form.submit(status=302) + res = res.form.submit(status=302).follow() - res = res.follow() assert "text/html" == res.content_type, res.json res = res.form.submit(name="answer", value="accept", status=302) @@ -63,14 +64,15 @@ def test_oidc_implicit(testclient, keypair, user, client, other_client): scope="openid profile", nonce="somenonce", ), - ) + ).follow() assert "text/html" == res.content_type res.form["login"] = "user" + res = res.form.submit(status=302).follow() + res.form["password"] = "correct horse battery staple" - res = res.form.submit(status=302) + res = res.form.submit(status=302).follow() - res = res.follow(status=200) assert "text/html" == res.content_type, res.json res = res.form.submit(name="answer", value="accept", status=302) @@ -117,14 +119,15 @@ def test_oidc_implicit_with_group( scope="openid profile groups", nonce="somenonce", ), - ) + ).follow() assert "text/html" == res.content_type res.form["login"] = "user" + res = res.form.submit(status=302).follow() + res.form["password"] = "correct horse battery staple" - res = res.form.submit(status=302) + res = res.form.submit(status=302).follow() - res = res.follow(status=200) assert "text/html" == res.content_type, res.json res = res.form.submit(name="answer", value="accept", status=302)