Merge lp:~rvb/maas/bug-1437388 into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 3839
Proposed branch: lp:~rvb/maas/bug-1437388
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 137 lines (+46/-15)
2 files modified
src/maasserver/websockets/protocol.py (+18/-5)
src/maasserver/websockets/tests/test_protocol.py (+28/-10)
To merge this branch: bzr merge lp:~rvb/maas/bug-1437388
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Gavin Panella (community) Approve
Review via email: mp+257237@code.launchpad.net

Commit message

Add the client object to the list of "active" clients only after authentication succeeds. This fixes a race condition where the notifications would be passed on to a client before it's authenticated.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good, nicely sleuthed. One comment about handing errors, but otherwise it's grand.

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

Thanks for the review, I've fixed authenticate (using the decorator @deferred).

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good to me as well.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/websockets/protocol.py'
2--- src/maasserver/websockets/protocol.py 2015-04-16 07:38:04 +0000
3+++ src/maasserver/websockets/protocol.py 2015-04-23 13:20:47 +0000
4@@ -36,7 +36,10 @@
5 from maasserver.websockets import handlers
6 from maasserver.websockets.listener import PostgresListener
7 from maasserver.websockets.websockets import STATUSES
8-from provisioningserver.utils.twisted import synchronous
9+from provisioningserver.utils.twisted import (
10+ deferred,
11+ synchronous,
12+)
13 from twisted.internet import reactor
14 from twisted.internet.defer import inlineCallbacks
15 from twisted.internet.protocol import (
16@@ -89,21 +92,29 @@
17
18 def connectionMade(self):
19 """Connection has been made to client."""
20- self.factory.clients.append(self)
21-
22 # Using the provided cookies on the connection request, authenticate
23 # the client. If this fails or if the CSRF token can't be found, it
24 # will call loseConnection. A websocket connection is only allowed
25 # from an authenticated user.
26 cookies = self.transport.cookies
27- self.authenticate(
28+ d = self.authenticate(
29 get_cookie(cookies, 'sessionid'),
30 get_cookie(cookies, 'csrftoken'),
31 )
32
33+ # Only add the client to the list of known clients if/when the
34+ # authentication succeeds.
35+ def add_client(user):
36+ if user is not None:
37+ self.factory.clients.append(self)
38+ d.addCallback(add_client)
39+
40 def connectionLost(self, reason):
41 """Connection to the client has been lost."""
42- self.factory.clients.remove(self)
43+ # If the connection is lost before the authentication happens, the
44+ # 'client' will not have been added to the list.
45+ if self in self.factory.clients:
46+ self.factory.clients.remove(self)
47
48 def loseConnection(self, status, reason):
49 """Close connection with status and reason."""
50@@ -146,6 +157,7 @@
51 else:
52 return None
53
54+ @deferred
55 def authenticate(self, session_id, csrftoken):
56 """Authenticate the connection.
57
58@@ -170,6 +182,7 @@
59 self.user = user
60
61 self.processMessages()
62+ return self.user
63
64 def got_user_error(failure):
65 self.loseConnection(
66
67=== modified file 'src/maasserver/websockets/tests/test_protocol.py'
68--- src/maasserver/websockets/tests/test_protocol.py 2015-04-16 07:38:04 +0000
69+++ src/maasserver/websockets/tests/test_protocol.py 2015-04-23 13:20:47 +0000
70@@ -49,6 +49,7 @@
71 Is,
72 IsInstance,
73 )
74+from twisted.internet import defer
75 from twisted.internet.defer import (
76 fail,
77 inlineCallbacks,
78@@ -90,12 +91,23 @@
79 call = protocol.transport.write.call_args_list.pop()
80 return json.loads(call[0][0])
81
82- def test_connectionMade_adds_self_to_factory(self):
83- protocol, factory = self.make_protocol()
84- protocol.connectionMade()
85- self.addCleanup(lambda: protocol.connectionLost(""))
86- self.assertItemsEqual(
87- [protocol], factory.clients)
88+ def test_connectionMade_adds_self_to_factory_if_auth_succeeds(self):
89+ protocol, factory = self.make_protocol()
90+ mock_authenticate = self.patch(protocol, "authenticate")
91+ user = maas_factory.make_User()
92+ mock_authenticate.return_value = defer.succeed(user)
93+ protocol.connectionMade()
94+ self.addCleanup(lambda: protocol.connectionLost(""))
95+ self.assertItemsEqual([protocol], factory.clients)
96+
97+ def test_connectionMade_doesnt_add_self_to_factory_if_auth_fails(self):
98+ protocol, factory = self.make_protocol()
99+ mock_authenticate = self.patch(protocol, "authenticate")
100+ fake_error = maas_factory.make_name()
101+ mock_authenticate.return_value = defer.fail(Exception(fake_error))
102+ protocol.connectionMade()
103+ self.addCleanup(lambda: protocol.connectionLost(""))
104+ self.assertNotIn(protocol, factory.clients)
105
106 def test_connectionMade_extracts_sessionid_and_csrftoken(self):
107 protocol, factory = self.make_protocol(patch_authenticate=False)
108@@ -118,10 +130,16 @@
109
110 def test_connectionLost_removes_self_from_factory(self):
111 protocol, factory = self.make_protocol()
112+ mock_authenticate = self.patch(protocol, "authenticate")
113+ mock_authenticate.return_value = defer.succeed(None)
114 protocol.connectionMade()
115 protocol.connectionLost("")
116- self.assertItemsEqual(
117- [], factory.clients)
118+ self.assertItemsEqual([], factory.clients)
119+
120+ def test_connectionLost_succeeds_if_client_hasnt_been_recorded(self):
121+ protocol, factory = self.make_protocol()
122+ self.assertIsNone(protocol.connectionLost(""))
123+ self.assertItemsEqual([], factory.clients)
124
125 def test_loseConnection_writes_to_log(self):
126 protocol, factory = self.make_protocol()
127@@ -571,8 +589,8 @@
128 protocol.transport = MagicMock()
129 if user is None:
130 user = maas_factory.make_User()
131- protocol.user = user
132- self.patch(protocol, "authenticate")
133+ mock_authenticate = self.patch(protocol, "authenticate")
134+ mock_authenticate.return_value = defer.succeed(user)
135 protocol.connectionMade()
136 self.addCleanup(lambda: protocol.connectionLost(""))
137 return protocol, factory