"""OIDC callback logic without an Okta tenant. We monkeypatch Authlib's token exchange to return a userinfo dict, which exercises the whole /authorize view (allow / deny / role / session / redirect). Authlib's own state and id_token validation is Authlib's responsibility, not ours, so we don't re-test it. """ from pathlib import Path import pytest import auth from config_store import ConfigStore def _oidc_config(tmp_path) -> Path: cfg = tmp_path / "config.toml" cfg.write_text( '[pps]\nbase_url="http://x:10000"\nusername="u"\npassword="p"\n' '[quarantine]\nfolders=["Quarantine"]\ndefault_folder="Quarantine"\n' 'deleted_folder="Quarantine"\nlist_query="from=*"\n' 'default_sort_field="subject"\ndefault_sort_dir="asc"\n' '[quarantine.report_release]\nsteps=["release"]\nmove_target=""\n' '[app]\nsecret_key="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"\n' '[auth]\nmode="oidc"\ndenied_message="Nope."\n' '[[auth.users]]\nemail="alice@example.com"\nrole="admin"\n' '[[auth.users]]\nemail="bob@example.com"\nrole="user"\n' '[okta]\nissuer="https://ex.okta.com"\nclient_id="cid"\n' 'client_secret="csec"\nredirect_uri="https://app.example.com/authorize"\n' ) return cfg @pytest.fixture def oidc_client(tmp_path, monkeypatch): from app import create_app from prefs import PrefStore from worker import JobQueue store = ConfigStore(_oidc_config(tmp_path)) queue = JobQueue(str(tmp_path / "jobs.db"), lambda: None, lambda: store.snapshot().quarantine, ops_log_path=str(tmp_path / "w.log")) prefs = PrefStore(str(tmp_path / "jobs.db")) app = create_app(store, queue, prefs) app.config["TESTING"] = True return app.test_client() def _patch_token(monkeypatch, email): monkeypatch.setattr( auth._oauth.okta, "authorize_access_token", lambda: {"userinfo": {"email": email, "name": email.split("@")[0]}}, raising=False, ) def test_authorize_allows_known_user(oidc_client, monkeypatch): _patch_token(monkeypatch, "alice@example.com") r = oidc_client.get("/authorize") assert r.status_code == 302 # redirected into the app cfg = oidc_client.get("/api/config").get_json() assert cfg["is_admin"] is True def test_authorize_denies_unknown_user(oidc_client, monkeypatch): _patch_token(monkeypatch, "stranger@evil.com") r = oidc_client.get("/authorize") assert r.status_code == 403 assert b"Nope." in r.data def test_authorize_role_from_list(oidc_client, monkeypatch): _patch_token(monkeypatch, "bob@example.com") oidc_client.get("/authorize") cfg = oidc_client.get("/api/config").get_json() assert cfg["is_admin"] is False def test_role_reresolved_on_demotion(oidc_client, monkeypatch): _patch_token(monkeypatch, "alice@example.com") oidc_client.get("/authorize") assert oidc_client.get("/api/admin/config").status_code == 200 # Demote alice in the live config -> next request loses admin without re-login. from flask import current_app store = oidc_client.application.config["STORE"] store.apply({"auth": {"users": [ {"email": "alice@example.com", "role": "user"}, {"email": "bob@example.com", "role": "user"}, ]}}, actor="t") assert oidc_client.get("/api/admin/config").status_code == 403