Merge lp:~frankban/charms/precise/juju-gui/guiserver-auth-fixes into lp:~juju-gui/charms/precise/juju-gui/trunk
- Precise Pangolin (12.04)
- guiserver-auth-fixes
- Merge into trunk
Proposed by
Francesco Banconi
Status: | Merged |
---|---|
Merged at revision: | 86 |
Proposed branch: | lp:~frankban/charms/precise/juju-gui/guiserver-auth-fixes |
Merge into: | lp:~juju-gui/charms/precise/juju-gui/trunk |
Diff against target: |
409 lines (+82/-50) 7 files modified
server/guiserver/apps.py (+8/-5) server/guiserver/auth.py (+11/-5) server/guiserver/handlers.py (+7/-3) server/guiserver/tests/test_apps.py (+0/-7) server/guiserver/tests/test_auth.py (+17/-2) server/guiserver/tests/test_handlers.py (+37/-27) server/guiserver/tests/test_manage.py (+2/-1) |
To merge this branch: | bzr merge lp:~frankban/charms/precise/juju-gui/guiserver-auth-fixes |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
charmers | Pending | ||
Review via email:
|
Commit message
Description of the change
Authentication fixes.
Fixed an error in the guiserver authentication
when the request id is set to 0.
Moved the auth_backend from the app to the
handler: it is only relevant in the WebSocketHandler
context.
Some clean up in the tests.
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Benjamin Saller (bcsaller) wrote : | # |
LGTM
The whole 'is not None' things gets us all from time to time.
Thanks
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Madison Scott-Clary (makyo) wrote : | # |
Did not QA, but LGTM, thanks!
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Authentication fixes.
Fixed an error in the guiserver authentication
when the request id is set to 0.
Moved the auth_backend from the app to the
handler: it is only relevant in the WebSocketHandler
context.
Some clean up in the tests.
R=benjamin.saller, matthew.scott
CC=
https:/
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
Thank you both for the reviews!
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'server/guiserver/apps.py' | |||
2 | --- server/guiserver/apps.py 2013-07-25 11:33:00 +0000 | |||
3 | +++ server/guiserver/apps.py 2013-08-07 16:40:41 +0000 | |||
4 | @@ -36,12 +36,16 @@ | |||
5 | 36 | # Set up static paths. | 36 | # Set up static paths. |
6 | 37 | guiroot = options.guiroot | 37 | guiroot = options.guiroot |
7 | 38 | static_path = os.path.join(guiroot, 'juju-ui') | 38 | static_path = os.path.join(guiroot, 'juju-ui') |
8 | 39 | # Set up the authentication backend. | ||
9 | 40 | auth_backend = auth.get_backend(options.apiversion) | ||
10 | 41 | # Set up handlers. | 39 | # Set up handlers. |
11 | 40 | websocket_handler_options = { | ||
12 | 41 | # The Juju API backend url. | ||
13 | 42 | 'apiurl': options.apiurl, | ||
14 | 43 | # The backend to use for user authentication. | ||
15 | 44 | 'auth_backend': auth.get_backend(options.apiversion), | ||
16 | 45 | } | ||
17 | 42 | server_handlers = [ | 46 | server_handlers = [ |
18 | 43 | # Handle WebSocket connections. | 47 | # Handle WebSocket connections. |
20 | 44 | (r'^/ws$', handlers.WebSocketHandler, {'apiurl': options.apiurl}), | 48 | (r'^/ws$', handlers.WebSocketHandler, websocket_handler_options), |
21 | 45 | # Handle static files. | 49 | # Handle static files. |
22 | 46 | (r'^/juju-ui/(.*)', web.StaticFileHandler, {'path': static_path}), | 50 | (r'^/juju-ui/(.*)', web.StaticFileHandler, {'path': static_path}), |
23 | 47 | (r'^/(favicon\.ico)$', web.StaticFileHandler, {'path': guiroot}), | 51 | (r'^/(favicon\.ico)$', web.StaticFileHandler, {'path': guiroot}), |
24 | @@ -56,8 +60,7 @@ | |||
25 | 56 | # Any other path is served by index.html. | 60 | # Any other path is served by index.html. |
26 | 57 | (r'^/(.*)', handlers.IndexHandler, {'path': guiroot}), | 61 | (r'^/(.*)', handlers.IndexHandler, {'path': guiroot}), |
27 | 58 | ) | 62 | ) |
30 | 59 | return web.Application( | 63 | return web.Application(server_handlers, debug=options.debug) |
29 | 60 | server_handlers, debug=options.debug, auth_backend=auth_backend) | ||
31 | 61 | 64 | ||
32 | 62 | 65 | ||
33 | 63 | def redirector(): | 66 | def redirector(): |
34 | 64 | 67 | ||
35 | === modified file 'server/guiserver/auth.py' | |||
36 | --- server/guiserver/auth.py 2013-07-25 15:42:46 +0000 | |||
37 | +++ server/guiserver/auth.py 2013-08-07 16:40:41 +0000 | |||
38 | @@ -21,10 +21,10 @@ | |||
39 | 21 | - User: a simple data structure representing a logged in or anonymous user; | 21 | - User: a simple data structure representing a logged in or anonymous user; |
40 | 22 | - authentication backends (GoBackend and PythonBackend): any object | 22 | - authentication backends (GoBackend and PythonBackend): any object |
41 | 23 | implementing the following interface: | 23 | implementing the following interface: |
46 | 24 | - get_request_id(data); | 24 | - get_request_id(data) -> id or None; |
47 | 25 | - request_is_login(data); | 25 | - request_is_login(data) -> bool; |
48 | 26 | - get_credentials(data); | 26 | - get_credentials(data) -> (str, str); |
49 | 27 | - login_succeeded(data). | 27 | - login_succeeded(data) -> bool. |
50 | 28 | The only purpose of auth backends is to provide the logic to parse | 28 | The only purpose of auth backends is to provide the logic to parse |
51 | 29 | requests' data based on the API implementation currently in use. Backends | 29 | requests' data based on the API implementation currently in use. Backends |
52 | 30 | don't know anything about the authentication process or the current user, | 30 | don't know anything about the authentication process or the current user, |
53 | @@ -36,6 +36,8 @@ | |||
54 | 36 | if the authentication succeeds. | 36 | if the authentication succeeds. |
55 | 37 | """ | 37 | """ |
56 | 38 | 38 | ||
57 | 39 | import logging | ||
58 | 40 | |||
59 | 39 | 41 | ||
60 | 40 | class User(object): | 42 | class User(object): |
61 | 41 | """The current WebSocket user.""" | 43 | """The current WebSocket user.""" |
62 | @@ -57,6 +59,9 @@ | |||
63 | 57 | username = self.username or 'anonymous' | 59 | username = self.username or 'anonymous' |
64 | 58 | return '<User: {} ({})>'.format(username, status) | 60 | return '<User: {} ({})>'.format(username, status) |
65 | 59 | 61 | ||
66 | 62 | def __str__(self): | ||
67 | 63 | return self.username | ||
68 | 64 | |||
69 | 60 | 65 | ||
70 | 61 | class AuthMiddleware(object): | 66 | class AuthMiddleware(object): |
71 | 62 | """Handle user authentication. | 67 | """Handle user authentication. |
72 | @@ -84,7 +89,7 @@ | |||
73 | 84 | """ | 89 | """ |
74 | 85 | backend = self._backend | 90 | backend = self._backend |
75 | 86 | request_id = backend.get_request_id(data) | 91 | request_id = backend.get_request_id(data) |
77 | 87 | if request_id and backend.request_is_login(data): | 92 | if request_id is not None and backend.request_is_login(data): |
78 | 88 | self._request_id = request_id | 93 | self._request_id = request_id |
79 | 89 | credentials = backend.get_credentials(data) | 94 | credentials = backend.get_credentials(data) |
80 | 90 | self._user.username, self._user.password = credentials | 95 | self._user.username, self._user.password = credentials |
81 | @@ -100,6 +105,7 @@ | |||
82 | 100 | if request_id == self._request_id: | 105 | if request_id == self._request_id: |
83 | 101 | logged_in = self._backend.login_succeeded(data) | 106 | logged_in = self._backend.login_succeeded(data) |
84 | 102 | if logged_in: | 107 | if logged_in: |
85 | 108 | logging.info('auth: user {} logged in'.format(self._user)) | ||
86 | 103 | self._user.is_authenticated = True | 109 | self._user.is_authenticated = True |
87 | 104 | else: | 110 | else: |
88 | 105 | self._user.username = self._user.password = '' | 111 | self._user.username = self._user.password = '' |
89 | 106 | 112 | ||
90 | === modified file 'server/guiserver/handlers.py' | |||
91 | --- server/guiserver/handlers.py 2013-07-24 16:22:21 +0000 | |||
92 | +++ server/guiserver/handlers.py 2013-08-07 16:40:41 +0000 | |||
93 | @@ -64,8 +64,13 @@ | |||
94 | 64 | """ | 64 | """ |
95 | 65 | 65 | ||
96 | 66 | @gen.coroutine | 66 | @gen.coroutine |
99 | 67 | def initialize(self, apiurl, io_loop=None): | 67 | def initialize(self, apiurl, auth_backend, io_loop=None): |
100 | 68 | """Create a new WebSocket client and connect it to the Juju API.""" | 68 | """Initialize the WebSocket server. |
101 | 69 | |||
102 | 70 | Create a new WebSocket client and connect it to the Juju API. | ||
103 | 71 | Set up the authentication system. | ||
104 | 72 | Handle the queued messages. | ||
105 | 73 | """ | ||
106 | 69 | if io_loop is None: | 74 | if io_loop is None: |
107 | 70 | io_loop = IOLoop.current() | 75 | io_loop = IOLoop.current() |
108 | 71 | self._io_loop = io_loop | 76 | self._io_loop = io_loop |
109 | @@ -76,7 +81,6 @@ | |||
110 | 76 | self._juju_message_queue = queue = deque() | 81 | self._juju_message_queue = queue = deque() |
111 | 77 | # Set up the authentication infrastructure. | 82 | # Set up the authentication infrastructure. |
112 | 78 | self.user = User() | 83 | self.user = User() |
113 | 79 | auth_backend = self.application.settings['auth_backend'] | ||
114 | 80 | self.auth = AuthMiddleware(self.user, auth_backend) | 84 | self.auth = AuthMiddleware(self.user, auth_backend) |
115 | 81 | # Juju requires the Origin header to be included in the WebSocket | 85 | # Juju requires the Origin header to be included in the WebSocket |
116 | 82 | # client handshake request. Propagate the client origin if present; | 86 | # client handshake request. Propagate the client origin if present; |
117 | 83 | 87 | ||
118 | === modified file 'server/guiserver/tests/test_apps.py' | |||
119 | --- server/guiserver/tests/test_apps.py 2013-07-26 09:45:42 +0000 | |||
120 | +++ server/guiserver/tests/test_apps.py 2013-08-07 16:40:41 +0000 | |||
121 | @@ -22,7 +22,6 @@ | |||
122 | 22 | 22 | ||
123 | 23 | from guiserver import ( | 23 | from guiserver import ( |
124 | 24 | apps, | 24 | apps, |
125 | 25 | auth, | ||
126 | 26 | handlers, | 25 | handlers, |
127 | 27 | ) | 26 | ) |
128 | 28 | 27 | ||
129 | @@ -90,12 +89,6 @@ | |||
130 | 90 | spec = self.get_url_spec(app, r'^/test/(.*)$') | 89 | spec = self.get_url_spec(app, r'^/test/(.*)$') |
131 | 91 | self.assertIsNone(spec) | 90 | self.assertIsNone(spec) |
132 | 92 | 91 | ||
133 | 93 | def test_auth_backend(self): | ||
134 | 94 | # The server settings include the currently used auth backend. | ||
135 | 95 | app = self.get_app(apiversion='go') | ||
136 | 96 | self.assertIn('auth_backend', app.settings) | ||
137 | 97 | self.assertIsInstance(app.settings['auth_backend'], auth.GoBackend) | ||
138 | 98 | |||
139 | 99 | 92 | ||
140 | 100 | class TestRedirector(AppsTestMixin, unittest.TestCase): | 93 | class TestRedirector(AppsTestMixin, unittest.TestCase): |
141 | 101 | 94 | ||
142 | 102 | 95 | ||
143 | === modified file 'server/guiserver/tests/test_auth.py' | |||
144 | --- server/guiserver/tests/test_auth.py 2013-07-25 15:42:46 +0000 | |||
145 | +++ server/guiserver/tests/test_auth.py 2013-08-07 16:40:41 +0000 | |||
146 | @@ -18,6 +18,8 @@ | |||
147 | 18 | 18 | ||
148 | 19 | import unittest | 19 | import unittest |
149 | 20 | 20 | ||
150 | 21 | from tornado.testing import LogTrapTestCase | ||
151 | 22 | |||
152 | 21 | from guiserver import auth | 23 | from guiserver import auth |
153 | 22 | from guiserver.tests import helpers | 24 | from guiserver.tests import helpers |
154 | 23 | 25 | ||
155 | @@ -44,6 +46,11 @@ | |||
156 | 44 | expected = '<User: anonymous (not authenticated)>' | 46 | expected = '<User: anonymous (not authenticated)>' |
157 | 45 | self.assertEqual(expected, repr(user)) | 47 | self.assertEqual(expected, repr(user)) |
158 | 46 | 48 | ||
159 | 49 | def test_str(self): | ||
160 | 50 | # The string representation of an user is correctly generated. | ||
161 | 51 | user = auth.User(username='the-doctor') | ||
162 | 52 | self.assertEqual('the-doctor', str(user)) | ||
163 | 53 | |||
164 | 47 | 54 | ||
165 | 48 | class AuthMiddlewareTestMixin(object): | 55 | class AuthMiddlewareTestMixin(object): |
166 | 49 | """Include tests for the AuthMiddleware. | 56 | """Include tests for the AuthMiddleware. |
167 | @@ -118,15 +125,23 @@ | |||
168 | 118 | self.assert_user('user2', 'passwd2', True) | 125 | self.assert_user('user2', 'passwd2', True) |
169 | 119 | self.assertFalse(self.auth.in_progress()) | 126 | self.assertFalse(self.auth.in_progress()) |
170 | 120 | 127 | ||
171 | 128 | def test_request_id_is_zero(self): | ||
172 | 129 | # The authentication process starts if a login request is processed | ||
173 | 130 | # and the request id is zero. | ||
174 | 131 | request = self.make_login_request(request_id=0) | ||
175 | 132 | self.auth.process_request(request) | ||
176 | 133 | self.assertTrue(self.auth.in_progress()) | ||
177 | 134 | |||
178 | 121 | 135 | ||
179 | 122 | class TestGoAuthMiddleware( | 136 | class TestGoAuthMiddleware( |
181 | 123 | helpers.GoAPITestMixin, AuthMiddlewareTestMixin, unittest.TestCase): | 137 | helpers.GoAPITestMixin, AuthMiddlewareTestMixin, |
182 | 138 | LogTrapTestCase, unittest.TestCase): | ||
183 | 124 | pass | 139 | pass |
184 | 125 | 140 | ||
185 | 126 | 141 | ||
186 | 127 | class TestPythonAuthMiddleware( | 142 | class TestPythonAuthMiddleware( |
187 | 128 | helpers.PythonAPITestMixin, AuthMiddlewareTestMixin, | 143 | helpers.PythonAPITestMixin, AuthMiddlewareTestMixin, |
189 | 129 | unittest.TestCase): | 144 | LogTrapTestCase, unittest.TestCase): |
190 | 130 | pass | 145 | pass |
191 | 131 | 146 | ||
192 | 132 | 147 | ||
193 | 133 | 148 | ||
194 | === modified file 'server/guiserver/tests/test_handlers.py' | |||
195 | --- server/guiserver/tests/test_handlers.py 2013-07-25 15:42:46 +0000 | |||
196 | +++ server/guiserver/tests/test_handlers.py 2013-08-07 16:40:41 +0000 | |||
197 | @@ -24,6 +24,7 @@ | |||
198 | 24 | import mock | 24 | import mock |
199 | 25 | from tornado import ( | 25 | from tornado import ( |
200 | 26 | concurrent, | 26 | concurrent, |
201 | 27 | gen, | ||
202 | 27 | web, | 28 | web, |
203 | 28 | ) | 29 | ) |
204 | 29 | from tornado.testing import ( | 30 | from tornado.testing import ( |
205 | @@ -46,6 +47,7 @@ | |||
206 | 46 | class WebSocketHandlerTestMixin(object): | 47 | class WebSocketHandlerTestMixin(object): |
207 | 47 | """Base set up for all the WebSocketHandler test cases.""" | 48 | """Base set up for all the WebSocketHandler test cases.""" |
208 | 48 | 49 | ||
209 | 50 | auth_backend = auth.get_backend(manage.DEFAULT_API_VERSION) | ||
210 | 49 | hello_message = json.dumps({'hello': 'world'}) | 51 | hello_message = json.dumps({'hello': 'world'}) |
211 | 50 | 52 | ||
212 | 51 | def get_app(self): | 53 | def get_app(self): |
213 | @@ -57,20 +59,21 @@ | |||
214 | 57 | # ws-client -> ws-server -> ws-forwarding-client -> ws-echo-server | 59 | # ws-client -> ws-server -> ws-forwarding-client -> ws-echo-server |
215 | 58 | # Messages arriving to the echo server are returned back to the client: | 60 | # Messages arriving to the echo server are returned back to the client: |
216 | 59 | # ws-echo-server -> ws-forwarding-client -> ws-server -> ws-client | 61 | # ws-echo-server -> ws-forwarding-client -> ws-server -> ws-client |
219 | 60 | self.echo_server_address = self.get_wss_url('/echo') | 62 | self.apiurl = self.get_wss_url('/echo') |
220 | 61 | self.echo_server_closed_future = concurrent.Future() | 63 | self.api_close_future = concurrent.Future() |
221 | 62 | echo_options = { | 64 | echo_options = { |
223 | 63 | 'close_future': self.echo_server_closed_future, | 65 | 'close_future': self.api_close_future, |
224 | 64 | 'io_loop': self.io_loop, | 66 | 'io_loop': self.io_loop, |
225 | 65 | } | 67 | } |
226 | 66 | ws_options = { | 68 | ws_options = { |
228 | 67 | 'apiurl': self.echo_server_address, | 69 | 'apiurl': self.apiurl, |
229 | 70 | 'auth_backend': self.auth_backend, | ||
230 | 68 | 'io_loop': self.io_loop, | 71 | 'io_loop': self.io_loop, |
231 | 69 | } | 72 | } |
232 | 70 | return web.Application([ | 73 | return web.Application([ |
233 | 71 | (r'/echo', helpers.EchoWebSocketHandler, echo_options), | 74 | (r'/echo', helpers.EchoWebSocketHandler, echo_options), |
234 | 72 | (r'/ws', handlers.WebSocketHandler, ws_options), | 75 | (r'/ws', handlers.WebSocketHandler, ws_options), |
236 | 73 | ], auth_backend=auth.get_backend(manage.DEFAULT_API_VERSION)) | 76 | ]) |
237 | 74 | 77 | ||
238 | 75 | def make_client(self): | 78 | def make_client(self): |
239 | 76 | """Return a WebSocket client ready to be connected to the server.""" | 79 | """Return a WebSocket client ready to be connected to the server.""" |
240 | @@ -90,6 +93,17 @@ | |||
241 | 90 | handler.ws_connection = mock.Mock() | 93 | handler.ws_connection = mock.Mock() |
242 | 91 | return handler | 94 | return handler |
243 | 92 | 95 | ||
244 | 96 | @gen.coroutine | ||
245 | 97 | def make_initialized_handler( | ||
246 | 98 | self, apiurl=None, headers=None, mock_protocol=False): | ||
247 | 99 | """Create and return an initialized WebSocketHandler instance.""" | ||
248 | 100 | if apiurl is None: | ||
249 | 101 | apiurl = self.apiurl | ||
250 | 102 | handler = self.make_handler( | ||
251 | 103 | headers=headers, mock_protocol=mock_protocol) | ||
252 | 104 | yield handler.initialize(apiurl, self.auth_backend, self.io_loop) | ||
253 | 105 | raise gen.Return(handler) | ||
254 | 106 | |||
255 | 93 | 107 | ||
256 | 94 | class TestWebSocketHandlerConnection( | 108 | class TestWebSocketHandlerConnection( |
257 | 95 | WebSocketHandlerTestMixin, helpers.WSSTestMixin, LogTrapTestCase, | 109 | WebSocketHandlerTestMixin, helpers.WSSTestMixin, LogTrapTestCase, |
258 | @@ -107,8 +121,7 @@ | |||
259 | 107 | def test_initialization(self): | 121 | def test_initialization(self): |
260 | 108 | # A WebSocket client is created and connected when the handler is | 122 | # A WebSocket client is created and connected when the handler is |
261 | 109 | # initialized. | 123 | # initialized. |
264 | 110 | handler = self.make_handler() | 124 | handler = yield self.make_initialized_handler() |
263 | 111 | yield handler.initialize(self.echo_server_address, self.io_loop) | ||
265 | 112 | self.assertTrue(handler.connected) | 125 | self.assertTrue(handler.connected) |
266 | 113 | self.assertTrue(handler.juju_connected) | 126 | self.assertTrue(handler.juju_connected) |
267 | 114 | self.assertIsInstance( | 127 | self.assertIsInstance( |
268 | @@ -120,38 +133,36 @@ | |||
269 | 120 | def test_juju_connection_failure(self): | 133 | def test_juju_connection_failure(self): |
270 | 121 | # If the connection to the Juju API server does not succeed, an | 134 | # If the connection to the Juju API server does not succeed, an |
271 | 122 | # error is reported and the client is disconnected. | 135 | # error is reported and the client is disconnected. |
272 | 123 | handler = self.make_handler() | ||
273 | 124 | expected_log = '.*unable to connect to the Juju API' | 136 | expected_log = '.*unable to connect to the Juju API' |
274 | 125 | with ExpectLog('', expected_log, required=True): | 137 | with ExpectLog('', expected_log, required=True): |
277 | 126 | yield handler.initialize( | 138 | handler = yield self.make_initialized_handler( |
278 | 127 | 'wss://127.0.0.1/does-not-exist', self.io_loop) | 139 | apiurl='wss://127.0.0.1/no-such') |
279 | 128 | self.assertFalse(handler.connected) | 140 | self.assertFalse(handler.connected) |
280 | 129 | self.assertFalse(handler.juju_connected) | 141 | self.assertFalse(handler.juju_connected) |
281 | 130 | 142 | ||
282 | 131 | @gen_test | 143 | @gen_test |
283 | 132 | def test_juju_connection_propagated_request_headers(self): | 144 | def test_juju_connection_propagated_request_headers(self): |
284 | 133 | # The Origin header is propagated to the client connection. | 145 | # The Origin header is propagated to the client connection. |
287 | 134 | handler = self.make_handler(headers={'Origin': 'https://example.com'}) | 146 | expected = {'Origin': 'https://example.com'} |
288 | 135 | yield handler.initialize(self.echo_server_address, self.io_loop) | 147 | handler = yield self.make_initialized_handler(headers=expected) |
289 | 136 | headers = handler.juju_connection.request.headers | 148 | headers = handler.juju_connection.request.headers |
290 | 137 | self.assertIn('Origin', headers) | 149 | self.assertIn('Origin', headers) |
292 | 138 | self.assertEqual('https://example.com', headers['Origin']) | 150 | self.assertEqual(expected['Origin'], headers['Origin']) |
293 | 139 | 151 | ||
294 | 140 | @gen_test | 152 | @gen_test |
295 | 141 | def test_juju_connection_default_request_headers(self): | 153 | def test_juju_connection_default_request_headers(self): |
296 | 142 | # The default Origin header is included in the client connection | 154 | # The default Origin header is included in the client connection |
297 | 143 | # handshake if not found in the original request. | 155 | # handshake if not found in the original request. |
300 | 144 | handler = self.make_handler() | 156 | handler = yield self.make_initialized_handler() |
299 | 145 | yield handler.initialize(self.echo_server_address, self.io_loop) | ||
301 | 146 | headers = handler.juju_connection.request.headers | 157 | headers = handler.juju_connection.request.headers |
302 | 147 | self.assertIn('Origin', headers) | 158 | self.assertIn('Origin', headers) |
303 | 148 | self.assertEqual(self.get_url('/echo'), headers['Origin']) | 159 | self.assertEqual(self.get_url('/echo'), headers['Origin']) |
304 | 149 | 160 | ||
305 | 161 | @gen_test | ||
306 | 150 | def test_client_callback(self): | 162 | def test_client_callback(self): |
307 | 151 | # The WebSocket client is created passing the proper callback. | 163 | # The WebSocket client is created passing the proper callback. |
308 | 152 | handler = self.make_handler() | ||
309 | 153 | with self.mock_websocket_connect() as mock_websocket_connect: | 164 | with self.mock_websocket_connect() as mock_websocket_connect: |
311 | 154 | handler.initialize(self.echo_server_address, self.io_loop) | 165 | handler = yield self.make_initialized_handler() |
312 | 155 | self.assertEqual(1, mock_websocket_connect.call_count) | 166 | self.assertEqual(1, mock_websocket_connect.call_count) |
313 | 156 | self.assertIn( | 167 | self.assertIn( |
314 | 157 | handler.on_juju_message, mock_websocket_connect.call_args[0]) | 168 | handler.on_juju_message, mock_websocket_connect.call_args[0]) |
315 | @@ -161,7 +172,7 @@ | |||
316 | 161 | # The proxy connection is terminated when the client disconnects. | 172 | # The proxy connection is terminated when the client disconnects. |
317 | 162 | client = yield self.make_client() | 173 | client = yield self.make_client() |
318 | 163 | yield client.close() | 174 | yield client.close() |
320 | 164 | yield self.echo_server_closed_future | 175 | yield self.api_close_future |
321 | 165 | 176 | ||
322 | 166 | @gen_test | 177 | @gen_test |
323 | 167 | def test_connection_closed_by_server(self): | 178 | def test_connection_closed_by_server(self): |
324 | @@ -171,7 +182,7 @@ | |||
325 | 171 | expected_log = '.*Juju API unexpectedly disconnected' | 182 | expected_log = '.*Juju API unexpectedly disconnected' |
326 | 172 | with ExpectLog('', expected_log, required=True): | 183 | with ExpectLog('', expected_log, required=True): |
327 | 173 | # Fire the Future in order to force an echo server disconnection. | 184 | # Fire the Future in order to force an echo server disconnection. |
329 | 174 | self.echo_server_closed_future.set_result(None) | 185 | self.api_close_future.set_result(None) |
330 | 175 | message = yield client.read_message() | 186 | message = yield client.read_message() |
331 | 176 | self.assertIsNone(message) | 187 | self.assertIsNone(message) |
332 | 177 | 188 | ||
333 | @@ -183,16 +194,15 @@ | |||
334 | 183 | @mock.patch('guiserver.clients.WebSocketClientConnection') | 194 | @mock.patch('guiserver.clients.WebSocketClientConnection') |
335 | 184 | def test_from_browser_to_juju(self, mock_juju_connection): | 195 | def test_from_browser_to_juju(self, mock_juju_connection): |
336 | 185 | # A message from the browser is forwarded to the remote server. | 196 | # A message from the browser is forwarded to the remote server. |
339 | 186 | handler = self.make_handler() | 197 | handler = yield self.make_initialized_handler() |
338 | 187 | yield handler.initialize(self.echo_server_address, self.io_loop) | ||
340 | 188 | handler.on_message(self.hello_message) | 198 | handler.on_message(self.hello_message) |
341 | 189 | mock_juju_connection.write_message.assert_called_once_with( | 199 | mock_juju_connection.write_message.assert_called_once_with( |
342 | 190 | self.hello_message) | 200 | self.hello_message) |
343 | 191 | 201 | ||
344 | 202 | @gen_test | ||
345 | 192 | def test_from_juju_to_browser(self): | 203 | def test_from_juju_to_browser(self): |
346 | 193 | # A message from the remote server is returned to the browser. | 204 | # A message from the remote server is returned to the browser. |
349 | 194 | handler = self.make_handler() | 205 | handler = yield self.make_initialized_handler() |
348 | 195 | handler.initialize(self.echo_server_address, self.io_loop) | ||
350 | 196 | with mock.patch('guiserver.handlers.WebSocketHandler.write_message'): | 206 | with mock.patch('guiserver.handlers.WebSocketHandler.write_message'): |
351 | 197 | handler.on_juju_message(self.hello_message) | 207 | handler.on_juju_message(self.hello_message) |
352 | 198 | handler.write_message.assert_called_once_with(self.hello_message) | 208 | handler.write_message.assert_called_once_with(self.hello_message) |
353 | @@ -205,7 +215,7 @@ | |||
354 | 205 | mock_path = 'guiserver.clients.WebSocketClientConnection.write_message' | 215 | mock_path = 'guiserver.clients.WebSocketClientConnection.write_message' |
355 | 206 | with mock.patch(mock_path) as mock_write_message: | 216 | with mock.patch(mock_path) as mock_write_message: |
356 | 207 | initialization = handler.initialize( | 217 | initialization = handler.initialize( |
358 | 208 | self.echo_server_address, self.io_loop) | 218 | self.apiurl, self.auth_backend, self.io_loop) |
359 | 209 | handler.on_message(self.hello_message) | 219 | handler.on_message(self.hello_message) |
360 | 210 | self.assertFalse(mock_write_message.called) | 220 | self.assertFalse(mock_write_message.called) |
361 | 211 | yield initialization | 221 | yield initialization |
362 | @@ -246,7 +256,7 @@ | |||
363 | 246 | def setUp(self): | 256 | def setUp(self): |
364 | 247 | super(TestWebSocketHandlerAuthentication, self).setUp() | 257 | super(TestWebSocketHandlerAuthentication, self).setUp() |
365 | 248 | self.handler = self.make_handler(mock_protocol=True) | 258 | self.handler = self.make_handler(mock_protocol=True) |
367 | 249 | self.handler.initialize(self.echo_server_address, self.io_loop) | 259 | self.handler.initialize(self.apiurl, self.auth_backend, self.io_loop) |
368 | 250 | 260 | ||
369 | 251 | def send_login_request(self): | 261 | def send_login_request(self): |
370 | 252 | """Create a login request and send it to the handler.""" | 262 | """Create a login request and send it to the handler.""" |
371 | @@ -292,7 +302,7 @@ | |||
372 | 292 | self.assertFalse(self.handler.auth.in_progress()) | 302 | self.assertFalse(self.handler.auth.in_progress()) |
373 | 293 | 303 | ||
374 | 294 | 304 | ||
376 | 295 | class TestIndexHandler(AsyncHTTPTestCase, LogTrapTestCase): | 305 | class TestIndexHandler(LogTrapTestCase, AsyncHTTPTestCase): |
377 | 296 | 306 | ||
378 | 297 | def setUp(self): | 307 | def setUp(self): |
379 | 298 | # Set up a static path with an index.html in it. | 308 | # Set up a static path with an index.html in it. |
380 | @@ -328,7 +338,7 @@ | |||
381 | 328 | self.ensure_index('/:flag:/activated/?my=query') | 338 | self.ensure_index('/:flag:/activated/?my=query') |
382 | 329 | 339 | ||
383 | 330 | 340 | ||
385 | 331 | class TestHttpsRedirectHandler(AsyncHTTPTestCase, LogTrapTestCase): | 341 | class TestHttpsRedirectHandler(LogTrapTestCase, AsyncHTTPTestCase): |
386 | 332 | 342 | ||
387 | 333 | def get_app(self): | 343 | def get_app(self): |
388 | 334 | return web.Application([(r'.*', handlers.HttpsRedirectHandler)]) | 344 | return web.Application([(r'.*', handlers.HttpsRedirectHandler)]) |
389 | 335 | 345 | ||
390 | === modified file 'server/guiserver/tests/test_manage.py' | |||
391 | --- server/guiserver/tests/test_manage.py 2013-07-31 10:29:56 +0000 | |||
392 | +++ server/guiserver/tests/test_manage.py 2013-08-07 16:40:41 +0000 | |||
393 | @@ -21,6 +21,7 @@ | |||
394 | 21 | import unittest | 21 | import unittest |
395 | 22 | 22 | ||
396 | 23 | import mock | 23 | import mock |
397 | 24 | from tornado.testing import LogTrapTestCase | ||
398 | 24 | 25 | ||
399 | 25 | from guiserver import manage | 26 | from guiserver import manage |
400 | 26 | 27 | ||
401 | @@ -138,7 +139,7 @@ | |||
402 | 138 | self.assertEqual(expected, manage._get_ssl_options()) | 139 | self.assertEqual(expected, manage._get_ssl_options()) |
403 | 139 | 140 | ||
404 | 140 | 141 | ||
406 | 141 | class TestRun(unittest.TestCase): | 142 | class TestRun(LogTrapTestCase, unittest.TestCase): |
407 | 142 | 143 | ||
408 | 143 | def mock_and_run(self, **kwargs): | 144 | def mock_and_run(self, **kwargs): |
409 | 144 | """Run the application after mocking the IO loop and the options/apps. | 145 | """Run the application after mocking the IO loop and the options/apps. |
Reviewers: mp+179014_ code.launchpad. net,
Message:
Please take a look.
Description:
Authentication fixes.
Fixed an error in the guiserver authentication
when the request id is set to 0.
Moved the auth_backend from the app to the
handler: it is only relevant in the WebSocketHandler
context.
Some clean up in the tests.
https:/ /code.launchpad .net/~frankban/ charms/ precise/ juju-gui/ guiserver- auth-fixes/ +merge/ 179014
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/12612043/
Affected files: guiserver/ apps.py guiserver/ auth.py guiserver/ handlers. py guiserver/ tests/test_ apps.py guiserver/ tests/test_ auth.py guiserver/ tests/test_ handlers. py guiserver/ tests/test_ manage. py
A [revision details]
M server/
M server/
M server/
M server/
M server/
M server/
M server/