Spaces:
Sleeping
Sleeping
File size: 10,235 Bytes
0fdbc7e fdfbfb3 99e8ddc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | """The `gr.api` machine endpoints are gated by caller-token verification (ADR-0004/0018).
A machine call carries no OAuth session, so identity comes from a caller token resolved via
`whoami`. These endpoints are the orchestrator's front door β if they are open, the allow-list
on the UI is decorative.
"""
from __future__ import annotations
import json
import pytest
import gradio_ui
class _Request:
"""Minimal stand-in for `gr.Request` β only `.headers.get` is used."""
def __init__(self, headers=None):
self.headers = headers or {}
@pytest.fixture(autouse=True)
def _enforce(monkeypatch, tmp_path):
monkeypatch.setenv("ACCESS_CONTROL", "enforce")
monkeypatch.setenv("ALLOWED_IDS", "orchestrator-bot")
monkeypatch.setenv("LOG_SINK", "local")
monkeypatch.setenv("LOG_SINK_LOCAL_DIR", str(tmp_path / "run_logs"))
gradio_ui._WHOAMI_CACHE.clear()
yield
gradio_ui._WHOAMI_CACHE.clear()
def _explode(*a, **k): # pragma: no cover
raise AssertionError("tool was called despite a denied access decision")
MACHINE_CASES = [
("query_variant_status", {"genes": "KRAS", "source": "cbioportal:paad_tcga"}),
("variant_by_subtype", {"genes": "KRAS", "source": "cbioportal:paad_tcga"}),
("panel", {}),
]
@pytest.mark.parametrize("endpoint,kwargs", MACHINE_CASES)
def test_machine_endpoint_denies_tokenless_call(monkeypatch, endpoint, kwargs):
"""No token header β denied, fail-closed, before any tool call."""
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode)
monkeypatch.setattr(gradio_ui, "_variant_by_subtype", _explode)
body = json.loads(getattr(gradio_ui, endpoint)(**kwargs, request=_Request()))
assert body["status"] == "denied"
@pytest.mark.parametrize("endpoint,kwargs", MACHINE_CASES)
def test_machine_endpoint_denies_unresolvable_token(monkeypatch, endpoint, kwargs):
"""A token `whoami` cannot resolve is an anonymous caller, not a trusted one."""
monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: None)
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode)
monkeypatch.setattr(gradio_ui, "_variant_by_subtype", _explode)
request = _Request({"x-orchestrator-token": "Bearer nonsense"})
body = json.loads(getattr(gradio_ui, endpoint)(**kwargs, request=request))
assert body["status"] == "denied"
def test_machine_endpoint_denies_resolvable_but_unlisted_identity(monkeypatch):
"""A real HF account that is not allow-listed is still refused."""
monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "random-person")
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode)
request = _Request({"x-orchestrator-token": "tok"})
body = json.loads(gradio_ui.query_variant_status(genes="KRAS", source="x", request=request))
assert body["status"] == "denied"
assert "random-person" in body["reason"]
def test_machine_endpoint_allows_listed_identity(monkeypatch):
monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "orchestrator-bot")
monkeypatch.setattr(
gradio_ui,
"_query_variant_status",
lambda genes, source: {"n_samples": 7, "genes": {}, "source": source},
)
request = _Request({"x-orchestrator-token": "Bearer good-token"})
body = json.loads(
gradio_ui.query_variant_status(
genes="KRAS,TP53", source="cbioportal:paad_tcga", request=request
)
)
assert body["n_samples"] == 7
def test_bearer_prefix_is_stripped(monkeypatch):
seen = {}
monkeypatch.setattr(
gradio_ui, "_resolve_token_identity", lambda token: seen.setdefault("token", token)
)
gradio_ui._machine_caller_identity(_Request({"x-orchestrator-token": "Bearer abc123"}))
assert seen["token"] == "abc123"
def test_empty_genes_falls_back_to_full_panel():
"""The machine contract sends a comma-separated string; empty means 'the whole panel'."""
assert gradio_ui._split_genes("") == list(gradio_ui.PANEL)
assert gradio_ui._split_genes("kras, tp53") == ["KRAS", "TP53"]
# --------------------------------------------------------------------------- #
# Denial DIAGNOSIS β the three failure modes must be distinguishable
# --------------------------------------------------------------------------- #
# Regression guard for a real misdiagnosis (2026-07-27β28). The orchestrator was denied and
# the response said "Please sign in with your HuggingFace account", so the investigation went
# server-side β probing headers, suspecting `whoami`. Nothing was wrong here: the caller had
# simply sent no header at all (its own HF_TOKEN was unset). These three states are
# operationally different and must never again be one indistinguishable message.
def test_no_token_header_is_diagnosed_as_caller_misconfiguration(monkeypatch):
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode)
body = json.loads(gradio_ui.query_variant_status(source="x", request=_Request()))
assert body["status"] == "denied"
assert body["machine_auth"] == "no_token_header"
# names the calling Space's secret, and does NOT tell a machine to sign in
assert "HF_TOKEN" in body["reason"]
assert "sign in" not in body["reason"].lower()
def test_unresolvable_token_is_diagnosed_separately_from_a_missing_one(monkeypatch):
monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: None)
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode)
body = json.loads(
gradio_ui.query_variant_status(
source="x", request=_Request({"x-orchestrator-token": "bad-token"})
)
)
assert body["machine_auth"] == "token_unresolved"
def test_resolved_but_unlisted_identity_is_the_only_real_authorization_denial(monkeypatch):
"""Identity resolved fine β this one IS an allow-list decision, and still names who."""
monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "stranger-bot")
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode)
body = json.loads(
gradio_ui.query_variant_status(
source="x", request=_Request({"x-orchestrator-token": "good-token"})
)
)
assert body["machine_auth"] == "not_allowlisted"
assert "stranger-bot" in body["reason"]
def test_identity_resolution_reports_resolved_on_success(monkeypatch):
monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "orchestrator-bot")
identity, diagnosis = gradio_ui._machine_caller_identity(
_Request({"x-orchestrator-token": "good-token"})
)
assert (identity, diagnosis) == ("orchestrator-bot", "resolved")
# --------------------------------------------------------------------------- #
# Argument DIAGNOSIS β the same lesson, applied to caller-side type mistakes
# --------------------------------------------------------------------------- #
# Found while verifying the ADR-0007 deploy: passing the obvious-looking
# `genes=["KRAS","TP53"]` returned
# {"status": "error", "reason": "AttributeError: 'list' object has no attribute 'split'"}
# β indistinguishable from a server fault, so it sends the reader into this repo when the fix
# is in their own call. Exactly the failure mode the denial-diagnosis block above exists for.
def _allowed(monkeypatch):
monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "orchestrator-bot")
return _Request({"x-orchestrator-token": "tok"})
def test_list_genes_reports_a_caller_side_argument_error(monkeypatch):
"""A list of genes is named as such, with the exact string to send instead."""
request = _allowed(monkeypatch)
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) # never runs
body = json.loads(
gradio_ui.query_variant_status(
genes=["KRAS", "TP53"], source="cbioportal:paad_tcga", request=request
)
)
assert body["machine_input"] == "genes_not_a_string"
assert 'genes="KRAS,TP53"' in body["reason"] # the fix, spelled out
assert "caller-side" in body["reason"]
assert "AttributeError" not in body["reason"]
def test_non_string_source_does_not_raise_before_the_gate(monkeypatch):
"""`registered_source` runs pre-gate; a non-string `source` must not blow up there.
This was an uncaught exception (a 500), not a result β the one input that escaped
`_safe_result` entirely because the audit descriptor is built before the auth check.
"""
request = _allowed(monkeypatch)
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode)
body = json.loads(
gradio_ui.query_variant_status(genes="KRAS", source=["cbioportal:paad_tcga"], request=request)
)
assert body["machine_input"] == "source_not_a_string"
def test_argument_errors_are_reported_only_after_the_auth_gate(monkeypatch):
"""A tokenless caller gets `denied`, not a critique of their arguments."""
monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode)
body = json.loads(
gradio_ui.query_variant_status(genes=["KRAS"], source=1234, request=_Request())
)
assert body["status"] == "denied"
assert "machine_input" not in body
@pytest.mark.parametrize("field", ["subtype_attribute", "modality"])
def test_every_string_argument_is_covered(monkeypatch, field):
request = _allowed(monkeypatch)
monkeypatch.setattr(gradio_ui, "_variant_by_subtype", _explode)
body = json.loads(
gradio_ui.variant_by_subtype(
genes="KRAS", source="cbioportal:paad_tcga", request=request, **{field: ["x"]}
)
)
assert body["machine_input"] == f"{field}_not_a_string"
def test_valid_strings_are_unaffected(monkeypatch):
"""The happy path must not acquire a new way to fail."""
request = _allowed(monkeypatch)
monkeypatch.setattr(
gradio_ui, "_query_variant_status", lambda genes, source: {"n_samples": 7, "genes": {}}
)
body = json.loads(
gradio_ui.query_variant_status(
genes="KRAS,TP53", source="cbioportal:paad_tcga", request=request
)
)
assert body["n_samples"] == 7
assert "machine_input" not in body
|