Merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.2 into lp:ubuntu/natty/ubuntu-sso-client

Proposed by Natalia Bidart on 2010-11-04
Status: Merged
Merged at revision: 19
Proposed branch: lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.2
Merge into: lp:ubuntu/natty/ubuntu-sso-client
Diff against target: 1448 lines (+537/-177)
13 files modified
PKG-INFO (+1/-1)
data/ui.glade (+75/-53)
debian/changelog (+13/-0)
debian/control (+2/-1)
setup.py (+1/-1)
ubuntu_sso/account.py (+21/-0)
ubuntu_sso/credentials.py (+40/-16)
ubuntu_sso/gui.py (+76/-45)
ubuntu_sso/main.py (+31/-18)
ubuntu_sso/tests/test_account.py (+37/-0)
ubuntu_sso/tests/test_credentials.py (+89/-9)
ubuntu_sso/tests/test_gui.py (+113/-30)
ubuntu_sso/tests/test_main.py (+38/-3)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.2
Reviewer Review Type Date Requested Status
Ubuntu branches 2010-11-04 Pending
Review via email: mp+40103@code.launchpad.net

Description of the Change

  * Adding dpkg (>= 1.15.7.2) as Pre-Depends (fixes LP: #658768).

  * Adding gnome-keyring as dep since python-gnomekeyring doesn't install it.

  * New upstream release:
    [ Natalia B. Bidart <email address hidden> ]
      * The success page is not shown until the backend notifies that the ping
      finished successfully (LP: #667893).
      * Added a new DBus signal UserNotValidated to indicate when a user is
      registered but not validated (LP: #667899).
      * Added new workflow so email validation is requested if necessary.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'PKG-INFO'
2--- PKG-INFO 2010-10-11 13:22:16 +0000
3+++ PKG-INFO 2010-11-04 15:07:47 +0000
4@@ -1,6 +1,6 @@
5 Metadata-Version: 1.1
6 Name: ubuntu-sso-client
7-Version: 1.1.1
8+Version: 1.1.2
9 Summary: Ubuntu Single Sign-On client
10 Home-page: https://launchpad.net/ubuntu-sso-client
11 Author: Natalia Bidart
12
13=== modified file 'data/ui.glade'
14--- data/ui.glade 2010-10-11 13:22:16 +0000
15+++ data/ui.glade 2010-11-04 15:07:47 +0000
16@@ -105,21 +105,42 @@
17 </packing>
18 </child>
19 <child>
20- <object class="GtkHBox" id="hbox1">
21+ <object class="GtkAlignment" id="alignment5">
22 <property name="visible">True</property>
23+ <property name="xscale">0</property>
24+ <property name="yscale">0</property>
25 <child>
26- <object class="GtkVBox" id="captcha_vbox">
27- <property name="width_request">300</property>
28- <property name="height_request">60</property>
29+ <object class="GtkHBox" id="hbox1">
30 <property name="visible">True</property>
31- <property name="homogeneous">True</property>
32 <child>
33- <object class="GtkEventBox" id="captcha_loading">
34+ <object class="GtkVBox" id="captcha_vbox">
35 <property name="width_request">300</property>
36 <property name="height_request">60</property>
37 <property name="visible">True</property>
38 <child>
39- <placeholder/>
40+ <object class="GtkEventBox" id="captcha_loading">
41+ <property name="width_request">300</property>
42+ <property name="height_request">60</property>
43+ <property name="visible">True</property>
44+ <child>
45+ <placeholder/>
46+ </child>
47+ </object>
48+ <packing>
49+ <property name="expand">False</property>
50+ <property name="fill">False</property>
51+ <property name="position">0</property>
52+ </packing>
53+ </child>
54+ <child>
55+ <object class="GtkImage" id="captcha_image">
56+ <property name="width_request">300</property>
57+ <property name="visible">True</property>
58+ <property name="stock">gtk-missing-image</property>
59+ </object>
60+ <packing>
61+ <property name="position">1</property>
62+ </packing>
63 </child>
64 </object>
65 <packing>
66@@ -129,55 +150,41 @@
67 </packing>
68 </child>
69 <child>
70- <object class="GtkImage" id="captcha_image">
71- <property name="width_request">300</property>
72- <property name="height_request">60</property>
73+ <object class="GtkVBox" id="vbox1">
74 <property name="visible">True</property>
75- <property name="stock">gtk-missing-image</property>
76+ <child>
77+ <object class="GtkButton" id="captcha_reload_button">
78+ <property name="visible">True</property>
79+ <property name="can_focus">True</property>
80+ <property name="receives_default">True</property>
81+ <property name="relief">none</property>
82+ <property name="focus_on_click">False</property>
83+ <signal name="clicked" handler="on_captcha_reload_button_clicked"/>
84+ <child>
85+ <object class="GtkImage" id="image1">
86+ <property name="visible">True</property>
87+ <property name="icon_name">reload</property>
88+ </object>
89+ </child>
90+ </object>
91+ <packing>
92+ <property name="expand">False</property>
93+ <property name="position">0</property>
94+ </packing>
95+ </child>
96+ <child>
97+ <placeholder/>
98+ </child>
99+ <child>
100+ <placeholder/>
101+ </child>
102 </object>
103 <packing>
104+ <property name="expand">False</property>
105 <property name="position">1</property>
106 </packing>
107 </child>
108 </object>
109- <packing>
110- <property name="position">0</property>
111- </packing>
112- </child>
113- <child>
114- <object class="GtkVBox" id="vbox1">
115- <property name="visible">True</property>
116- <child>
117- <object class="GtkButton" id="captcha_reload_button">
118- <property name="visible">True</property>
119- <property name="can_focus">True</property>
120- <property name="receives_default">True</property>
121- <property name="relief">none</property>
122- <property name="focus_on_click">False</property>
123- <signal name="clicked" handler="on_captcha_reload_button_clicked"/>
124- <child>
125- <object class="GtkImage" id="image1">
126- <property name="visible">True</property>
127- <property name="icon_name">reload</property>
128- </object>
129- </child>
130- </object>
131- <packing>
132- <property name="expand">False</property>
133- <property name="position">0</property>
134- </packing>
135- </child>
136- <child>
137- <placeholder/>
138- </child>
139- <child>
140- <placeholder/>
141- </child>
142- </object>
143- <packing>
144- <property name="expand">False</property>
145- <property name="position">1</property>
146- </packing>
147 </child>
148 </object>
149 <packing>
150@@ -333,7 +340,22 @@
151 <property name="visible">True</property>
152 <property name="spacing">10</property>
153 <child>
154- <placeholder/>
155+ <object class="GtkAlignment" id="alignment4">
156+ <property name="visible">True</property>
157+ <property name="xscale">0</property>
158+ <property name="yscale">0</property>
159+ <child>
160+ <object class="GtkVBox" id="verify_email_details_vbox">
161+ <property name="visible">True</property>
162+ <child>
163+ <placeholder/>
164+ </child>
165+ </object>
166+ </child>
167+ </object>
168+ <packing>
169+ <property name="position">0</property>
170+ </packing>
171 </child>
172 <child>
173 <object class="GtkHButtonBox" id="hbuttonbox2">
174@@ -668,11 +690,11 @@
175 </packing>
176 </child>
177 </object>
178- <object class="GtkVBox" id="success_vbox">
179+ <object class="GtkVBox" id="finish_vbox">
180 <property name="visible">True</property>
181 <property name="spacing">10</property>
182 <child>
183- <object class="GtkLabel" id="success_label">
184+ <object class="GtkLabel" id="finish_label">
185 <property name="visible">True</property>
186 <property name="wrap">True</property>
187 </object>
188@@ -686,7 +708,7 @@
189 <property name="visible">True</property>
190 <property name="layout_style">end</property>
191 <child>
192- <object class="GtkButton" id="success_close_button">
193+ <object class="GtkButton" id="finish_close_button">
194 <property name="label">gtk-close</property>
195 <property name="visible">True</property>
196 <property name="can_focus">True</property>
197
198=== modified file 'debian/changelog'
199--- debian/changelog 2010-10-18 15:03:42 +0000
200+++ debian/changelog 2010-11-04 15:07:47 +0000
201@@ -1,3 +1,16 @@
202+ubuntu-sso-client (1.1.2-0ubuntu1) UNRELEASED; urgency=low
203+
204+ * New upstream release:
205+
206+ [ Natalia B. Bidart <natalia.bidart@canonical.com> ]
207+ * The success page is not shown until the backend notifies that the ping
208+ finished successfully (LP: #667893).
209+ * Added a new DBus signal UserNotValidated to indicate when a user is
210+ registered but not validated (LP: #667899).
211+ * Added new workflow so email validation is requested if necessary.
212+
213+ -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Thu, 04 Nov 2010 11:00:45 -0300
214+
215 ubuntu-sso-client (1.1.1-0ubuntu1) natty; urgency=low
216
217 * New upstream release (1.1.1):
218
219=== modified file 'debian/control'
220--- debian/control 2010-09-13 15:02:42 +0000
221+++ debian/control 2010-11-04 15:07:47 +0000
222@@ -13,9 +13,10 @@
223 Package: ubuntu-sso-client
224 Architecture: all
225 XB-Python-Version: ${python:Versions}
226+Pre-Depends: dpkg (>=1.15.7.2),
227 Depends: ${misc:Depends},
228 ${python:Depends},
229- dpkg (>=1.15.7.2),
230+ gnome-keyring,
231 python-dbus,
232 python-gnomekeyring,
233 python-gtk2,
234
235=== modified file 'setup.py'
236--- setup.py 2010-10-11 13:22:16 +0000
237+++ setup.py 2010-11-04 15:07:47 +0000
238@@ -86,7 +86,7 @@
239
240 DistUtilsExtra.auto.setup(
241 name='ubuntu-sso-client',
242- version='1.1.1',
243+ version='1.1.2',
244 license='GPL v3',
245 author='Natalia Bidart',
246 author_email='natalia.bidart@canonical.com',
247
248=== modified file 'ubuntu_sso/account.py'
249--- ubuntu_sso/account.py 2010-10-11 13:22:16 +0000
250+++ ubuntu_sso/account.py 2010-11-04 15:07:47 +0000
251@@ -170,6 +170,27 @@
252 'token_name: %r', credentials['consumer_key'], token_name)
253 return credentials
254
255+ def is_validated(self, email, password, token_name, sso_service=None):
256+ """Return if user with 'email' and 'password' is validated."""
257+ if sso_service is None:
258+ token = self.login(email=email, password=password,
259+ token_name=token_name)
260+
261+ oauth_token = oauth.OAuthToken(token['token'],
262+ token['token_secret'])
263+ authorizer = OAuthAuthorizer(token['consumer_key'],
264+ token['consumer_secret'],
265+ oauth_token)
266+ sso_service = self.sso_service_class(authorizer, self.service_url)
267+
268+ me_info = sso_service.accounts.me()
269+ key = 'preferred_email'
270+ result = key in me_info and me_info[key] != None
271+
272+ logger.debug('is_validated: email: %r token_name: %r, result: %r.',
273+ email, token_name, result)
274+ return result
275+
276 def validate_email(self, email, password, email_token, token_name):
277 """Validate an email token for user with 'email' and 'password'."""
278 logger.debug('validate_email: email: %r password: <hidden>, '
279
280=== modified file 'ubuntu_sso/credentials.py'
281--- ubuntu_sso/credentials.py 2010-10-11 13:22:16 +0000
282+++ ubuntu_sso/credentials.py 2010-11-04 15:07:47 +0000
283@@ -105,7 +105,7 @@
284 f.__name__, self.app_name, self.error_cb)
285 error_dict = {ERROR_KEY: msg,
286 ERROR_DETAIL_KEY: traceback.format_exc()}
287- self.error_cb(self.app_name, error_dict)
288+ self.error_cb(error_dict)
289 return result
290
291 return inner
292@@ -145,10 +145,10 @@
293 'consumer_secret': <value>,
294 'name': <the token name, matches "[app_name] @ [host name]">}
295
296- 'error_cb' will be called when the credentials retrieval failed. Three
297- params will be passed: the app_name, the error message (user friendly,
298- not translatable so far), and the detailed error (usually the
299- traceback).
300+ 'error_cb' will be called when the credentials retrieval failed. Two
301+ params will be passed: the app_name, and an error dict with 2 keys:
302+ the error message (user friendly, not translatable so far), and
303+ the detailed error (usually the traceback).
304
305 'denial_cb' will be called when the user denied the credentials to the
306 caller. A single param is passed: the app_name.
307@@ -159,9 +159,10 @@
308 self.window_id = window_id
309 self.ping_url = ping_url
310 self.tc_url = tc_url
311- self.success_cb = success_cb
312- self.error_cb = error_cb
313+ self._success_cb = success_cb
314+ self._error_cb = error_cb
315 self.denial_cb = denial_cb
316+ self.gui = None # will hold the GUI instance
317
318 @handle_exceptions(msg='Problem while retrieving credentials')
319 def _login_success_cb(self, dialog, app_name, email):
320@@ -181,13 +182,13 @@
321 if status is None:
322 self.clear_credentials()
323 else:
324- self.success_cb(app_name, creds)
325+ self.success_cb(creds)
326
327 def _login_error_cb(self, dialog, app_name, error):
328 """Handle UI error when login/registration failed."""
329 logger.warning('Login/registration failed app %r, error %r',
330 app_name, error)
331- self.error_cb(app_name, {ERROR_KEY: error})
332+ self.error_cb({ERROR_KEY: error})
333
334 def _auth_denial_cb(self, dialog, app_name):
335 """The user decided not to allow the registration or login."""
336@@ -226,22 +227,23 @@
337 """Shows the UI, connect outcome signals."""
338 # delay gui import to be able to function on non-graphical envs
339 from ubuntu_sso import gui
340- app = gui.UbuntuSSOClientGUI(app_name=self.app_name,
341+ self.gui = gui.UbuntuSSOClientGUI(app_name=self.app_name,
342 tc_url=self.tc_url, help_text=self.help_text,
343 window_id=self.window_id, login_only=login_only)
344
345- app.connect(gui.SIG_LOGIN_SUCCEEDED, self._login_success_cb)
346- app.connect(gui.SIG_LOGIN_FAILED, self._login_error_cb)
347- app.connect(gui.SIG_REGISTRATION_SUCCEEDED, self._login_success_cb)
348- app.connect(gui.SIG_REGISTRATION_FAILED, self._login_error_cb)
349- app.connect(gui.SIG_USER_CANCELATION, self._auth_denial_cb)
350+ self.gui.connect(gui.SIG_LOGIN_SUCCEEDED, self._login_success_cb)
351+ self.gui.connect(gui.SIG_LOGIN_FAILED, self._login_error_cb)
352+ self.gui.connect(gui.SIG_REGISTRATION_SUCCEEDED,
353+ self._login_success_cb)
354+ self.gui.connect(gui.SIG_REGISTRATION_FAILED, self._login_error_cb)
355+ self.gui.connect(gui.SIG_USER_CANCELATION, self._auth_denial_cb)
356
357 @handle_exceptions(msg='Problem while retrieving credentials')
358 def _login_or_register(self, login_only):
359 """Get credentials if found else prompt the GUI."""
360 token = self.find_credentials()
361 if token is not None and len(token) > 0:
362- self.success_cb(self.app_name, token)
363+ self.success_cb(token)
364 elif token == {}:
365 gobject.idle_add(self._show_ui, login_only)
366 else:
367@@ -249,6 +251,28 @@
368 logger.info('_login_or_register: call to "find_credentials" went '
369 'wrong, and was already handled.')
370
371+ def error_cb(self, error_dict):
372+ """Handle error.
373+
374+ Notify the caller and finish the GUI with error.
375+
376+ """
377+ self._error_cb(self.app_name, error_dict)
378+ if self.gui is not None:
379+ self.gui.finish_error(error=error_dict)
380+ self.gui = None
381+
382+ def success_cb(self, creds):
383+ """Handle success.
384+
385+ Notify the caller and finish the GUI with success.
386+
387+ """
388+ self._success_cb(self.app_name, creds)
389+ if self.gui is not None:
390+ self.gui.finish_success()
391+ self.gui = None
392+
393 @handle_exceptions(msg='Problem while retrieving credentials')
394 def find_credentials(self):
395 """Get the credentials for 'self.app_name'. Return {} if not there."""
396
397=== modified file 'ubuntu_sso/gui.py'
398--- ubuntu_sso/gui.py 2010-10-11 13:22:16 +0000
399+++ ubuntu_sso/gui.py 2010-11-04 15:07:47 +0000
400@@ -205,6 +205,8 @@
401 """Ubuntu single sign on GUI."""
402
403 CAPTCHA_SOLUTION_ENTRY = _('Type the characters above')
404+ CAPTCHA_LOAD_ERROR = _('There was a problem getting the captcha, '
405+ 'reloading...')
406 CONNECT_HELP_LABEL = _('To connect this computer to %(app_name)s ' \
407 'enter your details below.')
408 EMAIL1_ENTRY = _('Email address')
409@@ -213,6 +215,7 @@
410 'and try entering them again.')
411 EMAIL_INVALID = _('The email must be a valid email address.')
412 EMAIL_TOKEN_ENTRY = _('Enter code verification here')
413+ ERROR = _('The process did not finish successfully.')
414 FIELD_REQUIRED = _('This field is required.')
415 FORGOTTEN_PASSWORD_BUTTON = _('I\'ve forgotten my password')
416 JOIN_HEADER_LABEL = _('Create %(app_name)s account')
417@@ -256,7 +259,7 @@
418 YES_TO_UPDATES = _('Yes! Email me %(app_name)s tips and updates.')
419 CAPTCHA_RELOAD_TOOLTIP = _('Reload')
420
421- def __init__(self, app_name, tc_url, help_text,
422+ def __init__(self, app_name, tc_url='', help_text='',
423 window_id=0, login_only=False, close_callback=None):
424 """Create the GUI and initialize widgets."""
425 gtk.link_button_set_uri_hook(NO_OP)
426@@ -264,12 +267,15 @@
427 self._captcha_filename = tempfile.mktemp()
428 self._captcha_id = None
429 self._signals_receivers = {}
430+ self._done = False # whether the whole process was completed or not
431
432 self.app_name = app_name
433 self.app_label = '<b>%s</b>' % self.app_name
434 self.tc_url = tc_url
435 self.help_text = help_text
436 self.close_callback = close_callback
437+ self.user_email = None
438+ self.user_password = None
439
440 ui_filename = get_data_file('ui.glade')
441 builder = gtk.Builder()
442@@ -325,23 +331,23 @@
443 logger.debug('UbuntuSSOClientGUI: backend created: %r', self.backend)
444
445 self.pages = (self.enter_details_vbox, self.processing_vbox,
446- self.verify_email_vbox, self.success_vbox,
447+ self.verify_email_vbox, self.finish_vbox,
448 self.tc_browser_vbox, self.login_vbox,
449 self.request_password_token_vbox,
450 self.set_new_password_vbox)
451
452 self._append_page(self._build_processing_page())
453- self._append_page(self._build_success_page())
454+ self._append_page(self._build_finish_page())
455 self._append_page(self._build_login_page())
456 self._append_page(self._build_request_password_token_page())
457 self._append_page(self._build_set_new_password_page())
458+ self._append_page(self._build_verify_email_page())
459
460 window_size = None
461 if not login_only:
462 window_size = (550, 500)
463 self._append_page(self._build_enter_details_page())
464 self._append_page(self._build_tc_page())
465- self._append_page(self._build_verify_email_page())
466 self.login_button.grab_focus()
467 self._set_current_page(self.enter_details_vbox)
468 else:
469@@ -373,6 +379,8 @@
470 self._filter_by_app_name(self.on_logged_in),
471 'LoginError':
472 self._filter_by_app_name(self.on_login_error),
473+ 'UserNotValidated':
474+ self._filter_by_app_name(self.on_user_not_validated),
475 'PasswordResetTokenSent':
476 self._filter_by_app_name(self.on_password_reset_token_sent),
477 'PasswordResetError':
478@@ -383,7 +391,6 @@
479 self._filter_by_app_name(self.on_password_change_error),
480 }
481 self._setup_signals()
482- self._gtk_signal_log = []
483
484 if window_id != 0:
485 # be as robust as possible:
486@@ -405,6 +412,22 @@
487
488 self.window.show()
489
490+ @property
491+ def success_vbox(self):
492+ """The success page."""
493+ self.finish_vbox.label.set_markup('<span size="x-large">%s</span>' %
494+ self.SUCCESS)
495+ return self.finish_vbox
496+
497+ @property
498+ def error_vbox(self):
499+ """The error page."""
500+ self.finish_vbox.label.set_markup('<span size="x-large">%s</span>' %
501+ self.ERROR)
502+ return self.finish_vbox
503+
504+ # helpers
505+
506 def _filter_by_app_name(self, f):
507 """Excecute the decorated function only for 'self.app_name'."""
508
509@@ -460,10 +483,14 @@
510 alignment.add(hbox)
511 alignment.show_all()
512
513+ # remove children to avoid:
514+ # GtkWarning: Attempting to add a widget with type GtkAlignment to a
515+ # GtkEventBox, but as a GtkBin subclass a GtkEventBox can only contain
516+ # one widget at a time
517+ for child in container.get_children():
518+ container.remove(child)
519 container.add(alignment)
520
521- # helpers
522-
523 def _set_warning_message(self, widget, message):
524 """Set 'message' as text for 'widget'."""
525 widget.set_text(message)
526@@ -600,19 +627,17 @@
527 self.verify_email_vbox.default_widget = self.verify_token_button
528 self.verify_email_vbox.default_widget.set_flags(gtk.CAN_DEFAULT)
529
530- self.verify_email_vbox.pack_start(self.email_token_entry, expand=False)
531- self.verify_email_vbox.reorder_child(self.email_token_entry, 0)
532+ self.verify_email_details_vbox.pack_start(self.email_token_entry,
533+ expand=False)
534
535 return self.verify_email_vbox
536
537- def _build_success_page(self):
538+ def _build_finish_page(self):
539 """Build the success page."""
540- self.success_vbox.default_widget = self.success_close_button
541- self.success_vbox.default_widget.set_flags(gtk.CAN_DEFAULT)
542-
543- self.success_label.set_markup('<span size="x-large">%s</span>' %
544- self.SUCCESS)
545- return self.success_vbox
546+ self.finish_vbox.default_widget = self.finish_close_button
547+ self.finish_vbox.default_widget.set_flags(gtk.CAN_DEFAULT)
548+ self.finish_vbox.label = self.finish_label
549+ return self.finish_vbox
550
551 def _build_login_page(self):
552 """Build the login page."""
553@@ -716,6 +741,21 @@
554 signal_name, handler, args, kwargs)
555 self.window.connect(signal_name, handler, *args, **kwargs)
556
557+ def emit(self, *args, **kwargs):
558+ """Emit a signal proxing the main window."""
559+ logger.debug('emit: args %r, kwargs, %r', args, kwargs)
560+ self.window.emit(*args, **kwargs)
561+
562+ def finish_success(self):
563+ """The whole process was completed succesfully. Show success page."""
564+ self._done = True
565+ self._set_current_page(self.success_vbox)
566+
567+ def finish_error(self, error):
568+ """The whole process was not completed succesfully. Show error page."""
569+ self._done = True
570+ self._set_current_page(self.error_vbox)
571+
572 def on_close_clicked(self, *args, **kwargs):
573 """Call self.close_callback if defined."""
574 if os.path.exists(self._captcha_filename):
575@@ -737,15 +777,8 @@
576 while gtk.events_pending():
577 gtk.main_iteration()
578
579- if len(args) > 0 and args[0] in self.cancels:
580- self.window.emit(SIG_USER_CANCELATION, self.app_name)
581- elif len(self._gtk_signal_log) > 0:
582- signal = self._gtk_signal_log[-1][0]
583- args = self._gtk_signal_log[-1][1:]
584- logger.info('emitting %r with args %r.', signal, args)
585- self.window.emit(signal, *args)
586- else:
587- self.window.emit(SIG_USER_CANCELATION, self.app_name)
588+ if not self._done:
589+ self.emit(SIG_USER_CANCELATION, self.app_name)
590
591 # call user defined callback
592 if self.close_callback is not None:
593@@ -805,6 +838,8 @@
594 return
595
596 self._set_current_page(self.processing_vbox)
597+ self.user_email = email1
598+ self.user_password = password1
599
600 logger.info('Calling register_user with email %r, password <hidden>,' \
601 ' captcha_id %r and captcha_solution %r.', email1,
602@@ -825,8 +860,8 @@
603 self.email_token_entry.set_warning(self.FIELD_REQUIRED)
604 return
605
606- email = self.email1_entry.get_text()
607- password = self.password1_entry.get_text()
608+ email = self.user_email
609+ password = self.user_password
610 f = self.backend.validate_email
611 logger.info('Calling validate_email with email %r, password <hidden>' \
612 ', app_name %r and email_token %r.', email, self.app_name,
613@@ -864,6 +899,8 @@
614 reply_handler=NO_OP, error_handler=NO_OP)
615
616 self._set_current_page(self.processing_vbox)
617+ self.user_email = email
618+ self.user_password = password
619
620 def on_login_back_button_clicked(self, *args, **kwargs):
621 """User wants to go to the previous page."""
622@@ -1025,15 +1062,6 @@
623 result = msg1 if msg1 is not None else msg2
624 return result
625
626- def _append_error_signal(self, signal, error):
627- """Append 'signal' to the log with a formatted version of 'error'.
628-
629- Format DBus dict 'error' into a nice string.
630-
631- """
632- msg = '\n'.join('%s: %s' % i for i in error.iteritems())
633- self._gtk_signal_log.append((signal, self.app_name, msg))
634-
635 @log_call
636 def on_captcha_generated(self, app_name, captcha_id, *args, **kwargs):
637 """Captcha image has been generated and is available to be shown."""
638@@ -1046,6 +1074,8 @@
639 @log_call
640 def on_captcha_generation_error(self, app_name, error, *args, **kwargs):
641 """Captcha image generation failed."""
642+ self._set_warning_message(self.warning_label, self.CAPTCHA_LOAD_ERROR)
643+ self._generate_captcha()
644
645 @log_call
646 def on_user_registered(self, app_name, email, *args, **kwargs):
647@@ -1069,15 +1099,14 @@
648 self.password2_entry.set_warning(msg)
649
650 msg = self._build_general_error_message(error)
651+ self._generate_captcha()
652 self._set_current_page(self.enter_details_vbox, warning_text=msg)
653- self._append_error_signal(SIG_REGISTRATION_FAILED, error)
654
655 @log_call
656 def on_email_validated(self, app_name, email, *args, **kwargs):
657 """User email was successfully verified."""
658- self._set_current_page(self.success_vbox)
659- self._gtk_signal_log.append((SIG_REGISTRATION_SUCCEEDED, self.app_name,
660- email))
661+ self._done = True
662+ self.emit(SIG_REGISTRATION_SUCCEEDED, self.app_name, email)
663
664 @log_call
665 def on_email_validation_error(self, app_name, error, *args, **kwargs):
666@@ -1088,21 +1117,23 @@
667
668 msg = self._build_general_error_message(error)
669 self._set_current_page(self.verify_email_vbox, warning_text=msg)
670- self._append_error_signal(SIG_REGISTRATION_FAILED, error)
671
672 @log_call
673 def on_logged_in(self, app_name, email, *args, **kwargs):
674 """User was successfully logged in."""
675- self._set_current_page(self.success_vbox)
676- self._gtk_signal_log.append((SIG_LOGIN_SUCCEEDED, self.app_name,
677- email))
678+ self._done = True
679+ self.emit(SIG_LOGIN_SUCCEEDED, self.app_name, email)
680
681 @log_call
682 def on_login_error(self, app_name, error, *args, **kwargs):
683 """User was not successfully logged in."""
684 msg = self._build_general_error_message(error)
685 self._set_current_page(self.login_vbox, warning_text=msg)
686- self._append_error_signal(SIG_LOGIN_FAILED, error)
687+
688+ @log_call
689+ def on_user_not_validated(self, app_name, email, *args, **kwargs):
690+ """User was not validated."""
691+ self.on_user_registered(app_name, email)
692
693 @log_call
694 def on_password_reset_token_sent(self, app_name, email, *args, **kwargs):
695
696=== modified file 'ubuntu_sso/main.py'
697--- ubuntu_sso/main.py 2010-10-11 13:22:16 +0000
698+++ ubuntu_sso/main.py 2010-11-04 15:07:47 +0000
699@@ -106,12 +106,8 @@
700 dbus.service.Object.__init__(self, object_path=object_path,
701 bus_name=bus_name)
702 self.sso_login_processor_class = sso_login_processor_class
703- self.sso_service_class = sso_service_class
704-
705- def processor(self):
706- """Create a login processor with the given class and service class."""
707- return self.sso_login_processor_class(
708- sso_service_class=self.sso_service_class)
709+ self.processor = self.sso_login_processor_class(
710+ sso_service_class=sso_service_class)
711
712 # generate_capcha signals
713 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
714@@ -132,7 +128,7 @@
715 """Call the matching method in the processor."""
716 def f():
717 """Inner function that will be run in a thread."""
718- return self.processor().generate_captcha(filename)
719+ return self.processor.generate_captcha(filename)
720 blocking(f, app_name, self.CaptchaGenerated,
721 self.CaptchaGenerationError)
722
723@@ -156,8 +152,8 @@
724 """Call the matching method in the processor."""
725 def f():
726 """Inner function that will be run in a thread."""
727- return self.processor().register_user(email, password,
728- captcha_id, captcha_solution)
729+ return self.processor.register_user(email, password,
730+ captcha_id, captcha_solution)
731 blocking(f, app_name, self.UserRegistered, self.UserRegistrationError)
732
733 # login signals
734@@ -173,6 +169,12 @@
735 logger.debug('SSOLogin: emitting LoginError with '
736 'app_name "%s" and error %r', app_name, error)
737
738+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
739+ def UserNotValidated(self, app_name, result):
740+ """Signal thrown when the user is not validated."""
741+ logger.debug('SSOLogin: emitting UserNotValidated with app_name "%s" '
742+ 'and result %r', app_name, result)
743+
744 @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
745 in_signature='sss')
746 def login(self, app_name, email, password):
747@@ -182,12 +184,23 @@
748 token_name = get_token_name(app_name)
749 logger.debug('login: token_name %r, email %r, password <hidden>.',
750 token_name, email)
751- credentials = self.processor().login(email, password, token_name)
752+ credentials = self.processor.login(email, password, token_name)
753 logger.debug('login returned not None credentials? %r.',
754 credentials is not None)
755- keyring_store_credentials(app_name, credentials)
756- return email
757- blocking(f, app_name, self.LoggedIn, self.LoginError)
758+ return credentials
759+
760+ def success_cb(app_name, credentials):
761+ """Login finished successfull."""
762+ token_name = get_token_name(app_name)
763+ is_validated = self.processor.is_validated(email, password,
764+ token_name)
765+ logger.debug('user is validated? %r.', is_validated)
766+ if is_validated:
767+ keyring_store_credentials(app_name, credentials)
768+ self.LoggedIn(app_name, email)
769+ else:
770+ self.UserNotValidated(app_name, email)
771+ blocking(f, app_name, success_cb, self.LoginError)
772
773 # validate_email signals
774 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
775@@ -209,8 +222,8 @@
776 def f():
777 """Inner function that will be run in a thread."""
778 token_name = get_token_name(app_name)
779- credentials = self.processor().validate_email(email, password,
780- email_token, token_name)
781+ credentials = self.processor.validate_email(email, password,
782+ email_token, token_name)
783 keyring_store_credentials(app_name, credentials)
784 return email
785 blocking(f, app_name, self.EmailValidated, self.EmailValidationError)
786@@ -234,7 +247,7 @@
787 """Call the matching method in the processor."""
788 def f():
789 """Inner function that will be run in a thread."""
790- return self.processor().request_password_reset_token(email)
791+ return self.processor.request_password_reset_token(email)
792 blocking(f, app_name, self.PasswordResetTokenSent,
793 self.PasswordResetError)
794
795@@ -257,8 +270,8 @@
796 """Call the matching method in the processor."""
797 def f():
798 """Inner function that will be run in a thread."""
799- return self.processor().set_new_password(email, token,
800- new_password)
801+ return self.processor.set_new_password(email, token,
802+ new_password)
803 blocking(f, app_name, self.PasswordChanged, self.PasswordChangeError)
804
805
806
807=== modified file 'ubuntu_sso/tests/test_account.py'
808--- ubuntu_sso/tests/test_account.py 2010-10-11 13:22:16 +0000
809+++ ubuntu_sso/tests/test_account.py 2010-11-04 15:07:47 +0000
810@@ -102,6 +102,9 @@
811 class FakedAccounts(object):
812 """Fake the accounts service."""
813
814+ def __init__(self):
815+ self.preferred_email = EMAIL
816+
817 def validate_email(self, email_token):
818 """Fake the email validation. Return a fix result."""
819 if email_token is None:
820@@ -114,6 +117,17 @@
821 else:
822 return STATUS_EMAIL_OK
823
824+ # pylint: disable=E0202, C0103
825+
826+ def me(self):
827+ """Fake the 'me' information."""
828+ return {u'username': u'Wh46bKY',
829+ u'preferred_email': self.preferred_email,
830+ u'displayname': u'',
831+ u'unverified_emails': [u'aaaaaa@example.com'],
832+ u'verified_emails': [],
833+ u'openid_identifier': u'Wh46bKY'}
834+
835
836 class FakedSSOServer(object):
837 """Fake an SSO server."""
838@@ -229,6 +243,29 @@
839 result = self.processor.login(**self.login_kwargs)
840 self.assertEqual(TOKEN, result, 'authentication was successful.')
841
842+ # is_validated
843+
844+ def test_is_validated(self):
845+ """If preferred email is not None, user is validated."""
846+ result = self.processor.is_validated(**self.login_kwargs)
847+ self.assertTrue(result, 'user must be validated.')
848+
849+ def test_is_not_validated(self):
850+ """If preferred email is None, user is not validated."""
851+ service = FakedSSOServer(None, None)
852+ service.accounts.preferred_email = None
853+ result = self.processor.is_validated(sso_service=service,
854+ **self.login_kwargs)
855+ self.assertFalse(result, 'user must not be validated.')
856+
857+ def test_is_not_validated_empty_result(self):
858+ """If preferred email is None, user is not validated."""
859+ service = FakedSSOServer(None, None)
860+ service.accounts.me = lambda: {}
861+ result = self.processor.is_validated(sso_service=service,
862+ **self.login_kwargs)
863+ self.assertFalse(result, 'user must not be validated.')
864+
865 # validate_email
866
867 def test_validate_email_if_status_ok(self):
868
869=== modified file 'ubuntu_sso/tests/test_credentials.py'
870--- ubuntu_sso/tests/test_credentials.py 2010-10-11 13:22:16 +0000
871+++ ubuntu_sso/tests/test_credentials.py 2010-11-04 15:07:47 +0000
872@@ -78,6 +78,7 @@
873 self.args = None
874 self.kwargs = None
875 self.signals = []
876+ self.methods = []
877
878 class FakedUbuntuSSOClientGUI(object):
879 """Fake a SSO GUI."""
880@@ -90,6 +91,14 @@
881 self.kwargs = kwargs
882 sself.connect = lambda *a: self.signals.append(a)
883
884+ def finish_success(sself):
885+ """Fake the success finish."""
886+ self.methods.append('finish_success')
887+
888+ def finish_error(sself, error):
889+ """Fake the error finish."""
890+ self.methods.append(('finish_error', error))
891+
892 self.patch(credentials.keyring.gnomekeyring, 'is_available',
893 lambda: True)
894 self.patch(gui, 'UbuntuSSOClientGUI', FakedUbuntuSSOClientGUI)
895@@ -133,15 +142,15 @@
896
897 def test_callbacks_are_stored(self):
898 """Creation callbacks are stored."""
899- self.assertEqual(self.obj.success_cb, self.success)
900- self.assertEqual(self.obj.error_cb, self.error)
901+ self.assertEqual(self.obj._success_cb, self.success)
902+ self.assertEqual(self.obj._error_cb, self.error)
903 self.assertEqual(self.obj.denial_cb, self.denial)
904
905 def test_callbacks_default_to_no_op(self):
906 """The callbacks are a no-operation if not given."""
907 self.obj = credentials.Credentials(**KWARGS)
908- self.assertEqual(self.obj.success_cb, NO_OP)
909- self.assertEqual(self.obj.error_cb, NO_OP)
910+ self.assertEqual(self.obj._success_cb, NO_OP)
911+ self.assertEqual(self.obj._error_cb, NO_OP)
912 self.assertEqual(self.obj.denial_cb, NO_OP)
913
914 def test_creation_parameters_are_stored(self):
915@@ -181,7 +190,55 @@
916
917 self.assertEqual(getattr(self.obj, PING_URL_KEY), None)
918
919- def test_login_success_cb_if_cred_not_found(self):
920+ def test_success_cb_gui_none(self):
921+ """Success callback calls the caller but not the GUI."""
922+ self.obj.gui = None
923+ self.obj.success_cb(creds=TOKEN)
924+
925+ self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}),
926+ 'caller _success_cb was called.')
927+ self.assertEqual(self.methods, [],
928+ 'GUI finish_success was not called.')
929+
930+ def test_success_cb_gui_not_none(self):
931+ """Success callback calls the caller and finish the GUI."""
932+ self.obj.gui = gui.UbuntuSSOClientGUI(APP_NAME)
933+ self.obj.success_cb(creds=TOKEN)
934+
935+ self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}),
936+ 'caller _success_cb was called.')
937+ self.assertEqual(self.methods, ['finish_success'],
938+ 'GUI finish_success was called.')
939+ self.assertTrue(self.obj.gui is None, 'gui is reset to None.')
940+
941+ def test_error_cb_gui_none(self):
942+ """Error callback calls the caller but not the GUI."""
943+ self.obj.gui = None
944+ error_dict = {'foo': 'bar'}
945+ self.obj.error_cb(error_dict=error_dict)
946+
947+ self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}),
948+ 'caller _error_cb was called.')
949+ self.assertEqual(self.methods, [],
950+ 'GUI finish_error was not called.')
951+
952+ def test_error_cb_gui_not_none(self):
953+ """Error callback calls the caller and finish the GUI."""
954+ self.obj.gui = gui.UbuntuSSOClientGUI(APP_NAME)
955+ error_dict = {'foo': 'bar'}
956+ self.obj.error_cb(error_dict=error_dict)
957+
958+ self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}),
959+ 'caller _error_cb was called.')
960+ self.assertEqual(self.methods, [('finish_error', error_dict)],
961+ 'GUI finish_error was called.')
962+ self.assertTrue(self.obj.gui is None, 'gui is reset to None.')
963+
964+
965+class CredentialsLoginSuccessTestCase(CredentialsTestCase):
966+ """Test suite for the Credentials login success callback."""
967+
968+ def test_cred_not_found(self):
969 """On auth success, if cred not found, self.error_cb is called."""
970 self.patch(credentials, 'keyring_get_credentials', lambda app: None)
971
972@@ -191,7 +248,7 @@
973 self.assert_error_cb_called(msg='Problem while retrieving credentials',
974 detailed_error=AssertionError(msg))
975
976- def test_login_success_cb_if_cred_error(self):
977+ def test_cred_error(self):
978 """On auth success, if cred failed, self.error_cb is called."""
979 self.patch(credentials, 'keyring_get_credentials', self.fail)
980
981@@ -201,7 +258,7 @@
982 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(APP_NAME))
983 self.assertTrue(self.memento.check_exception(FailTest, APP_NAME))
984
985- def test_login_success_cb_if_ping_success(self):
986+ def test_ping_success(self):
987 """Auth success + cred found + ping success, success_cb is called."""
988 self.patch(credentials, 'keyring_get_credentials', lambda app: TOKEN)
989 self.patch(self.obj, '_ping_url', lambda *a, **kw: 200)
990@@ -210,7 +267,7 @@
991
992 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
993
994- def test_login_success_cb_if_ping_error(self):
995+ def test_ping_error(self):
996 """Auth success + cred found + ping error, error_cb is called.
997
998 Credentials are removed. The exception is logged.
999@@ -235,7 +292,7 @@
1000 # exception logged
1001 self.assertTrue(self.memento.check_exception(FailTest, error))
1002
1003- def test_login_success_cb_pings_url(self):
1004+ def test_pings_url(self):
1005 """On auth success, self.ping_url is opened."""
1006 self.patch(credentials, 'keyring_get_credentials', lambda app: TOKEN)
1007
1008@@ -247,15 +304,24 @@
1009
1010 self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {}))
1011
1012+
1013+class CredentialsLoginErrorTestCase(CredentialsTestCase):
1014+ """Test suite for the Credentials login error callback."""
1015+
1016 def test_login_error_cb(self):
1017 """On login/register error, self.error_cb is called."""
1018 self.obj._login_error_cb(dialog=None, app_name=APP_NAME, error='Bla')
1019 self.assert_error_cb_called(msg='Bla')
1020
1021+
1022+class CredentialsAuthDeniedTestCase(CredentialsTestCase):
1023+ """Test suite for the Credentials auth denied callback."""
1024+
1025 def test_auth_denial_cb(self):
1026 """On auth denied, self.denial_cb is called."""
1027 self.obj._auth_denial_cb(dialog=None, app_name=APP_NAME)
1028 self.assertEqual(self._called, (('denial', APP_NAME), {}))
1029+ self.assertEqual(self.methods, [], 'no GUI method was called.')
1030
1031
1032 class PingUrlTestCase(CredentialsTestCase):
1033@@ -436,6 +502,20 @@
1034 getattr(self.obj, self.operation)()
1035 self.assertEqual(self.signals, expected)
1036
1037+ def test_gui_is_created(self):
1038+ """The GUI is created and stored."""
1039+ self.patch(credentials, 'keyring_get_credentials', lambda app: None)
1040+
1041+ getattr(self.obj, self.operation)()
1042+
1043+ self.assertIsInstance(self.obj.gui, gui.UbuntuSSOClientGUI)
1044+ self.assertEqual(self.args, ())
1045+
1046+ expected = KWARGS.copy()
1047+ expected.pop(PING_URL_KEY)
1048+ expected['login_only'] = self.login_only
1049+ self.assertEqual(self.kwargs, expected)
1050+
1051
1052 class LoginTestCase(RegisterTestCase):
1053 """Test suite for the login method."""
1054
1055=== modified file 'ubuntu_sso/tests/test_gui.py'
1056--- ubuntu_sso/tests/test_gui.py 2010-10-11 13:22:16 +0000
1057+++ ubuntu_sso/tests/test_gui.py 2010-11-04 15:07:47 +0000
1058@@ -386,7 +386,7 @@
1059 super(UbuntuSSOClientTestCase, self).setUp()
1060 self.patch(dbus, 'SessionBus', FakedSessionBus)
1061 self.patch(dbus, 'Interface', FakedSSOBackend)
1062- self.pages = ('enter_details', 'processing', 'verify_email', 'success',
1063+ self.pages = ('enter_details', 'processing', 'verify_email', 'finish',
1064 'tc_browser', 'login', 'request_password_token',
1065 'set_new_password')
1066 self.ui = self.gui_class(**self.kwargs)
1067@@ -657,6 +657,18 @@
1068 self.gui_class(window_id=xid, **self.kwargs)
1069 self.assertEqual(self._called, ((xid,), {}))
1070
1071+ def test_finish_success_shows_success_page(self):
1072+ """When calling 'finish_success' the success page is shown."""
1073+ self.ui.finish_success()
1074+ self.assert_pages_visibility(finish=True)
1075+ self.assertEqual(self.ui.SUCCESS, self.ui.finish_vbox.label.get_text())
1076+
1077+ def test_finish_error_shows_error_page(self):
1078+ """When calling 'finish_error' the error page is shown."""
1079+ self.ui.finish_error(error=self.error)
1080+ self.assert_pages_visibility(finish=True)
1081+ self.assertEqual(self.ui.ERROR, self.ui.finish_vbox.label.get_text())
1082+
1083
1084 class EnterDetailsTestCase(UbuntuSSOClientTestCase):
1085 """Test suite for the user registration (enter details page)."""
1086@@ -679,7 +691,7 @@
1087 self.assert_entries_are_packed_to_ui('enter_details_vbox', ('name',))
1088 self.assert_entries_are_packed_to_ui('captcha_solution_vbox',
1089 ('captcha_solution',))
1090- self.assert_entries_are_packed_to_ui('verify_email_vbox',
1091+ self.assert_entries_are_packed_to_ui('verify_email_details_vbox',
1092 ('email_token',))
1093
1094 def test_initial_texts_for_checkbuttons(self):
1095@@ -866,6 +878,20 @@
1096 self.ui.join_ok_button.clicked()
1097 self.assertTrue(self._called)
1098
1099+ def test_user_and_pass_are_cached(self):
1100+ """Username and password are temporarly cached for further use."""
1101+ self.click_join_with_valid_data()
1102+ self.assertEqual(self.ui.user_email, EMAIL)
1103+ self.assertEqual(self.ui.user_password, PASSWORD)
1104+
1105+ def test_on_captcha_generation_error(self):
1106+ """on_captcha_generation_error shows an error and reloads captcha."""
1107+ self.patch(self.ui, '_generate_captcha', self._set_called)
1108+ self.ui.on_captcha_generation_error(APP_NAME, error=self.error)
1109+ self.assert_correct_label_warning(self.ui.warning_label,
1110+ self.ui.CAPTCHA_LOAD_ERROR)
1111+ self.assertEqual(self._called, ((), {}))
1112+
1113
1114 class NoTermsAndConditionsTestCase(UbuntuSSOClientTestCase):
1115 """Test suite for the user registration (with no t&c link)."""
1116@@ -1054,6 +1080,12 @@
1117 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1118 self.assert_pages_visibility(enter_details=True)
1119
1120+ def test_captcha_is_reloaded(self):
1121+ """On UserRegistrationError the captcha is reloaded."""
1122+ self.patch(self.ui, '_generate_captcha', self._set_called)
1123+ self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1124+ self.assertEqual(self._called, ((), {}))
1125+
1126 def test_warning_label_is_shown(self):
1127 """On UserRegistrationError the warning label is shown."""
1128 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1129@@ -1114,6 +1146,17 @@
1130 dict(reply_handler=gui.NO_OP,
1131 error_handler=gui.NO_OP)))
1132
1133+ def test_on_verify_token_button_clicked(self):
1134+ """Verify token uses cached user_email and user_password."""
1135+ self.ui.user_email = 'test@me.com'
1136+ self.ui.user_password = 'yadda-yedda'
1137+ self.ui.on_verify_token_button_clicked()
1138+ self.assertEqual(self.ui.backend._called['validate_email'],
1139+ ((APP_NAME, self.ui.user_email,
1140+ self.ui.user_password, EMAIL_TOKEN),
1141+ dict(reply_handler=gui.NO_OP,
1142+ error_handler=gui.NO_OP)))
1143+
1144 def test_on_verify_token_button_shows_processing_page(self):
1145 """Verify token button triggers call to backend."""
1146 self.click_verify_email_with_valid_data()
1147@@ -1125,10 +1168,10 @@
1148 self.click_verify_email_with_valid_data()
1149 self.assert_warnings_visibility()
1150
1151- def test_on_email_validated_shows_success_page(self):
1152- """On email validated the success page is shown."""
1153+ def test_on_email_validated_shows_processing_page(self):
1154+ """On email validated the procesing page is still shown."""
1155 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1156- self.assert_pages_visibility(success=True)
1157+ self.assert_pages_visibility(processing=True)
1158
1159 def test_on_email_validated_does_not_clear_the_help_text(self):
1160 """On email validated the help text is not removed."""
1161@@ -1159,13 +1202,21 @@
1162
1163 def test_success_label_is_correct(self):
1164 """The success message is correct."""
1165- self.assertEqual(self.ui.SUCCESS, self.ui.success_label.get_text())
1166- markup = self.ui.success_label.get_label()
1167- self.assertTrue('<span size="x-large">' in markup)
1168-
1169- def test_on_success_close_button_clicked_closes_window(self):
1170+ self.assertEqual(self.ui.SUCCESS,
1171+ self.ui.success_vbox.label.get_text())
1172+ markup = self.ui.success_vbox.label.get_label()
1173+ self.assertTrue('<span size="x-large">' in markup)
1174+
1175+ def test_error_label_is_correct(self):
1176+ """The error message is correct."""
1177+ self.assertEqual(self.ui.ERROR,
1178+ self.ui.error_vbox.label.get_text())
1179+ markup = self.ui.error_vbox.label.get_label()
1180+ self.assertTrue('<span size="x-large">' in markup)
1181+
1182+ def test_on_finish_close_button_clicked_closes_window(self):
1183 """When done the window is closed."""
1184- self.ui.success_close_button.clicked()
1185+ self.ui.finish_close_button.clicked()
1186 self.assertFalse(self.ui.window.get_property('visible'))
1187
1188 def test_verify_token_button_does_nothing_if_clicked_but_disabled(self):
1189@@ -1182,7 +1233,7 @@
1190
1191
1192 class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase):
1193- """Test suite for the user registration (verify email page)."""
1194+ """Test suite for the user registration validation (verify email page)."""
1195
1196 def setUp(self):
1197 """Init."""
1198@@ -1217,6 +1268,20 @@
1199 self.assert_warnings_visibility()
1200
1201
1202+class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):
1203+ """Test suite for the user login (verify email page)."""
1204+
1205+ kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
1206+ login_only=True)
1207+
1208+
1209+class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):
1210+ """Test suite for the user login validation (verify email page)."""
1211+
1212+ kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
1213+ login_only=True)
1214+
1215+
1216 class RegistrationValidationTestCase(UbuntuSSOClientTestCase):
1217 """Test suite for the user registration validations."""
1218
1219@@ -1419,11 +1484,11 @@
1220 self.click_connect_with_valid_data()
1221 self.assert_pages_visibility(processing=True)
1222
1223- def test_on_logged_in_morphs_to_success_page(self):
1224- """When user logged in the success page is shown."""
1225+ def test_on_logged_in_morphs_to_processing_page(self):
1226+ """When user logged in the processing page is still shown."""
1227 self.click_connect_with_valid_data()
1228 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1229- self.assert_pages_visibility(success=True)
1230+ self.assert_pages_visibility(processing=True)
1231
1232 def test_on_login_error_morphs_to_login_page(self):
1233 """On user login error, the previous page is shown."""
1234@@ -1431,6 +1496,12 @@
1235 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
1236 self.assert_pages_visibility(login=True)
1237
1238+ def test_on_user_not_validated_morphs_to_verify_page(self):
1239+ """On user not validated, the verify page is shown."""
1240+ self.click_connect_with_valid_data()
1241+ self.ui.on_user_not_validated(app_name=APP_NAME, email=EMAIL)
1242+ self.assert_pages_visibility(verify_email=True)
1243+
1244 def test_on_login_error_a_warning_is_shown(self):
1245 """On user login error, a warning is shown with proper wording."""
1246 self.click_connect_with_valid_data()
1247@@ -1468,6 +1539,12 @@
1248 self.ui.login_ok_button.clicked()
1249 self.assertTrue(self._called)
1250
1251+ def test_user_and_pass_are_cached(self):
1252+ """Username and password are temporarly cached for further use."""
1253+ self.click_connect_with_valid_data()
1254+ self.assertEqual(self.ui.user_email, EMAIL)
1255+ self.assertEqual(self.ui.user_password, PASSWORD)
1256+
1257
1258 class LoginValidationTestCase(UbuntuSSOClientTestCase):
1259 """Test suite for the user login validation."""
1260@@ -1731,8 +1808,8 @@
1261 dict(reply_handler=gui.NO_OP,
1262 error_handler=gui.NO_OP)))
1263
1264- def test_on_password_changed_shows_success_page(self):
1265- """When password was successfuly changed the success page is shown."""
1266+ def test_on_password_changed_shows_login_page(self):
1267+ """When password was successfuly changed the login page is shown."""
1268 self.ui.on_password_changed(app_name=APP_NAME, email=EMAIL)
1269 self.assert_correct_label_warning(self.ui.warning_label,
1270 self.ui.PASSWORD_CHANGED)
1271@@ -1846,7 +1923,7 @@
1272 """All the backend signals are listed to be binded."""
1273 for sig in ('CaptchaGenerated', 'CaptchaGenerationError',
1274 'UserRegistered', 'UserRegistrationError',
1275- 'LoggedIn', 'LoginError',
1276+ 'LoggedIn', 'LoginError', 'UserNotValidated',
1277 'EmailValidated', 'EmailValidationError',
1278 'PasswordResetTokenSent', 'PasswordResetError',
1279 'PasswordChanged', 'PasswordChangeError'):
1280@@ -1928,6 +2005,13 @@
1281 self.ui._signals['LoginError'](mismatch_app_name, 'dummy')
1282 self.assertFalse(self._called)
1283
1284+ def test_on_user_not_validated_is_not_called(self):
1285+ """on_user_not_validated is not called if incorrect app_name."""
1286+ self.patch(self.ui, 'on_user_not_validated', self._set_called)
1287+ mismatch_app_name = self.ui.app_name * 2
1288+ self.ui._signals['UserNotValidated'](mismatch_app_name, 'dummy')
1289+ self.assertFalse(self._called)
1290+
1291 def test_on_password_reset_token_sent_is_not_called(self):
1292 """on_password_reset_token_sent is not called if incorrect app_name."""
1293 self.patch(self.ui, 'on_password_reset_token_sent', self._set_called)
1294@@ -1986,8 +2070,6 @@
1295 def setUp(self):
1296 """Init."""
1297 super(SignalsTestCase, self).setUp()
1298- self._expected_error = (APP_NAME, '\n'.join('%s: %s' % i
1299- for i in self.error.iteritems()))
1300 self._called = {}
1301 for sig_name, _ in gui.SIGNAL_ARGUMENTS:
1302 self.ui.connect(sig_name, self._set_called, sig_name)
1303@@ -2018,12 +2100,12 @@
1304 self._called = {}
1305
1306 def test_on_user_registration_error_proper_signal_is_emitted(self):
1307- """On UserRegistrationError, 'registration-failed' signal is sent."""
1308+ """On UserRegistrationError, SIG_USER_CANCELATION signal is sent."""
1309 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1310 self.ui.on_close_clicked()
1311- expected = (self.ui.window, self._expected_error, {})
1312+ expected = (self.ui.window, (APP_NAME,), {})
1313 self.assertEqual(expected,
1314- self._called[gui.SIG_REGISTRATION_FAILED])
1315+ self._called[gui.SIG_USER_CANCELATION])
1316
1317 def test_on_email_validated_proper_signals_is_emitted(self):
1318 """On EmailValidated, 'registration-succeeded' signal is sent."""
1319@@ -2033,12 +2115,12 @@
1320 self._called[gui.SIG_REGISTRATION_SUCCEEDED])
1321
1322 def test_on_email_validation_error_proper_signals_is_emitted(self):
1323- """On EmailValidationError, 'registration-failed' signal is sent."""
1324+ """On EmailValidationError, SIG_USER_CANCELATION signal is sent."""
1325 self.ui.on_email_validation_error(app_name=APP_NAME, error=self.error)
1326 self.ui.on_close_clicked()
1327- expected = (self.ui.window, self._expected_error, {})
1328+ expected = (self.ui.window, (APP_NAME,), {})
1329 self.assertEqual(expected,
1330- self._called[gui.SIG_REGISTRATION_FAILED])
1331+ self._called[gui.SIG_USER_CANCELATION])
1332
1333 def test_on_logged_in_proper_signals_is_emitted(self):
1334 """On LoggedIn, 'login-succeeded' signal is sent."""
1335@@ -2048,13 +2130,13 @@
1336 self._called[gui.SIG_LOGIN_SUCCEEDED])
1337
1338 def test_on_login_error_proper_signals_is_emitted(self):
1339- """On LoginError, 'login-failed' signal is sent."""
1340+ """On LoginError, SIG_USER_CANCELATION signal is sent."""
1341 self.click_connect_with_valid_data()
1342 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
1343 self.ui.on_close_clicked()
1344- expected = (self.ui.window, self._expected_error, {})
1345+ expected = (self.ui.window, (APP_NAME,), {})
1346 self.assertEqual(expected,
1347- self._called[gui.SIG_LOGIN_FAILED])
1348+ self._called[gui.SIG_USER_CANCELATION])
1349
1350 def test_registration_successfull_even_if_prior_registration_error(self):
1351 """Only one signal is sent with the final outcome."""
1352@@ -2111,7 +2193,8 @@
1353 ('request_password_token_vbox',
1354 'request_password_token_ok_button'),
1355 ('set_new_password_vbox', 'set_new_password_ok_button'),
1356- ('success_vbox', 'success_close_button'),
1357+ ('success_vbox', 'finish_close_button'),
1358+ ('error_vbox', 'finish_close_button'),
1359 ('processing_vbox', None))
1360
1361 def test_pages_have_default_widget_set(self):
1362
1363=== modified file 'ubuntu_sso/tests/test_main.py'
1364--- ubuntu_sso/tests/test_main.py 2010-10-11 13:22:16 +0000
1365+++ ubuntu_sso/tests/test_main.py 2010-11-04 15:07:47 +0000
1366@@ -204,8 +204,11 @@
1367 def test_login(self):
1368 """Test that the login method works ok."""
1369 d = Deferred()
1370- self.create_mock_processor().login(EMAIL, PASSWORD, TOKEN_NAME)
1371+ processor = self.create_mock_processor()
1372+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
1373 self.mocker.result(TOKEN)
1374+ processor.is_validated(EMAIL, PASSWORD, TOKEN_NAME)
1375+ self.mocker.result(True)
1376 self.patch(ubuntu_sso.main, "blocking", fake_ok_blocking)
1377 self.mocker.replay()
1378
1379@@ -220,13 +223,40 @@
1380 sso_login_processor_class=self.mockprocessorclass)
1381 self.patch(client, "LoggedIn", verify)
1382 self.patch(client, "LoginError", d.errback)
1383+ self.patch(client, "UserNotValidated", d.errback)
1384+ client.login(APP_NAME, EMAIL, PASSWORD)
1385+ return d
1386+
1387+ def test_login_user_not_validated(self):
1388+ """Test that the login sends EmailNotValidated signal."""
1389+ d = Deferred()
1390+ processor = self.create_mock_processor()
1391+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
1392+ self.mocker.result(TOKEN)
1393+ processor.is_validated(EMAIL, PASSWORD, TOKEN_NAME)
1394+ self.mocker.result(False)
1395+ self.patch(ubuntu_sso.main, "blocking", fake_ok_blocking)
1396+ self.mocker.replay()
1397+
1398+ def verify(app_name, email):
1399+ """The actual test."""
1400+ self.assertEqual(app_name, APP_NAME)
1401+ self.assertEqual(email, EMAIL)
1402+ self.assertFalse(self.keyring_was_set, "Keyring should not be set")
1403+ d.callback("Ok")
1404+
1405+ client = SSOLogin(self.mockbusname,
1406+ sso_login_processor_class=self.mockprocessorclass)
1407+ self.patch(client, "LoggedIn", d.errback)
1408+ self.patch(client, "LoginError", d.errback)
1409+ self.patch(client, "UserNotValidated", verify)
1410 client.login(APP_NAME, EMAIL, PASSWORD)
1411 return d
1412
1413 def test_login_error(self):
1414 """Test that the login method fails as expected."""
1415 d = Deferred()
1416- self.mockprocessorclass = self.mocker.mock()
1417+ self.create_mock_processor()
1418 self.patch(ubuntu_sso.main, "blocking", fake_err_blocking)
1419
1420 def fake_gtn(*args):
1421@@ -247,6 +277,7 @@
1422 sso_login_processor_class=self.mockprocessorclass)
1423 self.patch(client, "LoggedIn", d.errback)
1424 self.patch(client, "LoginError", verify)
1425+ self.patch(client, "UserNotValidated", d.errback)
1426 client.login(APP_NAME, EMAIL, PASSWORD)
1427 return d
1428
1429@@ -276,7 +307,7 @@
1430 def test_validate_email_error(self):
1431 """Test that the validate_email method fails as expected."""
1432 d = Deferred()
1433- self.mockprocessorclass = self.mocker.mock()
1434+ self.create_mock_processor()
1435 self.patch(ubuntu_sso.main, "blocking", fake_err_blocking)
1436
1437 def fake_gtn(*args):
1438@@ -710,6 +741,10 @@
1439
1440 timeout = 5
1441
1442+ def setUp(self):
1443+ super(CredentialsManagementFindClearTestCase, self).setUp()
1444+ self.patch(ubuntu_sso.main, 'blocking', fake_ok_blocking)
1445+
1446 def test_find_credentials(self):
1447 """The credentials are asked and returned in signals."""
1448 self.create_mock_backend().find_credentials()

Subscribers

People subscribed via source and target branches