Skip to content

Commit

Permalink
Activate XSRF protection during dev and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bkis committed Dec 27, 2023
1 parent 909a698 commit 21dffce
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 123 deletions.
29 changes: 14 additions & 15 deletions Tekst-API/tekst/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,20 @@ async def lifespan(app: FastAPI):
separate_input_output_schemas=False,
)

# add and configure CSRF middleware
if not _cfg.dev_mode:
app.add_middleware(
CSRFMiddleware,
secret=_cfg.security_secret,
required_urls=[re.compile(r".*/auth/cookie/login.*")],
exempt_urls=[re.compile(r".*/auth/cookie/logout.*")],
sensitive_cookies={_cfg.security_auth_cookie_name},
cookie_name="XSRF-TOKEN",
cookie_path="/",
cookie_domain=_cfg.security_auth_cookie_domain or None,
cookie_secure=not _cfg.dev_mode,
cookie_samesite="Lax",
header_name="X-XSRF-TOKEN",
)
# add and configure XSRF/CSRF middleware
app.add_middleware(
CSRFMiddleware,
secret=_cfg.security_secret,
required_urls=[re.compile(r".*/auth/cookie/login.*")],
exempt_urls=[re.compile(r".*/auth/cookie/logout.*")],
sensitive_cookies={_cfg.security_auth_cookie_name},
cookie_name="XSRF-TOKEN",
cookie_path="/",
cookie_domain=_cfg.security_auth_cookie_domain or None,
cookie_secure=not _cfg.dev_mode,
cookie_samesite="Lax",
header_name="X-XSRF-TOKEN",
)

# add and configure CORS middleware
app.add_middleware(
Expand Down
6 changes: 6 additions & 0 deletions Tekst-API/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ async def test_client(test_app, config) -> AsyncClient:
async with AsyncClient(
app=test_app, base_url=f"{config.server_url}{config.api_path}"
) as client:
# prepare XSRF token
resp = await client.get("/")
xsrf_token = resp.cookies.get("XSRF-TOKEN")
client.headers.setdefault("X-XSRF-TOKEN", xsrf_token) # set XSRF token header
client.cookies.setdefault("XSRF-TOKEN", xsrf_token) # set XSRF token cookie
# yield client instance
yield client


Expand Down
8 changes: 4 additions & 4 deletions Tekst-API/tests/test_api_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ async def test_get_stats(
):
await insert_sample_data("texts", "nodes", "resources", "units")
user = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(user)
resp = await test_client.get("/admin/stats", cookies=session_cookie)
await get_session_cookie(user)
resp = await test_client.get("/admin/stats")
assert resp.status_code == 200, status_fail_msg(200, resp)
assert "usersCount" in resp.json()
assert resp.json()["usersCount"] == 1
Expand All @@ -28,8 +28,8 @@ async def test_get_users(
get_session_cookie,
):
user = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(user)
resp = await test_client.get("/admin/users", cookies=session_cookie)
await get_session_cookie(user)
resp = await test_client.get("/admin/users")
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
assert len(resp.json()) == 1
Expand Down
7 changes: 2 additions & 5 deletions Tekst-API/tests/test_api_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,10 @@ async def test_user_updates_self(
status_fail_msg,
):
user_data = await register_test_user()
session_cookie = await get_session_cookie(user_data)
await get_session_cookie(user_data)
# get user data from /users/me
resp = await test_client.get(
"/users/me",
cookies=session_cookie,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert "id" in resp.json()
Expand All @@ -149,7 +148,6 @@ async def test_user_updates_self(
resp = await test_client.patch(
"/users/me",
json=updates,
cookies=session_cookie,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert resp.json()["id"] == user_id
Expand All @@ -165,11 +163,10 @@ async def test_user_deletes_self(
status_fail_msg,
):
user_data = await register_test_user()
session_cookie = await get_session_cookie(user_data)
await get_session_cookie(user_data)
# delete self
resp = await test_client.delete(
"/users/me",
cookies=session_cookie,
)
assert resp.status_code == 204, status_fail_msg(204, resp)

Expand Down
58 changes: 39 additions & 19 deletions Tekst-API/tests/test_api_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ async def test_create_node(

# create superuser
superuser_data = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(superuser_data)
await get_session_cookie(superuser_data)

for node in nodes:
resp = await test_client.post("/nodes", json=node, cookies=session_cookie)
resp = await test_client.post(
"/nodes",
json=node,
)
assert resp.status_code == 201, status_fail_msg(201, resp)


Expand All @@ -40,10 +43,13 @@ async def test_child_node_io(

# create superuser
superuser_data = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(superuser_data)
await get_session_cookie(superuser_data)

# create parent
resp = await test_client.post("/nodes", json=node, cookies=session_cookie)
resp = await test_client.post(
"/nodes",
json=node,
)
assert resp.status_code == 201, status_fail_msg(201, resp)
parent = resp.json()
assert parent["id"]
Expand All @@ -53,7 +59,10 @@ async def test_child_node_io(
child["parentId"] = parent["id"]
child["level"] = parent["level"] + 1
child["position"] = 0
resp = await test_client.post("/nodes", json=child, cookies=session_cookie)
resp = await test_client.post(
"/nodes",
json=child,
)
assert resp.status_code == 201, status_fail_msg(201, resp)
child = resp.json()
assert "id" in resp.json()
Expand All @@ -73,7 +82,6 @@ async def test_child_node_io(
resp = await test_client.get(
"/nodes/children",
params={"parentId": child["parentId"]},
cookies=session_cookie,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
Expand All @@ -82,7 +90,8 @@ async def test_child_node_io(

# find children by text ID and null parent ID using dedicated children endpoint
resp = await test_client.get(
"/nodes/children", params={"textId": text_id}, cookies=session_cookie
"/nodes/children",
params={"textId": text_id},
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
Expand All @@ -105,9 +114,12 @@ async def test_create_node_invalid_text_fail(

# create superuser
superuser_data = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(superuser_data)
await get_session_cookie(superuser_data)

resp = await test_client.post("/nodes", json=node, cookies=session_cookie)
resp = await test_client.post(
"/nodes",
json=node,
)
assert resp.status_code == 400, status_fail_msg(400, resp)


Expand Down Expand Up @@ -186,11 +198,12 @@ async def test_update_node(
node = resp.json()[0]
# create superuser
superuser_data = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(superuser_data)
await get_session_cookie(superuser_data)
# update node
node_update = {"label": "A fresh label"}
resp = await test_client.patch(
f"/nodes/{node['id']}", json=node_update, cookies=session_cookie
f"/nodes/{node['id']}",
json=node_update,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert "id" in resp.json()
Expand All @@ -199,13 +212,17 @@ async def test_update_node(
assert resp.json()["label"] == "A fresh label"
# update unchanged node
resp = await test_client.patch(
f"/nodes/{node['id']}", json=node_update, cookies=session_cookie
f"/nodes/{node['id']}",
json=node_update,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
# update invalid node
node_update = {"label": "Brand new label"}
resp = await test_client.patch("/nodes/637b9ad396d541a505e5439b", json=node_update)
assert resp.status_code == 400, status_fail_msg(400, resp, cookies=session_cookie)
assert resp.status_code == 400, status_fail_msg(
400,
resp,
)


@pytest.mark.anyio
Expand All @@ -229,7 +246,7 @@ async def test_delete_node(

# create superuser
superuser_data = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(superuser_data)
await get_session_cookie(superuser_data)

# get existing resource
resp = await test_client.get("/resources", params={"textId": text_id})
Expand All @@ -246,15 +263,20 @@ async def test_delete_node(
"text": "Ein Raabe geht im Feld spazieren.",
"comment": "This is a comment",
}
resp = await test_client.post("/units", json=payload, cookies=session_cookie)
resp = await test_client.post(
"/units",
json=payload,
)
assert resp.status_code == 201, status_fail_msg(201, resp)
assert isinstance(resp.json(), dict)
assert resp.json()["text"] == payload["text"]
assert resp.json()["comment"] == payload["comment"]
assert "id" in resp.json()

# delete node
resp = await test_client.delete(f"/nodes/{node['id']}", cookies=session_cookie)
resp = await test_client.delete(
f"/nodes/{node['id']}",
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert resp.json().get("nodes", None) > 1
assert resp.json().get("units", None) == 1
Expand All @@ -272,13 +294,12 @@ async def test_move_node(

# create superuser
superuser_data = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(superuser_data)
await get_session_cookie(superuser_data)

# get node from db
resp = await test_client.get(
"/nodes",
params={"textId": text_id, "level": 0, "position": 0},
cookies=session_cookie,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
Expand All @@ -289,7 +310,6 @@ async def test_move_node(
resp = await test_client.post(
f"/nodes/{node['id']}/move",
json={"position": 1, "after": True, "parentId": None},
cookies=session_cookie,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), dict)
Expand Down
16 changes: 7 additions & 9 deletions Tekst-API/tests/test_api_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ async def test_platform_users(
get_session_cookie,
):
user = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(user)
await get_session_cookie(user)
resp = await test_client.get(
"/platform/users", params={"q": user.get("username")}, cookies=session_cookie
"/platform/users",
params={"q": user.get("username")},
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
Expand All @@ -31,7 +32,8 @@ async def test_platform_users(
assert "name" in resp.json()[0]
assert "isActive" not in resp.json()[0]
resp = await test_client.get(
"/platform/users", params={"q": "nonsense"}, cookies=session_cookie
"/platform/users",
params={"q": "nonsense"},
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
Expand All @@ -46,11 +48,10 @@ async def test_update_platform_settings(
get_session_cookie,
):
user = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(user)
await get_session_cookie(user)
resp = await test_client.patch(
"/platform/settings",
json={"availableLocales": ["enUS"]},
cookies=session_cookie,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), dict)
Expand Down Expand Up @@ -80,13 +81,12 @@ async def test_crud_segment(
get_session_cookie,
):
user = await register_test_user(is_superuser=True)
session_cookie = await get_session_cookie(user)
await get_session_cookie(user)

# create segment
resp = await test_client.post(
"/platform/segments",
json={"key": "foo", "locale": "*", "title": "Foo", "html": "<p>Foo</p>"},
cookies=session_cookie,
)
assert resp.status_code == 201, status_fail_msg(201, resp)
assert isinstance(resp.json(), dict)
Expand All @@ -97,7 +97,6 @@ async def test_crud_segment(
resp = await test_client.patch(
f"/platform/segments/{resp.json()['id']}",
json={"title": "Bar"},
cookies=session_cookie,
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), dict)
Expand All @@ -107,6 +106,5 @@ async def test_crud_segment(
# delete segment
resp = await test_client.delete(
f"/platform/segments/{resp.json()['id']}",
cookies=session_cookie,
)
assert resp.status_code == 204, status_fail_msg(204, resp)
Loading

0 comments on commit 21dffce

Please sign in to comment.