ASE-GLM / tests /test_oauth.py
Bit-Trading-Company's picture
CI deploy local
e14044c verified
Raw History Blame Contribute Delete
5.62 kB
"""The sign-in flow, exercised without ever reaching Hugging Face.
Everything here is about refusing bad input. The happy path needs a real
provider and a real Space, and is verified against the deployment rather than
faked here — a mocked token exchange would only assert that the mock works.
"""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from server import oauth
from server.main import app
@pytest.fixture(autouse=True)
def clean_env(monkeypatch):
for var in ("OAUTH_CLIENT_ID", "OAUTH_CLIENT_SECRET", "OAUTH_SCOPES",
"OPENID_PROVIDER_URL", "SPACE_ID", "SPACE_HOST",
"HF_TOKEN", "HUGGING_FACE_HUB_TOKEN", "ASE_ALLOW_SPACE_CREDITS"):
monkeypatch.delenv(var, raising=False)
monkeypatch.setenv("ASE_KEY_FILE", "/nonexistent/ase-glm-test-key")
@pytest.fixture
def client():
return TestClient(app)
def test_not_configured_without_a_space(client):
assert oauth.configured() is False
r = client.get("/api/auth/login", follow_redirects=False)
assert r.status_code == 503
assert "Space" in r.json()["error"]
def test_config_says_sign_in_is_unavailable(client):
auth = client.get("/api/config").json()["auth"]
assert auth["canSignIn"] is False
assert auth["profile"] is None
def test_login_redirects_to_the_provider_with_the_billing_scope(client, monkeypatch):
monkeypatch.setenv("OAUTH_CLIENT_ID", "cid")
monkeypatch.setenv("OAUTH_CLIENT_SECRET", "shh")
monkeypatch.setenv("SPACE_HOST", "example.hf.space")
r = client.get("/api/auth/login", follow_redirects=False)
assert r.status_code == 302
url = r.headers["location"]
assert url.startswith("https://huggingface.co/oauth/authorize?")
# inference-api is the whole point: without it we learn who someone is and
# still cannot bill them.
assert "inference-api" in url
assert "example.hf.space%2Fauth%2Fcallback" in url
assert "state=" in url
assert "shh" not in url
def start_login(client) -> str:
"""Begin a sign-in and return the state we were issued."""
r = client.get("/api/auth/login", follow_redirects=False)
query = r.headers["location"].split("?", 1)[1]
return dict(p.split("=", 1) for p in query.split("&"))["state"]
def test_the_callback_refuses_a_state_it_did_not_issue(client, monkeypatch):
monkeypatch.setenv("OAUTH_CLIENT_ID", "cid")
monkeypatch.setenv("OAUTH_CLIENT_SECRET", "shh")
# A real session, so the rejection is about the state and not the cookie.
start_login(client)
r = client.get("/auth/callback", params={"code": "abc", "state": "forged"},
follow_redirects=False)
assert r.status_code == 200
assert "state did not match" in r.text
# And nothing was stored.
assert client.get("/api/config").json()["auth"]["mode"] == "none"
def test_the_callback_refuses_a_missing_state(client):
r = client.get("/auth/callback", params={"code": "abc"}, follow_redirects=False)
# No session came back at all: reported as a cookie problem, because that
# is what it almost always is and it has a different fix from a forgery.
assert "not returned by your browser" in r.text
def test_the_session_cookie_crosses_sites_on_a_space(monkeypatch):
# A Space is viewed inside an iframe on huggingface.co, so this cookie is
# third-party. Under SameSite=Lax the browser withholds it on the way back
# from the provider and every sign-in fails as a state mismatch.
monkeypatch.setenv("SPACE_ID", "someone/ase-glm")
monkeypatch.setenv("OAUTH_CLIENT_ID", "cid")
monkeypatch.setenv("OAUTH_CLIENT_SECRET", "shh")
import importlib
from server import main as main_mod
importlib.reload(main_mod)
r = TestClient(main_mod.app).get("/api/auth/login", follow_redirects=False)
cookie = r.headers.get("set-cookie", "").lower()
assert "samesite=none" in cookie
# SameSite=None is only honoured on a Secure cookie.
assert "secure" in cookie
importlib.reload(main_mod)
def test_the_callback_reports_a_provider_error(client):
r = client.get("/auth/callback", params={"error": "access_denied"})
assert "access_denied" in r.text
def test_a_state_cannot_be_replayed(client, monkeypatch):
monkeypatch.setenv("OAUTH_CLIENT_ID", "cid")
monkeypatch.setenv("OAUTH_CLIENT_SECRET", "shh")
monkeypatch.setenv("SPACE_HOST", "example.hf.space")
state = start_login(client)
# The first use consumes it (and then fails at the token exchange, which is
# fine — what matters is that a second attempt cannot reuse the same state).
client.get("/auth/callback", params={"code": "abc", "state": state})
again = client.get("/auth/callback", params={"code": "abc", "state": state})
assert "Signed in" not in again.text
assert client.get("/api/config").json()["auth"]["mode"] == "none"
def test_logout_is_a_post_and_clears_the_session(client):
assert client.get("/auth/callback", params={"error": "x"}).status_code == 200
assert client.post("/api/auth/logout").json() == {"ok": True}
# GET must not log anyone out: a prefetched link would sign them out.
assert client.get("/api/auth/logout").status_code in (404, 405)
def test_the_session_cookie_is_not_readable_by_script(client, monkeypatch):
monkeypatch.setenv("OAUTH_CLIENT_ID", "cid")
monkeypatch.setenv("OAUTH_CLIENT_SECRET", "shh")
r = client.get("/api/auth/login", follow_redirects=False)
cookie = r.headers.get("set-cookie", "")
assert "httponly" in cookie.lower()
assert "samesite=lax" in cookie.lower()