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["CANAILLE_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 audience and client_id don't match", "status": "error", } 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) 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, expect_errors=True) 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)