Add text format, CORS, and an importer so other instances can use the list
docker-build-push / build-push (push) Successful in 4s
docker-build-push / build-push (push) Successful in 4s
Mastodon has no RBL/DNSBL support — nothing in it consults an outside list when deciding whether to accept a signup, confirmed against v4.7.1 (the only outbound lookup in that path is EmailMxValidator's MX resolution, which feeds the local email_domain_blocks table). So a shared blocklist has to be imported rather than queried live, the same way Garden Fence and other community lists propagate. These are the pieces that make ours importable. - ?format=txt on both per-list endpoints: one entry per line under a '#' comment header, the form blocklists are traded in and the form a cron job can consume without a JSON parser. The combined /api/blocks stays JSON-only — a text rendering would mix CIDRs with domains in one file, defeating the point. An unrecognised ?format is a 400 rather than a silent fallback to JSON, and the enabled-gate still runs first so a disabled deployment 404s either way. - Access-Control-Allow-Origin: * on blocklist responses, so browser-side admin tooling can read them. The data is already public. - bin/yttrx-blocklist-import: standard-library-only importer another admin can run against their own instance. Dry run unless --apply; 422 from Mastodon counts as "already blocked", not an error. The importer defaults IP entries to sign_up_requires_approval rather than the sign_up_block yttrx uses itself. The entries are whole ASN routes, which suits yttrx because a flagged signup here is nearly always datacenter/VPN/proxy space — that calibration isn't transferable, so an importer gets the reviewable severity by default and has to opt into the hard one.
This commit is contained in:
1 parent
af49aab79c
commit
8966de2a1d
5 files changed
+331
-9
No files matched your search
@@ -859,6 +859,39 @@ def blocks_api_tests():
|
||||
assert len(client.get("/api/blocks/ip-ranges?limit=999").json()["items"]) == 1
|
||||
main.BLOCKS_API_MAX_ITEMS = 5000
|
||||
|
||||
# G. CORS is open — other instances' tooling may fetch this from a browser.
|
||||
assert r.headers["access-control-allow-origin"] == "*"
|
||||
|
||||
# H. ?format=txt renders one entry per line under a comment header, so it
|
||||
# pipes into another admin's tooling without a JSON parser.
|
||||
t = client.get("/api/blocks/ip-ranges?format=txt")
|
||||
assert t.status_code == 200, t.status_code
|
||||
assert t.headers["content-type"].startswith("text/plain"), t.headers["content-type"]
|
||||
assert t.headers["access-control-allow-origin"] == "*"
|
||||
lines = t.text.strip().split("\n")
|
||||
header = [ln for ln in lines if ln.startswith("#")]
|
||||
payload = [ln for ln in lines if not ln.startswith("#")]
|
||||
assert any("source:" in ln for ln in header), header
|
||||
assert f"entries: {len(ips_only['items'])}" in "\n".join(header)
|
||||
assert payload == [e["cidr"] for e in ips_only["items"]], payload[:3]
|
||||
|
||||
td = client.get("/api/blocks/email-domains?format=txt")
|
||||
assert [ln for ln in td.text.strip().split("\n") if not ln.startswith("#")] \
|
||||
== [e["domain"] for e in domains_only["items"]]
|
||||
|
||||
# I. An unknown format is rejected rather than quietly serving JSON, and
|
||||
# the combined endpoint refuses text (it would mix CIDRs with domains).
|
||||
assert client.get("/api/blocks/ip-ranges?format=yaml").status_code == 400
|
||||
assert client.get("/api/blocks?format=txt").status_code == 400
|
||||
assert client.get("/api/blocks?format=json").status_code == 200
|
||||
|
||||
# J. The format gate never leaks existence when the API is switched off.
|
||||
main.BLOCKS_API_ENABLED = False
|
||||
assert client.get("/api/blocks/ip-ranges?format=txt").status_code == 404
|
||||
assert client.get("/api/blocks?format=txt").status_code == 404
|
||||
main.BLOCKS_API_ENABLED = True
|
||||
main.BLOCKS_API_MAX_ITEMS = 5000
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
routing_tests()
|
||||
|
||||
Reference in new issue
Block a user