Merge lp:~nataliabidart/ubuntu-sso-client/restore-register-api into lp:ubuntu-sso-client

Proposed by Natalia Bidart
Status: Superseded
Proposed branch: lp:~nataliabidart/ubuntu-sso-client/restore-register-api
Merge into: lp:ubuntu-sso-client
Diff against target: 2041 lines (+1387/-28) (has conflicts)
10 files modified
bin/ubuntu-sso-login (+31/-0)
data/ui.glade (+78/-1)
run-tests (+7/-0)
setup.py (+4/-0)
ubuntu_sso/gtk/gui.py (+71/-2)
ubuntu_sso/gtk/tests/test_gui.py (+65/-25)
ubuntu_sso/keyring/linux.py (+50/-0)
ubuntu_sso/keyring/tests/test_linux.py (+76/-0)
ubuntu_sso/main/linux.py (+453/-0)
ubuntu_sso/main/tests/test_linux.py (+552/-0)
Text conflict in bin/ubuntu-sso-login
Text conflict in data/ui.glade
Text conflict in run-tests
Text conflict in setup.py
Text conflict in ubuntu_sso/gtk/gui.py
Text conflict in ubuntu_sso/gtk/tests/test_gui.py
Text conflict in ubuntu_sso/keyring/linux.py
Text conflict in ubuntu_sso/keyring/tests/test_linux.py
Text conflict in ubuntu_sso/main/linux.py
Text conflict in ubuntu_sso/main/tests/test_linux.py
To merge this branch: bzr merge lp:~nataliabidart/ubuntu-sso-client/restore-register-api
Reviewer Review Type Date Requested Status
Ubuntu One hackers Pending
Review via email: mp+55391@code.launchpad.net

Commit message

- Restoring former API for register_user. Added a new method call register_with_name to ensure no API is broke (LP: #709494).

Description of the change

To properly test please use the SRU instructions in the attached bug report. Thanks!

To post a comment you must log in.

Unmerged revisions

646. By Natalia Bidart

Making UI using the new register_with_name backend metod.

645. By Natalia Bidart

Restoring former API for register_user. Added a new method call
register_with_name to ensure no API is broke (LP: #709494).

644. By Natalia Bidart

- Register now uses the 'displayname' field to pass it on to SSO as display name (LP: #709494).

643. By Natalia Bidart

[release] v1.0.8

642. By Natalia Bidart

 * Avoid generating an extra token when attempting to validate a user account (LP: #687523).

641. By Natalia Bidart

[release] 1.0.7

640. By Alejandro J. Cura

Store credentials on the keyring *only* from the main thread (LP: #656545)

639. By Natalia Bidart

The verify email page should be always built, not only on registration.

638. By Natalia Bidart

[release] 1.0.6.

637. By Natalia Bidart

* 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.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/ubuntu-sso-login'
--- bin/ubuntu-sso-login 2011-01-11 19:13:19 +0000
+++ bin/ubuntu-sso-login 2011-03-29 17:35:28 +0000
@@ -21,6 +21,7 @@
2121
22"""Run the dbus service for UserManagement and ApplicationCredentials."""22"""Run the dbus service for UserManagement and ApplicationCredentials."""
2323
24<<<<<<< TREE
24# Invalid name "ubuntu-sso-login", pylint: disable=C010325# Invalid name "ubuntu-sso-login", pylint: disable=C0103
2526
26# import decimal even if we don't need it, pylint: disable=W061127# import decimal even if we don't need it, pylint: disable=W0611
@@ -41,6 +42,26 @@
41# val = globals()[globalname]42# val = globals()[globalname]
42# KeyError: 'ROUND_CEiLiNG'43# KeyError: 'ROUND_CEiLiNG'
4344
45=======
46# import decimal even if we don't need it.
47import decimal
48# This is a workaround for LP: #467397. Some module in our depency chain sets
49# the locale and imports decimal, and that generates the following trace:
50# Traceback (most recent call last):
51# File "/usr/lib/ubuntu-sso-client/ubuntu-sso-login", line 33
52# from ubuntu_sso.main import SSOLogin, SSOCredentials
53# File "/usr/lib/pymodules/python2.6/ubuntu_sso/main.py", line 42
54# from lazr.restfulclient.resource import ServiceRoot
55# File "/usr/lib/python2.6/dist-packages/lazr/restfulclient/resource.py",
56# line 34
57# import simplejson
58# File "/usr/lib/pymodules/python2.6/simplejson/__init__.py", line 109
59# from decimal import Decimal
60# File "/usr/lib/python2.6/decimal.py", line 3649, in <module>
61# val = globals()[globalname]
62# KeyError: 'ROUND_CEiLiNG'
63
64>>>>>>> MERGE-SOURCE
44import signal65import signal
45import sys66import sys
4667
@@ -67,6 +88,7 @@
6788
6889
69def sighup_handler(*a, **kw):90def sighup_handler(*a, **kw):
91<<<<<<< TREE
70 """Stop the service."""92 """Stop the service."""
71 # This handler may be called in any thread, so is not thread safe.93 # This handler may be called in any thread, so is not thread safe.
72 # See the link below for info:94 # See the link below for info:
@@ -74,6 +96,15 @@
74 #96 #
75 # gtk.main_quit and the logger methods are safe to be called from any97 # gtk.main_quit and the logger methods are safe to be called from any
76 # thread. Just don't call other random stuff here.98 # thread. Just don't call other random stuff here.
99=======
100 """Stop the service."""
101 # This handler may be called in any thread, so is not thread safe.
102 # See the link below for info:
103 # www.listware.net/201004/gtk-devel-list/115067-unix-signals-in-glib.html
104 #
105 # gtk.main_quit and the logger methods are safe to be called from any thread.
106 # Just don't call other random stuff here.
107>>>>>>> MERGE-SOURCE
77 logger.info("Stoping Ubuntu SSO login manager since SIGHUP was received.")108 logger.info("Stoping Ubuntu SSO login manager since SIGHUP was received.")
78 gtk.main_quit()109 gtk.main_quit()
79110
80111
=== modified file 'data/ui.glade'
--- data/ui.glade 2010-11-29 16:04:26 +0000
+++ data/ui.glade 2011-03-29 17:35:28 +0000
@@ -279,16 +279,42 @@
279 </object>279 </object>
280 <packing>280 <packing>
281 <property name="expand">False</property>281 <property name="expand">False</property>
282<<<<<<< TREE
282 <property name="position">6</property>283 <property name="position">6</property>
283 </packing>284 </packing>
284 </child>285 </child>
285 <child>286 <child>
286 <object class="GtkHBox" id="hbox2">287 <object class="GtkHBox" id="hbox2">
287 <property name="visible">True</property>288=======
289 <property name="position">10</property>
290 </packing>
291 </child>
292 <child>
293 <object class="GtkLabel" id="tc_warning_label">
294 <property name="visible">True</property>
295 <property name="wrap">True</property>
296 </object>
297 <packing>
298 <property name="position">11</property>
299 </packing>
300 </child>
301 <child>
302 <object class="GtkHBox" id="hbox2">
303>>>>>>> MERGE-SOURCE
304 <property name="visible">True</property>
305<<<<<<< TREE
288 <property name="spacing">5</property>306 <property name="spacing">5</property>
307=======
308>>>>>>> MERGE-SOURCE
289 <child>309 <child>
310<<<<<<< TREE
290 <object class="GtkHButtonBox" id="hbuttonbox9">311 <object class="GtkHButtonBox" id="hbuttonbox9">
312=======
313 <object class="GtkLinkButton" id="login_button">
314 <property name="label">login button</property>
315>>>>>>> MERGE-SOURCE
291 <property name="visible">True</property>316 <property name="visible">True</property>
317<<<<<<< TREE
292 <property name="layout_style">start</property>318 <property name="layout_style">start</property>
293 <child>319 <child>
294 <object class="GtkLinkButton" id="login_button">320 <object class="GtkLinkButton" id="login_button">
@@ -305,15 +331,26 @@
305 <property name="position">0</property>331 <property name="position">0</property>
306 </packing>332 </packing>
307 </child>333 </child>
334=======
335 <property name="can_focus">True</property>
336 <property name="receives_default">True</property>
337 <property name="relief">none</property>
338 <signal name="clicked" handler="on_sign_in_button_clicked" swapped="no"/>
339>>>>>>> MERGE-SOURCE
308 </object>340 </object>
309 <packing>341 <packing>
310 <property name="expand">False</property>342 <property name="expand">False</property>
343<<<<<<< TREE
344=======
345 <property name="fill">True</property>
346>>>>>>> MERGE-SOURCE
311 <property name="position">0</property>347 <property name="position">0</property>
312 </packing>348 </packing>
313 </child>349 </child>
314 <child>350 <child>
315 <object class="GtkHButtonBox" id="hbuttonbox1">351 <object class="GtkHButtonBox" id="hbuttonbox1">
316 <property name="visible">True</property>352 <property name="visible">True</property>
353<<<<<<< TREE
317 <property name="spacing">5</property>354 <property name="spacing">5</property>
318 <property name="layout_style">end</property>355 <property name="layout_style">end</property>
319 <child>356 <child>
@@ -345,6 +382,40 @@
345 <property name="position">1</property>382 <property name="position">1</property>
346 </packing>383 </packing>
347 </child>384 </child>
385=======
386 <property name="can_focus">False</property>
387 <property name="spacing">5</property>
388 <property name="layout_style">end</property>
389 <child>
390 <object class="GtkButton" id="join_cancel_button">
391 <property name="label">gtk-cancel</property>
392 <property name="visible">True</property>
393 <property name="can_focus">True</property>
394 <property name="receives_default">True</property>
395 <property name="use_stock">True</property>
396 </object>
397 <packing>
398 <property name="expand">False</property>
399 <property name="fill">False</property>
400 <property name="position">0</property>
401 </packing>
402 </child>
403 <child>
404 <object class="GtkButton" id="join_ok_button">
405 <property name="label">gtk-go-forward</property>
406 <property name="visible">True</property>
407 <property name="can_focus">True</property>
408 <property name="receives_default">True</property>
409 <property name="use_stock">True</property>
410 <signal name="clicked" handler="on_join_ok_button_clicked" swapped="no"/>
411 </object>
412 <packing>
413 <property name="expand">False</property>
414 <property name="fill">False</property>
415 <property name="position">1</property>
416 </packing>
417 </child>
418>>>>>>> MERGE-SOURCE
348 </object>419 </object>
349 <packing>420 <packing>
350 <property name="expand">False</property>421 <property name="expand">False</property>
@@ -355,10 +426,16 @@
355 </object>426 </object>
356 <packing>427 <packing>
357 <property name="expand">False</property>428 <property name="expand">False</property>
429<<<<<<< TREE
358 <property name="pack_type">end</property>430 <property name="pack_type">end</property>
359 <property name="position">7</property>431 <property name="position">7</property>
360 </packing>432 </packing>
361 </child>433 </child>
434=======
435 <property name="position">12</property>
436 </packing>
437 </child>
438>>>>>>> MERGE-SOURCE
362 </object>439 </object>
363 <object class="GtkVBox" id="processing_vbox">440 <object class="GtkVBox" id="processing_vbox">
364 <property name="visible">True</property>441 <property name="visible">True</property>
365442
=== modified file 'run-tests'
--- run-tests 2011-03-18 09:30:20 +0000
+++ run-tests 2011-03-29 17:35:28 +0000
@@ -15,9 +15,16 @@
15# You should have received a copy of the GNU General Public License along15# You should have received a copy of the GNU General Public License along
16# with this program. If not, see <http://www.gnu.org/licenses/>.16# with this program. If not, see <http://www.gnu.org/licenses/>.
1717
18<<<<<<< TREE
18if [ $# -ne 0 ]; then19if [ $# -ne 0 ]; then
19 # an extra argument was given20 # an extra argument was given
20 MODULE="$@"21 MODULE="$@"
22=======
23`which xvfb-run` ./contrib/test "$@"
24pylint ubuntu_sso
25if [ -x `which pep8` ]; then
26 pep8 --repeat bin/ contrib/ ubuntu_sso/
27>>>>>>> MERGE-SOURCE
21else28else
22 # run all tests, useful for tarmac and reviews29 # run all tests, useful for tarmac and reviews
23 MODULE="ubuntu_sso"30 MODULE="ubuntu_sso"
2431
=== modified file 'setup.py'
--- setup.py 2011-03-22 22:32:28 +0000
+++ setup.py 2011-03-29 17:35:28 +0000
@@ -86,7 +86,11 @@
8686
87DistUtilsExtra.auto.setup(87DistUtilsExtra.auto.setup(
88 name='ubuntu-sso-client',88 name='ubuntu-sso-client',
89<<<<<<< TREE
89 version='1.1.12',90 version='1.1.12',
91=======
92 version='1.0.8',
93>>>>>>> MERGE-SOURCE
90 license='GPL v3',94 license='GPL v3',
91 author='Natalia Bidart',95 author='Natalia Bidart',
92 author_email='natalia.bidart@canonical.com',96 author_email='natalia.bidart@canonical.com',
9397
=== modified file 'ubuntu_sso/gtk/gui.py'
--- ubuntu_sso/gtk/gui.py 2011-03-16 01:36:07 +0000
+++ ubuntu_sso/gtk/gui.py 2011-03-29 17:35:28 +0000
@@ -30,7 +30,13 @@
3030
31import dbus31import dbus
32import gettext32import gettext
33<<<<<<< TREE
33import gtk34import gtk
35=======
36import gobject
37import gtk # pylint: disable=W0403
38import webkit
39>>>>>>> MERGE-SOURCE
34import xdg40import xdg
3541
36from dbus.mainloop.glib import DBusGMainLoop42from dbus.mainloop.glib import DBusGMainLoop
@@ -125,7 +131,7 @@
125 self.is_password = is_password131 self.is_password = is_password
126 self.warning = None132 self.warning = None
127133
128 super(LabeledEntry, self).__init__(*args, **kwargs)134 gtk.Entry.__init__(self, *args, **kwargs)
129135
130 self.set_width_chars(DEFAULT_WIDTH)136 self.set_width_chars(DEFAULT_WIDTH)
131 self._set_label(self, None)137 self._set_label(self, None)
@@ -161,7 +167,7 @@
161167
162 def get_text(self):168 def get_text(self):
163 """Get text only if it's not the label nor empty."""169 """Get text only if it's not the label nor empty."""
164 result = super(LabeledEntry, self).get_text()170 result = gtk.Entry.get_text(self)
165 if result == self.label or result.isspace():171 if result == self.label or result.isspace():
166 result = ''172 result = ''
167 return result173 return result
@@ -255,6 +261,7 @@
255 self.app_label = '<b>%s</b>' % self.app_name261 self.app_label = '<b>%s</b>' % self.app_name
256 self.tc_url = tc_url262 self.tc_url = tc_url
257 self.help_text = help_text263 self.help_text = help_text
264<<<<<<< TREE
258 self.login_only = login_only265 self.login_only = login_only
259266
260 self.close_callback = NO_OP267 self.close_callback = NO_OP
@@ -264,6 +271,11 @@
264271
265 self.user_email = None272 self.user_email = None
266 self.user_password = None273 self.user_password = None
274=======
275 self.close_callback = close_callback
276 self.user_email = None
277 self.user_password = None
278>>>>>>> MERGE-SOURCE
267279
268 ui_filename = get_data_file('ui.glade')280 ui_filename = get_data_file('ui.glade')
269 builder = gtk.Builder()281 builder = gtk.Builder()
@@ -325,7 +337,35 @@
325 self.request_password_token_vbox,337 self.request_password_token_vbox,
326 self.set_new_password_vbox)338 self.set_new_password_vbox)
327339
340<<<<<<< TREE
328 self._append_pages()341 self._append_pages()
342=======
343 self._append_page(self._build_processing_page())
344 self._append_page(self._build_success_page())
345 self._append_page(self._build_login_page())
346 self._append_page(self._build_request_password_token_page())
347 self._append_page(self._build_set_new_password_page())
348 self._append_page(self._build_verify_email_page())
349
350 window_size = None
351 if not login_only:
352 window_size = (550, 500)
353 self._append_page(self._build_enter_details_page())
354 self._append_page(self._build_tc_page())
355 self.login_button.grab_focus()
356 self._set_current_page(self.enter_details_vbox)
357 else:
358 window_size = (400, 350)
359 self.login_back_button.hide()
360 self.login_ok_button.grab_focus()
361 self.login_vbox.help_text = help_text
362 self._set_current_page(self.login_vbox)
363
364 self.window.set_size_request(*window_size)
365 size_req = (int(window_size[0] * 0.9), -1)
366 for label in self.labels:
367 label.set_size_request(*size_req)
368>>>>>>> MERGE-SOURCE
329369
330 self._signals = {370 self._signals = {
331 'CaptchaGenerated':371 'CaptchaGenerated':
@@ -750,12 +790,27 @@
750 if self.window is not None:790 if self.window is not None:
751 self.window.hide()791 self.window.hide()
752792
793<<<<<<< TREE
753 # process any pending events before callbacking with result794 # process any pending events before callbacking with result
754 while gtk.events_pending():795 while gtk.events_pending():
755 gtk.main_iteration()796 gtk.main_iteration()
756797
757 if not self._done:798 if not self._done:
758 self.user_cancellation_callback(self.app_name)799 self.user_cancellation_callback(self.app_name)
800=======
801 # process any pending events before emitting signals
802 while gtk.events_pending():
803 gtk.main_iteration()
804
805 if len(args) > 0 and args[0] in self.cancels:
806 self.window.emit(SIG_USER_CANCELATION, self.app_name)
807 elif len(self._gtk_signal_log) > 0:
808 signal = self._gtk_signal_log[-1][0]
809 args = self._gtk_signal_log[-1][1:]
810 self.window.emit(signal, *args)
811 else:
812 self.window.emit(SIG_USER_CANCELATION, self.app_name)
813>>>>>>> MERGE-SOURCE
759814
760 # call user defined callback815 # call user defined callback
761 logger.info('Calling custom close_callback %r with params %r, %r',816 logger.info('Calling custom close_callback %r with params %r, %r',
@@ -816,12 +871,21 @@
816 self.user_email = email1871 self.user_email = email1
817 self.user_password = password1872 self.user_password = password1
818873
874<<<<<<< TREE
819 logger.info('Calling register_user with email %r, password <hidden>,' \875 logger.info('Calling register_user with email %r, password <hidden>,' \
820 ' name %r, captcha_id %r and captcha_solution %r.', email1,876 ' name %r, captcha_id %r and captcha_solution %r.', email1,
821 name, self._captcha_id, captcha_solution)877 name, self._captcha_id, captcha_solution)
822 f = self.backend.register_user878 f = self.backend.register_user
823 f(self.app_name, email1, password1, name,879 f(self.app_name, email1, password1, name,
824 self._captcha_id, captcha_solution,880 self._captcha_id, captcha_solution,
881=======
882 logger.info('Calling register_with_name with email %r, password, '
883 '<hidden> name %r, captcha_id %r and captcha_solution %r.',
884 email1, name, self._captcha_id, captcha_solution)
885 f = self.backend.register_with_name
886 f(self.app_name, email1, password1, name,
887 self._captcha_id, captcha_solution,
888>>>>>>> MERGE-SOURCE
825 reply_handler=NO_OP, error_handler=NO_OP)889 reply_handler=NO_OP, error_handler=NO_OP)
826890
827 def on_verify_token_button_clicked(self, *args, **kwargs):891 def on_verify_token_button_clicked(self, *args, **kwargs):
@@ -1122,6 +1186,11 @@
1122 self.on_user_registered(app_name, email)1186 self.on_user_registered(app_name, email)
11231187
1124 @log_call1188 @log_call
1189 def on_user_not_validated(self, app_name, email, *args, **kwargs):
1190 """User was not validated."""
1191 self.on_user_registered(app_name, email)
1192
1193 @log_call
1125 def on_password_reset_token_sent(self, app_name, email, *args, **kwargs):1194 def on_password_reset_token_sent(self, app_name, email, *args, **kwargs):
1126 """Password reset token was successfully sent."""1195 """Password reset token was successfully sent."""
1127 msg = self.SET_NEW_PASSWORD_LABEL % {'email': email}1196 msg = self.SET_NEW_PASSWORD_LABEL % {'email': email}
11281197
=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
--- ubuntu_sso/gtk/tests/test_gui.py 2011-03-16 01:36:07 +0000
+++ ubuntu_sso/gtk/tests/test_gui.py 2011-03-29 17:35:28 +0000
@@ -51,7 +51,7 @@
51 self._args = args51 self._args = args
52 self._kwargs = kwargs52 self._kwargs = kwargs
53 self._called = {}53 self._called = {}
54 for i in ('generate_captcha', 'login', 'register_user',54 for i in ('generate_captcha', 'login', 'register_with_name',
55 'validate_email', 'request_password_reset_token',55 'validate_email', 'request_password_reset_token',
56 'set_new_password'):56 'set_new_password'):
57 setattr(self, i, self._record_call(i))57 setattr(self, i, self._record_call(i))
@@ -709,8 +709,8 @@
709 """Clicking 'join_ok_button' sends info to backend using 'register'."""709 """Clicking 'join_ok_button' sends info to backend using 'register'."""
710 self.click_join_with_valid_data()710 self.click_join_with_valid_data()
711711
712 # assert register_user was called712 # assert register_with_name was called
713 expected = 'register_user'713 expected = 'register_with_name'
714 self.assertIn(expected, self.ui.backend._called)714 self.assertIn(expected, self.ui.backend._called)
715 self.assertEqual(self.ui.backend._called[expected],715 self.assertEqual(self.ui.backend._called[expected],
716 ((APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,716 ((APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
@@ -866,6 +866,7 @@
866 self.ui.join_ok_button.clicked()866 self.ui.join_ok_button.clicked()
867 self.assertTrue(self._called)867 self.assertTrue(self._called)
868868
869<<<<<<< TREE
869 def test_user_and_pass_are_cached(self):870 def test_user_and_pass_are_cached(self):
870 """Username and password are temporarly cached for further use."""871 """Username and password are temporarly cached for further use."""
871 self.click_join_with_valid_data()872 self.click_join_with_valid_data()
@@ -880,6 +881,14 @@
880 self.ui.CAPTCHA_LOAD_ERROR)881 self.ui.CAPTCHA_LOAD_ERROR)
881 self.assertEqual(self._called, ((), {}))882 self.assertEqual(self._called, ((), {}))
882883
884=======
885 def test_user_and_pass_are_cached(self):
886 """Username and password are temporarly cached for further use."""
887 self.click_join_with_valid_data()
888 self.assertEqual(self.ui.user_email, EMAIL)
889 self.assertEqual(self.ui.user_password, PASSWORD)
890
891>>>>>>> MERGE-SOURCE
883892
884class NoTermsAndConditionsTestCase(UbuntuSSOClientTestCase):893class NoTermsAndConditionsTestCase(UbuntuSSOClientTestCase):
885 """Test suite for the user registration (with no t&c link)."""894 """Test suite for the user registration (with no t&c link)."""
@@ -1282,20 +1291,37 @@
1282 self.assert_warnings_visibility()1291 self.assert_warnings_visibility()
12831292
12841293
1285class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):1294<<<<<<< TREE
1286 """Test suite for the user login (verify email page)."""1295class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):
12871296 """Test suite for the user login (verify email page)."""
1288 kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,1297
1289 login_only=True)1298 kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
12901299 login_only=True)
12911300
1292class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):1301
1293 """Test suite for the user login validation (verify email page)."""1302class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):
12941303 """Test suite for the user login validation (verify email page)."""
1295 kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,1304
1296 login_only=True)1305 kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
12971306 login_only=True)
12981307
1308
1309=======
1310class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):
1311 """Test suite for the user login (verify email page)."""
1312
1313 kwargs = dict(app_name=APP_NAME, tc_uri=TC_URI, help_text=HELP_TEXT,
1314 login_only=True)
1315
1316
1317class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):
1318 """Test suite for the user login validation (verify email page)."""
1319
1320 kwargs = dict(app_name=APP_NAME, tc_uri=TC_URI, help_text=HELP_TEXT,
1321 login_only=True)
1322
1323
1324>>>>>>> MERGE-SOURCE
1299class RegistrationValidationTestCase(UbuntuSSOClientTestCase):1325class RegistrationValidationTestCase(UbuntuSSOClientTestCase):
1300 """Test suite for the user registration validations."""1326 """Test suite for the user registration validations."""
13011327
@@ -1312,8 +1338,13 @@
13121338
1313 self.assert_correct_entry_warning(self.ui.name_entry,1339 self.assert_correct_entry_warning(self.ui.name_entry,
1314 self.ui.FIELD_REQUIRED)1340 self.ui.FIELD_REQUIRED)
1341<<<<<<< TREE
1315 self.assertNotIn('register_user', self.ui.backend._called)1342 self.assertNotIn('register_user', self.ui.backend._called)
13161343
1344=======
1345 self.assertNotIn('register_with_name', self.ui.backend._called)
1346
1347>>>>>>> MERGE-SOURCE
1317 def test_warning_is_shown_if_empty_email(self):1348 def test_warning_is_shown_if_empty_email(self):
1318 """A warning message is shown if emails are empty."""1349 """A warning message is shown if emails are empty."""
1319 self.ui.email1_entry.set_text('')1350 self.ui.email1_entry.set_text('')
@@ -1325,7 +1356,7 @@
1325 self.ui.FIELD_REQUIRED)1356 self.ui.FIELD_REQUIRED)
1326 self.assert_correct_entry_warning(self.ui.email2_entry,1357 self.assert_correct_entry_warning(self.ui.email2_entry,
1327 self.ui.FIELD_REQUIRED)1358 self.ui.FIELD_REQUIRED)
1328 self.assertNotIn('register_user', self.ui.backend._called)1359 self.assertNotIn('register_with_name', self.ui.backend._called)
13291360
1330 def test_warning_is_shown_if_email_mismatch(self):1361 def test_warning_is_shown_if_email_mismatch(self):
1331 """A warning message is shown if emails doesn't match."""1362 """A warning message is shown if emails doesn't match."""
@@ -1338,7 +1369,7 @@
1338 self.ui.EMAIL_MISMATCH)1369 self.ui.EMAIL_MISMATCH)
1339 self.assert_correct_entry_warning(self.ui.email2_entry,1370 self.assert_correct_entry_warning(self.ui.email2_entry,
1340 self.ui.EMAIL_MISMATCH)1371 self.ui.EMAIL_MISMATCH)
1341 self.assertNotIn('register_user', self.ui.backend._called)1372 self.assertNotIn('register_with_name', self.ui.backend._called)
13421373
1343 def test_warning_is_shown_if_invalid_email(self):1374 def test_warning_is_shown_if_invalid_email(self):
1344 """A warning message is shown if email is invalid."""1375 """A warning message is shown if email is invalid."""
@@ -1351,7 +1382,7 @@
1351 self.ui.EMAIL_INVALID)1382 self.ui.EMAIL_INVALID)
1352 self.assert_correct_entry_warning(self.ui.email2_entry,1383 self.assert_correct_entry_warning(self.ui.email2_entry,
1353 self.ui.EMAIL_INVALID)1384 self.ui.EMAIL_INVALID)
1354 self.assertNotIn('register_user', self.ui.backend._called)1385 self.assertNotIn('register_with_name', self.ui.backend._called)
13551386
1356 def test_password_help_is_always_shown(self):1387 def test_password_help_is_always_shown(self):
1357 """Password help text is correctly displayed."""1388 """Password help text is correctly displayed."""
@@ -1359,7 +1390,7 @@
1359 'password help text is visible.')1390 'password help text is visible.')
1360 self.assertEqual(self.ui.password_help_label.get_text(),1391 self.assertEqual(self.ui.password_help_label.get_text(),
1361 self.ui.PASSWORD_HELP)1392 self.ui.PASSWORD_HELP)
1362 self.assertNotIn('register_user', self.ui.backend._called)1393 self.assertNotIn('register_with_name', self.ui.backend._called)
13631394
1364 def test_warning_is_shown_if_password_mismatch(self):1395 def test_warning_is_shown_if_password_mismatch(self):
1365 """A warning message is shown if password doesn't match."""1396 """A warning message is shown if password doesn't match."""
@@ -1372,7 +1403,7 @@
1372 self.ui.PASSWORD_MISMATCH)1403 self.ui.PASSWORD_MISMATCH)
1373 self.assert_correct_entry_warning(self.ui.password2_entry,1404 self.assert_correct_entry_warning(self.ui.password2_entry,
1374 self.ui.PASSWORD_MISMATCH)1405 self.ui.PASSWORD_MISMATCH)
1375 self.assertNotIn('register_user', self.ui.backend._called)1406 self.assertNotIn('register_with_name', self.ui.backend._called)
13761407
1377 def test_warning_is_shown_if_password_too_weak(self):1408 def test_warning_is_shown_if_password_too_weak(self):
1378 """A warning message is shown if password is too weak."""1409 """A warning message is shown if password is too weak."""
@@ -1387,7 +1418,7 @@
1387 self.ui.PASSWORD_TOO_WEAK)1418 self.ui.PASSWORD_TOO_WEAK)
1388 self.assert_correct_entry_warning(self.ui.password2_entry,1419 self.assert_correct_entry_warning(self.ui.password2_entry,
1389 self.ui.PASSWORD_TOO_WEAK)1420 self.ui.PASSWORD_TOO_WEAK)
1390 self.assertNotIn('register_user', self.ui.backend._called)1421 self.assertNotIn('register_with_name', self.ui.backend._called)
13911422
1392 def test_warning_is_shown_if_tc_not_accepted(self):1423 def test_warning_is_shown_if_tc_not_accepted(self):
1393 """A warning message is shown if TC are not accepted."""1424 """A warning message is shown if TC are not accepted."""
@@ -1398,7 +1429,7 @@
13981429
1399 self.assert_correct_label_warning(self.ui.tc_warning_label,1430 self.assert_correct_label_warning(self.ui.tc_warning_label,
1400 self.ui.TC_NOT_ACCEPTED)1431 self.ui.TC_NOT_ACCEPTED)
1401 self.assertNotIn('register_user', self.ui.backend._called)1432 self.assertNotIn('register_with_name', self.ui.backend._called)
14021433
1403 def test_warning_is_shown_if_not_captcha_solution(self):1434 def test_warning_is_shown_if_not_captcha_solution(self):
1404 """A warning message is shown if TC are not accepted."""1435 """A warning message is shown if TC are not accepted."""
@@ -1409,7 +1440,7 @@
14091440
1410 self.assert_correct_entry_warning(self.ui.captcha_solution_entry,1441 self.assert_correct_entry_warning(self.ui.captcha_solution_entry,
1411 self.ui.FIELD_REQUIRED)1442 self.ui.FIELD_REQUIRED)
1412 self.assertNotIn('register_user', self.ui.backend._called)1443 self.assertNotIn('register_with_name', self.ui.backend._called)
14131444
1414 def test_no_warning_messages_if_valid_data(self):1445 def test_no_warning_messages_if_valid_data(self):
1415 """No warning messages are shown if the data is valid."""1446 """No warning messages are shown if the data is valid."""
@@ -1548,6 +1579,7 @@
1548 self.ui.login_ok_button.clicked()1579 self.ui.login_ok_button.clicked()
1549 self.assertTrue(self._called)1580 self.assertTrue(self._called)
15501581
1582<<<<<<< TREE
1551 def test_user_and_pass_are_cached(self):1583 def test_user_and_pass_are_cached(self):
1552 """Username and password are temporarly cached for further use."""1584 """Username and password are temporarly cached for further use."""
1553 self.click_connect_with_valid_data()1585 self.click_connect_with_valid_data()
@@ -1580,6 +1612,14 @@
15801612
1581 self.assertEqual(self._called, ((), {}))1613 self.assertEqual(self._called, ((), {}))
15821614
1615=======
1616 def test_user_and_pass_are_cached(self):
1617 """Username and password are temporarly cached for further use."""
1618 self.click_connect_with_valid_data()
1619 self.assertEqual(self.ui.user_email, EMAIL)
1620 self.assertEqual(self.ui.user_password, PASSWORD)
1621
1622>>>>>>> MERGE-SOURCE
15831623
1584class LoginValidationTestCase(UbuntuSSOClientTestCase):1624class LoginValidationTestCase(UbuntuSSOClientTestCase):
1585 """Test suite for the user login validation."""1625 """Test suite for the user login validation."""
15861626
=== modified file 'ubuntu_sso/keyring/linux.py'
--- ubuntu_sso/keyring/linux.py 2011-03-03 15:19:29 +0000
+++ ubuntu_sso/keyring/linux.py 2011-03-29 17:35:28 +0000
@@ -29,6 +29,7 @@
29from twisted.internet.defer import inlineCallbacks, returnValue29from twisted.internet.defer import inlineCallbacks, returnValue
3030
31from ubuntu_sso.logger import setup_logging31from ubuntu_sso.logger import setup_logging
32<<<<<<< TREE
32from ubuntu_sso.utils.txsecrets import SecretService33from ubuntu_sso.utils.txsecrets import SecretService
33from ubuntu_sso.keyring import (34from ubuntu_sso.keyring import (
34 get_token_name,35 get_token_name,
@@ -38,17 +39,58 @@
3839
3940
40logger = setup_logging("ubuntu_sso.keyring")41logger = setup_logging("ubuntu_sso.keyring")
42=======
43
44
45logger = setup_logging("ubuntu_sso.keyring")
46TOKEN_SEPARATOR = ' @ '
47SEPARATOR_REPLACEMENT = ' AT '
48
49U1_APP_NAME = "Ubuntu One"
50U1_KEY_NAME = "UbuntuOne token for https://ubuntuone.com"
51U1_KEY_ATTR = {
52 "oauth-consumer-key": "ubuntuone",
53 "ubuntuone-realm": "https://ubuntuone.com",
54}
55
56
57def get_old_token_name(app_name):
58 """Build the token name (old style)."""
59 quoted_app_name = urllib.quote(app_name)
60 computer_name = socket.gethostname()
61 quoted_computer_name = urllib.quote(computer_name)
62 return "%s - %s" % (quoted_app_name, quoted_computer_name)
63
64
65def get_token_name(app_name):
66 """Build the token name."""
67 computer_name = socket.gethostname()
68 computer_name = computer_name.replace(TOKEN_SEPARATOR,
69 SEPARATOR_REPLACEMENT)
70 return TOKEN_SEPARATOR.join((app_name, computer_name)).encode('utf-8')
71>>>>>>> MERGE-SOURCE
4172
4273
43class Keyring(object):74class Keyring(object):
44 """A Keyring for a given application name."""75 """A Keyring for a given application name."""
4576
77<<<<<<< TREE
46 def __init__(self):78 def __init__(self):
47 """Initialize this instance."""79 """Initialize this instance."""
48 self.service = SecretService()80 self.service = SecretService()
4981
50 @inlineCallbacks82 @inlineCallbacks
51 def _find_keyring_item(self, app_name, attr=None):83 def _find_keyring_item(self, app_name, attr=None):
84=======
85 def __init__(self, app_name):
86 """Initialize this instance given the app_name."""
87 if not gnomekeyring.is_available():
88 raise gnomekeyring.NoKeyringDaemonError
89 self.app_name = app_name
90 self.token_name = get_token_name(self.app_name)
91
92 def _find_keyring_item(self, attr=None):
93>>>>>>> MERGE-SOURCE
52 """Return the keyring item or None if not found."""94 """Return the keyring item or None if not found."""
53 if attr is None:95 if attr is None:
54 logger.debug("getting attr")96 logger.debug("getting attr")
@@ -75,11 +117,19 @@
75 # Creates the secret from the credentials117 # Creates the secret from the credentials
76 secret = urllib.urlencode(cred)118 secret = urllib.urlencode(cred)
77119
120<<<<<<< TREE
78 attr = self._get_keyring_attr(app_name)121 attr = self._get_keyring_attr(app_name)
122=======
123>>>>>>> MERGE-SOURCE
79 # Add our SSO credentials to the keyring124 # Add our SSO credentials to the keyring
125<<<<<<< TREE
80 yield self.service.open_session()126 yield self.service.open_session()
81 collection = yield self.service.get_default_collection()127 collection = yield self.service.get_default_collection()
82 yield collection.create_item(app_name, attr, secret, True)128 yield collection.create_item(app_name, attr, secret, True)
129=======
130 gnomekeyring.item_create_sync(None, gnomekeyring.ITEM_GENERIC_SECRET,
131 self.app_name, self._get_keyring_attr(), secret, True)
132>>>>>>> MERGE-SOURCE
83133
84 @inlineCallbacks134 @inlineCallbacks
85 def _migrate_old_token_name(self, app_name):135 def _migrate_old_token_name(self, app_name):
86136
=== modified file 'ubuntu_sso/keyring/tests/test_linux.py'
--- ubuntu_sso/keyring/tests/test_linux.py 2011-03-18 09:30:20 +0000
+++ ubuntu_sso/keyring/tests/test_linux.py 2011-03-29 17:35:28 +0000
@@ -65,6 +65,7 @@
65 return True65 return True
6666
6767
68<<<<<<< TREE
68class MockCollection(object):69class MockCollection(object):
69 """A collection of items containing secrets."""70 """A collection of items containing secrets."""
7071
@@ -114,6 +115,44 @@
114 if len(self.collections) == 0:115 if len(self.collections) == 0:
115 self.create_collection("default")116 self.create_collection("default")
116 return defer.succeed(self.collections["default"])117 return defer.succeed(self.collections["default"])
118=======
119class MockGnomeKeyring(object):
120 """A mock keyring that stores keys according to a given attr."""
121
122 def __init__(self):
123 """Initialize this instance."""
124 self.id_counter = itertools.count()
125 self.store = {}
126 self.deleted = []
127
128 def _get_next_id(self):
129 """Return the next keyring id."""
130 return self.id_counter.next()
131
132 def item_create_sync(self, keyring_name, item_type, key_name, attr,
133 secret, update_if_exists):
134 """Add a key to a keyring."""
135 new_id = self._get_next_id()
136 i = MockKeyringItem(new_id, keyring_name, attr, secret)
137 self.store[new_id] = i
138
139 def item_delete_sync(self, keyring_name, item_id):
140 """Delete a key from a keyring, and keep a list of deleted keys."""
141 item = self.store.pop(item_id)
142 assert keyring_name == item.keyring
143 self.deleted.append(item)
144
145 def find_items_sync(self, item_type, attr):
146 """Find all keys that match the given attributes."""
147 items = [i for i in self.store.values() if i.matches(attr)]
148 if len(items) == 0:
149 raise gnomekeyring.NoMatchError()
150 return items
151
152 def is_available(self):
153 """A very available keyring."""
154 return True
155>>>>>>> MERGE-SOURCE
117156
118157
119class TestTokenNameBuilder(TestCase):158class TestTokenNameBuilder(TestCase):
@@ -160,9 +199,17 @@
160199
161 def setUp(self):200 def setUp(self):
162 """Initialize the mock used in these tests."""201 """Initialize the mock used in these tests."""
202<<<<<<< TREE
163 self.mock_service = None203 self.mock_service = None
164 self.service = self.patch(keyring, "SecretService",204 self.service = self.patch(keyring, "SecretService",
165 self.get_mock_service)205 self.get_mock_service)
206=======
207 self.mgk = MockGnomeKeyring()
208 self.patch(gnomekeyring, "item_create_sync", self.mgk.item_create_sync)
209 self.patch(gnomekeyring, "is_available", self.mgk.is_available)
210 self.patch(gnomekeyring, "find_items_sync", self.mgk.find_items_sync)
211 self.patch(gnomekeyring, "item_delete_sync", self.mgk.item_delete_sync)
212>>>>>>> MERGE-SOURCE
166 fake_gethostname = build_fake_gethostname("darkstar")213 fake_gethostname = build_fake_gethostname("darkstar")
167 self.patch(socket, "gethostname", fake_gethostname)214 self.patch(socket, "gethostname", fake_gethostname)
168215
@@ -177,6 +224,7 @@
177 """Test that the set method does not erase previous keys."""224 """Test that the set method does not erase previous keys."""
178 sample_creds = {"name": "sample creds name"}225 sample_creds = {"name": "sample creds name"}
179 sample_creds2 = {"name": "sample creds name 2"}226 sample_creds2 = {"name": "sample creds name 2"}
227<<<<<<< TREE
180 kr = keyring.Keyring()228 kr = keyring.Keyring()
181 yield kr.set_credentials("appname", sample_creds)229 yield kr.set_credentials("appname", sample_creds)
182 yield kr.set_credentials("appname", sample_creds2)230 yield kr.set_credentials("appname", sample_creds2)
@@ -186,6 +234,15 @@
186234
187 @inlineCallbacks235 @inlineCallbacks
188 def test_delete_credentials(self):236 def test_delete_credentials(self):
237=======
238 keyring.Keyring("appname").set_ubuntusso_attr(sample_creds)
239 keyring.Keyring("appname").set_ubuntusso_attr(sample_creds2)
240
241 self.assertEqual(len(self.mgk.store), 2)
242 self.assertEqual(len(self.mgk.deleted), 0)
243
244 def test_delete_ubuntusso(self):
245>>>>>>> MERGE-SOURCE
189 """Test that a given key is deleted."""246 """Test that a given key is deleted."""
190 sample_creds = {"name": "sample creds name"}247 sample_creds = {"name": "sample creds name"}
191 kr = keyring.Keyring()248 kr = keyring.Keyring()
@@ -225,11 +282,22 @@
225 "oauth_token": sample_oauth_token,282 "oauth_token": sample_oauth_token,
226 "oauth_token_secret": sample_oauth_secret,283 "oauth_token_secret": sample_oauth_secret,
227 }284 }
285<<<<<<< TREE
228 u1kr = common_keyring.UbuntuOneOAuthKeyring()286 u1kr = common_keyring.UbuntuOneOAuthKeyring()
229 yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)287 yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)
230288
231 kr = keyring.Keyring()289 kr = keyring.Keyring()
232 result = yield kr.get_credentials(keyring.U1_APP_NAME)290 result = yield kr.get_credentials(keyring.U1_APP_NAME)
291=======
292 secret = urllib.urlencode(old_creds)
293 self.mgk.item_create_sync(None, None,
294 keyring.U1_APP_NAME,
295 keyring.U1_KEY_ATTR,
296 secret, True)
297
298 result = keyring.Keyring(keyring.U1_APP_NAME).get_ubuntusso_attr()
299
300>>>>>>> MERGE-SOURCE
233 self.assertIn("token", result)301 self.assertIn("token", result)
234 self.assertEqual(result["token"], sample_oauth_token)302 self.assertEqual(result["token"], sample_oauth_token)
235 self.assertIn("token_secret", result)303 self.assertIn("token_secret", result)
@@ -244,8 +312,16 @@
244 "oauth_token": sample_oauth_token,312 "oauth_token": sample_oauth_token,
245 "oauth_token_secret": sample_oauth_secret,313 "oauth_token_secret": sample_oauth_secret,
246 }314 }
315<<<<<<< TREE
247 u1kr = common_keyring.UbuntuOneOAuthKeyring()316 u1kr = common_keyring.UbuntuOneOAuthKeyring()
248 yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)317 yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)
318=======
319 secret = urllib.urlencode(old_creds)
320 self.mgk.item_create_sync(None, None,
321 keyring.U1_APP_NAME,
322 keyring.U1_KEY_ATTR,
323 secret, True)
324>>>>>>> MERGE-SOURCE
249325
250 kr = keyring.Keyring()326 kr = keyring.Keyring()
251 result = yield kr.get_credentials("Software Center")327 result = yield kr.get_credentials("Software Center")
252328
=== modified file 'ubuntu_sso/main/linux.py'
--- ubuntu_sso/main/linux.py 2011-03-29 14:16:36 +0000
+++ ubuntu_sso/main/linux.py 2011-03-29 17:35:28 +0000
@@ -45,6 +45,279 @@
4545
4646
47logger = setup_logging("ubuntu_sso.main")47logger = setup_logging("ubuntu_sso.main")
48<<<<<<< TREE
49=======
50PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
51SERVICE_URL = "https://login.ubuntu.com/api/1.0"
52NO_OP = lambda *args, **kwargs: None
53
54
55class NoDefaultConfigError(Exception):
56 """No default section in configuration file"""
57
58
59class BadRealmError(Exception):
60 """Realm must be a URL."""
61
62
63class InvalidEmailError(Exception):
64 """The email is not valid."""
65
66
67class InvalidPasswordError(Exception):
68 """The password is not valid.
69
70 Must provide at least 8 characters, one upper case, one number.
71 """
72
73
74class RegistrationError(Exception):
75 """The registration failed."""
76
77
78class AuthenticationError(Exception):
79 """The authentication failed."""
80
81
82class EmailTokenError(Exception):
83 """The email token is not valid."""
84
85
86class ResetPasswordTokenError(Exception):
87 """The token for password reset could not be generated."""
88
89
90class NewPasswordError(Exception):
91 """The new password could not be set."""
92
93
94def keyring_store_credentials(app_name, credentials, callback, *cb_args):
95 """Store the credentials in the keyring."""
96
97 def _inner():
98 """Store the credentials, and trigger the callback."""
99 logger.info('keyring_store_credentials: app_name "%s".', app_name)
100 Keyring(app_name).set_ubuntusso_attr(credentials)
101 callback(*cb_args)
102
103 gobject.idle_add(_inner)
104
105
106def keyring_get_credentials(app_name):
107 """Get the credentials from the keyring or None if not there."""
108 creds = Keyring(app_name).get_ubuntusso_attr()
109 logger.info('keyring_get_credentials: app_name "%s", resulting credentials'
110 ' is not None? %r', app_name, creds is not None)
111 return creds
112
113
114class SSOLoginProcessor(object):
115 """Login and register users using the Ubuntu Single Sign On service."""
116
117 def __init__(self, sso_service_class=None):
118 """Create a new SSO login processor."""
119 if sso_service_class is None:
120 self.sso_service_class = ServiceRoot
121 else:
122 self.sso_service_class = sso_service_class
123
124 self.service_url = os.environ.get('USSOC_SERVICE_URL', SERVICE_URL)
125
126 def _valid_email(self, email):
127 """Validate the given email."""
128 return email is not None and '@' in email
129
130 def _valid_password(self, password):
131 """Validate the given password."""
132 res = (len(password) > 7 and # at least 8 characters
133 re.search('[A-Z]', password) and # one upper case
134 re.search('\d+', password)) # one number
135 return res
136
137 def _format_webservice_errors(self, errdict):
138 """Turn each list of strings in the errdict into a LF separated str."""
139 result = {}
140 for k, v in errdict.iteritems():
141 # workaround until bug #624955 is solved
142 if isinstance(v, basestring):
143 result[k] = v
144 else:
145 result[k] = "\n".join(v)
146 return result
147
148 def generate_captcha(self, filename):
149 """Generate a captcha using the SSO service."""
150 logger.debug('generate_captcha: requesting captcha, filename: %r',
151 filename)
152 sso_service = self.sso_service_class(None, self.service_url)
153 captcha = sso_service.captchas.new()
154
155 # download captcha and save to 'filename'
156 logger.debug('generate_captcha: server answered: %r', captcha)
157 try:
158 res = urllib2.urlopen(captcha['image_url'])
159 with open(filename, 'wb') as f:
160 f.write(res.read())
161 except:
162 msg = 'generate_captcha crashed while downloading the image.'
163 logger.exception(msg)
164 raise
165
166 return captcha['captcha_id']
167
168 def register_with_name(self, email, password, displayname,
169 captcha_id, captcha_solution):
170 """Register a new user with 'email' and 'password'."""
171 logger.debug('register_with_name: email: %r password: <hidden>, '
172 'displayname: %r, captcha_id: %r, captcha_solution: %r',
173 email, displayname, captcha_id, captcha_solution)
174 sso_service = self.sso_service_class(None, self.service_url)
175 if not self._valid_email(email):
176 logger.error('register_with_name: InvalidEmailError for email: %r',
177 email)
178 raise InvalidEmailError()
179 if not self._valid_password(password):
180 logger.error('register_with_name: InvalidPasswordError')
181 raise InvalidPasswordError()
182
183 result = sso_service.registrations.register(
184 email=email, password=password,
185 displayname=displayname,
186 captcha_id=captcha_id,
187 captcha_solution=captcha_solution)
188 logger.info('register_with_name: email: %r result: %r', email, result)
189
190 if result['status'] == 'error':
191 errorsdict = self._format_webservice_errors(result['errors'])
192 raise RegistrationError(errorsdict)
193 elif result['status'] != 'ok':
194 raise RegistrationError('Received unknown status: %s' % result)
195 else:
196 return email
197
198 def register_user(self, email, password,
199 captcha_id, captcha_solution):
200 """Register a new user with 'email' and 'password'."""
201 logger.debug('register_user: email: %r password: <hidden>, '
202 'captcha_id: %r, captcha_solution: %r',
203 email, captcha_id, captcha_solution)
204 res = self.register_with_name(email, password, displayname='',
205 captcha_id=captcha_id,
206 captcha_solution=captcha_solution)
207 return res
208
209 def login(self, email, password, token_name):
210 """Login a user with 'email' and 'password'."""
211 logger.debug('login: email: %r password: <hidden>, token_name: %r',
212 email, token_name)
213 basic = BasicHttpAuthorizer(email, password)
214 sso_service = self.sso_service_class(basic, self.service_url)
215 service = sso_service.authentications.authenticate
216
217 try:
218 credentials = service(token_name=token_name)
219 except HTTPError:
220 logger.exception('login failed with:')
221 raise AuthenticationError()
222
223 logger.debug('login: authentication successful! consumer_key: %r, ' \
224 'token_name: %r', credentials['consumer_key'], token_name)
225 return credentials
226
227 def is_validated(self, token, sso_service=None):
228 """Return if user with 'email' and 'password' is validated."""
229 logger.debug('is_validated: requesting accounts.me() info.')
230 if sso_service is None:
231 oauth_token = oauth.OAuthToken(token['token'],
232 token['token_secret'])
233 authorizer = OAuthAuthorizer(token['consumer_key'],
234 token['consumer_secret'],
235 oauth_token)
236 sso_service = self.sso_service_class(authorizer, self.service_url)
237
238 me_info = sso_service.accounts.me()
239 key = 'preferred_email'
240 result = key in me_info and me_info[key] != None
241
242 logger.info('is_validated: consumer_key: %r, result: %r.',
243 token['consumer_key'], result)
244 return result
245
246 def validate_email(self, email, password, email_token, token_name):
247 """Validate an email token for user with 'email' and 'password'."""
248 logger.debug('validate_email: email: %r password: <hidden>, '
249 'email_token: %r, token_name: %r.',
250 email, email_token, token_name)
251 token = self.login(email=email, password=password,
252 token_name=token_name)
253
254 oauth_token = oauth.OAuthToken(token['token'], token['token_secret'])
255 authorizer = OAuthAuthorizer(token['consumer_key'],
256 token['consumer_secret'],
257 oauth_token)
258 sso_service = self.sso_service_class(authorizer, self.service_url)
259 result = sso_service.accounts.validate_email(email_token=email_token)
260 logger.info('validate_email: email: %r result: %r', email, result)
261 if 'errors' in result:
262 errorsdict = self._format_webservice_errors(result['errors'])
263 raise EmailTokenError(errorsdict)
264 elif 'email' in result:
265 return token
266 else:
267 raise EmailTokenError('Received invalid reply: %s' % result)
268
269 def request_password_reset_token(self, email):
270 """Request a token to reset the password for the account 'email'."""
271 sso_service = self.sso_service_class(None, self.service_url)
272 service = sso_service.registrations.request_password_reset_token
273 try:
274 result = service(email=email)
275 except HTTPError, e:
276 logger.exception('request_password_reset_token failed with:')
277 raise ResetPasswordTokenError(e.content.split('\n')[0])
278
279 if result['status'] == 'ok':
280 return email
281 else:
282 raise ResetPasswordTokenError('Received invalid reply: %s' %
283 result)
284
285 def set_new_password(self, email, token, new_password):
286 """Set a new password for the account 'email' to be 'new_password'.
287
288 The 'token' has to be the one resulting from a call to
289 'request_password_reset_token'.
290
291 """
292 sso_service = self.sso_service_class(None, self.service_url)
293 service = sso_service.registrations.set_new_password
294 try:
295 result = service(email=email, token=token,
296 new_password=new_password)
297 except HTTPError, e:
298 logger.exception('set_new_password failed with:')
299 raise NewPasswordError(e.content.split('\n')[0])
300
301 if result['status'] == 'ok':
302 return email
303 else:
304 raise NewPasswordError('Received invalid reply: %s' % result)
305
306
307def except_to_errdict(e):
308 """Turn an exception into a dictionary to return thru DBus."""
309 result = {
310 "errtype": e.__class__.__name__,
311 }
312 if len(e.args) == 0:
313 result["message"] = e.__class__.__doc__
314 elif isinstance(e.args[0], dict):
315 result.update(e.args[0])
316 elif isinstance(e.args[0], basestring):
317 result["message"] = e.args[0]
318
319 return result
320>>>>>>> MERGE-SOURCE
48321
49322
50def blocking(f, app_name, result_cb, error_cb):323def blocking(f, app_name, result_cb, error_cb):
@@ -72,7 +345,13 @@
72 """Initiate the Login object."""345 """Initiate the Login object."""
73 dbus.service.Object.__init__(self, object_path=object_path,346 dbus.service.Object.__init__(self, object_path=object_path,
74 bus_name=bus_name)347 bus_name=bus_name)
348<<<<<<< TREE
75 self.root = SSOLoginRoot(sso_login_processor_class, sso_service_class)349 self.root = SSOLoginRoot(sso_login_processor_class, sso_service_class)
350=======
351 self.sso_login_processor_class = sso_login_processor_class
352 self.processor = self.sso_login_processor_class(
353 sso_service_class=sso_service_class)
354>>>>>>> MERGE-SOURCE
76355
77 # generate_capcha signals356 # generate_capcha signals
78 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")357 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
@@ -91,9 +370,17 @@
91 in_signature='ss')370 in_signature='ss')
92 def generate_captcha(self, app_name, filename):371 def generate_captcha(self, app_name, filename):
93 """Call the matching method in the processor."""372 """Call the matching method in the processor."""
373<<<<<<< TREE
94 self.root.generate_captcha(app_name, filename, blocking,374 self.root.generate_captcha(app_name, filename, blocking,
95 self.CaptchaGenerated,375 self.CaptchaGenerated,
96 self.CaptchaGenerationError)376 self.CaptchaGenerationError)
377=======
378 def f():
379 """Inner function that will be run in a thread."""
380 return self.processor.generate_captcha(filename)
381 blocking(f, app_name, self.CaptchaGenerated,
382 self.CaptchaGenerationError)
383>>>>>>> MERGE-SOURCE
97384
98 # register_user signals385 # register_user signals
99 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")386 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
@@ -113,10 +400,29 @@
113 def register_user(self, app_name, email, password, name,400 def register_user(self, app_name, email, password, name,
114 captcha_id, captcha_solution):401 captcha_id, captcha_solution):
115 """Call the matching method in the processor."""402 """Call the matching method in the processor."""
403<<<<<<< TREE
116 self.root.register_user(app_name, email, password, name, captcha_id,404 self.root.register_user(app_name, email, password, name, captcha_id,
117 captcha_solution, blocking,405 captcha_solution, blocking,
118 self.UserRegistered,406 self.UserRegistered,
119 self.UserRegistrationError)407 self.UserRegistrationError)
408=======
409 def f():
410 """Inner function that will be run in a thread."""
411 return self.processor.register_user(email, password,
412 captcha_id, captcha_solution)
413 blocking(f, app_name, self.UserRegistered, self.UserRegistrationError)
414
415 @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
416 in_signature='ssssss')
417 def register_with_name(self, app_name, email, password, name,
418 captcha_id, captcha_solution):
419 """Call the matching method in the processor."""
420 def f():
421 """Inner function that will be run in a thread."""
422 return self.processor.register_with_name(email, password, name,
423 captcha_id, captcha_solution)
424 blocking(f, app_name, self.UserRegistered, self.UserRegistrationError)
425>>>>>>> MERGE-SOURCE
120426
121 # login signals427 # login signals
122 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")428 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
@@ -141,8 +447,31 @@
141 in_signature='sss')447 in_signature='sss')
142 def login(self, app_name, email, password):448 def login(self, app_name, email, password):
143 """Call the matching method in the processor."""449 """Call the matching method in the processor."""
450<<<<<<< TREE
144 self.root.login(app_name, email, password, blocking, self.LoggedIn,451 self.root.login(app_name, email, password, blocking, self.LoggedIn,
145 self.LoginError, self.UserNotValidated)452 self.LoginError, self.UserNotValidated)
453=======
454 def f():
455 """Inner function that will be run in a thread."""
456 token_name = get_token_name(app_name)
457 logger.debug('login: token_name %r, email %r, password <hidden>.',
458 token_name, email)
459 credentials = self.processor.login(email, password, token_name)
460 logger.debug('login returned not None credentials? %r.',
461 credentials is not None)
462 return credentials
463
464 def success_cb(app_name, credentials):
465 """Login finished successfull."""
466 is_validated = self.processor.is_validated(credentials)
467 logger.debug('user is validated? %r.', is_validated)
468 if is_validated:
469 keyring_store_credentials(app_name, credentials,
470 self.LoggedIn, app_name, email)
471 else:
472 self.UserNotValidated(app_name, email)
473 blocking(f, app_name, success_cb, self.LoginError)
474>>>>>>> MERGE-SOURCE
146475
147 # validate_email signals476 # validate_email signals
148 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")477 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
@@ -161,9 +490,25 @@
161 in_signature='ssss')490 in_signature='ssss')
162 def validate_email(self, app_name, email, password, email_token):491 def validate_email(self, app_name, email, password, email_token):
163 """Call the matching method in the processor."""492 """Call the matching method in the processor."""
493<<<<<<< TREE
164 self.root.validate_email(app_name, email, password, email_token,494 self.root.validate_email(app_name, email, password, email_token,
165 blocking, self.EmailValidated,495 blocking, self.EmailValidated,
166 self.EmailValidationError)496 self.EmailValidationError)
497=======
498 def f():
499 """Inner function that will be run in a thread."""
500 token_name = get_token_name(app_name)
501 credentials = self.processor.validate_email(email, password,
502 email_token, token_name)
503
504 def _email_stored():
505 """The email was stored, so call the signal."""
506 self.EmailValidated(app_name, email)
507
508 keyring_store_credentials(app_name, credentials, _email_stored)
509
510 blocking(f, app_name, NO_OP, self.EmailValidationError)
511>>>>>>> MERGE-SOURCE
167512
168 # request_password_reset_token signals513 # request_password_reset_token signals
169 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")514 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
@@ -182,9 +527,17 @@
182 in_signature='ss')527 in_signature='ss')
183 def request_password_reset_token(self, app_name, email):528 def request_password_reset_token(self, app_name, email):
184 """Call the matching method in the processor."""529 """Call the matching method in the processor."""
530<<<<<<< TREE
185 self.root.request_password_reset_token(app_name, email, blocking,531 self.root.request_password_reset_token(app_name, email, blocking,
186 self.PasswordResetTokenSent,532 self.PasswordResetTokenSent,
187 self.PasswordResetError)533 self.PasswordResetError)
534=======
535 def f():
536 """Inner function that will be run in a thread."""
537 return self.processor.request_password_reset_token(email)
538 blocking(f, app_name, self.PasswordResetTokenSent,
539 self.PasswordResetError)
540>>>>>>> MERGE-SOURCE
188541
189 # set_new_password signals542 # set_new_password signals
190 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")543 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
@@ -203,9 +556,17 @@
203 in_signature='ssss')556 in_signature='ssss')
204 def set_new_password(self, app_name, email, token, new_password):557 def set_new_password(self, app_name, email, token, new_password):
205 """Call the matching method in the processor."""558 """Call the matching method in the processor."""
559<<<<<<< TREE
206 self.root.set_new_password(app_name, email, token, new_password,560 self.root.set_new_password(app_name, email, token, new_password,
207 blocking, self.PasswordChanged,561 blocking, self.PasswordChanged,
208 self.PasswordChangeError)562 self.PasswordChangeError)
563=======
564 def f():
565 """Inner function that will be run in a thread."""
566 return self.processor.set_new_password(email, token,
567 new_password)
568 blocking(f, app_name, self.PasswordChanged, self.PasswordChangeError)
569>>>>>>> MERGE-SOURCE
209570
210571
211class SSOCredentials(dbus.service.Object):572class SSOCredentials(dbus.service.Object):
@@ -243,11 +604,92 @@
243 '"%s" and error_message %r', app_name, error_message)604 '"%s" and error_message %r', app_name, error_message)
244605
245 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,606 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
607<<<<<<< TREE
246 in_signature="s", out_signature="a{ss}",608 in_signature="s", out_signature="a{ss}",
247 async_callbacks=("callback", "errback"))609 async_callbacks=("callback", "errback"))
248 def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):610 def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):
249 """Get the credentials from the keyring or {} if not there."""611 """Get the credentials from the keyring or {} if not there."""
250 self.root.find_credentials(app_name, callback, errback)612 self.root.find_credentials(app_name, callback, errback)
613=======
614 in_signature="s", out_signature="a{ss}")
615 def find_credentials(self, app_name):
616 """Get the credentials from the keyring or '' if not there."""
617 token = keyring_get_credentials(app_name)
618 logger.info('find_credentials: app_name "%s", result is {}? %s',
619 app_name, token is None)
620 if token is None:
621 return {}
622 else:
623 return token
624
625 def _login_success_cb(self, dialog, app_name, email):
626 """Handles the response from the UI dialog."""
627 logger.info('Login successful for app %r, email %r. Still pending to '
628 'ping server and send result signal.', app_name, email)
629 try:
630 creds = keyring_get_credentials(app_name)
631 self._ping_url(app_name, email, creds)
632 self.CredentialsFound(app_name, creds)
633 except: # pylint: disable=W0702
634 msg = "Problem getting the credentials from the keyring."
635 logger.exception(msg)
636 self.clear_token(app_name)
637 self.CredentialsError(app_name, msg, traceback.format_exc())
638
639 def _login_error_cb(self, dialog, app_name, error):
640 """Handles a problem in the UI."""
641 logger.info('Login unsuccessful for app %r, error %r', app_name, error)
642 msg = "Problem getting the credentials from the keyring."
643 self.CredentialsError(app_name, msg, "no more info available")
644
645 def _login_auth_denied_cb(self, dialog, app_name):
646 """The user decides not to allow the registration or login."""
647 self.AuthorizationDenied(app_name)
648
649 def _ping_url(self, app_name, email, credentials):
650 """Ping the given url."""
651 logger.info('Maybe pinging server for app_name "%s"', app_name)
652 if app_name == U1_APP_NAME:
653 url = self.ping_url + email
654 consumer = oauth.OAuthConsumer(credentials['consumer_key'],
655 credentials['consumer_secret'])
656 token = oauth.OAuthToken(credentials['token'],
657 credentials['token_secret'])
658 get_request = oauth.OAuthRequest.from_consumer_and_token
659 oauth_req = get_request(oauth_consumer=consumer, token=token,
660 http_method="GET", http_url=url)
661 oauth_req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),
662 consumer, token)
663 request = urllib2.Request(url, headers=oauth_req.to_header())
664 logger.debug('Opening the ping url %s with urllib2.urlopen. ' \
665 'Request to: %s', PING_URL, request.get_full_url())
666 response = urllib2.urlopen(request)
667 logger.debug('Url opened. Response: %s.', response.code)
668 return response.code
669
670 def _show_login_or_register_ui(self, app_name, tc_url, help_text,
671 win_id, login_only=False):
672 """Shows the UI so the user can login or register."""
673 try:
674 # delay gui import to be able to function on non-graphical envs
675 from ubuntu_sso import gui
676 gui_app = gui.UbuntuSSOClientGUI(app_name, tc_url,
677 help_text, win_id, login_only)
678 gui_app.connect(gui.SIG_LOGIN_SUCCEEDED, self._login_success_cb)
679 gui_app.connect(gui.SIG_LOGIN_FAILED, self._login_error_cb)
680 gui_app.connect(gui.SIG_REGISTRATION_SUCCEEDED,
681 self._login_success_cb)
682 gui_app.connect(gui.SIG_REGISTRATION_FAILED, self._login_error_cb)
683 gui_app.connect(gui.SIG_USER_CANCELATION,
684 self._login_auth_denied_cb)
685 except: # pylint: disable=W0702
686 msg = '_show_login_or_register_ui failed when calling ' \
687 'gui.UbuntuSSOClientGUI(%r, %r, %r, %r, %r)'
688 logger.exception(msg, app_name, tc_url, help_text,
689 win_id, login_only)
690 msg = "Problem opening the Ubuntu SSO user interface."
691 self.CredentialsError(app_name, msg, traceback.format_exc())
692>>>>>>> MERGE-SOURCE
251693
252 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,694 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
253 in_signature="sssx", out_signature="")695 in_signature="sssx", out_signature="")
@@ -296,6 +738,7 @@
296738
297 'app_name' is the name of the application.739 'app_name' is the name of the application.
298 """740 """
741<<<<<<< TREE
299 self.root.clear_token(app_name, callback, errback)742 self.root.clear_token(app_name, callback, errback)
300743
301744
@@ -452,3 +895,13 @@
452 def login(self, app_name, args):895 def login(self, app_name, args):
453 """Get credentials if found else prompt GUI to login."""896 """Get credentials if found else prompt GUI to login."""
454 self.root.login(app_name, args)897 self.root.login(app_name, args)
898=======
899 logger.info('Clearing credentials for app %r.', app_name)
900 try:
901 creds = Keyring(app_name)
902 creds.delete_ubuntusso_attr()
903 except: # pylint: disable=W0702
904 logger.exception(
905 "problem removing credentials from keyring for %s",
906 app_name)
907>>>>>>> MERGE-SOURCE
455908
=== modified file 'ubuntu_sso/main/tests/test_linux.py'
--- ubuntu_sso/main/tests/test_linux.py 2011-03-29 14:16:36 +0000
+++ ubuntu_sso/main/tests/test_linux.py 2011-03-29 17:35:28 +0000
@@ -50,6 +50,369 @@
50# pylint: disable=W021250# pylint: disable=W0212
5151
5252
53<<<<<<< TREE
54=======
55APP_NAME = 'The Coolest App Ever'
56CAPTCHA_PATH = os.path.abspath(os.path.join(os.curdir, 'ubuntu_sso', 'tests',
57 'files', 'captcha.png'))
58CAPTCHA_ID = 'test'
59CAPTCHA_SOLUTION = 'william Byrd'
60CANT_RESET_PASSWORD_CONTENT = "CanNotResetPassowrdError: " \
61 "Can't reset password for this account"
62RESET_TOKEN_INVALID_CONTENT = "AuthToken matching query does not exist."
63EMAIL = 'test@example.com'
64EMAIL_ALREADY_REGISTERED = 'a@example.com'
65EMAIL_TOKEN = 'B2Pgtf'
66HELP = 'help text'
67NAME = 'Juanito Pérez'
68PASSWORD = 'be4tiFul'
69RESET_PASSWORD_TOKEN = '8G5Wtq'
70TOKEN = {u'consumer_key': u'xQ7xDAz',
71 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
72 u'token_name': u'test',
73 u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
74 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
75TOKEN_NAME = get_token_name(APP_NAME)
76STATUS_UNKNOWN = {'status': 'yadda-yadda'}
77STATUS_ERROR = {'status': 'error', 'errors': {'something': ['Bla', 'Ble']}}
78STATUS_OK = {'status': 'ok'}
79STATUS_EMAIL_UNKNOWN = {'status': 'yadda-yadda'}
80STATUS_EMAIL_ERROR = {'errors': {'email_token': ['Error1', 'Error2']}}
81STATUS_EMAIL_OK = {'email': EMAIL}
82TC_URL = 'tcurl'
83WINDOW_ID = 5
84
85NO_OP = lambda *args, **kwargs: None
86LOGIN_OR_REGISTER_ARGS = (APP_NAME, TC_URL, HELP, WINDOW_ID)
87LOGIN_OR_REGISTER_GUI_ARGS = LOGIN_OR_REGISTER_ARGS + (False,)
88LOGIN_ONLY_ARGS = (APP_NAME, HELP, WINDOW_ID)
89LOGIN_ONLY_GUI_ARGS = (APP_NAME, None, HELP, WINDOW_ID, True)
90
91
92class FakedResponse(object):
93 """Fake a urlopen response."""
94
95 def __init__(self, *args, **kwargs):
96 for k, val in kwargs.iteritems():
97 setattr(self, k, val)
98
99
100class FakedCaptchas(object):
101 """Fake the captcha generator."""
102
103 def new(self):
104 """Return a fix captcha)."""
105 return {'image_url': 'file://%s' % CAPTCHA_PATH,
106 'captcha_id': CAPTCHA_ID}
107
108
109class FakedRegistrations(object):
110 """Fake the registrations service."""
111
112 def register(self, email, password, displayname,
113 captcha_id, captcha_solution):
114 """Fake registration. Return a fix result."""
115 if email == EMAIL_ALREADY_REGISTERED:
116 return {'status': 'error',
117 'errors': {'email': 'Email already registered'}}
118 elif captcha_id is None and captcha_solution is None:
119 return STATUS_UNKNOWN
120 elif captcha_id != CAPTCHA_ID or captcha_solution != CAPTCHA_SOLUTION:
121 return STATUS_ERROR
122 else:
123 return STATUS_OK
124
125 def request_password_reset_token(self, email):
126 """Fake password reset token. Return a fix result."""
127 if email is None:
128 return STATUS_UNKNOWN
129 elif email != EMAIL:
130 raise HTTPError(response=None, content=CANT_RESET_PASSWORD_CONTENT)
131 else:
132 return STATUS_OK
133
134 def set_new_password(self, email, token, new_password):
135 """Fake the setting of new password. Return a fix result."""
136 if email is None and token is None and new_password is None:
137 return STATUS_UNKNOWN
138 elif email != EMAIL or token != RESET_PASSWORD_TOKEN:
139 raise HTTPError(response=None, content=RESET_TOKEN_INVALID_CONTENT)
140 else:
141 return STATUS_OK
142
143
144class FakedAuthentications(object):
145 """Fake the authentications service."""
146
147 def authenticate(self, token_name):
148 """Fake authenticate. Return a fix result."""
149 if not token_name.startswith(TOKEN_NAME):
150 raise HTTPError(response=None, content=None)
151 else:
152 return TOKEN
153
154
155class FakedAccounts(object):
156 """Fake the accounts service."""
157
158 def __init__(self):
159 self.preferred_email = EMAIL
160
161 def validate_email(self, email_token):
162 """Fake the email validation. Return a fix result."""
163 if email_token is None:
164 return STATUS_EMAIL_UNKNOWN
165 elif email_token == EMAIL_ALREADY_REGISTERED:
166 return {'status': 'error',
167 'errors': {'email': 'Email already registered'}}
168 elif email_token != EMAIL_TOKEN:
169 return STATUS_EMAIL_ERROR
170 else:
171 return STATUS_EMAIL_OK
172
173 # pylint: disable=E0202, C0103
174
175 def me(self):
176 """Fake the 'me' information."""
177 return {u'username': u'Wh46bKY',
178 u'preferred_email': self.preferred_email,
179 u'displayname': u'',
180 u'unverified_emails': [u'aaaaaa@example.com'],
181 u'verified_emails': [],
182 u'openid_identifier': u'Wh46bKY'}
183
184
185class FakedSSOServer(object):
186 """Fake an SSO server."""
187
188 def __init__(self, authorizer, service_root):
189 self.captchas = FakedCaptchas()
190 self.registrations = FakedRegistrations()
191 self.authentications = FakedAuthentications()
192 self.accounts = FakedAccounts()
193
194
195class SSOLoginProcessorTestCase(TestCase, MockerTestCase):
196 """Test suite for the SSO login processor."""
197
198 def setUp(self):
199 """Init."""
200 self.processor = SSOLoginProcessor(sso_service_class=FakedSSOServer)
201 self.register_kwargs = dict(email=EMAIL, password=PASSWORD,
202 captcha_id=CAPTCHA_ID,
203 captcha_solution=CAPTCHA_SOLUTION)
204 self.login_kwargs = dict(email=EMAIL, password=PASSWORD,
205 token_name=TOKEN_NAME)
206
207 def tearDown(self):
208 """Clean up."""
209 self.processor = None
210
211 def test_generate_captcha(self):
212 """Captcha can be generated."""
213 filename = self.mktemp()
214 self.addCleanup(lambda: os.remove(filename))
215 captcha_id = self.processor.generate_captcha(filename)
216 self.assertEqual(CAPTCHA_ID, captcha_id, 'captcha id must be correct.')
217 self.assertTrue(os.path.isfile(filename), '%s must exist.' % filename)
218
219 with open(CAPTCHA_PATH) as f:
220 expected = f.read()
221 with open(filename) as f:
222 actual = f.read()
223 self.assertEqual(expected, actual, 'captcha image must be correct.')
224
225 def test_register_user_checks_valid_email(self):
226 """Email is validated."""
227 self.register_kwargs['email'] = 'notavalidemail'
228 self.assertRaises(InvalidEmailError,
229 self.processor.register_user, **self.register_kwargs)
230
231 def test_register_user_checks_valid_password(self):
232 """Password is validated."""
233 self.register_kwargs['password'] = ''
234 self.assertRaises(InvalidPasswordError,
235 self.processor.register_user, **self.register_kwargs)
236
237 # 7 chars, one less than expected
238 self.register_kwargs['password'] = 'tesT3it'
239 self.assertRaises(InvalidPasswordError,
240 self.processor.register_user, **self.register_kwargs)
241
242 self.register_kwargs['password'] = 'test3it!' # no upper case
243 self.assertRaises(InvalidPasswordError,
244 self.processor.register_user, **self.register_kwargs)
245
246 self.register_kwargs['password'] = 'testIt!!' # no number
247 self.assertRaises(InvalidPasswordError,
248 self.processor.register_user, **self.register_kwargs)
249
250 # register
251
252 def test_register_user_if_status_ok(self):
253 """A user is succesfuy registered into the SSO server."""
254 result = self.processor.register_user(**self.register_kwargs)
255 self.assertEqual(EMAIL, result, 'registration was successful.')
256
257 def test_register_user_if_status_error(self):
258 """Proper error is raised if register fails."""
259 self.register_kwargs['captcha_id'] = CAPTCHA_ID * 2 # incorrect
260 failure = self.assertRaises(RegistrationError,
261 self.processor.register_user,
262 **self.register_kwargs)
263 for k, val in failure.args[0].items():
264 self.assertIn(k, STATUS_ERROR['errors'])
265 self.assertEqual(val, "\n".join(STATUS_ERROR['errors'][k]))
266
267 def test_register_user_if_status_error_with_string_message(self):
268 """Proper error is raised if register fails."""
269 self.register_kwargs['email'] = EMAIL_ALREADY_REGISTERED
270 failure = self.assertRaises(RegistrationError,
271 self.processor.register_user,
272 **self.register_kwargs)
273 for k, val in failure.args[0].items():
274 self.assertIn(k, {'email': 'Email already registered'})
275 self.assertEqual(val, 'Email already registered')
276
277 def test_register_user_if_status_unknown(self):
278 """Proper error is raised if register returns an unknown status."""
279 self.register_kwargs['captcha_id'] = None
280 self.register_kwargs['captcha_solution'] = None
281 failure = self.assertRaises(RegistrationError,
282 self.processor.register_user,
283 **self.register_kwargs)
284 self.assertIn('Received unknown status: %s' % STATUS_UNKNOWN, failure)
285
286 # login
287
288 def test_login_if_http_error(self):
289 """Proper error is raised if authentication fails."""
290 self.login_kwargs['token_name'] = APP_NAME * 2 # invalid token name
291 self.assertRaises(AuthenticationError,
292 self.processor.login, **self.login_kwargs)
293
294 def test_login_if_no_error(self):
295 """A user can be succesfully logged in into the SSO service."""
296 result = self.processor.login(**self.login_kwargs)
297 self.assertEqual(TOKEN, result, 'authentication was successful.')
298
299 # is_validated
300
301 def test_is_validated(self):
302 """If preferred email is not None, user is validated."""
303 result = self.processor.is_validated(token=TOKEN)
304 self.assertTrue(result, 'user must be validated.')
305
306 def test_is_not_validated(self):
307 """If preferred email is None, user is not validated."""
308 service = FakedSSOServer(None, None)
309 service.accounts.preferred_email = None
310 result = self.processor.is_validated(sso_service=service,
311 token=TOKEN)
312 self.assertFalse(result, 'user must not be validated.')
313
314 def test_is_not_validated_empty_result(self):
315 """If preferred email is None, user is not validated."""
316 service = FakedSSOServer(None, None)
317 service.accounts.me = lambda: {}
318 result = self.processor.is_validated(sso_service=service,
319 token=TOKEN)
320 self.assertFalse(result, 'user must not be validated.')
321
322 # validate_email
323
324 def test_validate_email_if_status_ok(self):
325 """A email is succesfuy validated in the SSO server."""
326 self.login_kwargs['email_token'] = EMAIL_TOKEN # valid email token
327 result = self.processor.validate_email(**self.login_kwargs)
328 self.assertEqual(TOKEN, result, 'email validation was successful.')
329
330 def test_validate_email_if_status_error(self):
331 """Proper error is raised if email validation fails."""
332 self.login_kwargs['email_token'] = EMAIL_TOKEN * 2 # invalid token
333 failure = self.assertRaises(EmailTokenError,
334 self.processor.validate_email,
335 **self.login_kwargs)
336 for k, val in failure.args[0].items():
337 self.assertIn(k, STATUS_EMAIL_ERROR['errors'])
338 self.assertEqual(val, "\n".join(STATUS_EMAIL_ERROR['errors'][k]))
339
340 def test_validate_email_if_status_error_with_string_message(self):
341 """Proper error is raised if register fails."""
342 self.login_kwargs['email_token'] = EMAIL_ALREADY_REGISTERED
343 failure = self.assertRaises(EmailTokenError,
344 self.processor.validate_email,
345 **self.login_kwargs)
346 for k, val in failure.args[0].items():
347 self.assertIn(k, {'email': 'Email already registered'})
348 self.assertEqual(val, 'Email already registered')
349
350 def test_validate_email_if_status_unknown(self):
351 """Proper error is raised if email validation returns unknown."""
352 self.login_kwargs['email_token'] = None
353 failure = self.assertRaises(EmailTokenError,
354 self.processor.validate_email,
355 **self.login_kwargs)
356 self.assertIn('Received invalid reply: %s' % STATUS_UNKNOWN, failure)
357
358 # reset_password
359
360 def test_request_password_reset_token_if_status_ok(self):
361 """A reset password token is succesfuly sent."""
362 result = self.processor.request_password_reset_token(email=EMAIL)
363 self.assertEqual(EMAIL, result,
364 'password reset token must be successful.')
365
366 def test_request_password_reset_token_if_http_error(self):
367 """Proper error is raised if password token request fails."""
368 exc = self.assertRaises(ResetPasswordTokenError,
369 self.processor.request_password_reset_token,
370 email=EMAIL * 2)
371 self.assertIn(CANT_RESET_PASSWORD_CONTENT, exc)
372
373 def test_request_password_reset_token_if_status_unknown(self):
374 """Proper error is raised if password token request returns unknown."""
375 exc = self.assertRaises(ResetPasswordTokenError,
376 self.processor.request_password_reset_token,
377 email=None)
378 self.assertIn('Received invalid reply: %s' % STATUS_UNKNOWN, exc)
379
380 def test_set_new_password_if_status_ok(self):
381 """A new password is succesfuy set."""
382 result = self.processor.set_new_password(email=EMAIL,
383 token=RESET_PASSWORD_TOKEN,
384 new_password=PASSWORD)
385 self.assertEqual(EMAIL, result,
386 'new password must be set successfully.')
387
388 def test_set_new_password_if_http_error(self):
389 """Proper error is raised if setting a new password fails."""
390 exc = self.assertRaises(NewPasswordError,
391 self.processor.set_new_password,
392 email=EMAIL * 2,
393 token=RESET_PASSWORD_TOKEN * 2,
394 new_password=PASSWORD)
395 self.assertIn(RESET_TOKEN_INVALID_CONTENT, exc)
396
397 def test_set_new_password_if_status_unknown(self):
398 """Proper error is raised if setting a new password returns unknown."""
399 exc = self.assertRaises(NewPasswordError,
400 self.processor.set_new_password,
401 email=None, token=None, new_password=None)
402 self.assertIn('Received invalid reply: %s' % STATUS_UNKNOWN, exc)
403
404
405class SSORegistrationWithNameTestCase(SSOLoginProcessorTestCase):
406 """Test suite for the SSO login processor for register_with_name."""
407
408 def setUp(self):
409 """Init."""
410 super(SSORegistrationWithNameTestCase, self).setUp()
411 self.register_kwargs['displayname'] = NAME
412 self.processor.register_user = self.processor.register_with_name
413
414
415>>>>>>> MERGE-SOURCE
53class BlockingSampleException(Exception):416class BlockingSampleException(Exception):
54 """The exception that will be thrown by the fake blocking."""417 """The exception that will be thrown by the fake blocking."""
55418
@@ -84,13 +447,21 @@
84 mockbus._register_object_path(ARGS)447 mockbus._register_object_path(ARGS)
85 self.mockprocessorclass = None448 self.mockprocessorclass = None
86449
450<<<<<<< TREE
87 def ksc(keyring, k, val):451 def ksc(keyring, k, val):
452=======
453 def ksc(k, val, callback, *cb_args):
454>>>>>>> MERGE-SOURCE
88 """Assert over token and app_name."""455 """Assert over token and app_name."""
89 self.assertEqual(k, APP_NAME)456 self.assertEqual(k, APP_NAME)
90 self.assertEqual(val, TOKEN)457 self.assertEqual(val, TOKEN)
91 self.keyring_was_set = True458 self.keyring_was_set = True
92 self.keyring_values = k, val459 self.keyring_values = k, val
460<<<<<<< TREE
93 return defer.succeed(None)461 return defer.succeed(None)
462=======
463 callback(*cb_args)
464>>>>>>> MERGE-SOURCE
94465
95 self.patch(ubuntu_sso.main.Keyring, "set_credentials", ksc)466 self.patch(ubuntu_sso.main.Keyring, "set_credentials", ksc)
96 self.keyring_was_set = False467 self.keyring_was_set = False
@@ -164,8 +535,13 @@
164 """Test that the register_user method works ok."""535 """Test that the register_user method works ok."""
165 d = Deferred()536 d = Deferred()
166 expected_result = "expected result"537 expected_result = "expected result"
538<<<<<<< TREE
167 self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,539 self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,
168 CAPTCHA_ID, CAPTCHA_SOLUTION)540 CAPTCHA_ID, CAPTCHA_SOLUTION)
541=======
542 self.create_mock_processor().register_user(EMAIL, PASSWORD,
543 CAPTCHA_ID, CAPTCHA_SOLUTION)
544>>>>>>> MERGE-SOURCE
169 self.mocker.result(expected_result)545 self.mocker.result(expected_result)
170 self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)546 self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
171 self.mocker.replay()547 self.mocker.replay()
@@ -188,8 +564,13 @@
188 """Test that the register_user method fails as expected."""564 """Test that the register_user method fails as expected."""
189 d = Deferred()565 d = Deferred()
190 expected_result = "expected result"566 expected_result = "expected result"
567<<<<<<< TREE
191 self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,568 self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,
192 CAPTCHA_ID, CAPTCHA_SOLUTION)569 CAPTCHA_ID, CAPTCHA_SOLUTION)
570=======
571 self.create_mock_processor().register_user(EMAIL, PASSWORD,
572 CAPTCHA_ID, CAPTCHA_SOLUTION)
573>>>>>>> MERGE-SOURCE
193 self.mocker.result(expected_result)574 self.mocker.result(expected_result)
194 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)575 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
195 self.mocker.replay()576 self.mocker.replay()
@@ -208,15 +589,69 @@
208 CAPTCHA_SOLUTION)589 CAPTCHA_SOLUTION)
209 return d590 return d
210591
592 def test_register_with_name(self):
593 """Test that the register_with_name method works ok."""
594 d = Deferred()
595 expected_result = "expected result"
596 self.create_mock_processor().register_with_name(EMAIL, PASSWORD, NAME,
597 CAPTCHA_ID, CAPTCHA_SOLUTION)
598 self.mocker.result(expected_result)
599 self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
600 self.mocker.replay()
601
602 def verify(app_name, result):
603 """The actual test."""
604 self.assertEqual(result, expected_result)
605 self.assertEqual(app_name, APP_NAME)
606 d.callback(result)
607
608 client = SSOLogin(self.mockbusname,
609 sso_login_processor_class=self.mockprocessorclass)
610 self.patch(client, "UserRegistered", verify)
611 self.patch(client, "UserRegistrationError", d.errback)
612 client.register_with_name(APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
613 CAPTCHA_SOLUTION)
614 return d
615
616 def test_register_with_name_error(self):
617 """Test that the register_with_name method fails as expected."""
618 d = Deferred()
619 expected_result = "expected result"
620 self.create_mock_processor().register_with_name(EMAIL, PASSWORD, NAME,
621 CAPTCHA_ID, CAPTCHA_SOLUTION)
622 self.mocker.result(expected_result)
623 self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking)
624 self.mocker.replay()
625
626 def verify(app_name, errdict):
627 """The actual test."""
628 self.assertEqual(errdict["errtype"], "BlockingSampleException")
629 self.assertEqual(app_name, APP_NAME)
630 d.callback("Ok")
631
632 client = SSOLogin(self.mockbusname,
633 sso_login_processor_class=self.mockprocessorclass)
634 self.patch(client, "UserRegistered", d.errback)
635 self.patch(client, "UserRegistrationError", verify)
636 client.register_with_name(APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
637 CAPTCHA_SOLUTION)
638 return d
639
211 def test_login(self):640 def test_login(self):
212 """Test that the login method works ok."""641 """Test that the login method works ok."""
213 d = Deferred()642 d = Deferred()
214 processor = self.create_mock_processor()643 processor = self.create_mock_processor()
215 processor.login(EMAIL, PASSWORD, TOKEN_NAME)644 processor.login(EMAIL, PASSWORD, TOKEN_NAME)
216 self.mocker.result(TOKEN)645 self.mocker.result(TOKEN)
646<<<<<<< TREE
217 processor.is_validated(TOKEN)647 processor.is_validated(TOKEN)
218 self.mocker.result(True)648 self.mocker.result(True)
219 self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)649 self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
650=======
651 processor.is_validated(TOKEN)
652 self.mocker.result(True)
653 self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
654>>>>>>> MERGE-SOURCE
220 self.mocker.replay()655 self.mocker.replay()
221656
222 def verify(app_name, result):657 def verify(app_name, result):
@@ -230,6 +665,7 @@
230 sso_login_processor_class=self.mockprocessorclass)665 sso_login_processor_class=self.mockprocessorclass)
231 self.patch(client, "LoggedIn", verify)666 self.patch(client, "LoggedIn", verify)
232 self.patch(client, "LoginError", d.errback)667 self.patch(client, "LoginError", d.errback)
668<<<<<<< TREE
233 self.patch(client, "UserNotValidated", d.errback)669 self.patch(client, "UserNotValidated", d.errback)
234 client.login(APP_NAME, EMAIL, PASSWORD)670 client.login(APP_NAME, EMAIL, PASSWORD)
235 return d671 return d
@@ -265,6 +701,43 @@
265 d = Deferred()701 d = Deferred()
266 self.create_mock_processor()702 self.create_mock_processor()
267 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)703 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
704=======
705 self.patch(client, "UserNotValidated", d.errback)
706 client.login(APP_NAME, EMAIL, PASSWORD)
707 return d
708
709 def test_login_user_not_validated(self):
710 """Test that the login sends EmailNotValidated signal."""
711 d = Deferred()
712 processor = self.create_mock_processor()
713 processor.login(EMAIL, PASSWORD, TOKEN_NAME)
714 self.mocker.result(TOKEN)
715 processor.is_validated(TOKEN)
716 self.mocker.result(False)
717 self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
718 self.mocker.replay()
719
720 def verify(app_name, email):
721 """The actual test."""
722 self.assertEqual(app_name, APP_NAME)
723 self.assertEqual(email, EMAIL)
724 self.assertFalse(self.keyring_was_set, "Keyring should not be set")
725 d.callback("Ok")
726
727 client = SSOLogin(self.mockbusname,
728 sso_login_processor_class=self.mockprocessorclass)
729 self.patch(client, "LoggedIn", d.errback)
730 self.patch(client, "LoginError", d.errback)
731 self.patch(client, "UserNotValidated", verify)
732 client.login(APP_NAME, EMAIL, PASSWORD)
733 return d
734
735 def test_login_error(self):
736 """Test that the login method fails as expected."""
737 d = Deferred()
738 self.create_mock_processor()
739 self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking)
740>>>>>>> MERGE-SOURCE
268741
269 def fake_gtn(*args):742 def fake_gtn(*args):
270 """A fake get_token_name that fails."""743 """A fake get_token_name that fails."""
@@ -284,6 +757,7 @@
284 sso_login_processor_class=self.mockprocessorclass)757 sso_login_processor_class=self.mockprocessorclass)
285 self.patch(client, "LoggedIn", d.errback)758 self.patch(client, "LoggedIn", d.errback)
286 self.patch(client, "LoginError", verify)759 self.patch(client, "LoginError", verify)
760<<<<<<< TREE
287 self.patch(client, "UserNotValidated", d.errback)761 self.patch(client, "UserNotValidated", d.errback)
288 client.login(APP_NAME, EMAIL, PASSWORD)762 client.login(APP_NAME, EMAIL, PASSWORD)
289 return d763 return d
@@ -319,6 +793,9 @@
319 self.patch(client, "LoggedIn", fail)793 self.patch(client, "LoggedIn", fail)
320 self.patch(client, "LoginError", verify)794 self.patch(client, "LoginError", verify)
321 self.patch(client, "UserNotValidated", fail)795 self.patch(client, "UserNotValidated", fail)
796=======
797 self.patch(client, "UserNotValidated", d.errback)
798>>>>>>> MERGE-SOURCE
322 client.login(APP_NAME, EMAIL, PASSWORD)799 client.login(APP_NAME, EMAIL, PASSWORD)
323 return d800 return d
324801
@@ -348,8 +825,13 @@
348 def test_validate_email_error(self):825 def test_validate_email_error(self):
349 """Test that the validate_email method fails as expected."""826 """Test that the validate_email method fails as expected."""
350 d = Deferred()827 d = Deferred()
828<<<<<<< TREE
351 self.create_mock_processor()829 self.create_mock_processor()
352 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)830 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
831=======
832 self.create_mock_processor()
833 self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking)
834>>>>>>> MERGE-SOURCE
353835
354 def fake_gtn(*args):836 def fake_gtn(*args):
355 """A fake get_token_name that fails."""837 """A fake get_token_name that fails."""
@@ -559,6 +1041,58 @@
559 self.assertEqual(result["errtype"], e.__class__.__name__)1041 self.assertEqual(result["errtype"], e.__class__.__name__)
5601042
5611043
1044<<<<<<< TREE
1045=======
1046class KeyringCredentialsTestCase(TestCase, MockerTestCase):
1047 """Check the functions that access the keyring."""
1048
1049 # Invalid name (should match ([a-z_][a-z0-9_]*|[A-Z_][A-Z0-9_]*)$)
1050 # pylint: disable=C0103
1051
1052 def test_keyring_store_cred(self):
1053 """Verify the method that stores credentials."""
1054 idle_add = lambda f, *args, **kwargs: f(*args, **kwargs)
1055 self.patch(gobject, "idle_add", idle_add)
1056 token_value = TOKEN
1057 mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
1058 mockKeyringClass(APP_NAME)
1059 mockKeyring = self.mocker.mock()
1060 callback = self.mocker.mock()
1061 self.mocker.result(mockKeyring)
1062 mockKeyring.set_ubuntusso_attr(token_value)
1063 callback(1, 2, 3)
1064 self.mocker.replay()
1065
1066 keyring_store_credentials(APP_NAME, token_value, callback, 1, 2, 3)
1067
1068 def test_keyring_get_cred(self):
1069 """The method returns the right token."""
1070 mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
1071 mockKeyringClass(APP_NAME)
1072 mockKeyring = self.mocker.mock()
1073 self.mocker.result(mockKeyring)
1074 mockKeyring.get_ubuntusso_attr()
1075 self.mocker.result(TOKEN)
1076 self.mocker.replay()
1077
1078 token = keyring_get_credentials(APP_NAME)
1079 self.assertEqual(token, TOKEN)
1080
1081 def test_keyring_get_cred_not_found(self):
1082 """The method returns None when the token is not found."""
1083 mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
1084 mockKeyringClass(APP_NAME)
1085 mockKeyring = self.mocker.mock()
1086 self.mocker.result(mockKeyring)
1087 mockKeyring.get_ubuntusso_attr()
1088 self.mocker.result(None)
1089 self.mocker.replay()
1090
1091 token = keyring_get_credentials(APP_NAME)
1092 self.assertEqual(token, None)
1093
1094
1095>>>>>>> MERGE-SOURCE
562class RegisterSampleException(Exception):1096class RegisterSampleException(Exception):
563 """A mock exception thrown just when testing."""1097 """A mock exception thrown just when testing."""
5641098
@@ -677,6 +1211,24 @@
677 client = SSOCredentials(self.mocker.mock())1211 client = SSOCredentials(self.mocker.mock())
678 client.clear_token(APP_NAME)1212 client.clear_token(APP_NAME)
6791213
1214 def test_credentials_are_not_stored_if_ping_failed(self):
1215 """Credentials are not stored if the ping fails."""
1216
1217 def fail(*args, **kwargs):
1218 """Raise an exception."""
1219 self.args = AssertionError((args, kwargs))
1220 # pylint: disable=E0702
1221 raise self.args
1222
1223 self.patch(self.client, '_ping_url', fail)
1224 self._patch('clear_token')
1225
1226 self.client._login_success_cb(None, APP_NAME, EMAIL)
1227
1228 self.assertEqual(len(self.calls), 1)
1229 self.assertEqual(self.calls[0][0], 'clear_token')
1230 self.assertEqual(self.calls[0][1][0], APP_NAME)
1231
6801232
681class EnvironOverridesTestCase(TestCase):1233class EnvironOverridesTestCase(TestCase):
682 """Some URLs can be set from the environment for testing/QA purposes."""1234 """Some URLs can be set from the environment for testing/QA purposes."""

Subscribers

People subscribed via source and target branches