Skip to content
Merged
3 changes: 2 additions & 1 deletion ooniapi/services/ooniprobe/src/ooniprobe/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from . import models
from .routers.v2 import vpn
from .routers.v1 import probe_services
from .routers import reports
from .routers import reports, bouncer

from .download_geoip import try_update
from .dependencies import get_postgresql_session, get_clickhouse_session, SettingsDep
Expand Down Expand Up @@ -80,6 +80,7 @@ def update_geoip_task():
app.include_router(vpn.router, prefix="/api")
app.include_router(probe_services.router, prefix="/api")
app.include_router(reports.router)
app.include_router(bouncer.router)


@app.get("/version")
Expand Down
110 changes: 110 additions & 0 deletions ooniapi/services/ooniprobe/src/ooniprobe/routers/bouncer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import logging
from typing import List, Optional, Any, Dict
from json.decoder import JSONDecodeError

from fastapi import APIRouter, HTTPException, Request, Response
from pydantic import Field, ValidationError

from ooniprobe.common.utils import setnocacheresponse
from ooniprobe.common.routers import BaseModel

router = APIRouter(prefix="/bouncer")

log = logging.getLogger(__name__)


class TestHelperEntry(BaseModel):
address: str
type: str
front: Optional[str] = None


class CollectorEntry(BaseModel):
address: str
type: str
front: Optional[str] = None


class NetTest(BaseModel):
name: str
collector: str
altcollector: List[CollectorEntry] = Field(alias="collector-alternate")
hashes: Optional[Any] = Field(None, alias="input-hashes")
helpers: Dict[str, str] = Field(alias="test-helpers")
althelpers: Dict[str, List[TestHelperEntry]] = Field(alias="test-helpers-alternate")
version: str


class NetTestRequest(BaseModel):
name: str
version: str


class NetTestsRequest(BaseModel):
nettests: List[NetTestRequest] = Field(alias="net-tests")


class NetTestResponse(BaseModel):
nettests: List[NetTest] = Field(alias="net-tests")


@router.post("/net-tests", tags=["bouncer"], response_model=NetTestResponse, response_model_exclude_unset=True)
async def bouncer_net_tests(
response: Response,
request: Request,
) -> Dict[str, List[NetTest]]:

try:
j = await request.json()
m = NetTestsRequest(**j)
except ValidationError as e:
raise HTTPException(400, detail=e.errors())
except JSONDecodeError as e:
raise HTTPException(400, detail=str(e))
except Exception as e:
log.warning("Unexpected Exception:" + str(e))
raise HTTPException(400, detail=str(e))

try:
name = m.nettests[0].name
version = m.nettests[0].version
except IndexError:
raise HTTPException(status_code=400, detail="invalid net-tests request")

# TODO: load this json from environment or filepath
j = {
"net-tests": [
{
"collector": "httpo://guegdifjy7bjpequ.onion",
"collector-alternate": [
{"type": "https", "address": "https://ams-pg.ooni.org"},
{
"front": "dkyhjv0wpi2dk.cloudfront.net",
"type": "cloudfront",
"address": "https://dkyhjv0wpi2dk.cloudfront.net",
},
],
"input-hashes": None,
"name": name,
"test-helpers": {
"tcp-echo": "37.218.241.93",
"http-return-json-headers": "http://37.218.241.94:80",
"web-connectivity": "httpo://y3zq5fwelrzkkv3s.onion",
},
"test-helpers-alternate": {
"web-connectivity": [
{"type": "https", "address": "https://wcth.ooni.io"},
{
"front": "d33d1gs9kpq1c5.cloudfront.net",
"type": "cloudfront",
"address": "https://d33d1gs9kpq1c5.cloudfront.net",
},
]
},
"version": version,
}
]
}
resp = NetTestResponse(**j)
setnocacheresponse(response)
return resp
74 changes: 74 additions & 0 deletions ooniapi/services/ooniprobe/tests/test_bouncer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Integration test for OONIProbe API
"""


def test_get_bouncer_nettests(client):
version = "1"

r = client.post("/bouncer/net-tests", json={"net-tests": [{"name": "foo", "version": version}]})
j = r.json()
assert "net-tests" in j
for v in j["net-tests"]:
for x in ["collector", "input-hashes", "name", "test-helpers", "test-helpers-alternate", "version"]:
assert x in v

def test_bouncer_net_tests(client):
j = {
"net-tests": [
{
"input-hashes": None,
"name": "web_connectivity",
"test-helpers": ["web-connectivity"],
"version": "0.0.1",
}
]
}
c = client.post("/bouncer/net-tests", json=j)
expected = {
"net-tests": [
{
"collector": "httpo://guegdifjy7bjpequ.onion",
"collector-alternate": [
{"type": "https", "address": "https://ams-pg.ooni.org"},
{
"front": "dkyhjv0wpi2dk.cloudfront.net",
"type": "cloudfront",
"address": "https://dkyhjv0wpi2dk.cloudfront.net",
},
],
"name": "web_connectivity",
"test-helpers": {
"tcp-echo": "37.218.241.93",
"http-return-json-headers": "http://37.218.241.94:80",
"web-connectivity": "httpo://y3zq5fwelrzkkv3s.onion",
},
"test-helpers-alternate": {
"web-connectivity": [
{"type": "https", "address": "https://wcth.ooni.io"},
{
"front": "d33d1gs9kpq1c5.cloudfront.net",
"type": "cloudfront",
"address": "https://d33d1gs9kpq1c5.cloudfront.net",
},
]
},
"version": "0.0.1",
"input-hashes": None,
}
]
}
assert c.json() == expected


def test_bouncer_net_tests_bad_request1(client):
resp = client.post("/bouncer/net-tests")
# XXX: returns status code 400 for backwards compatibility
assert resp.status_code == 400


def test_bouncer_net_tests_bad_request2(client):
j = {"net-tests": []}
resp = client.post("/bouncer/net-tests", json=j)
# XXX: returns status code 400 for backwards compatibility
assert resp.status_code == 400