canaille-globuzma/tests/oidc/test_end_session.py
Éloi Rivard 57af18d557 Use a unique identifier to indentify users in URLS
Previously we used the uid since we supposed this value was always
valid, but some users user the mail attribute as the User RDN in their
OpenLDAP installation, and do not have a uuid.
2023-06-29 15:55:39 +02:00

405 lines
12 KiB
Python

from authlib.oidc.core.grants.util import generate_id_token
from canaille.oidc.oauth import generate_user_info
from canaille.oidc.oauth import get_jwt_config
def test_end_session(testclient, backend, logged_user, client, id_token):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=302,
)
assert res.location == f"{post_logout_redirect_url}?state=foobar"
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_end_session_no_client_id(testclient, backend, logged_user, client, id_token):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=302,
)
assert res.location == f"{post_logout_redirect_url}?state=foobar"
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_no_redirect_uri_no_redirect(
testclient, backend, logged_user, client, id_token
):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"state": "foobar",
},
status=302,
)
assert res.location == "/"
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_bad_redirect_uri_no_redirect(
testclient, backend, logged_user, client, id_token
):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/invalid-uri"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=302,
)
assert res.location == "/"
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_no_client_hint_no_redirect(testclient, backend, logged_user, client, id_token):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.identifier,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=200,
)
res = res.form.submit(name="answer", value="logout", status=302)
res = res.follow(status=302)
assert res.location == "/"
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_end_session_invalid_client_id(testclient, backend, logged_user, client):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.identifier,
"post_logout_redirect_uri": post_logout_redirect_url,
"client_id": "invalid_client_id",
"state": "foobar",
},
status=200,
)
res = res.form.submit(name="answer", value="logout", status=302)
res = res.follow(status=302)
assert res.location == "/"
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_client_hint_invalid(testclient, backend, logged_user, client):
id_token = generate_id_token(
{},
generate_user_info(logged_user, client.scope),
aud="invalid-client-id",
**get_jwt_config(None),
)
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=302,
)
assert res.location == "/"
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_no_jwt_logout(testclient, backend, logged_user, client):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=200,
)
res = res.form.submit(name="answer", value="logout", status=302)
res = res.follow(status=302)
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
assert res.location == f"{post_logout_redirect_url}?state=foobar"
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_no_jwt_no_logout(testclient, backend, logged_user, client):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=200,
)
res = res.form.submit(name="answer", value="stay", status=302)
with testclient.session_transaction() as sess:
assert sess.get("user_id")
assert res.location == "/"
testclient.get(f"/profile/{logged_user.identifier}", status=200)
def test_jwt_not_issued_here(testclient, backend, logged_user, client, id_token):
testclient.app.config["OIDC"]["JWT"]["ISS"] = "https://foo.bar"
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=200,
)
assert res.json == {
"message": "id_token_hint has not been issued here",
"status": "error",
}
def test_client_hint_mismatch(testclient, backend, logged_user, client):
id_token = generate_id_token(
{},
generate_user_info(logged_user, client.scope),
aud="another_client_id",
**get_jwt_config(None),
)
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=200,
)
assert res.json == {
"message": "id_token_hint and client_id don't match",
"status": "error",
}
def test_bad_user_id_token_mismatch(testclient, backend, logged_user, client, admin):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
id_token = generate_id_token(
{},
generate_user_info(admin, client.scope),
aud=client.client_id,
**get_jwt_config(None),
)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=200,
)
res = res.form.submit(name="answer", value="logout", status=302)
res = res.follow(status=302)
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
assert res.location == f"{post_logout_redirect_url}?state=foobar"
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_bad_user_hint(testclient, backend, logged_user, client, id_token, admin):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": admin.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=200,
)
res = res.form.submit(name="answer", value="logout", status=302)
res = res.follow(status=302)
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
assert res.location == f"{post_logout_redirect_url}?state=foobar"
testclient.get(f"/profile/{logged_user.identifier}", status=403)
def test_no_jwt_bad_csrf(testclient, backend, logged_user, client):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=200,
)
form = res.form
form["csrf_token"] = "foobar"
res = form.submit(name="answer", value="logout", status=400)
def test_end_session_already_disconnected(testclient, backend, user, client, id_token):
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
status=302,
)
assert res.location == "/"
def test_end_session_no_state(testclient, backend, logged_user, client, id_token):
testclient.get(f"/profile/{logged_user.identifier}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.identifier,
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
},
status=302,
)
assert res.location == post_logout_redirect_url
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.identifier}", status=403)