Repository: Eyepea/aiosip

Issue:
Improper handling of 401 authentication failures
In a call flow such as

* send REGISTER 
* get 401 response with auth challenge
* send REGISTER with valid authentication
* get 401 response with no challenge (ie: your credentials are fine, but still denied)

The client will continue to retransmit the request with the authorization header 

Discussion:

Patch:
diff --git a/aiosip/transaction.py b/aiosip/transaction.py
index da61942..4f2b0ab 100644
--- a/aiosip/transaction.py
+++ b/aiosip/transaction.py
@@ -145,6 +145,9 @@ class FutureTransaction(BaseTransaction):
         self.dialog.end_transaction(self)
 
     def _result(self, msg):
+        if self.authentification:
+            self.authentification.cancel()
+            self.authentification = None
         self._future.set_result(msg)
         self.dialog.end_transaction(self)
 


Test patch:
diff --git a/tests/test_sip_scenario.py b/tests/test_sip_scenario.py
index d38e7c3..fdb1a89 100644
--- a/tests/test_sip_scenario.py
+++ b/tests/test_sip_scenario.py
@@ -92,6 +92,53 @@ async def test_authentication(test_server, protocol, loop, from_details, to_deta
     await app.close()
 
 
+async def test_authentication_rejection(test_server, protocol, loop, from_details, to_details):
+    received_messages = list()
+
+    class Dialplan(aiosip.BaseDialplan):
+
+        async def resolve(self, *args, **kwargs):
+            await super().resolve(*args, **kwargs)
+            return self.subscribe
+
+        async def subscribe(self, request, message):
+            dialog = request._create_dialog()
+
+            received_messages.append(message)
+            await dialog.unauthorized(message)
+
+            async for message in dialog:
+                received_messages.append(message)
+                await dialog.reply(message, 401)
+
+    app = aiosip.Application(loop=loop)
+    server_app = aiosip.Application(loop=loop, dialplan=Dialplan())
+    server = await test_server(server_app)
+
+    peer = await app.connect(
+        protocol=protocol,
+        remote_addr=(
+            server.sip_config['server_host'],
+            server.sip_config['server_port'],
+        )
+    )
+
+    result = await peer.register(
+        expires=1800,
+        from_details=aiosip.Contact.from_header(from_details),
+        to_details=aiosip.Contact.from_header(to_details),
+        password='testing_pass',
+    )
+
+    # wait long enough to ensure no improper retransmit
+    await asyncio.sleep(1)
+    assert len(received_messages) == 2
+    assert result.status_code == 401
+
+    await server_app.close()
+    await app.close()
+
+
 async def test_invite(test_server, protocol, loop, from_details, to_details):
     call_established = loop.create_future()
     call_disconnected = loop.create_future()