test_oidc.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. """OIDC callback logic without an Okta tenant.
  2. We monkeypatch Authlib's token exchange to return a userinfo dict, which exercises the
  3. whole /authorize view (allow / deny / role / session / redirect). Authlib's own state and
  4. id_token validation is Authlib's responsibility, not ours, so we don't re-test it.
  5. """
  6. from pathlib import Path
  7. import pytest
  8. import auth
  9. from config_store import ConfigStore
  10. def _oidc_config(tmp_path) -> Path:
  11. cfg = tmp_path / "config.toml"
  12. cfg.write_text(
  13. '[pps]\nbase_url="http://x:10000"\nusername="u"\npassword="p"\n'
  14. '[quarantine]\nfolders=["Quarantine"]\ndefault_folder="Quarantine"\n'
  15. 'deleted_folder="Quarantine"\nlist_query="from=*"\n'
  16. 'default_sort_field="subject"\ndefault_sort_dir="asc"\n'
  17. '[quarantine.report_release]\nsteps=["release"]\nmove_target=""\n'
  18. '[app]\nsecret_key="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"\n'
  19. '[auth]\nmode="oidc"\ndenied_message="Nope."\n'
  20. '[[auth.users]]\nemail="alice@example.com"\nrole="admin"\n'
  21. '[[auth.users]]\nemail="bob@example.com"\nrole="user"\n'
  22. '[okta]\nissuer="https://ex.okta.com"\nclient_id="cid"\n'
  23. 'client_secret="csec"\nredirect_uri="https://app.example.com/authorize"\n'
  24. )
  25. return cfg
  26. @pytest.fixture
  27. def oidc_client(tmp_path, monkeypatch):
  28. from app import create_app
  29. from prefs import PrefStore
  30. from worker import JobQueue
  31. store = ConfigStore(_oidc_config(tmp_path))
  32. queue = JobQueue(str(tmp_path / "jobs.db"), lambda: None,
  33. lambda: store.snapshot().quarantine, ops_log_path=str(tmp_path / "w.log"))
  34. prefs = PrefStore(str(tmp_path / "jobs.db"))
  35. app = create_app(store, queue, prefs)
  36. app.config["TESTING"] = True
  37. return app.test_client()
  38. def _patch_token(monkeypatch, email):
  39. monkeypatch.setattr(
  40. auth._oauth.okta, "authorize_access_token",
  41. lambda: {"userinfo": {"email": email, "name": email.split("@")[0]}},
  42. raising=False,
  43. )
  44. def test_authorize_allows_known_user(oidc_client, monkeypatch):
  45. _patch_token(monkeypatch, "alice@example.com")
  46. r = oidc_client.get("/authorize")
  47. assert r.status_code == 302 # redirected into the app
  48. cfg = oidc_client.get("/api/config").get_json()
  49. assert cfg["is_admin"] is True
  50. def test_authorize_denies_unknown_user(oidc_client, monkeypatch):
  51. _patch_token(monkeypatch, "stranger@evil.com")
  52. r = oidc_client.get("/authorize")
  53. assert r.status_code == 403
  54. assert b"Nope." in r.data
  55. def test_authorize_role_from_list(oidc_client, monkeypatch):
  56. _patch_token(monkeypatch, "bob@example.com")
  57. oidc_client.get("/authorize")
  58. cfg = oidc_client.get("/api/config").get_json()
  59. assert cfg["is_admin"] is False
  60. def test_role_reresolved_on_demotion(oidc_client, monkeypatch):
  61. _patch_token(monkeypatch, "alice@example.com")
  62. oidc_client.get("/authorize")
  63. assert oidc_client.get("/api/admin/config").status_code == 200
  64. # Demote alice in the live config -> next request loses admin without re-login.
  65. from flask import current_app
  66. store = oidc_client.application.config["STORE"]
  67. store.apply({"auth": {"users": [
  68. {"email": "alice@example.com", "role": "user"},
  69. {"email": "bob@example.com", "role": "user"},
  70. ]}}, actor="t")
  71. assert oidc_client.get("/api/admin/config").status_code == 403