Merge lp:~nataliabidart/ubuntuone-control-panel/raise-and-handle-unauthorized into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 135
Merged at revision: 129
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/raise-and-handle-unauthorized
Merge into: lp:ubuntuone-control-panel
Diff against target: 757 lines (+218/-44)
14 files modified
ubuntuone/controlpanel/backend.py (+29/-2)
ubuntuone/controlpanel/dbus_service.py (+29/-21)
ubuntuone/controlpanel/gtk/gui.py (+19/-5)
ubuntuone/controlpanel/gtk/package_manager.py (+3/-2)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+29/-1)
ubuntuone/controlpanel/gtk/tests/test_gui_basic.py (+9/-0)
ubuntuone/controlpanel/gtk/tests/test_package_manager.py (+2/-2)
ubuntuone/controlpanel/integrationtests/__init__.py (+1/-2)
ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+29/-0)
ubuntuone/controlpanel/integrationtests/test_webclient.py (+11/-1)
ubuntuone/controlpanel/replication_client.py (+1/-1)
ubuntuone/controlpanel/tests/test_backend.py (+43/-5)
ubuntuone/controlpanel/utils.py (+1/-1)
ubuntuone/controlpanel/webclient.py (+12/-1)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/raise-and-handle-unauthorized
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Roman Yepishev (community) fieldtest Approve
Review via email: mp+56782@code.launchpad.net

Commit message

- If servers reply with a 401, clear credentials and ask user to authenticate (LP: #726612).

Description of the change

To test IRL, you need to do a little tweak in the source code due to bug #751895.

- edit ubuntuone/controlpanel/webclient.py:66 and add the 400 error to the list of unauthorized errors
- killall ubuntuone-control-panel-backend
- in 2 terminals, run both the backend and the frontend from this branch:

DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-backend
DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-gtk

While you have your panel opened, go to https://one.ubuntu.com/account/machines/ and remove the computer you're currently using. Then, try to go to the folders tab or to remove a device (browsing devices will show an empty list due to bug #695820.
You should be redirected to the overview page and you should get your U1 credentials cleared from your local system.

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) wrote :

+1

Revision history for this message
Roman Yepishev (rye) wrote :

Awesome!

review: Approve (fieldtest)
Revision history for this message
Roberto Alsina (ralsina) :
review: Approve
Revision history for this message
Natalia Bidart (nataliabidart) wrote :
Download full text (79.8 KiB)

The attempt to merge lp:~nataliabidart/ubuntuone-control-panel/raise-and-handle-unauthorized into lp:ubuntuone-control-panel failed. Below is the output from the failed tests.

Running test suite for ubuntuone/controlpanel
Xlib: extension "RANDR" missing on display ":99".
ubuntuone.controlpanel.gtk.tests
  BaseTestCase
    runTest ... [OK]
ubuntuone.controlpanel.gtk.tests.test_gui_basic
  ControlPanelMixinTestCase
    test_is_a_control_panel_mixin ... [OK]
    test_ui_can_be_created ... [OK]
  ControlPanelTestCase
    test_backend_is_shutdown_on_close ... [OK]
    test_credentials_found_connects_syncdaemon ... [OK]
    test_credentials_found_shows_dashboard_panel ... [OK]
    test_credentials_found_shows_services_panel ... [OK]
    test_is_a_notebook ... [OK]
    test_local_device_removed_shows_overview_panel ... [OK]
    test_main_window_is_passed_to_child ... [OK]
    test_on_show_management_panel ... [OK]
    test_on_show_management_panel_is_idempotent ... [OK]
    test_overview_is_shown_at_startup ... [OK]
    test_startup_props ... [OK]
    test_startup_visibility ... [OK]
    test_unauthorized_shows_overview_panel ... [OK]
  ControlPanelWindowAlertParamTestCase
    test_alert ... [OK]
  ControlPanelWindowInvalidParamsTestCase
    test_closing_stops_the_main_lopp ... [OK]
    test_control_panel_is_the_only_child ... [OK]
    test_icon_name_is_correct ... [OK]
    test_is_a_window ... [OK]
    test_main_start_gtk_main_loop ... [OK]
    test_main_window_is_passed_to_child ... [OK]
    test_max_size ... [OK]
    test_startup_visibility ... [OK]
    test_switch_to ... [OK]
    test_title_is_correct ... [OK]
  ControlPanelWindowParamsNoneTestCase
    test_closing_stops_the_main_lopp ... [OK]
    test_control_panel_is_the_only_child ... [OK]
    test_icon_name_is_correct ... [OK]
    test_is_a_window ... [OK]
    test_main_start_gtk_main_loop ... [OK]
    test_main_window_is_passed_to_child ... [OK]
    test_max_size ....

135. By Natalia Bidart

Increasing the timeout fr webclient tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/controlpanel/backend.py'
2--- ubuntuone/controlpanel/backend.py 2011-04-03 03:07:53 +0000
3+++ ubuntuone/controlpanel/backend.py 2011-04-07 15:22:46 +0000
4@@ -20,14 +20,17 @@
5 """A backend for the Ubuntu One Control Panel."""
6
7 from collections import defaultdict
8+from functools import wraps
9
10 from twisted.internet.defer import inlineCallbacks, returnValue
11
12 from ubuntuone.controlpanel import dbus_client
13 from ubuntuone.controlpanel import replication_client
14 from ubuntuone.controlpanel.logger import setup_logging, log_call
15-from ubuntuone.controlpanel.webclient import WebClient, WebClientError
16-
17+# pylint: disable=W0611
18+from ubuntuone.controlpanel.webclient import (UnauthorizedError,
19+ WebClient, WebClientError)
20+# pylint: enable=W0611
21
22 logger = setup_logging('backend')
23
24@@ -71,6 +74,25 @@
25 return result
26
27
28+def process_unauthorized(f):
29+ """Decorator to catch UnauthorizedError from the webclient and act upon."""
30+
31+ @inlineCallbacks
32+ @wraps(f)
33+ def inner(*args, **kwargs):
34+ """Handle UnauthorizedError and clear credentials."""
35+ try:
36+ result = yield f(*args, **kwargs)
37+ except UnauthorizedError, e:
38+ logger.exception('process_unauthorized (clearing credentials):')
39+ yield dbus_client.clear_credentials()
40+ raise e
41+
42+ returnValue(result)
43+
44+ return inner
45+
46+
47 class ControlBackend(object):
48 """The control panel backend."""
49
50@@ -224,6 +246,7 @@
51 returnValue(is_local)
52
53 @log_call(logger.debug)
54+ @process_unauthorized
55 @inlineCallbacks
56 def account_info(self):
57 """Get the user account info."""
58@@ -251,6 +274,7 @@
59 returnValue(result)
60
61 @log_call(logger.debug)
62+ @process_unauthorized
63 @inlineCallbacks
64 def devices_info(self):
65 """Get the user devices info."""
66@@ -267,6 +291,8 @@
67
68 try:
69 devices = yield self.wc.call_api(DEVICES_API)
70+ except UnauthorizedError:
71+ raise
72 except WebClientError:
73 logger.exception('devices_info: web client failure:')
74 else:
75@@ -345,6 +371,7 @@
76 returnValue(device_id)
77
78 @log_call(logger.warning, with_args=False)
79+ @process_unauthorized
80 @inlineCallbacks
81 def remove_device(self, device_id):
82 """Remove a device's tokens from the sso server."""
83
84=== modified file 'ubuntuone/controlpanel/dbus_service.py'
85--- ubuntuone/controlpanel/dbus_service.py 2011-03-31 14:43:41 +0000
86+++ ubuntuone/controlpanel/dbus_service.py 2011-04-07 15:22:46 +0000
87@@ -32,7 +32,7 @@
88 from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
89 DBUS_PREFERENCES_IFACE)
90 from ubuntuone.controlpanel.backend import (
91- ControlBackend, filter_field,
92+ ControlBackend, filter_field, UnauthorizedError,
93 FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED,
94 FILE_SYNC_ERROR, FILE_SYNC_IDLE, FILE_SYNC_STARTING, FILE_SYNC_STOPPED,
95 FILE_SYNC_SYNCING,
96@@ -78,7 +78,7 @@
97 return result
98
99
100-def transform_failure(f):
101+def transform_failure(f, auth_error=None):
102 """Decorator to apply to DBus error signals.
103
104 With this call, a Failure is transformed into a string-string dict.
105@@ -88,7 +88,9 @@
106 """Do the Failure transformation."""
107 logger.error('processing failure: %r', error.printTraceback())
108 error_dict = error_handler(error)
109- if _ is not None:
110+ if auth_error is not None and error.check(UnauthorizedError):
111+ result = auth_error(error_dict)
112+ elif _ is not None:
113 result = f(_, error_dict)
114 else:
115 result = f(error_dict)
116@@ -117,19 +119,25 @@
117 super(ControlPanelBackend, self).__init__(*args, **kwargs)
118 self.backend = backend
119 self.backend.status_changed_handler = self.process_status
120+ self.transform = lambda f: transform_failure(f, self.UnauthorizedError)
121 logger.debug('ControlPanelBackend: created with %r, %r.\n'
122 'status_changed_handler is %r.',
123 args, kwargs, self.process_status)
124
125 # pylint: disable=C0103
126
127+ @log_call(logger.error)
128+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
129+ def UnauthorizedError(self, error):
130+ """The credentials are not valid."""
131+
132 @log_call(logger.debug)
133 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
134 def account_info(self):
135 """Find out the account info for the current logged in user."""
136 d = self.backend.account_info()
137 d.addCallback(self.AccountInfoReady)
138- d.addErrback(transform_failure(self.AccountInfoError))
139+ d.addErrback(self.transform(self.AccountInfoError))
140
141 @log_call(logger.debug)
142 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
143@@ -149,7 +157,7 @@
144 """Find out the devices info for the logged in user."""
145 d = self.backend.devices_info()
146 d.addCallback(self.DevicesInfoReady)
147- d.addErrback(transform_failure(self.DevicesInfoError))
148+ d.addErrback(self.transform(self.DevicesInfoError))
149
150 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
151 def DevicesInfoReady(self, info):
152@@ -170,7 +178,7 @@
153 """Configure a given device."""
154 d = self.backend.change_device_settings(device_id, settings)
155 d.addCallback(self.DeviceSettingsChanged)
156- d.addErrback(transform_failure(self.DeviceSettingsChangeError),
157+ d.addErrback(self.transform(self.DeviceSettingsChangeError),
158 device_id)
159
160 @log_call(logger.info, with_args=False)
161@@ -191,7 +199,7 @@
162 """Remove a given device."""
163 d = self.backend.remove_device(device_id)
164 d.addCallback(self.DeviceRemoved)
165- d.addErrback(transform_failure(self.DeviceRemovalError), device_id)
166+ d.addErrback(self.transform(self.DeviceRemovalError), device_id)
167
168 @log_call(logger.warning, with_args=False)
169 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
170@@ -237,7 +245,7 @@
171 """Get the status of the file sync service."""
172 d = self.backend.file_sync_status()
173 d.addCallback(self.process_status)
174- d.addErrback(transform_failure(self.FileSyncStatusError))
175+ d.addErrback(self.transform(self.FileSyncStatusError))
176
177 @log_call(logger.debug)
178 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
179@@ -287,7 +295,7 @@
180 """Enable the files service."""
181 d = self.backend.enable_files()
182 d.addCallback(lambda _: self.FilesEnabled())
183- d.addErrback(transform_failure(self.FilesEnableError))
184+ d.addErrback(self.transform(self.FilesEnableError))
185
186 @log_call(logger.debug)
187 @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
188@@ -307,7 +315,7 @@
189 """Disable the files service."""
190 d = self.backend.disable_files()
191 d.addCallback(lambda _: self.FilesDisabled())
192- d.addErrback(transform_failure(self.FilesDisableError))
193+ d.addErrback(self.transform(self.FilesDisableError))
194
195 @log_call(logger.debug)
196 @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
197@@ -327,7 +335,7 @@
198 """Connect the files service."""
199 d = self.backend.connect_files()
200 d.addCallback(lambda _: self.FilesConnected())
201- d.addErrback(transform_failure(self.FilesConnectError))
202+ d.addErrback(self.transform(self.FilesConnectError))
203
204 @log_call(logger.debug)
205 @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
206@@ -347,7 +355,7 @@
207 """Disconnect the files service."""
208 d = self.backend.disconnect_files()
209 d.addCallback(lambda _: self.FilesDisconnected())
210- d.addErrback(transform_failure(self.FilesDisconnectError))
211+ d.addErrback(self.transform(self.FilesDisconnectError))
212
213 @log_call(logger.debug)
214 @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
215@@ -367,7 +375,7 @@
216 """Restart the files service."""
217 d = self.backend.restart_files()
218 d.addCallback(lambda _: self.FilesRestarted())
219- d.addErrback(transform_failure(self.FilesRestartError))
220+ d.addErrback(self.transform(self.FilesRestartError))
221
222 @log_call(logger.debug)
223 @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
224@@ -387,7 +395,7 @@
225 """Start the files service."""
226 d = self.backend.start_files()
227 d.addCallback(lambda _: self.FilesStarted())
228- d.addErrback(transform_failure(self.FilesStartError))
229+ d.addErrback(self.transform(self.FilesStartError))
230
231 @log_call(logger.debug)
232 @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
233@@ -407,7 +415,7 @@
234 """Stop the files service."""
235 d = self.backend.stop_files()
236 d.addCallback(lambda _: self.FilesStopped())
237- d.addErrback(transform_failure(self.FilesStopError))
238+ d.addErrback(self.transform(self.FilesStopError))
239
240 @log_call(logger.debug)
241 @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
242@@ -427,7 +435,7 @@
243 """Find out the volumes info for the logged in user."""
244 d = self.backend.volumes_info()
245 d.addCallback(self.VolumesInfoReady)
246- d.addErrback(transform_failure(self.VolumesInfoError))
247+ d.addErrback(self.transform(self.VolumesInfoError))
248
249 @log_call(logger.debug)
250 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a(ssaa{ss})")
251@@ -447,7 +455,7 @@
252 """Configure a given volume."""
253 d = self.backend.change_volume_settings(volume_id, settings)
254 d.addCallback(self.VolumeSettingsChanged)
255- d.addErrback(transform_failure(self.VolumeSettingsChangeError),
256+ d.addErrback(self.transform(self.VolumeSettingsChangeError),
257 volume_id)
258
259 @log_call(logger.info)
260@@ -468,7 +476,7 @@
261 """Return the replications info."""
262 d = self.backend.replications_info()
263 d.addCallback(self.ReplicationsInfoReady)
264- d.addErrback(transform_failure(self.ReplicationsInfoError))
265+ d.addErrback(self.transform(self.ReplicationsInfoError))
266
267 @log_call(logger.debug)
268 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
269@@ -488,7 +496,7 @@
270 """Configure a given replication."""
271 d = self.backend.change_replication_settings(replication_id, settings)
272 d.addCallback(self.ReplicationSettingsChanged)
273- d.addErrback(transform_failure(self.ReplicationSettingsChangeError),
274+ d.addErrback(self.transform(self.ReplicationSettingsChangeError),
275 replication_id)
276
277 @log_call(logger.info)
278@@ -509,7 +517,7 @@
279 """Check if the extension to sync bookmarks is installed."""
280 d = self.backend.query_bookmark_extension()
281 d.addCallback(self.QueryBookmarksResult)
282- d.addErrback(transform_failure(self.QueryBookmarksError))
283+ d.addErrback(self.transform(self.QueryBookmarksError))
284
285 @log_call(logger.debug)
286 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b")
287@@ -529,7 +537,7 @@
288 """Install the extension to sync bookmarks."""
289 d = self.backend.install_bookmarks_extension()
290 d.addCallback(lambda _: self.InstallBookmarksSuccess())
291- d.addErrback(transform_failure(self.InstallBookmarksError))
292+ d.addErrback(self.transform(self.InstallBookmarksError))
293
294 @log_call(logger.info)
295 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="")
296
297=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
298--- ubuntuone/controlpanel/gtk/gui.py 2011-04-06 22:25:04 +0000
299+++ ubuntuone/controlpanel/gtk/gui.py 2011-04-07 15:22:46 +0000
300@@ -1323,6 +1323,8 @@
301 self.button.connect('clicked', self._on_button_clicked)
302 self.pack_start(self.button, expand=False)
303
304+ self.show_all()
305+
306 self.backend.connect_to_signal('FileSyncStatusDisabled',
307 self.on_file_sync_status_disabled)
308 self.backend.connect_to_signal('FileSyncStatusStarting',
309@@ -1344,10 +1346,6 @@
310 self.backend.connect_to_signal('FilesDisabled',
311 self.on_file_sync_status_disabled)
312
313- self.backend.file_sync_status(reply_handler=NO_OP,
314- error_handler=error_handler)
315- self.show_all()
316-
317 def _update_status(self, msg, action, callback,
318 icon=None, color=None, tooltip=None):
319 """Update the status info."""
320@@ -1460,6 +1458,11 @@
321 self.backend.stop_files(reply_handler=NO_OP,
322 error_handler=error_handler)
323
324+ def load(self):
325+ """Load the information."""
326+ self.backend.file_sync_status(reply_handler=NO_OP,
327+ error_handler=error_handler)
328+
329
330 class ManagementPanel(gtk.VBox, ControlPanelMixin):
331 """The management panel.
332@@ -1471,6 +1474,7 @@
333 __gsignals__ = {
334 'local-device-removed': (gobject.SIGNAL_RUN_FIRST,
335 gobject.TYPE_NONE, ()),
336+ 'unauthorized': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()),
337 }
338
339 QUOTA_LABEL = _('Using %(used)s of %(total)s (%(percentage).0f%%)')
340@@ -1496,6 +1500,8 @@
341 self.on_account_info_ready)
342 self.backend.connect_to_signal('AccountInfoError',
343 self.on_account_info_error)
344+ self.backend.connect_to_signal('UnauthorizedError',
345+ self.on_unauthorized_error)
346
347 self.quota_progressbar.set_sensitive(False)
348
349@@ -1536,7 +1542,6 @@
350
351 self.services_button.set_name(self.SERVICES_BUTTON_NAME)
352 self.services_button.set_tooltip_text(self.SERVICES_BUTTON_TOOLTIP)
353- self.services.load()
354
355 self.enable_volumes = lambda: self.volumes_button.set_sensitive(True)
356 self.disable_volumes = lambda: self.volumes_button.set_sensitive(False)
357@@ -1571,6 +1576,8 @@
358 """Load the account info and file sync status list."""
359 self.backend.account_info(reply_handler=NO_OP,
360 error_handler=error_handler)
361+ self.status_label.load()
362+ self.services.load()
363
364 @log_call(logger.debug)
365 def on_account_info_ready(self, info):
366@@ -1586,6 +1593,11 @@
367 """Backend notifies of an error when fetching account info."""
368 self._update_quota(msg='')
369
370+ @log_call(logger.error)
371+ def on_unauthorized_error(self, error_dict=None):
372+ """Backend notifies that credentials are not valid."""
373+ self.emit('unauthorized')
374+
375
376 class ControlPanel(gtk.Notebook, ControlPanelMixin):
377 """The control panel per se, can be added into any other widget."""
378@@ -1613,6 +1625,8 @@
379 self.on_show_management_panel)
380 self.management.connect('local-device-removed',
381 self.on_show_overview_panel)
382+ self.management.connect('unauthorized',
383+ self.on_show_overview_panel)
384
385 self.show()
386 self.on_show_overview_panel()
387
388=== modified file 'ubuntuone/controlpanel/gtk/package_manager.py'
389--- ubuntuone/controlpanel/gtk/package_manager.py 2011-02-22 19:28:14 +0000
390+++ ubuntuone/controlpanel/gtk/package_manager.py 2011-04-07 15:22:46 +0000
391@@ -20,13 +20,14 @@
392
393 import apt
394 import aptdaemon.client
395+# pylint: disable=W0404
396 import aptdaemon.enums
397
398 try:
399- # Unable to import 'defer', pylint: disable=F0401,E0611
400+ # Unable to import 'defer', pylint: disable=F0401,E0611,W0404
401 from aptdaemon.defer import inline_callbacks, return_value
402 except ImportError:
403- # Unable to import 'defer', pylint: disable=F0401,E0611
404+ # Unable to import 'defer', pylint: disable=F0401,E0611,W0404
405 from defer import inline_callbacks, return_value
406 from aptdaemon.gtkwidgets import AptProgressBar
407
408
409=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
410--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-04-04 21:28:18 +0000
411+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-04-07 15:22:46 +0000
412@@ -1680,8 +1680,9 @@
413 for sig, handlers in matches:
414 self.assertEqual(self.ui.backend._signals[sig], handlers)
415
416- def test_file_sync_status_is_requested(self):
417+ def test_file_sync_status_is_requested_on_load(self):
418 """The file sync status is requested to the backend."""
419+ self.ui.load()
420 self.assert_backend_called('file_sync_status', ())
421
422 def test_on_file_sync_status_disabled(self):
423@@ -1912,11 +1913,32 @@
424 self.assertEqual(self.ui.backend._signals['AccountInfoError'],
425 [self.ui.on_account_info_error])
426
427+ def test_backend_unauthorized_signal(self):
428+ """The proper signals are connected to the backend."""
429+ self.assertEqual(self.ui.backend._signals['UnauthorizedError'],
430+ [self.ui.on_unauthorized_error])
431+
432+ def test_no_backend_calls_before_load(self):
433+ """No calls are made to the backend before load() is called."""
434+ self.assertEqual(self.ui.backend._called, {})
435+
436 def test_account_info_is_requested_on_load(self):
437 """The account info is requested to the backend."""
438 self.ui.load()
439 self.assert_backend_called('account_info', ())
440
441+ def test_file_sync_status_info_is_requested_on_load(self):
442+ """The file sync status info is requested to the backend."""
443+ self.patch(self.ui.status_label, 'load', self._set_called)
444+ self.ui.load()
445+ self.assertEqual(self._called, ((), {}))
446+
447+ def test_replications_info_is_requested_on_load(self):
448+ """The replications info is requested to the backend."""
449+ self.patch(self.ui.services, 'load', self._set_called)
450+ self.ui.load()
451+ self.assertEqual(self._called, ((), {}))
452+
453 def test_dashboard_panel_is_packed(self):
454 """The dashboard panel is packed."""
455 self.assertIsInstance(self.ui.dashboard, gui.DashboardPanel)
456@@ -2071,3 +2093,9 @@
457 actual = getattr(self.ui, '%s_button' % tab).get_tooltip_text()
458 expected = getattr(self.ui, '%s_BUTTON_TOOLTIP' % tab.upper())
459 self.assertEqual(actual, expected)
460+
461+ def test_on_unauthorized_error(self):
462+ """On invalid credentials, proper signal is sent."""
463+ self.ui.connect('unauthorized', self._set_called)
464+ self.ui.on_unauthorized_error()
465+ self.assertEqual(self._called, ((self.ui,), {}))
466
467=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui_basic.py'
468--- ubuntuone/controlpanel/gtk/tests/test_gui_basic.py 2011-04-01 16:10:44 +0000
469+++ ubuntuone/controlpanel/gtk/tests/test_gui_basic.py 2011-04-07 15:22:46 +0000
470@@ -206,8 +206,10 @@
471
472 def test_on_show_management_panel(self):
473 """A ManagementPanel is shown when the callback is executed."""
474+ self.patch(self.ui.management, 'load', self._set_called)
475 self.ui.on_show_management_panel()
476 self.assert_current_tab_correct(self.ui.management)
477+ self.assertEqual(self._called, ((), {}))
478
479 def test_on_show_management_panel_is_idempotent(self):
480 """Only one ManagementPanel is shown."""
481@@ -256,6 +258,13 @@
482
483 self.assert_current_tab_correct(self.ui.overview)
484
485+ def test_unauthorized_shows_overview_panel(self):
486+ """On 'unauthorized' signal, the overview panel is shown."""
487+ self.ui.overview.emit('credentials-found', True, object())
488+ self.ui.management.emit('unauthorized')
489+
490+ self.assert_current_tab_correct(self.ui.overview)
491+
492 def test_backend_is_shutdown_on_close(self):
493 """When the control panel is closed, the backend is shutdown."""
494 self.ui.emit('destroy')
495
496=== modified file 'ubuntuone/controlpanel/gtk/tests/test_package_manager.py'
497--- ubuntuone/controlpanel/gtk/tests/test_package_manager.py 2011-01-03 14:20:12 +0000
498+++ ubuntuone/controlpanel/gtk/tests/test_package_manager.py 2011-04-07 15:22:46 +0000
499@@ -21,10 +21,10 @@
500 import collections
501
502 try:
503- # Unable to import 'defer', pylint: disable=F0401,E0611
504+ # Unable to import 'defer', pylint: disable=F0401,E0611,W0404
505 from aptdaemon import defer
506 except ImportError:
507- # Unable to import 'defer', pylint: disable=F0401,E0611
508+ # Unable to import 'defer', pylint: disable=F0401,E0611,W0404
509 import defer
510
511 from ubuntuone.controlpanel.gtk import package_manager
512
513=== modified file 'ubuntuone/controlpanel/integrationtests/__init__.py'
514--- ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-08 19:44:53 +0000
515+++ ubuntuone/controlpanel/integrationtests/__init__.py 2011-04-07 15:22:46 +0000
516@@ -20,7 +20,6 @@
517 """Integration tests for the Ubuntu One Control Panel."""
518
519 import dbus
520-import dbus.service
521
522 from ubuntuone.devtools.testcase import DBusTestCase as TestCase
523
524@@ -31,7 +30,7 @@
525 """A DBus exception to be used in tests."""
526
527
528-class MockDBusNoMethods(dbus.service.Object):
529+class MockDBusNoMethods(dbus_service.dbus.service.Object):
530 """A mock that fails at the DBus layer (because it's got no methods!)."""
531
532
533
534=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py'
535--- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-03-28 17:25:21 +0000
536+++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-04-07 15:22:46 +0000
537@@ -624,6 +624,35 @@
538 error_sig, success_sig, got_error_signal, method, *args)
539
540
541+class OperationsAuthErrorTestCase(OperationsTestCase):
542+ """Test for the DBus service operations when UnauthorizedError happens."""
543+
544+ def setUp(self):
545+ super(OperationsAuthErrorTestCase, self).setUp()
546+ self.patch(MockBackend, 'exception',
547+ dbus_service.UnauthorizedError)
548+
549+ def assert_correct_method_call(self, success_sig, error_sig, success_cb,
550+ method, *args):
551+ """Call parent instance expecting UnauthorizedError signal."""
552+
553+ def inner_success_cb(*a):
554+ """The success signal was received."""
555+ if len(a) == 1:
556+ error_dict = a[0]
557+ else:
558+ an_id, error_dict = a
559+ self.assertEqual(an_id, args[0])
560+
561+ self.assertEqual(error_dict[dbus_service.ERROR_TYPE],
562+ 'UnauthorizedError')
563+ self.deferred.callback('success')
564+
565+ parent = super(OperationsAuthErrorTestCase, self)
566+ return parent.assert_correct_method_call(
567+ "UnauthorizedError", error_sig, inner_success_cb, method, *args)
568+
569+
570 class FileSyncTestCase(BaseTestCase):
571 """Test for the DBus service when requesting file sync status."""
572
573
574=== modified file 'ubuntuone/controlpanel/integrationtests/test_webclient.py'
575--- ubuntuone/controlpanel/integrationtests/test_webclient.py 2010-12-02 16:23:03 +0000
576+++ ubuntuone/controlpanel/integrationtests/test_webclient.py 2011-04-07 15:22:46 +0000
577@@ -73,6 +73,10 @@
578 devices_resource.contents = SAMPLE_RESOURCE
579 root.putChild("devices", devices_resource)
580 root.putChild("throwerror", resource.NoResource())
581+ unauthorized = resource.ErrorPage(resource.http.UNAUTHORIZED,
582+ "Unauthrorized", "Unauthrorized")
583+ root.putChild("unauthorized", unauthorized)
584+
585 site = server.Site(root)
586 application = service.Application('web')
587 self.service_collection = service.IServiceCollection(application)
588@@ -96,7 +100,7 @@
589 class WebClientTestCase(TestCase):
590 """Test for the webservice client."""
591
592- timeout = 5
593+ timeout = 8
594
595 def setUp(self):
596 super(WebClientTestCase, self).setUp()
597@@ -123,6 +127,12 @@
598 yield self.assertFailure(self.wc.call_api("throwerror"),
599 webclient.WebClientError)
600
601+ @inlineCallbacks
602+ def test_unauthorized(self):
603+ """Detect when a request failed with UNAUTHORIZED."""
604+ yield self.assertFailure(self.wc.call_api("unauthorized"),
605+ webclient.UnauthorizedError)
606+
607
608 class OAuthTestCase(TestCase):
609 """Test for the oauth signing code."""
610
611=== modified file 'ubuntuone/controlpanel/replication_client.py'
612--- ubuntuone/controlpanel/replication_client.py 2011-01-06 20:40:15 +0000
613+++ ubuntuone/controlpanel/replication_client.py 2011-04-07 15:22:46 +0000
614@@ -57,7 +57,7 @@
615 if replication_module is None:
616 # delay import in case DC is not installed at module import time
617 # Unable to import 'desktopcouch.application.replication_services'
618- # pylint: disable=F0401
619+ # pylint: disable=W0404
620 from desktopcouch.application.replication_services \
621 import ubuntuone as replication_module
622 try:
623
624=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
625--- ubuntuone/controlpanel/tests/test_backend.py 2011-04-03 03:07:53 +0000
626+++ ubuntuone/controlpanel/tests/test_backend.py 2011-04-07 15:22:46 +0000
627@@ -59,7 +59,6 @@
628 SHARES_PATH_LINK,
629 TOKEN,
630 )
631-from ubuntuone.controlpanel.webclient import WebClientError
632
633
634 class MockWebClient(object):
635@@ -73,8 +72,10 @@
636
637 def call_api(self, method):
638 """Get a given url from the webservice."""
639- if self.failure:
640- return defer.fail(WebClientError(self.failure))
641+ if self.failure == 401:
642+ return defer.fail(backend.UnauthorizedError(self.failure))
643+ elif self.failure:
644+ return defer.fail(backend.WebClientError(self.failure))
645 else:
646 result = simplejson.loads(self.results[method])
647 return defer.succeed(result)
648@@ -326,7 +327,20 @@
649 """The account_info method exercises its errback."""
650 # pylint: disable=E1101
651 self.be.wc.failure = 404
652- yield self.assertFailure(self.be.account_info(), WebClientError)
653+ yield self.assertFailure(self.be.account_info(),
654+ backend.WebClientError)
655+
656+ @inlineCallbacks
657+ def test_account_info_fails_with_unauthorized(self):
658+ """The account_info clears the credentials on unauthorized."""
659+ # pylint: disable=E1101
660+ self.be.wc.failure = 401
661+ d = defer.Deferred()
662+ self.patch(backend.dbus_client, 'clear_credentials',
663+ lambda: d.callback('called'))
664+ yield self.assertFailure(self.be.account_info(),
665+ backend.UnauthorizedError)
666+ yield d
667
668
669 class BackendDevicesTestCase(BackendBasicTestCase):
670@@ -362,6 +376,18 @@
671 'web client failure'))
672
673 @inlineCallbacks
674+ def test_devices_info_fails_with_unauthorized(self):
675+ """The devices_info clears the credentials on unauthorized."""
676+ # pylint: disable=E1101
677+ self.be.wc.failure = 401
678+ d = defer.Deferred()
679+ self.patch(backend.dbus_client, 'clear_credentials',
680+ lambda: d.callback('called'))
681+ yield self.assertFailure(self.be.devices_info(),
682+ backend.UnauthorizedError)
683+ yield d
684+
685+ @inlineCallbacks
686 def test_devices_info_if_files_disable(self):
687 """The devices_info returns device only info if files is disabled."""
688 yield self.be.disable_files()
689@@ -436,7 +462,19 @@
690 # pylint: disable=E1101
691 self.be.wc.failure = 404
692 yield self.assertFailure(self.be.remove_device(self.local_token),
693- WebClientError)
694+ backend.WebClientError)
695+
696+ @inlineCallbacks
697+ def test_remove_device_fails_with_unauthorized(self):
698+ """The remove_device clears the credentials on unauthorized."""
699+ # pylint: disable=E1101
700+ self.be.wc.failure = 401
701+ d = defer.Deferred()
702+ self.patch(backend.dbus_client, 'clear_credentials',
703+ lambda: d.callback('called'))
704+ yield self.assertFailure(self.be.remove_device(self.local_token),
705+ backend.UnauthorizedError)
706+ yield d
707
708 @inlineCallbacks
709 def test_remove_device_does_not_log_device_id(self):
710
711=== modified file 'ubuntuone/controlpanel/utils.py'
712--- ubuntuone/controlpanel/utils.py 2010-12-08 13:56:25 +0000
713+++ ubuntuone/controlpanel/utils.py 2011-04-07 15:22:46 +0000
714@@ -52,7 +52,7 @@
715
716 # otherwise, try to load PROJECT_DIR from installation path
717 try:
718- # pylint: disable=F0401, E0611
719+ # pylint: disable=F0401, E0611, W0404
720 from ubuntuone.controlpanel.constants import PROJECT_DIR
721 return PROJECT_DIR
722 except ImportError:
723
724=== modified file 'ubuntuone/controlpanel/webclient.py'
725--- ubuntuone/controlpanel/webclient.py 2011-03-31 18:40:33 +0000
726+++ ubuntuone/controlpanel/webclient.py 2011-04-07 15:22:46 +0000
727@@ -32,10 +32,18 @@
728 logger = setup_logging('webclient')
729
730
731+# full list of status codes
732+# http://library.gnome.org/devel/libsoup/stable/libsoup-2.4-soup-status.html
733+
734+
735 class WebClientError(Exception):
736 """An http error happened while calling the webservice."""
737
738
739+class UnauthorizedError(WebClientError):
740+ """The request ended with bad_request, unauthorized or forbidden."""
741+
742+
743 class WebClient(object):
744 """A client for the u1 webservice."""
745
746@@ -55,7 +63,10 @@
747 result = simplejson.loads(data)
748 d.callback(result)
749 else:
750- e = WebClientError(msg.status_code, data)
751+ if msg.status_code in (401,):
752+ e = UnauthorizedError(msg.status_code, data)
753+ else:
754+ e = WebClientError(msg.status_code, data)
755 d.errback(e)
756
757 def _call_api_with_creds(self, credentials, api_name):

Subscribers

People subscribed via source and target branches