Skip to content

Commit 4d071b8

Browse files
fix[ws]: handle unsupported paths and message types
1 parent 98eaec9 commit 4d071b8

File tree

5 files changed

+88
-17
lines changed

5 files changed

+88
-17
lines changed

poetry.lock

Lines changed: 13 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ rq-scheduler = "^0.10"
4444
# Experimental LDAP Integration https://issues.redhat.com/browse/AAP-16938
4545
# We are temporarily updating it to use latest from devel branch to keep consistency accross
4646
# other AAP components
47-
django-ansible-base = { git = "https://github.com/ansible/django-ansible-base.git" }
47+
django-ansible-base = { git = "https://github.com/ansible/django-ansible-base.git", branch="devel", extras=["authentication"] }
4848
jinja2 = "*"
4949

5050
[tool.poetry.group.test.dependencies]

src/aap_eda/wsapi/consumers.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,31 @@ class Event(Enum):
7272
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f%z"
7373

7474

75+
class DefaultConsumer(AsyncWebsocketConsumer):
76+
"""Default consumer for websocket connections.
77+
78+
This is the consumer to handle all the unexpected paths, it will close the
79+
connection with an error message.
80+
"""
81+
82+
async def connect(self):
83+
await self.accept()
84+
await self.send('{"error": "invalid path"}')
85+
await self.close()
86+
87+
7588
class AnsibleRulebookConsumer(AsyncWebsocketConsumer):
7689
async def receive(self, text_data=None, bytes_data=None):
7790
data = json.loads(text_data)
7891
logger.debug(f"AnsibleRulebookConsumer received: {data}")
7992

80-
msg_type = MessageType(data.get("type"))
93+
msg_type = data.get("type")
94+
try:
95+
msg_type = MessageType(data.get("type"))
96+
except ValueError:
97+
logger.error(f"Unsupported message type: {data}")
98+
await self.close()
99+
return
81100

82101
try:
83102
if msg_type == MessageType.WORKER:
@@ -94,8 +113,6 @@ async def receive(self, text_data=None, bytes_data=None):
94113
logger.info("Websocket connection is closed.")
95114
elif msg_type == MessageType.SESSION_STATS:
96115
await self.handle_heartbeat(HeartbeatMessage.parse_obj(data))
97-
else:
98-
logger.warning(f"Unsupported message received: {data}")
99116
except DatabaseError as err:
100117
logger.error(f"Failed to parse {data} due to DB error: {err}")
101118

src/aap_eda/wsapi/routes.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
from channels.routing import URLRouter
22
from django.conf import settings
3-
from django.urls import path
3+
from django.urls import path, re_path
44

55
from . import consumers
66

7+
default_path = re_path(r".*/?$", consumers.DefaultConsumer.as_asgi())
8+
9+
10+
default_router = URLRouter(
11+
[
12+
default_path,
13+
],
14+
)
15+
716
wsapi_router = URLRouter(
8-
[path("ansible-rulebook", consumers.AnsibleRulebookConsumer.as_asgi())]
17+
[
18+
path("ansible-rulebook", consumers.AnsibleRulebookConsumer.as_asgi()),
19+
default_path,
20+
],
921
)
1022

1123
router = URLRouter(
1224
[
1325
path(f"{settings.API_PREFIX}/ws/", wsapi_router),
14-
]
26+
path("", default_router),
27+
],
1528
)

tests/integration/wsapi/test_consumer.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import pytest_asyncio
66
from channels.db import database_sync_to_async
77
from channels.testing import WebsocketCommunicator
8+
from django.conf import settings
89
from django.utils import timezone
910
from pydantic.error_wrappers import ValidationError
1011

12+
from aap_eda.asgi import application
1113
from aap_eda.core import models
1214
from aap_eda.wsapi.consumers import AnsibleRulebookConsumer
1315

@@ -45,6 +47,42 @@
4547
DUMMY_UUID2 = "8472ff2c-6045-4418-8d4e-46f6cfffffff"
4648

4749

50+
@pytest.mark.parametrize("path", ["ws/unexpected", "unexpected"])
51+
async def test_invalid_websocket_route(path: str):
52+
"""Test that the websocket consumer rejects unsupported routes."""
53+
communicator = WebsocketCommunicator(application, path)
54+
55+
connected, _ = await communicator.connect()
56+
assert connected, "Connection failed"
57+
58+
response = await communicator.receive_from()
59+
assert response == '{"error": "invalid path"}', "Invalid error message"
60+
close_message = await communicator.receive_output()
61+
assert (
62+
close_message["type"] == "websocket.close"
63+
), "Did not receive close message"
64+
65+
await communicator.disconnect()
66+
67+
68+
async def test_valid_websocket_route_wrong_type():
69+
"""Test that the websocket consumer rejects unsupported types."""
70+
communicator = WebsocketCommunicator(
71+
application,
72+
f"{settings.API_PREFIX}/ws/ansible-rulebook",
73+
)
74+
connected, _ = await communicator.connect()
75+
assert connected, "Connection failed"
76+
nothing = await communicator.receive_nothing()
77+
assert nothing, "Received unexpected message"
78+
await communicator.send_to(text_data='{"type": "unsuported_type"}')
79+
close_message = await communicator.receive_output()
80+
assert (
81+
close_message["type"] == "websocket.close"
82+
), "Did not receive close message"
83+
await communicator.disconnect()
84+
85+
4886
@pytest.mark.django_db(transaction=True)
4987
async def test_handle_workers(ws_communicator: WebsocketCommunicator):
5088
activation_instance_with_extra_var = await _prepare_db_data()

0 commit comments

Comments
 (0)