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

Proposed by Natalia Bidart on 2010-11-04
Status: Merged
Merged at revision: 19
Proposed branch: lp:~nataliabidart/ubuntu/maverick/ubuntu-sso-client/ubuntu-sso-client-1.0.7
Merge into: lp:ubuntu/maverick-proposed/ubuntu-sso-client
Diff against target: 703 lines (+273/-48)
7 files modified
PKG-INFO (+1/-1)
debian/changelog (+23/-0)
setup.py (+1/-1)
ubuntu_sso/gui.py (+20/-7)
ubuntu_sso/main.py (+79/-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.7
Reviewer Review Type Date Requested Status
Ubuntu Development Team 2010-11-04 Pending
Review via email: mp+40077@code.launchpad.net

Description of the Change

* New upstream release (1.0.6, 1.0.7):

[ Natalia B. Bidart <email address hidden> ]
  * 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.
  * The verify email page should be always built, not only on registration.
[ Alejandro J. Cura <email address hidden> ]
  * Store credentials on the keyring *only* from the main thread
  (LP: #656545).

* New upstream release (1.0.5):

[ Natalia B. Bidart <email address hidden> ]
  * Credentials are removed if the pinging to the server fails or any
  other exception occurs (LP: #660516).

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

Subscribers

People subscribed via source and target branches