| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- """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
|