Skip to content

Commit

Permalink
Fix #427 websocket --admin-api-key connect issue
Browse files Browse the repository at this point in the history
This is the proposed option number 2. It uses the websocket
to expect a message with the admin api key before it is being
used to transmit any other messages than 'ping'

Signed-off-by: Matthias Binzer <[email protected]>
  • Loading branch information
Matthias Binzer committed May 27, 2020
1 parent b8e9594 commit 0c118e0
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 5 deletions.
41 changes: 36 additions & 5 deletions aries_cloudagent/admin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ async def make_application(self) -> web.Application:
@web.middleware
async def check_token(request, handler):
header_admin_api_key = request.headers.get("x-api-key")
# always allow websockets here
if request.path == "/ws":
return await handler(request)
if not header_admin_api_key:
raise web.HTTPUnauthorized()

Expand Down Expand Up @@ -355,6 +358,17 @@ async def websocket_handler(self, request):
queue = BasicMessageQueue()
loop = asyncio.get_event_loop()

admin_api_key = self.context.settings.get("admin.admin_api_key")
admin_insecure_mode = self.context.settings.get("admin.admin_insecure_mode")
if admin_insecure_mode:
# open to send websocket messages without api key auth
queue.api_key_authenticated = True
else:
header_admin_api_key = request.headers.get("x-api-key")
if header_admin_api_key == admin_api_key:
# authenticated via http header
queue.api_key_authenticated = True

try:
self.websocket_queues[socket_id] = queue
await queue.enqueue(
Expand All @@ -372,7 +386,7 @@ async def websocket_handler(self, request):
)

closed = False
receive = loop.create_task(ws.receive())
receive = loop.create_task(ws.receive_json())
send = loop.create_task(queue.dequeue(timeout=5.0))

while not closed:
Expand All @@ -384,9 +398,22 @@ async def websocket_handler(self, request):
closed = True

if receive.done():
# ignored
if not closed:
receive = loop.create_task(ws.receive())
msg_received = None
msg_api_key = None
try:
# this call can re-raise exeptions from inside the task
msg_received = receive.result()
msg_api_key = msg_received.get("x-api-key")
except Exception as ex:
LOGGER.error("Exception in websocket receiving task:")
LOGGER.exception(ex)
if admin_api_key:
if admin_api_key == msg_api_key:
# authenticated via websocket message
queue.api_key_authenticated = True

receive = loop.create_task(ws.receive_json())

if send.done():
try:
Expand All @@ -397,7 +424,10 @@ async def websocket_handler(self, request):
if msg is None:
# we send fake pings because the JS client
# can't detect real ones
msg = {"topic": "ping"}
msg = {
"topic": "ping",
"authenticated": queue.api_key_authenticated,
}
if not closed:
if msg:
await ws.send_json(msg)
Expand Down Expand Up @@ -441,4 +471,5 @@ async def send_webhook(self, topic: str, payload: dict):
)

for queue in self.websocket_queues.values():
await queue.enqueue({"topic": topic, "payload": payload})
if queue.api_key_authenticated:
await queue.enqueue({"topic": topic, "payload": payload})
29 changes: 29 additions & 0 deletions aries_cloudagent/admin/tests/test_admin_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,35 @@ async def test_status_secure(self):
result = await resp.json()
assert isinstance(result, dict)

@unittest_run_loop
async def test_websocket_with_api_key_message(self):
async with self.client.ws_connect("/ws") as ws:
result = await ws.receive_json()
assert result["topic"] == "settings"

ping1 = await ws.receive_json()
assert ping1["topic"] == "ping"
assert ping1["authenticated"] == False

await ws.send_json({"dummy": ""})
ping2 = await ws.receive_json()
assert ping2["authenticated"] == False

await ws.send_json({"x-api-key": self.TEST_API_KEY})
ping3 = await ws.receive_json()
assert ping3["authenticated"] == True

@unittest_run_loop
async def test_websocket_with_api_key_header(self):
async with self.client.ws_connect(
"/ws", headers={"x-api-key": self.TEST_API_KEY}
) as ws:
result = await ws.receive_json()
assert result["topic"] == "settings"

ping1 = await ws.receive_json()
assert ping1["authenticated"] == True


class TestAdminServerWebhook(AioHTTPTestCase):
async def setUpAsync(self):
Expand Down
1 change: 1 addition & 0 deletions aries_cloudagent/transport/queue/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def __init__(self):
self.queue = self.make_queue()
self.logger = logging.getLogger(__name__)
self.stop_event = asyncio.Event()
self.api_key_authenticated = False

def make_queue(self):
"""Create the queue instance."""
Expand Down

0 comments on commit 0c118e0

Please sign in to comment.