Spaces:
Running
Running
Tengo Gzirishvili
Adversarial audit of Plasmid Studio: fix XSS, DoS, and input-validation gaps
d67ba8e | """Endpoint tests for Plasmid Studio (Flask test client; auth stubbed, no DB). | |
| Covers the sign-in gate + import / restriction / digest / export wiring.""" | |
| import types | |
| import pytest | |
| from dee import server | |
| SEQ = ("TAATACGACTCACTATAG" + "GAATTC" + "AAAAGGGGAAAAGGGGAAAA" | |
| + "GAATTC" + "CCCCTTTTCCCCTTTTCCCC") | |
| def client(): | |
| app = server.create_app() | |
| app.config.update(TESTING=True) | |
| return app.test_client() | |
| def _signed_in(monkeypatch): | |
| monkeypatch.setattr(server._auth, "get_auth", | |
| lambda: types.SimpleNamespace(anonymous=False, user_id="u1", | |
| email="x@y.z", plan="free")) | |
| def _anon(monkeypatch): | |
| monkeypatch.setattr(server._auth, "get_auth", | |
| lambda: types.SimpleNamespace(anonymous=True, user_id=None, | |
| email=None, plan="anon")) | |
| def test_import_requires_signin(client, monkeypatch): | |
| _anon(monkeypatch) | |
| r = client.post("/api/plasmid/import", json={"text": SEQ}) | |
| assert r.status_code == 403 | |
| assert r.get_json()["kind"] == "signin_required" | |
| def test_import_raw_dna_annotates(client, monkeypatch): | |
| _signed_in(monkeypatch) | |
| r = client.post("/api/plasmid/import", json={"text": SEQ}) | |
| assert r.status_code == 200 | |
| p = r.get_json()["plasmid"] | |
| assert p["length"] == len(SEQ) | |
| assert p["topology"] == "circular" | |
| assert any(f["name"] == "T7 promoter" for f in p["features"]) | |
| def test_import_bad_input_400(client, monkeypatch): | |
| _signed_in(monkeypatch) | |
| r = client.post("/api/plasmid/import", json={"text": "this is not dna @@@"}) | |
| assert r.status_code == 400 | |
| assert "error" in r.get_json() | |
| def test_restriction(client, monkeypatch): | |
| _signed_in(monkeypatch) | |
| r = client.post("/api/plasmid/restriction", | |
| json={"sequence": SEQ, "topology": "circular", "enzymes": ["EcoRI", "BamHI"]}) | |
| assert r.status_code == 200 | |
| a = r.get_json()["analysis"] | |
| assert "EcoRI" in a["double_cutters"] | |
| assert "BamHI" in a["non_cutters"] | |
| def test_digest_fragments(client, monkeypatch): | |
| _signed_in(monkeypatch) | |
| r = client.post("/api/plasmid/digest", | |
| json={"sequence": SEQ, "topology": "circular", "enzymes": ["EcoRI"]}) | |
| assert r.status_code == 200 | |
| d = r.get_json()["digest"] | |
| assert d["n_fragments"] == 2 | |
| assert sum(d["fragments"]) == len(SEQ) | |
| def test_digest_requires_enzyme(client, monkeypatch): | |
| _signed_in(monkeypatch) | |
| r = client.post("/api/plasmid/digest", | |
| json={"sequence": SEQ, "topology": "circular", "enzymes": []}) | |
| assert r.status_code == 400 | |
| def test_import_rejects_oversize_text(client, monkeypatch): | |
| _signed_in(monkeypatch) | |
| from dee.core import plasmid as PL | |
| monkeypatch.setattr(PL, "MAX_INPUT_TEXT", 2000) | |
| r = client.post("/api/plasmid/import", json={"text": "A" * 5000}) | |
| assert r.status_code == 400 | |
| def test_export_sanitizes_malicious_features(client, monkeypatch): | |
| _signed_in(monkeypatch) | |
| # injection-laden colour + out-of-range coords + a flood of features: | |
| # must not crash, must produce a valid GenBank (sanitized server-side). | |
| feats = [{"name": "evil", "type": "cds", "start": 0, "end": 18, | |
| "color": '#fff" onload="alert(1)'}] | |
| feats += [{"name": "f", "type": "misc", "start": 0, "end": 3} for _ in range(20000)] | |
| r = client.post("/api/plasmid/export", json={ | |
| "sequence": SEQ, "topology": "circular", "name": "pX", "features": feats, "format": "genbank"}) | |
| assert r.status_code == 200 | |
| body = r.data.decode("utf-8", "replace") | |
| assert "LOCUS" in body | |
| assert "onload=" not in body # injection scrubbed out of the export | |
| def test_export_genbank_and_fasta(client, monkeypatch): | |
| _signed_in(monkeypatch) | |
| gb = client.post("/api/plasmid/export", json={ | |
| "sequence": SEQ, "topology": "circular", "name": "pTEST", | |
| "features": [{"name": "T7 promoter", "type": "promoter", "start": 0, "end": 18, "strand": 1}], | |
| "format": "genbank"}) | |
| assert gb.status_code == 200 | |
| assert b"LOCUS" in gb.data and b"promoter" in gb.data | |
| fa = client.post("/api/plasmid/export", | |
| json={"sequence": SEQ, "topology": "circular", "name": "pTEST", "format": "fasta"}) | |
| assert fa.status_code == 200 | |
| assert fa.data.startswith(b">") | |