fix: post_logout_redirect_uris was ignored during OIDC dynamic registration

This commit is contained in:
Éloi Rivard 2023-12-23 21:32:31 +01:00
parent 52ce547a54
commit 06b60e1747
No known key found for this signature in database
GPG key ID: 7EDA204EA57DD184
9 changed files with 85 additions and 33 deletions

View file

@ -12,6 +12,7 @@ Fixed
*****
- Correctly set up Client audience during OIDC dynamic registration.
- ``post_logout_redirect_uris`` was ignored during OIDC dynamic registration.
[0.0.40] - 2023-12-22
=====================

View file

@ -3,6 +3,7 @@ import uuid
from authlib.integrations.flask_oauth2 import current_token
from authlib.jose import jwt
from authlib.jose.errors import JoseError
from authlib.oauth2 import OAuth2Error
from canaille import csrf
from canaille.app import models
@ -273,9 +274,18 @@ def end_session():
return render_template("logout.html", form=form, client=client, menu=False)
if data.get("id_token_hint"):
try:
id_token = jwt.decode(
data["id_token_hint"], current_app.config["OIDC"]["JWT"]["PUBLIC_KEY"]
)
except JoseError as exc:
return jsonify(
{
"status": "error",
"message": str(exc),
}
)
if not id_token["iss"] == get_issuer():
return jsonify(
{

View file

@ -398,6 +398,10 @@ class ClientRegistrationEndpoint(ClientManagementMixin, _ClientRegistrationEndpo
def save_client(self, client_info, client_metadata, request):
client = models.Client(
# this won't be needed when OIDC RP Initiated Logout is
# directly implemented in authlib:
# https://gitlab.com/yaal/canaille/-/issues/157
post_logout_redirect_uris=request.data.get("post_logout_redirect_uris"),
**self.client_convert_data(**client_info, **client_metadata)
)
client.audience = [client]

View file

@ -13,23 +13,10 @@ from flask import session
from flask import url_for
def create_app():
app = Flask(__name__)
app.config.from_envvar("CONFIG")
app.static_folder = "../../canaille/static"
oauth = OAuth()
oauth = OAuth()
oauth.init_app(app)
oauth.register(
name="canaille",
client_id=app.config["OAUTH_CLIENT_ID"],
client_secret=app.config["OAUTH_CLIENT_SECRET"],
server_metadata_url=get_well_known_url(
app.config["OAUTH_AUTH_SERVER"], external=True
),
client_kwargs={"scope": "openid profile email phone address groups"},
)
def setup_routes(app):
@app.route("/")
@app.route("/tos")
@app.route("/policy")
@ -40,10 +27,12 @@ def create_app():
@app.route("/login")
def login():
return oauth.canaille.authorize_redirect(url_for("authorize", _external=True))
return oauth.canaille.authorize_redirect(
url_for("login_callback", _external=True)
)
@app.route("/authorize")
def authorize():
@app.route("/login_callback")
def login_callback():
try:
token = oauth.canaille.authorize_access_token()
session["user"] = token.get("userinfo")
@ -56,13 +45,6 @@ def create_app():
@app.route("/logout")
def logout():
try:
del session["user"]
except KeyError:
pass
flash("You have been successfully logged out", "success")
oauth.canaille.load_server_metadata()
end_session_endpoint = oauth.canaille.server_metadata.get(
"end_session_endpoint"
@ -71,10 +53,41 @@ def create_app():
end_session_endpoint,
client_id=current_app.config["OAUTH_CLIENT_ID"],
id_token_hint=session["id_token"],
post_logout_redirect_uri=url_for("index", _external=True),
post_logout_redirect_uri=url_for("logout_callback", _external=True),
)
return redirect(end_session_url)
@app.route("/logout_callback")
def logout_callback():
try:
del session["user"]
except KeyError:
pass
flash("You have been successfully logged out", "success")
return redirect(url_for("index"))
def setup_oauth(app):
oauth.init_app(app)
oauth.register(
name="canaille",
client_id=app.config["OAUTH_CLIENT_ID"],
client_secret=app.config["OAUTH_CLIENT_SECRET"],
server_metadata_url=get_well_known_url(
app.config["OAUTH_AUTH_SERVER"], external=True
),
client_kwargs={"scope": "openid profile email phone address groups"},
)
def create_app():
app = Flask(__name__)
app.config.from_envvar("CONFIG")
app.static_folder = "../../canaille/static"
setup_routes(app)
setup_oauth(app)
return app

View file

@ -1,6 +1,7 @@
SECRET_KEY="46bf9fb5-88d5-489b-9312-899588377ff0"
NAME = "Client 1"
SESSION_COOKIE_NAME="client1-session"
SERVER_NAME="localhost:5001"
OAUTH_CLIENT_ID="1JGkkzCbeHpGtlqgI5EENByf"
OAUTH_CLIENT_SECRET="2xYPSReTQRmGG1yppMVZQ0ASXwFejPyirvuPbKhNa6TmKC5x"

View file

@ -1,6 +1,7 @@
SECRET_KEY="8e953ecc-13be-497b-806f-c65faa1e328f"
NAME = "Client 2"
SESSION_COOKIE_NAME="client2-session"
SERVER_NAME="localhost:5002"
OAUTH_CLIENT_ID="gn4yFN7GDykL7QP8v8gS9YfV"
OAUTH_CLIENT_SECRET="ouFJE5WpICt6hxTyf8icXPeeklMektMY4gV0Rmf3aY60VElA"

View file

@ -107,8 +107,8 @@ def populate(app):
client_name="Client1",
contacts=["admin@mydomain.tld"],
client_uri="http://localhost:5001",
redirect_uris=["http://localhost:5001/authorize"],
post_logout_redirect_uris=["http://localhost:5001/"],
redirect_uris=["http://localhost:5001/login_callback"],
post_logout_redirect_uris=["http://localhost:5001/logout_callback"],
tos_uri="http://localhost:5001/tos",
policy_uri="http://localhost:5001/policy",
grant_types=["authorization_code", "refresh_token"],
@ -126,8 +126,8 @@ def populate(app):
client_name="Client2",
contacts=["admin@mydomain.tld"],
client_uri="http://localhost:5002",
redirect_uris=["http://localhost:5002/authorize"],
post_logout_redirect_uris=["http://localhost:5002/"],
redirect_uris=["http://localhost:5002/login_callback"],
post_logout_redirect_uris=["http://localhost:5002/logout_callback"],
tos_uri="http://localhost:5002/tos",
policy_uri="http://localhost:5002/policy",
grant_types=["authorization_code", "refresh_token"],

View file

@ -19,6 +19,9 @@ def test_client_registration_with_authentication_static_token(
"https://client.example.org/callback",
"https://client.example.org/callback2",
],
"post_logout_redirect_uris": [
"https://client.example.org/logout_callback",
],
"client_name": "My Example Client",
"token_endpoint_auth_method": "client_secret_basic",
"logo_uri": "https://client.example.org/logo.webp",
@ -53,6 +56,9 @@ def test_client_registration_with_authentication_static_token(
"https://client.example.org/callback",
"https://client.example.org/callback2",
]
assert client.post_logout_redirect_uris == [
"https://client.example.org/logout_callback",
]
assert client.token_endpoint_auth_method == "client_secret_basic"
assert client.logo_uri == "https://client.example.org/logo.webp"
assert client.jwks_uri == "https://client.example.org/my_public_keys.jwks"

View file

@ -284,6 +284,22 @@ def test_client_hint_mismatch(testclient, backend, logged_user, client):
}
def test_end_session_bad_id_token(testclient, backend, logged_user, client, id_token):
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": "invalid",
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
)
assert res.json == {"status": "error", "message": "Invalid input segments length: "}
def test_bad_user_id_token_mismatch(testclient, backend, logged_user, client, admin):
testclient.get(f"/profile/{logged_user.identifier}", status=200)