Merge lp:~nataliabidart/ubuntu/maverick/ubuntu-sso-client/ubuntu-sso-client-1.0.8 into lp:ubuntu/maverick/ubuntu-sso-client

Proposed by Natalia Bidart on 2010-12-16
Status: Merged
Merge reported by: Sebastien Bacher
Merged at revision: not available
Proposed branch: lp:~nataliabidart/ubuntu/maverick/ubuntu-sso-client/ubuntu-sso-client-1.0.8
Merge into: lp:ubuntu/maverick/ubuntu-sso-client
Diff against target: 842 lines (+314/-70)
10 files modified
PKG-INFO (+1/-1)
bin/ubuntu-sso-login (+11/-17)
debian/changelog (+51/-0)
debian/control (+2/-1)
debian/watch (+1/-1)
setup.py (+1/-1)
ubuntu_sso/gui.py (+23/-10)
ubuntu_sso/main.py (+75/-31)
ubuntu_sso/tests/test_gui.py (+52/-2)
ubuntu_sso/tests/test_main.py (+97/-6)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu/maverick/ubuntu-sso-client/ubuntu-sso-client-1.0.8
Reviewer Review Type Date Requested Status
Ubuntu Development Team 2010-12-16 Pending
Review via email: mp+43940@code.launchpad.net

Description of the Change

  * New upstream release.

    [ Natalia B. Bidart <email address hidden> ]
       * Avoid generating an extra token when attempting to validate a user
       account (LP: #687523).

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-01 14:57:22 +0000
3+++ PKG-INFO 2010-12-16 17:46:02 +0000
4@@ -1,6 +1,6 @@
5 Metadata-Version: 1.1
6 Name: ubuntu-sso-client
7-Version: 1.0.3
8+Version: 1.0.8
9 Summary: Ubuntu Single Sign-On client
10 Home-page: https://launchpad.net/ubuntu-sso-client
11 Author: Natalia Bidart
12
13=== modified file 'bin/ubuntu-sso-login'
14--- bin/ubuntu-sso-login 2010-10-01 14:57:22 +0000
15+++ bin/ubuntu-sso-login 2010-12-16 17:46:02 +0000
16@@ -42,6 +42,7 @@
17 import signal
18 import sys
19
20+import dbus.mainloop.glib
21 import dbus.service
22 import gtk
23
24@@ -57,21 +58,21 @@
25
26
27 logger = setup_logging("ubuntu-sso-login")
28+dbus.mainloop.glib.threads_init()
29 gtk.gdk.threads_init()
30 DBusGMainLoop(set_as_default=True)
31
32
33 def sighup_handler(*a, **kw):
34- """Stop the service.
35-
36- This is not thread safe, see the link below for info:
37- www.listware.net/201004/gtk-devel-list/115067-unix-signals-in-glib.html
38- """
39- from twisted.internet import reactor
40- # Module 'twisted.internet.reactor' has no 'stop' member
41- # pylint: disable=E1101
42+ """Stop the service."""
43+ # This handler may be called in any thread, so is not thread safe.
44+ # See the link below for info:
45+ # www.listware.net/201004/gtk-devel-list/115067-unix-signals-in-glib.html
46+ #
47+ # gtk.main_quit and the logger methods are safe to be called from any thread.
48+ # Just don't call other random stuff here.
49 logger.info("Stoping Ubuntu SSO login manager since SIGHUP was received.")
50- reactor.stop()
51+ gtk.main_quit()
52
53
54 if __name__ == "__main__":
55@@ -83,10 +84,6 @@
56 logger.error("Ubuntu SSO login manager already running, quitting.")
57 sys.exit(0)
58
59- logger.debug("Installing the Twisted gtk2reactor.")
60- from twisted.internet import gtk2reactor
61- gtk2reactor.install()
62-
63 logger.debug("Hooking up SIGHUP with handler %r.", sighup_handler)
64 signal.signal(signal.SIGHUP, sighup_handler)
65
66@@ -97,7 +94,4 @@
67 bus=dbus.SessionBus()),
68 object_path=DBUS_CRED_PATH)
69
70- from twisted.internet import reactor
71- # Module 'twisted.internet.reactor' has no 'run' member
72- # pylint: disable=E1101
73- reactor.run()
74+ gtk.main()
75
76=== modified file 'debian/changelog'
77--- debian/changelog 2010-10-01 15:35:43 +0000
78+++ debian/changelog 2010-12-16 17:46:02 +0000
79@@ -1,3 +1,54 @@
80+ubuntu-sso-client (1.0.8-0ubuntu1) UNRELEASED; urgency=low
81+
82+ * New upstream release.
83+
84+ [ Natalia B. Bidart <natalia.bidart@canonical.com> ]
85+ * Avoid generating an extra token when attempting to validate a user
86+ account (LP: #687523).
87+
88+ -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Thu, 16 Dec 2010 13:19:01 -0300
89+
90+ubuntu-sso-client (1.0.7-0ubuntu1) maverick-proposed; urgency=low
91+
92+ * New upstream release (1.0.6, 1.0.7):
93+
94+ [ Natalia B. Bidart <natalia.bidart@canonical.com> ]
95+ * Added a new DBus signal UserNotValidated to indicate when a user is
96+ registered but not validated (LP: #667899).
97+ * Added new workflow so email validation is requested if necessary.
98+ * The verify email page should be always built, not only on registration.
99+
100+ [ Alejandro J. Cura <alecu@canonical.com> ]
101+ * Store credentials on the keyring *only* from the main thread (LP:
102+ #656545).
103+
104+ * New upstream release (1.0.5):
105+
106+ [ Natalia B. Bidart <natalia.bidart@canonical.com> ]
107+
108+ * Credentials are removed if the pinging to the server fails or any
109+ other exception occurs (LP: #660516).
110+
111+ -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Thu, 04 Nov 2010 09:21:00 -0300
112+
113+ubuntu-sso-client (1.0.4-0ubuntu1) maverick-proposed; urgency=low
114+
115+ * New upstream release:
116+
117+ [ Alejandro J. Cura <alecu@canonical.com> ]
118+ * Replace twisted gtk reactor with the standard gtk mainloop. (LP: #655327).
119+
120+ [ Alejandro J. Cura <alecu@canonical.com> ]
121+ * Call the dbus mainloop thread init (fixes LP: #656545).
122+
123+ * Adding .bzr-builddeb/default.conf as per Michael Vog (mvo) request.
124+
125+ * Adding dpkg (>= 1.15.7.2) as Pre-Depends (fixes LP: #658768).
126+
127+ * Adding gnome-keyring as dep since python-gnomekeyring doesn't install it.
128+
129+ -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Tue, 12 Oct 2010 10:07:55 -0300
130+
131 ubuntu-sso-client (1.0.3-0ubuntu1) maverick; urgency=low
132
133 * New upstream release:
134
135=== modified file 'debian/control'
136--- debian/control 2010-09-13 15:02:42 +0000
137+++ debian/control 2010-12-16 17:46:02 +0000
138@@ -13,9 +13,10 @@
139 Package: ubuntu-sso-client
140 Architecture: all
141 XB-Python-Version: ${python:Versions}
142+Pre-Depends: dpkg (>=1.15.7.2),
143 Depends: ${misc:Depends},
144 ${python:Depends},
145- dpkg (>=1.15.7.2),
146+ gnome-keyring,
147 python-dbus,
148 python-gnomekeyring,
149 python-gtk2,
150
151=== modified file 'debian/watch'
152--- debian/watch 2010-09-22 18:53:18 +0000
153+++ debian/watch 2010-12-16 17:46:02 +0000
154@@ -1,3 +1,3 @@
155 version=3
156-http://launchpad.net/ubuntu-sso-client/+download?start=10 .*/ubuntu-sso-client-([0-9.]+)\.tar\.gz
157+http://launchpad.net/ubuntu-sso-client/+download?start=20 .*/ubuntu-sso-client-([0-9.]+)\.tar\.gz
158
159
160=== modified file 'setup.py'
161--- setup.py 2010-10-01 14:57:22 +0000
162+++ setup.py 2010-12-16 17:46:02 +0000
163@@ -86,7 +86,7 @@
164
165 DistUtilsExtra.auto.setup(
166 name='ubuntu-sso-client',
167- version='1.0.3',
168+ version='1.0.8',
169 license='GPL v3',
170 author='Natalia Bidart',
171 author_email='natalia.bidart@canonical.com',
172
173=== modified file 'ubuntu_sso/gui.py'
174--- ubuntu_sso/gui.py 2010-10-01 14:57:22 +0000
175+++ ubuntu_sso/gui.py 2010-12-16 17:46:02 +0000
176@@ -28,7 +28,7 @@
177 import dbus
178 import gettext
179 import gobject
180-import gtk
181+import gtk # pylint: disable=W0403
182 import webkit
183 import xdg
184
185@@ -125,7 +125,7 @@
186 self.is_password = is_password
187 self.warning = None
188
189- super(LabeledEntry, self).__init__(*args, **kwargs)
190+ gtk.Entry.__init__(self, *args, **kwargs)
191
192 self.set_width_chars(DEFAULT_WIDTH)
193 self._set_label(self, None)
194@@ -161,7 +161,7 @@
195
196 def get_text(self):
197 """Get text only if it's not the label nor empty."""
198- result = super(LabeledEntry, self).get_text()
199+ result = gtk.Entry.get_text(self)
200 if result == self.label or result.isspace():
201 result = ''
202 return result
203@@ -252,6 +252,8 @@
204 self.tc_uri = tc_uri
205 self.help_text = help_text
206 self.close_callback = close_callback
207+ self.user_email = None
208+ self.user_password = None
209
210 ui_filename = get_data_file('ui.glade')
211 builder = gtk.Builder()
212@@ -319,13 +321,13 @@
213 self._append_page(self._build_login_page())
214 self._append_page(self._build_request_password_token_page())
215 self._append_page(self._build_set_new_password_page())
216+ self._append_page(self._build_verify_email_page())
217
218 window_size = None
219 if not login_only:
220 window_size = (550, 500)
221 self._append_page(self._build_enter_details_page())
222 self._append_page(self._build_tc_page())
223- self._append_page(self._build_verify_email_page())
224 self.login_button.grab_focus()
225 self._set_current_page(self.enter_details_vbox)
226 else:
227@@ -357,6 +359,8 @@
228 self._filter_by_app_name(self.on_logged_in),
229 'LoginError':
230 self._filter_by_app_name(self.on_login_error),
231+ 'UserNotValidated':
232+ self._filter_by_app_name(self.on_user_not_validated),
233 'PasswordResetTokenSent':
234 self._filter_by_app_name(self.on_password_reset_token_sent),
235 'PasswordResetError':
236@@ -417,8 +421,8 @@
237
238 match = self.bus.add_signal_receiver(method, signal_name=signal,
239 dbus_interface=iface)
240- logger.info('Connecting signal %r with method %r at iface %r.' \
241- 'Match: %r', signal, method, iface, match)
242+ logger.debug('Connecting signal %r with method %r at iface %r.' \
243+ 'Match: %r', signal, method, iface, match)
244 self._signals_receivers[(iface, signal)] = method
245
246 def _debug(self, *args, **kwargs):
247@@ -725,8 +729,8 @@
248 remove = self.bus.remove_signal_receiver
249 for (iface, signal) in self._signals_receivers.keys():
250 method = self._signals_receivers.pop((iface, signal))
251- logger.info('Removing signal %r with method %r at iface %r.',
252- signal, method, iface)
253+ logger.debug('Removing signal %r with method %r at iface %r.',
254+ signal, method, iface)
255 remove(method, signal_name=signal, dbus_interface=iface)
256
257 # hide the main window
258@@ -804,6 +808,8 @@
259 return
260
261 self._set_current_page(self.processing_vbox)
262+ self.user_email = email1
263+ self.user_password = password1
264
265 logger.info('Calling register_user with email %r, password <hidden>,' \
266 ' captcha_id %r and captcha_solution %r.', email1,
267@@ -832,8 +838,8 @@
268 self.email_token_entry.set_warning(self.FIELD_REQUIRED)
269 return
270
271- email = self.email1_entry.get_text()
272- password = self.password1_entry.get_text()
273+ email = self.user_email
274+ password = self.user_password
275 f = self.backend.validate_email
276 logger.info('Calling validate_email with email %r, password <hidden>' \
277 ', app_name %r and email_token %r.', email, self.app_name,
278@@ -871,6 +877,8 @@
279 reply_handler=NO_OP, error_handler=NO_OP)
280
281 self._set_current_page(self.processing_vbox)
282+ self.user_email = email
283+ self.user_password = password
284
285 def on_login_back_button_clicked(self, *args, **kwargs):
286 """User wants to go to the previous page."""
287@@ -1061,6 +1069,11 @@
288 msg))
289
290 @log_call
291+ def on_user_not_validated(self, app_name, email, *args, **kwargs):
292+ """User was not validated."""
293+ self.on_user_registered(app_name, email)
294+
295+ @log_call
296 def on_password_reset_token_sent(self, app_name, email, *args, **kwargs):
297 """Password reset token was successfully sent."""
298 msg = self.SET_NEW_PASSWORD_LABEL % {'email': email}
299
300=== modified file 'ubuntu_sso/main.py'
301--- ubuntu_sso/main.py 2010-09-14 19:28:09 +0000
302+++ ubuntu_sso/main.py 2010-12-16 17:46:02 +0000
303@@ -55,6 +55,7 @@
304 logger = setup_logging("ubuntu_sso.main")
305 PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
306 SERVICE_URL = "https://login.ubuntu.com/api/1.0"
307+NO_OP = lambda *args, **kwargs: None
308
309
310 class NoDefaultConfigError(Exception):
311@@ -96,10 +97,16 @@
312 """The new password could not be set."""
313
314
315-def keyring_store_credentials(app_name, credentials):
316+def keyring_store_credentials(app_name, credentials, callback, *cb_args):
317 """Store the credentials in the keyring."""
318- logger.info('keyring_store_credentials: app_name "%s".', app_name)
319- Keyring(app_name).set_ubuntusso_attr(credentials)
320+
321+ def _inner():
322+ """Store the credentials, and trigger the callback."""
323+ logger.info('keyring_store_credentials: app_name "%s".', app_name)
324+ Keyring(app_name).set_ubuntusso_attr(credentials)
325+ callback(*cb_args)
326+
327+ gobject.idle_add(_inner)
328
329
330 def keyring_get_credentials(app_name):
331@@ -209,6 +216,25 @@
332 'token_name: %r', credentials['consumer_key'], token_name)
333 return credentials
334
335+ def is_validated(self, token, sso_service=None):
336+ """Return if user with 'email' and 'password' is validated."""
337+ logger.debug('is_validated: requesting accounts.me() info.')
338+ if sso_service is None:
339+ oauth_token = oauth.OAuthToken(token['token'],
340+ token['token_secret'])
341+ authorizer = OAuthAuthorizer(token['consumer_key'],
342+ token['consumer_secret'],
343+ oauth_token)
344+ sso_service = self.sso_service_class(authorizer, self.service_url)
345+
346+ me_info = sso_service.accounts.me()
347+ key = 'preferred_email'
348+ result = key in me_info and me_info[key] != None
349+
350+ logger.info('is_validated: consumer_key: %r, result: %r.',
351+ token['consumer_key'], result)
352+ return result
353+
354 def validate_email(self, email, password, email_token, token_name):
355 """Validate an email token for user with 'email' and 'password'."""
356 logger.debug('validate_email: email: %r password: <hidden>, '
357@@ -311,12 +337,8 @@
358 dbus.service.Object.__init__(self, object_path="/sso",
359 bus_name=bus_name)
360 self.sso_login_processor_class = sso_login_processor_class
361- self.sso_service_class = sso_service_class
362-
363- def processor(self):
364- """Create a login processor with the given class and service class."""
365- return self.sso_login_processor_class(
366- sso_service_class=self.sso_service_class)
367+ self.processor = self.sso_login_processor_class(
368+ sso_service_class=sso_service_class)
369
370 # generate_capcha signals
371 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
372@@ -337,7 +359,7 @@
373 """Call the matching method in the processor."""
374 def f():
375 """Inner function that will be run in a thread."""
376- return self.processor().generate_captcha(filename)
377+ return self.processor.generate_captcha(filename)
378 blocking(f, app_name, self.CaptchaGenerated,
379 self.CaptchaGenerationError)
380
381@@ -361,8 +383,8 @@
382 """Call the matching method in the processor."""
383 def f():
384 """Inner function that will be run in a thread."""
385- return self.processor().register_user(email, password,
386- captcha_id, captcha_solution)
387+ return self.processor.register_user(email, password,
388+ captcha_id, captcha_solution)
389 blocking(f, app_name, self.UserRegistered, self.UserRegistrationError)
390
391 # login signals
392@@ -378,6 +400,12 @@
393 logger.debug('SSOLogin: emitting LoginError with '
394 'app_name "%s" and error %r', app_name, error)
395
396+ @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
397+ def UserNotValidated(self, app_name, result):
398+ """Signal thrown when the user is not validated."""
399+ logger.debug('SSOLogin: emitting UserNotValidated with app_name "%s" '
400+ 'and result %r', app_name, result)
401+
402 @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
403 in_signature='sss')
404 def login(self, app_name, email, password):
405@@ -387,13 +415,21 @@
406 token_name = get_token_name(app_name)
407 logger.debug('login: token_name %r, email %r, password <hidden>.',
408 token_name, email)
409- credentials = self.processor().login(email, password, token_name)
410+ credentials = self.processor.login(email, password, token_name)
411 logger.debug('login returned not None credentials? %r.',
412 credentials is not None)
413- assert credentials is not None
414- keyring_store_credentials(app_name, credentials)
415- return email
416- blocking(f, app_name, self.LoggedIn, self.LoginError)
417+ return credentials
418+
419+ def success_cb(app_name, credentials):
420+ """Login finished successfull."""
421+ is_validated = self.processor.is_validated(credentials)
422+ logger.debug('user is validated? %r.', is_validated)
423+ if is_validated:
424+ keyring_store_credentials(app_name, credentials,
425+ self.LoggedIn, app_name, email)
426+ else:
427+ self.UserNotValidated(app_name, email)
428+ blocking(f, app_name, success_cb, self.LoginError)
429
430 # validate_email signals
431 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
432@@ -415,11 +451,16 @@
433 def f():
434 """Inner function that will be run in a thread."""
435 token_name = get_token_name(app_name)
436- credentials = self.processor().validate_email(email, password,
437- email_token, token_name)
438- keyring_store_credentials(app_name, credentials)
439- return email
440- blocking(f, app_name, self.EmailValidated, self.EmailValidationError)
441+ credentials = self.processor.validate_email(email, password,
442+ email_token, token_name)
443+
444+ def _email_stored():
445+ """The email was stored, so call the signal."""
446+ self.EmailValidated(app_name, email)
447+
448+ keyring_store_credentials(app_name, credentials, _email_stored)
449+
450+ blocking(f, app_name, NO_OP, self.EmailValidationError)
451
452 # request_password_reset_token signals
453 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
454@@ -440,7 +481,7 @@
455 """Call the matching method in the processor."""
456 def f():
457 """Inner function that will be run in a thread."""
458- return self.processor().request_password_reset_token(email)
459+ return self.processor.request_password_reset_token(email)
460 blocking(f, app_name, self.PasswordResetTokenSent,
461 self.PasswordResetError)
462
463@@ -463,8 +504,8 @@
464 """Call the matching method in the processor."""
465 def f():
466 """Inner function that will be run in a thread."""
467- return self.processor().set_new_password(email, token,
468- new_password)
469+ return self.processor.set_new_password(email, token,
470+ new_password)
471 blocking(f, app_name, self.PasswordChanged, self.PasswordChangeError)
472
473
474@@ -481,19 +522,19 @@
475 @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="s")
476 def AuthorizationDenied(self, app_name):
477 """Signal thrown when the user denies the authorization."""
478- logger.info('SSOLogin: emitting AuthorizationDenied with app_name '
479- '"%s"', app_name)
480+ logger.info('SSOCredentials: emitting AuthorizationDenied with '
481+ 'app_name "%s"', app_name)
482
483 @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="sa{ss}")
484 def CredentialsFound(self, app_name, credentials):
485 """Signal thrown when the credentials are found."""
486- logger.info('SSOLogin: emitting CredentialsFound with app_name "%s"',
487- app_name)
488+ logger.info('SSOCredentials: emitting CredentialsFound with '
489+ 'app_name "%s"', app_name)
490
491 @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="sss")
492 def CredentialsError(self, app_name, error_message, detailed_error):
493 """Signal thrown when there is a problem finding the credentials."""
494- logger.debug('SSOCredentials: emitting CredentialsError with app_name '
495+ logger.error('SSOCredentials: emitting CredentialsError with app_name '
496 '"%s" and error_message %r', app_name, error_message)
497
498 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
499@@ -510,7 +551,8 @@
500
501 def _login_success_cb(self, dialog, app_name, email):
502 """Handles the response from the UI dialog."""
503- logger.info('Login successful for app %r, email %r', app_name, email)
504+ logger.info('Login successful for app %r, email %r. Still pending to '
505+ 'ping server and send result signal.', app_name, email)
506 try:
507 creds = keyring_get_credentials(app_name)
508 self._ping_url(app_name, email, creds)
509@@ -518,6 +560,7 @@
510 except: # pylint: disable=W0702
511 msg = "Problem getting the credentials from the keyring."
512 logger.exception(msg)
513+ self.clear_token(app_name)
514 self.CredentialsError(app_name, msg, traceback.format_exc())
515
516 def _login_error_cb(self, dialog, app_name, error):
517@@ -639,6 +682,7 @@
518
519 'app_name' is the name of the application.
520 """
521+ logger.info('Clearing credentials for app %r.', app_name)
522 try:
523 creds = Keyring(app_name)
524 creds.delete_ubuntusso_attr()
525
526=== modified file 'ubuntu_sso/tests/test_gui.py'
527--- ubuntu_sso/tests/test_gui.py 2010-09-08 19:25:02 +0000
528+++ ubuntu_sso/tests/test_gui.py 2010-12-16 17:46:02 +0000
529@@ -856,6 +856,12 @@
530 self.ui.join_ok_button.clicked()
531 self.assertTrue(self._called)
532
533+ def test_user_and_pass_are_cached(self):
534+ """Username and password are temporarly cached for further use."""
535+ self.click_join_with_valid_data()
536+ self.assertEqual(self.ui.user_email, EMAIL)
537+ self.assertEqual(self.ui.user_password, PASSWORD)
538+
539
540 class NoTermsAndConditionsTestCase(UbuntuSSOClientTestCase):
541 """Test suite for the user registration (with no t&c link)."""
542@@ -1005,6 +1011,17 @@
543 dict(reply_handler=gui.NO_OP,
544 error_handler=gui.NO_OP)))
545
546+ def test_on_verify_token_button_clicked(self):
547+ """Verify token uses cached user_email and user_password."""
548+ self.ui.user_email = 'test@me.com'
549+ self.ui.user_password = 'yadda-yedda'
550+ self.ui.on_verify_token_button_clicked()
551+ self.assertEqual(self.ui.backend._called['validate_email'],
552+ ((APP_NAME, self.ui.user_email,
553+ self.ui.user_password, EMAIL_TOKEN),
554+ dict(reply_handler=gui.NO_OP,
555+ error_handler=gui.NO_OP)))
556+
557 def test_on_verify_token_button_shows_processing_page(self):
558 """Verify token button triggers call to backend."""
559 self.click_verify_email_with_valid_data()
560@@ -1079,7 +1096,7 @@
561
562
563 class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase):
564- """Test suite for the user registration (verify email page)."""
565+ """Test suite for the user registration validation (verify email page)."""
566
567 def setUp(self):
568 """Init."""
569@@ -1114,6 +1131,20 @@
570 self.assert_warnings_visibility()
571
572
573+class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):
574+ """Test suite for the user login (verify email page)."""
575+
576+ kwargs = dict(app_name=APP_NAME, tc_uri=TC_URI, help_text=HELP_TEXT,
577+ login_only=True)
578+
579+
580+class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):
581+ """Test suite for the user login validation (verify email page)."""
582+
583+ kwargs = dict(app_name=APP_NAME, tc_uri=TC_URI, help_text=HELP_TEXT,
584+ login_only=True)
585+
586+
587 class RegistrationValidationTestCase(UbuntuSSOClientTestCase):
588 """Test suite for the user registration validations."""
589
590@@ -1328,6 +1359,12 @@
591 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
592 self.assert_pages_visibility(login=True)
593
594+ def test_on_user_not_validated_morphs_to_verify_page(self):
595+ """On user not validated, the verify page is shown."""
596+ self.click_connect_with_valid_data()
597+ self.ui.on_user_not_validated(app_name=APP_NAME, email=EMAIL)
598+ self.assert_pages_visibility(verify_email=True)
599+
600 def test_on_login_error_a_warning_is_shown(self):
601 """On user login error, a warning is shown with proper wording."""
602 self.click_connect_with_valid_data()
603@@ -1377,6 +1414,12 @@
604 self.ui.login_ok_button.clicked()
605 self.assertTrue(self._called)
606
607+ def test_user_and_pass_are_cached(self):
608+ """Username and password are temporarly cached for further use."""
609+ self.click_connect_with_valid_data()
610+ self.assertEqual(self.ui.user_email, EMAIL)
611+ self.assertEqual(self.ui.user_password, PASSWORD)
612+
613
614 class LoginValidationTestCase(UbuntuSSOClientTestCase):
615 """Test suite for the user login validation."""
616@@ -1782,7 +1825,7 @@
617 """All the backend signals are listed to be binded."""
618 for sig in ('CaptchaGenerated', 'CaptchaGenerationError',
619 'UserRegistered', 'UserRegistrationError',
620- 'LoggedIn', 'LoginError',
621+ 'LoggedIn', 'LoginError', 'UserNotValidated',
622 'EmailValidated', 'EmailValidationError',
623 'PasswordResetTokenSent', 'PasswordResetError',
624 'PasswordChanged', 'PasswordChangeError'):
625@@ -1864,6 +1907,13 @@
626 self.ui._signals['LoginError'](mismatch_app_name, 'dummy')
627 self.assertFalse(self._called)
628
629+ def test_on_user_not_validated_is_not_called(self):
630+ """on_user_not_validated is not called if incorrect app_name."""
631+ self.patch(self.ui, 'on_user_not_validated', self._set_called)
632+ mismatch_app_name = self.ui.app_name * 2
633+ self.ui._signals['UserNotValidated'](mismatch_app_name, 'dummy')
634+ self.assertFalse(self._called)
635+
636 def test_on_password_reset_token_sent_is_not_called(self):
637 """on_password_reset_token_sent is not called if incorrect app_name."""
638 self.patch(self.ui, 'on_password_reset_token_sent', self._set_called)
639
640=== modified file 'ubuntu_sso/tests/test_main.py'
641--- ubuntu_sso/tests/test_main.py 2010-09-08 19:25:02 +0000
642+++ ubuntu_sso/tests/test_main.py 2010-12-16 17:46:02 +0000
643@@ -152,6 +152,9 @@
644 class FakedAccounts(object):
645 """Fake the accounts service."""
646
647+ def __init__(self):
648+ self.preferred_email = EMAIL
649+
650 def validate_email(self, email_token):
651 """Fake the email validation. Return a fix result."""
652 if email_token is None:
653@@ -164,6 +167,17 @@
654 else:
655 return STATUS_EMAIL_OK
656
657+ # pylint: disable=E0202, C0103
658+
659+ def me(self):
660+ """Fake the 'me' information."""
661+ return {u'username': u'Wh46bKY',
662+ u'preferred_email': self.preferred_email,
663+ u'displayname': u'',
664+ u'unverified_emails': [u'aaaaaa@example.com'],
665+ u'verified_emails': [],
666+ u'openid_identifier': u'Wh46bKY'}
667+
668
669 class FakedSSOServer(object):
670 """Fake an SSO server."""
671@@ -279,6 +293,29 @@
672 result = self.processor.login(**self.login_kwargs)
673 self.assertEqual(TOKEN, result, 'authentication was successful.')
674
675+ # is_validated
676+
677+ def test_is_validated(self):
678+ """If preferred email is not None, user is validated."""
679+ result = self.processor.is_validated(token=TOKEN)
680+ self.assertTrue(result, 'user must be validated.')
681+
682+ def test_is_not_validated(self):
683+ """If preferred email is None, user is not validated."""
684+ service = FakedSSOServer(None, None)
685+ service.accounts.preferred_email = None
686+ result = self.processor.is_validated(sso_service=service,
687+ token=TOKEN)
688+ self.assertFalse(result, 'user must not be validated.')
689+
690+ def test_is_not_validated_empty_result(self):
691+ """If preferred email is None, user is not validated."""
692+ service = FakedSSOServer(None, None)
693+ service.accounts.me = lambda: {}
694+ result = self.processor.is_validated(sso_service=service,
695+ token=TOKEN)
696+ self.assertFalse(result, 'user must not be validated.')
697+
698 # validate_email
699
700 def test_validate_email_if_status_ok(self):
701@@ -380,12 +417,13 @@
702 mockbus._register_object_path(ARGS)
703 self.mockprocessorclass = None
704
705- def ksc(k, val):
706+ def ksc(k, val, callback, *cb_args):
707 """Assert over token and app_name."""
708 self.assertEqual(k, APP_NAME)
709 self.assertEqual(val, TOKEN)
710 self.keyring_was_set = True
711 self.keyring_values = k, val
712+ callback(*cb_args)
713
714 self.patch(ubuntu_sso.main, "keyring_store_credentials", ksc)
715 self.keyring_was_set = False
716@@ -519,8 +557,11 @@
717 def test_login(self):
718 """Test that the login method works ok."""
719 d = Deferred()
720- self.create_mock_processor().login(EMAIL, PASSWORD, TOKEN_NAME)
721+ processor = self.create_mock_processor()
722+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
723 self.mocker.result(TOKEN)
724+ processor.is_validated(TOKEN)
725+ self.mocker.result(True)
726 self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
727 self.mocker.replay()
728
729@@ -535,13 +576,40 @@
730 sso_login_processor_class=self.mockprocessorclass)
731 self.patch(client, "LoggedIn", verify)
732 self.patch(client, "LoginError", d.errback)
733+ self.patch(client, "UserNotValidated", d.errback)
734+ client.login(APP_NAME, EMAIL, PASSWORD)
735+ return d
736+
737+ def test_login_user_not_validated(self):
738+ """Test that the login sends EmailNotValidated signal."""
739+ d = Deferred()
740+ processor = self.create_mock_processor()
741+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
742+ self.mocker.result(TOKEN)
743+ processor.is_validated(TOKEN)
744+ self.mocker.result(False)
745+ self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
746+ self.mocker.replay()
747+
748+ def verify(app_name, email):
749+ """The actual test."""
750+ self.assertEqual(app_name, APP_NAME)
751+ self.assertEqual(email, EMAIL)
752+ self.assertFalse(self.keyring_was_set, "Keyring should not be set")
753+ d.callback("Ok")
754+
755+ client = SSOLogin(self.mockbusname,
756+ sso_login_processor_class=self.mockprocessorclass)
757+ self.patch(client, "LoggedIn", d.errback)
758+ self.patch(client, "LoginError", d.errback)
759+ self.patch(client, "UserNotValidated", verify)
760 client.login(APP_NAME, EMAIL, PASSWORD)
761 return d
762
763 def test_login_error(self):
764 """Test that the login method fails as expected."""
765 d = Deferred()
766- self.mockprocessorclass = self.mocker.mock()
767+ self.create_mock_processor()
768 self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking)
769
770 def fake_gtn(*args):
771@@ -562,6 +630,7 @@
772 sso_login_processor_class=self.mockprocessorclass)
773 self.patch(client, "LoggedIn", d.errback)
774 self.patch(client, "LoginError", verify)
775+ self.patch(client, "UserNotValidated", d.errback)
776 client.login(APP_NAME, EMAIL, PASSWORD)
777 return d
778
779@@ -591,7 +660,7 @@
780 def test_validate_email_error(self):
781 """Test that the validate_email method fails as expected."""
782 d = Deferred()
783- self.mockprocessorclass = self.mocker.mock()
784+ self.create_mock_processor()
785 self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking)
786
787 def fake_gtn(*args):
788@@ -802,7 +871,7 @@
789 self.assertEqual(result["errtype"], e.__class__.__name__)
790
791
792-class KeyringCredentialsTestCase(MockerTestCase):
793+class KeyringCredentialsTestCase(TestCase, MockerTestCase):
794 """Check the functions that access the keyring."""
795
796 # Invalid name (should match ([a-z_][a-z0-9_]*|[A-Z_][A-Z0-9_]*)$)
797@@ -810,15 +879,19 @@
798
799 def test_keyring_store_cred(self):
800 """Verify the method that stores credentials."""
801+ idle_add = lambda f, *args, **kwargs: f(*args, **kwargs)
802+ self.patch(gobject, "idle_add", idle_add)
803 token_value = TOKEN
804 mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
805 mockKeyringClass(APP_NAME)
806 mockKeyring = self.mocker.mock()
807+ callback = self.mocker.mock()
808 self.mocker.result(mockKeyring)
809 mockKeyring.set_ubuntusso_attr(token_value)
810+ callback(1, 2, 3)
811 self.mocker.replay()
812
813- keyring_store_credentials(APP_NAME, token_value)
814+ keyring_store_credentials(APP_NAME, token_value, callback, 1, 2, 3)
815
816 def test_keyring_get_cred(self):
817 """The method returns the right token."""
818@@ -1367,6 +1440,24 @@
819 self.assertEqual(self.calls[0][0], 'CredentialsError')
820 self.assertEqual(self.calls[0][1][0], APP_NAME)
821
822+ def test_credentials_are_not_stored_if_ping_failed(self):
823+ """Credentials are not stored if the ping fails."""
824+
825+ def fail(*args, **kwargs):
826+ """Raise an exception."""
827+ self.args = AssertionError((args, kwargs))
828+ # pylint: disable=E0702
829+ raise self.args
830+
831+ self.patch(self.client, '_ping_url', fail)
832+ self._patch('clear_token')
833+
834+ self.client._login_success_cb(None, APP_NAME, EMAIL)
835+
836+ self.assertEqual(len(self.calls), 1)
837+ self.assertEqual(self.calls[0][0], 'clear_token')
838+ self.assertEqual(self.calls[0][1][0], APP_NAME)
839+
840
841 class EnvironOverridesTestCase(TestCase):
842 """Some URLs can be set from the environment for testing/QA purposes."""

Subscribers

People subscribed via source and target branches

to all changes: