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

Proposed by Natalia Bidart on 2011-03-29
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 2011-03-29 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 on 2011-03-29

Making UI using the new register_with_name backend metod.

645. By Natalia Bidart on 2011-03-29

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 on 2011-03-24

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

643. By Natalia Bidart on 2010-12-16

[release] v1.0.8

642. By Natalia Bidart on 2010-12-16

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

641. By Natalia Bidart on 2010-11-04

[release] 1.0.7

640. By Alejandro J. Cura on 2010-11-04

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

639. By Natalia Bidart on 2010-11-03

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

638. By Natalia Bidart on 2010-11-03

[release] 1.0.6.

637. By Natalia Bidart on 2010-11-02

* 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
1=== modified file 'bin/ubuntu-sso-login'
2--- bin/ubuntu-sso-login 2011-01-11 19:13:19 +0000
3+++ bin/ubuntu-sso-login 2011-03-29 17:35:28 +0000
4@@ -21,6 +21,7 @@
5
6 """Run the dbus service for UserManagement and ApplicationCredentials."""
7
8+<<<<<<< TREE
9 # Invalid name "ubuntu-sso-login", pylint: disable=C0103
10
11 # import decimal even if we don't need it, pylint: disable=W0611
12@@ -41,6 +42,26 @@
13 # val = globals()[globalname]
14 # KeyError: 'ROUND_CEiLiNG'
15
16+=======
17+# import decimal even if we don't need it.
18+import decimal
19+# This is a workaround for LP: #467397. Some module in our depency chain sets
20+# the locale and imports decimal, and that generates the following trace:
21+# Traceback (most recent call last):
22+# File "/usr/lib/ubuntu-sso-client/ubuntu-sso-login", line 33
23+# from ubuntu_sso.main import SSOLogin, SSOCredentials
24+# File "/usr/lib/pymodules/python2.6/ubuntu_sso/main.py", line 42
25+# from lazr.restfulclient.resource import ServiceRoot
26+# File "/usr/lib/python2.6/dist-packages/lazr/restfulclient/resource.py",
27+# line 34
28+# import simplejson
29+# File "/usr/lib/pymodules/python2.6/simplejson/__init__.py", line 109
30+# from decimal import Decimal
31+# File "/usr/lib/python2.6/decimal.py", line 3649, in <module>
32+# val = globals()[globalname]
33+# KeyError: 'ROUND_CEiLiNG'
34+
35+>>>>>>> MERGE-SOURCE
36 import signal
37 import sys
38
39@@ -67,6 +88,7 @@
40
41
42 def sighup_handler(*a, **kw):
43+<<<<<<< TREE
44 """Stop the service."""
45 # This handler may be called in any thread, so is not thread safe.
46 # See the link below for info:
47@@ -74,6 +96,15 @@
48 #
49 # gtk.main_quit and the logger methods are safe to be called from any
50 # thread. Just don't call other random stuff here.
51+=======
52+ """Stop the service."""
53+ # This handler may be called in any thread, so is not thread safe.
54+ # See the link below for info:
55+ # www.listware.net/201004/gtk-devel-list/115067-unix-signals-in-glib.html
56+ #
57+ # gtk.main_quit and the logger methods are safe to be called from any thread.
58+ # Just don't call other random stuff here.
59+>>>>>>> MERGE-SOURCE
60 logger.info("Stoping Ubuntu SSO login manager since SIGHUP was received.")
61 gtk.main_quit()
62
63
64=== modified file 'data/ui.glade'
65--- data/ui.glade 2010-11-29 16:04:26 +0000
66+++ data/ui.glade 2011-03-29 17:35:28 +0000
67@@ -279,16 +279,42 @@
68 </object>
69 <packing>
70 <property name="expand">False</property>
71+<<<<<<< TREE
72 <property name="position">6</property>
73 </packing>
74 </child>
75 <child>
76 <object class="GtkHBox" id="hbox2">
77- <property name="visible">True</property>
78+=======
79+ <property name="position">10</property>
80+ </packing>
81+ </child>
82+ <child>
83+ <object class="GtkLabel" id="tc_warning_label">
84+ <property name="visible">True</property>
85+ <property name="wrap">True</property>
86+ </object>
87+ <packing>
88+ <property name="position">11</property>
89+ </packing>
90+ </child>
91+ <child>
92+ <object class="GtkHBox" id="hbox2">
93+>>>>>>> MERGE-SOURCE
94+ <property name="visible">True</property>
95+<<<<<<< TREE
96 <property name="spacing">5</property>
97+=======
98+>>>>>>> MERGE-SOURCE
99 <child>
100+<<<<<<< TREE
101 <object class="GtkHButtonBox" id="hbuttonbox9">
102+=======
103+ <object class="GtkLinkButton" id="login_button">
104+ <property name="label">login button</property>
105+>>>>>>> MERGE-SOURCE
106 <property name="visible">True</property>
107+<<<<<<< TREE
108 <property name="layout_style">start</property>
109 <child>
110 <object class="GtkLinkButton" id="login_button">
111@@ -305,15 +331,26 @@
112 <property name="position">0</property>
113 </packing>
114 </child>
115+=======
116+ <property name="can_focus">True</property>
117+ <property name="receives_default">True</property>
118+ <property name="relief">none</property>
119+ <signal name="clicked" handler="on_sign_in_button_clicked" swapped="no"/>
120+>>>>>>> MERGE-SOURCE
121 </object>
122 <packing>
123 <property name="expand">False</property>
124+<<<<<<< TREE
125+=======
126+ <property name="fill">True</property>
127+>>>>>>> MERGE-SOURCE
128 <property name="position">0</property>
129 </packing>
130 </child>
131 <child>
132 <object class="GtkHButtonBox" id="hbuttonbox1">
133 <property name="visible">True</property>
134+<<<<<<< TREE
135 <property name="spacing">5</property>
136 <property name="layout_style">end</property>
137 <child>
138@@ -345,6 +382,40 @@
139 <property name="position">1</property>
140 </packing>
141 </child>
142+=======
143+ <property name="can_focus">False</property>
144+ <property name="spacing">5</property>
145+ <property name="layout_style">end</property>
146+ <child>
147+ <object class="GtkButton" id="join_cancel_button">
148+ <property name="label">gtk-cancel</property>
149+ <property name="visible">True</property>
150+ <property name="can_focus">True</property>
151+ <property name="receives_default">True</property>
152+ <property name="use_stock">True</property>
153+ </object>
154+ <packing>
155+ <property name="expand">False</property>
156+ <property name="fill">False</property>
157+ <property name="position">0</property>
158+ </packing>
159+ </child>
160+ <child>
161+ <object class="GtkButton" id="join_ok_button">
162+ <property name="label">gtk-go-forward</property>
163+ <property name="visible">True</property>
164+ <property name="can_focus">True</property>
165+ <property name="receives_default">True</property>
166+ <property name="use_stock">True</property>
167+ <signal name="clicked" handler="on_join_ok_button_clicked" swapped="no"/>
168+ </object>
169+ <packing>
170+ <property name="expand">False</property>
171+ <property name="fill">False</property>
172+ <property name="position">1</property>
173+ </packing>
174+ </child>
175+>>>>>>> MERGE-SOURCE
176 </object>
177 <packing>
178 <property name="expand">False</property>
179@@ -355,10 +426,16 @@
180 </object>
181 <packing>
182 <property name="expand">False</property>
183+<<<<<<< TREE
184 <property name="pack_type">end</property>
185 <property name="position">7</property>
186 </packing>
187 </child>
188+=======
189+ <property name="position">12</property>
190+ </packing>
191+ </child>
192+>>>>>>> MERGE-SOURCE
193 </object>
194 <object class="GtkVBox" id="processing_vbox">
195 <property name="visible">True</property>
196
197=== modified file 'run-tests'
198--- run-tests 2011-03-18 09:30:20 +0000
199+++ run-tests 2011-03-29 17:35:28 +0000
200@@ -15,9 +15,16 @@
201 # You should have received a copy of the GNU General Public License along
202 # with this program. If not, see <http://www.gnu.org/licenses/>.
203
204+<<<<<<< TREE
205 if [ $# -ne 0 ]; then
206 # an extra argument was given
207 MODULE="$@"
208+=======
209+`which xvfb-run` ./contrib/test "$@"
210+pylint ubuntu_sso
211+if [ -x `which pep8` ]; then
212+ pep8 --repeat bin/ contrib/ ubuntu_sso/
213+>>>>>>> MERGE-SOURCE
214 else
215 # run all tests, useful for tarmac and reviews
216 MODULE="ubuntu_sso"
217
218=== modified file 'setup.py'
219--- setup.py 2011-03-22 22:32:28 +0000
220+++ setup.py 2011-03-29 17:35:28 +0000
221@@ -86,7 +86,11 @@
222
223 DistUtilsExtra.auto.setup(
224 name='ubuntu-sso-client',
225+<<<<<<< TREE
226 version='1.1.12',
227+=======
228+ version='1.0.8',
229+>>>>>>> MERGE-SOURCE
230 license='GPL v3',
231 author='Natalia Bidart',
232 author_email='natalia.bidart@canonical.com',
233
234=== modified file 'ubuntu_sso/gtk/gui.py'
235--- ubuntu_sso/gtk/gui.py 2011-03-16 01:36:07 +0000
236+++ ubuntu_sso/gtk/gui.py 2011-03-29 17:35:28 +0000
237@@ -30,7 +30,13 @@
238
239 import dbus
240 import gettext
241+<<<<<<< TREE
242 import gtk
243+=======
244+import gobject
245+import gtk # pylint: disable=W0403
246+import webkit
247+>>>>>>> MERGE-SOURCE
248 import xdg
249
250 from dbus.mainloop.glib import DBusGMainLoop
251@@ -125,7 +131,7 @@
252 self.is_password = is_password
253 self.warning = None
254
255- super(LabeledEntry, self).__init__(*args, **kwargs)
256+ gtk.Entry.__init__(self, *args, **kwargs)
257
258 self.set_width_chars(DEFAULT_WIDTH)
259 self._set_label(self, None)
260@@ -161,7 +167,7 @@
261
262 def get_text(self):
263 """Get text only if it's not the label nor empty."""
264- result = super(LabeledEntry, self).get_text()
265+ result = gtk.Entry.get_text(self)
266 if result == self.label or result.isspace():
267 result = ''
268 return result
269@@ -255,6 +261,7 @@
270 self.app_label = '<b>%s</b>' % self.app_name
271 self.tc_url = tc_url
272 self.help_text = help_text
273+<<<<<<< TREE
274 self.login_only = login_only
275
276 self.close_callback = NO_OP
277@@ -264,6 +271,11 @@
278
279 self.user_email = None
280 self.user_password = None
281+=======
282+ self.close_callback = close_callback
283+ self.user_email = None
284+ self.user_password = None
285+>>>>>>> MERGE-SOURCE
286
287 ui_filename = get_data_file('ui.glade')
288 builder = gtk.Builder()
289@@ -325,7 +337,35 @@
290 self.request_password_token_vbox,
291 self.set_new_password_vbox)
292
293+<<<<<<< TREE
294 self._append_pages()
295+=======
296+ self._append_page(self._build_processing_page())
297+ self._append_page(self._build_success_page())
298+ self._append_page(self._build_login_page())
299+ self._append_page(self._build_request_password_token_page())
300+ self._append_page(self._build_set_new_password_page())
301+ self._append_page(self._build_verify_email_page())
302+
303+ window_size = None
304+ if not login_only:
305+ window_size = (550, 500)
306+ self._append_page(self._build_enter_details_page())
307+ self._append_page(self._build_tc_page())
308+ self.login_button.grab_focus()
309+ self._set_current_page(self.enter_details_vbox)
310+ else:
311+ window_size = (400, 350)
312+ self.login_back_button.hide()
313+ self.login_ok_button.grab_focus()
314+ self.login_vbox.help_text = help_text
315+ self._set_current_page(self.login_vbox)
316+
317+ self.window.set_size_request(*window_size)
318+ size_req = (int(window_size[0] * 0.9), -1)
319+ for label in self.labels:
320+ label.set_size_request(*size_req)
321+>>>>>>> MERGE-SOURCE
322
323 self._signals = {
324 'CaptchaGenerated':
325@@ -750,12 +790,27 @@
326 if self.window is not None:
327 self.window.hide()
328
329+<<<<<<< TREE
330 # process any pending events before callbacking with result
331 while gtk.events_pending():
332 gtk.main_iteration()
333
334 if not self._done:
335 self.user_cancellation_callback(self.app_name)
336+=======
337+ # process any pending events before emitting signals
338+ while gtk.events_pending():
339+ gtk.main_iteration()
340+
341+ if len(args) > 0 and args[0] in self.cancels:
342+ self.window.emit(SIG_USER_CANCELATION, self.app_name)
343+ elif len(self._gtk_signal_log) > 0:
344+ signal = self._gtk_signal_log[-1][0]
345+ args = self._gtk_signal_log[-1][1:]
346+ self.window.emit(signal, *args)
347+ else:
348+ self.window.emit(SIG_USER_CANCELATION, self.app_name)
349+>>>>>>> MERGE-SOURCE
350
351 # call user defined callback
352 logger.info('Calling custom close_callback %r with params %r, %r',
353@@ -816,12 +871,21 @@
354 self.user_email = email1
355 self.user_password = password1
356
357+<<<<<<< TREE
358 logger.info('Calling register_user with email %r, password <hidden>,' \
359 ' name %r, captcha_id %r and captcha_solution %r.', email1,
360 name, self._captcha_id, captcha_solution)
361 f = self.backend.register_user
362 f(self.app_name, email1, password1, name,
363 self._captcha_id, captcha_solution,
364+=======
365+ logger.info('Calling register_with_name with email %r, password, '
366+ '<hidden> name %r, captcha_id %r and captcha_solution %r.',
367+ email1, name, self._captcha_id, captcha_solution)
368+ f = self.backend.register_with_name
369+ f(self.app_name, email1, password1, name,
370+ self._captcha_id, captcha_solution,
371+>>>>>>> MERGE-SOURCE
372 reply_handler=NO_OP, error_handler=NO_OP)
373
374 def on_verify_token_button_clicked(self, *args, **kwargs):
375@@ -1122,6 +1186,11 @@
376 self.on_user_registered(app_name, email)
377
378 @log_call
379+ def on_user_not_validated(self, app_name, email, *args, **kwargs):
380+ """User was not validated."""
381+ self.on_user_registered(app_name, email)
382+
383+ @log_call
384 def on_password_reset_token_sent(self, app_name, email, *args, **kwargs):
385 """Password reset token was successfully sent."""
386 msg = self.SET_NEW_PASSWORD_LABEL % {'email': email}
387
388=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
389--- ubuntu_sso/gtk/tests/test_gui.py 2011-03-16 01:36:07 +0000
390+++ ubuntu_sso/gtk/tests/test_gui.py 2011-03-29 17:35:28 +0000
391@@ -51,7 +51,7 @@
392 self._args = args
393 self._kwargs = kwargs
394 self._called = {}
395- for i in ('generate_captcha', 'login', 'register_user',
396+ for i in ('generate_captcha', 'login', 'register_with_name',
397 'validate_email', 'request_password_reset_token',
398 'set_new_password'):
399 setattr(self, i, self._record_call(i))
400@@ -709,8 +709,8 @@
401 """Clicking 'join_ok_button' sends info to backend using 'register'."""
402 self.click_join_with_valid_data()
403
404- # assert register_user was called
405- expected = 'register_user'
406+ # assert register_with_name was called
407+ expected = 'register_with_name'
408 self.assertIn(expected, self.ui.backend._called)
409 self.assertEqual(self.ui.backend._called[expected],
410 ((APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
411@@ -866,6 +866,7 @@
412 self.ui.join_ok_button.clicked()
413 self.assertTrue(self._called)
414
415+<<<<<<< TREE
416 def test_user_and_pass_are_cached(self):
417 """Username and password are temporarly cached for further use."""
418 self.click_join_with_valid_data()
419@@ -880,6 +881,14 @@
420 self.ui.CAPTCHA_LOAD_ERROR)
421 self.assertEqual(self._called, ((), {}))
422
423+=======
424+ def test_user_and_pass_are_cached(self):
425+ """Username and password are temporarly cached for further use."""
426+ self.click_join_with_valid_data()
427+ self.assertEqual(self.ui.user_email, EMAIL)
428+ self.assertEqual(self.ui.user_password, PASSWORD)
429+
430+>>>>>>> MERGE-SOURCE
431
432 class NoTermsAndConditionsTestCase(UbuntuSSOClientTestCase):
433 """Test suite for the user registration (with no t&c link)."""
434@@ -1282,20 +1291,37 @@
435 self.assert_warnings_visibility()
436
437
438-class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):
439- """Test suite for the user login (verify email page)."""
440-
441- kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
442- login_only=True)
443-
444-
445-class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):
446- """Test suite for the user login validation (verify email page)."""
447-
448- kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
449- login_only=True)
450-
451-
452+<<<<<<< TREE
453+class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):
454+ """Test suite for the user login (verify email page)."""
455+
456+ kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
457+ login_only=True)
458+
459+
460+class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):
461+ """Test suite for the user login validation (verify email page)."""
462+
463+ kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
464+ login_only=True)
465+
466+
467+=======
468+class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):
469+ """Test suite for the user login (verify email page)."""
470+
471+ kwargs = dict(app_name=APP_NAME, tc_uri=TC_URI, help_text=HELP_TEXT,
472+ login_only=True)
473+
474+
475+class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):
476+ """Test suite for the user login validation (verify email page)."""
477+
478+ kwargs = dict(app_name=APP_NAME, tc_uri=TC_URI, help_text=HELP_TEXT,
479+ login_only=True)
480+
481+
482+>>>>>>> MERGE-SOURCE
483 class RegistrationValidationTestCase(UbuntuSSOClientTestCase):
484 """Test suite for the user registration validations."""
485
486@@ -1312,8 +1338,13 @@
487
488 self.assert_correct_entry_warning(self.ui.name_entry,
489 self.ui.FIELD_REQUIRED)
490+<<<<<<< TREE
491 self.assertNotIn('register_user', self.ui.backend._called)
492
493+=======
494+ self.assertNotIn('register_with_name', self.ui.backend._called)
495+
496+>>>>>>> MERGE-SOURCE
497 def test_warning_is_shown_if_empty_email(self):
498 """A warning message is shown if emails are empty."""
499 self.ui.email1_entry.set_text('')
500@@ -1325,7 +1356,7 @@
501 self.ui.FIELD_REQUIRED)
502 self.assert_correct_entry_warning(self.ui.email2_entry,
503 self.ui.FIELD_REQUIRED)
504- self.assertNotIn('register_user', self.ui.backend._called)
505+ self.assertNotIn('register_with_name', self.ui.backend._called)
506
507 def test_warning_is_shown_if_email_mismatch(self):
508 """A warning message is shown if emails doesn't match."""
509@@ -1338,7 +1369,7 @@
510 self.ui.EMAIL_MISMATCH)
511 self.assert_correct_entry_warning(self.ui.email2_entry,
512 self.ui.EMAIL_MISMATCH)
513- self.assertNotIn('register_user', self.ui.backend._called)
514+ self.assertNotIn('register_with_name', self.ui.backend._called)
515
516 def test_warning_is_shown_if_invalid_email(self):
517 """A warning message is shown if email is invalid."""
518@@ -1351,7 +1382,7 @@
519 self.ui.EMAIL_INVALID)
520 self.assert_correct_entry_warning(self.ui.email2_entry,
521 self.ui.EMAIL_INVALID)
522- self.assertNotIn('register_user', self.ui.backend._called)
523+ self.assertNotIn('register_with_name', self.ui.backend._called)
524
525 def test_password_help_is_always_shown(self):
526 """Password help text is correctly displayed."""
527@@ -1359,7 +1390,7 @@
528 'password help text is visible.')
529 self.assertEqual(self.ui.password_help_label.get_text(),
530 self.ui.PASSWORD_HELP)
531- self.assertNotIn('register_user', self.ui.backend._called)
532+ self.assertNotIn('register_with_name', self.ui.backend._called)
533
534 def test_warning_is_shown_if_password_mismatch(self):
535 """A warning message is shown if password doesn't match."""
536@@ -1372,7 +1403,7 @@
537 self.ui.PASSWORD_MISMATCH)
538 self.assert_correct_entry_warning(self.ui.password2_entry,
539 self.ui.PASSWORD_MISMATCH)
540- self.assertNotIn('register_user', self.ui.backend._called)
541+ self.assertNotIn('register_with_name', self.ui.backend._called)
542
543 def test_warning_is_shown_if_password_too_weak(self):
544 """A warning message is shown if password is too weak."""
545@@ -1387,7 +1418,7 @@
546 self.ui.PASSWORD_TOO_WEAK)
547 self.assert_correct_entry_warning(self.ui.password2_entry,
548 self.ui.PASSWORD_TOO_WEAK)
549- self.assertNotIn('register_user', self.ui.backend._called)
550+ self.assertNotIn('register_with_name', self.ui.backend._called)
551
552 def test_warning_is_shown_if_tc_not_accepted(self):
553 """A warning message is shown if TC are not accepted."""
554@@ -1398,7 +1429,7 @@
555
556 self.assert_correct_label_warning(self.ui.tc_warning_label,
557 self.ui.TC_NOT_ACCEPTED)
558- self.assertNotIn('register_user', self.ui.backend._called)
559+ self.assertNotIn('register_with_name', self.ui.backend._called)
560
561 def test_warning_is_shown_if_not_captcha_solution(self):
562 """A warning message is shown if TC are not accepted."""
563@@ -1409,7 +1440,7 @@
564
565 self.assert_correct_entry_warning(self.ui.captcha_solution_entry,
566 self.ui.FIELD_REQUIRED)
567- self.assertNotIn('register_user', self.ui.backend._called)
568+ self.assertNotIn('register_with_name', self.ui.backend._called)
569
570 def test_no_warning_messages_if_valid_data(self):
571 """No warning messages are shown if the data is valid."""
572@@ -1548,6 +1579,7 @@
573 self.ui.login_ok_button.clicked()
574 self.assertTrue(self._called)
575
576+<<<<<<< TREE
577 def test_user_and_pass_are_cached(self):
578 """Username and password are temporarly cached for further use."""
579 self.click_connect_with_valid_data()
580@@ -1580,6 +1612,14 @@
581
582 self.assertEqual(self._called, ((), {}))
583
584+=======
585+ def test_user_and_pass_are_cached(self):
586+ """Username and password are temporarly cached for further use."""
587+ self.click_connect_with_valid_data()
588+ self.assertEqual(self.ui.user_email, EMAIL)
589+ self.assertEqual(self.ui.user_password, PASSWORD)
590+
591+>>>>>>> MERGE-SOURCE
592
593 class LoginValidationTestCase(UbuntuSSOClientTestCase):
594 """Test suite for the user login validation."""
595
596=== modified file 'ubuntu_sso/keyring/linux.py'
597--- ubuntu_sso/keyring/linux.py 2011-03-03 15:19:29 +0000
598+++ ubuntu_sso/keyring/linux.py 2011-03-29 17:35:28 +0000
599@@ -29,6 +29,7 @@
600 from twisted.internet.defer import inlineCallbacks, returnValue
601
602 from ubuntu_sso.logger import setup_logging
603+<<<<<<< TREE
604 from ubuntu_sso.utils.txsecrets import SecretService
605 from ubuntu_sso.keyring import (
606 get_token_name,
607@@ -38,17 +39,58 @@
608
609
610 logger = setup_logging("ubuntu_sso.keyring")
611+=======
612+
613+
614+logger = setup_logging("ubuntu_sso.keyring")
615+TOKEN_SEPARATOR = ' @ '
616+SEPARATOR_REPLACEMENT = ' AT '
617+
618+U1_APP_NAME = "Ubuntu One"
619+U1_KEY_NAME = "UbuntuOne token for https://ubuntuone.com"
620+U1_KEY_ATTR = {
621+ "oauth-consumer-key": "ubuntuone",
622+ "ubuntuone-realm": "https://ubuntuone.com",
623+}
624+
625+
626+def get_old_token_name(app_name):
627+ """Build the token name (old style)."""
628+ quoted_app_name = urllib.quote(app_name)
629+ computer_name = socket.gethostname()
630+ quoted_computer_name = urllib.quote(computer_name)
631+ return "%s - %s" % (quoted_app_name, quoted_computer_name)
632+
633+
634+def get_token_name(app_name):
635+ """Build the token name."""
636+ computer_name = socket.gethostname()
637+ computer_name = computer_name.replace(TOKEN_SEPARATOR,
638+ SEPARATOR_REPLACEMENT)
639+ return TOKEN_SEPARATOR.join((app_name, computer_name)).encode('utf-8')
640+>>>>>>> MERGE-SOURCE
641
642
643 class Keyring(object):
644 """A Keyring for a given application name."""
645
646+<<<<<<< TREE
647 def __init__(self):
648 """Initialize this instance."""
649 self.service = SecretService()
650
651 @inlineCallbacks
652 def _find_keyring_item(self, app_name, attr=None):
653+=======
654+ def __init__(self, app_name):
655+ """Initialize this instance given the app_name."""
656+ if not gnomekeyring.is_available():
657+ raise gnomekeyring.NoKeyringDaemonError
658+ self.app_name = app_name
659+ self.token_name = get_token_name(self.app_name)
660+
661+ def _find_keyring_item(self, attr=None):
662+>>>>>>> MERGE-SOURCE
663 """Return the keyring item or None if not found."""
664 if attr is None:
665 logger.debug("getting attr")
666@@ -75,11 +117,19 @@
667 # Creates the secret from the credentials
668 secret = urllib.urlencode(cred)
669
670+<<<<<<< TREE
671 attr = self._get_keyring_attr(app_name)
672+=======
673+>>>>>>> MERGE-SOURCE
674 # Add our SSO credentials to the keyring
675+<<<<<<< TREE
676 yield self.service.open_session()
677 collection = yield self.service.get_default_collection()
678 yield collection.create_item(app_name, attr, secret, True)
679+=======
680+ gnomekeyring.item_create_sync(None, gnomekeyring.ITEM_GENERIC_SECRET,
681+ self.app_name, self._get_keyring_attr(), secret, True)
682+>>>>>>> MERGE-SOURCE
683
684 @inlineCallbacks
685 def _migrate_old_token_name(self, app_name):
686
687=== modified file 'ubuntu_sso/keyring/tests/test_linux.py'
688--- ubuntu_sso/keyring/tests/test_linux.py 2011-03-18 09:30:20 +0000
689+++ ubuntu_sso/keyring/tests/test_linux.py 2011-03-29 17:35:28 +0000
690@@ -65,6 +65,7 @@
691 return True
692
693
694+<<<<<<< TREE
695 class MockCollection(object):
696 """A collection of items containing secrets."""
697
698@@ -114,6 +115,44 @@
699 if len(self.collections) == 0:
700 self.create_collection("default")
701 return defer.succeed(self.collections["default"])
702+=======
703+class MockGnomeKeyring(object):
704+ """A mock keyring that stores keys according to a given attr."""
705+
706+ def __init__(self):
707+ """Initialize this instance."""
708+ self.id_counter = itertools.count()
709+ self.store = {}
710+ self.deleted = []
711+
712+ def _get_next_id(self):
713+ """Return the next keyring id."""
714+ return self.id_counter.next()
715+
716+ def item_create_sync(self, keyring_name, item_type, key_name, attr,
717+ secret, update_if_exists):
718+ """Add a key to a keyring."""
719+ new_id = self._get_next_id()
720+ i = MockKeyringItem(new_id, keyring_name, attr, secret)
721+ self.store[new_id] = i
722+
723+ def item_delete_sync(self, keyring_name, item_id):
724+ """Delete a key from a keyring, and keep a list of deleted keys."""
725+ item = self.store.pop(item_id)
726+ assert keyring_name == item.keyring
727+ self.deleted.append(item)
728+
729+ def find_items_sync(self, item_type, attr):
730+ """Find all keys that match the given attributes."""
731+ items = [i for i in self.store.values() if i.matches(attr)]
732+ if len(items) == 0:
733+ raise gnomekeyring.NoMatchError()
734+ return items
735+
736+ def is_available(self):
737+ """A very available keyring."""
738+ return True
739+>>>>>>> MERGE-SOURCE
740
741
742 class TestTokenNameBuilder(TestCase):
743@@ -160,9 +199,17 @@
744
745 def setUp(self):
746 """Initialize the mock used in these tests."""
747+<<<<<<< TREE
748 self.mock_service = None
749 self.service = self.patch(keyring, "SecretService",
750 self.get_mock_service)
751+=======
752+ self.mgk = MockGnomeKeyring()
753+ self.patch(gnomekeyring, "item_create_sync", self.mgk.item_create_sync)
754+ self.patch(gnomekeyring, "is_available", self.mgk.is_available)
755+ self.patch(gnomekeyring, "find_items_sync", self.mgk.find_items_sync)
756+ self.patch(gnomekeyring, "item_delete_sync", self.mgk.item_delete_sync)
757+>>>>>>> MERGE-SOURCE
758 fake_gethostname = build_fake_gethostname("darkstar")
759 self.patch(socket, "gethostname", fake_gethostname)
760
761@@ -177,6 +224,7 @@
762 """Test that the set method does not erase previous keys."""
763 sample_creds = {"name": "sample creds name"}
764 sample_creds2 = {"name": "sample creds name 2"}
765+<<<<<<< TREE
766 kr = keyring.Keyring()
767 yield kr.set_credentials("appname", sample_creds)
768 yield kr.set_credentials("appname", sample_creds2)
769@@ -186,6 +234,15 @@
770
771 @inlineCallbacks
772 def test_delete_credentials(self):
773+=======
774+ keyring.Keyring("appname").set_ubuntusso_attr(sample_creds)
775+ keyring.Keyring("appname").set_ubuntusso_attr(sample_creds2)
776+
777+ self.assertEqual(len(self.mgk.store), 2)
778+ self.assertEqual(len(self.mgk.deleted), 0)
779+
780+ def test_delete_ubuntusso(self):
781+>>>>>>> MERGE-SOURCE
782 """Test that a given key is deleted."""
783 sample_creds = {"name": "sample creds name"}
784 kr = keyring.Keyring()
785@@ -225,11 +282,22 @@
786 "oauth_token": sample_oauth_token,
787 "oauth_token_secret": sample_oauth_secret,
788 }
789+<<<<<<< TREE
790 u1kr = common_keyring.UbuntuOneOAuthKeyring()
791 yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)
792
793 kr = keyring.Keyring()
794 result = yield kr.get_credentials(keyring.U1_APP_NAME)
795+=======
796+ secret = urllib.urlencode(old_creds)
797+ self.mgk.item_create_sync(None, None,
798+ keyring.U1_APP_NAME,
799+ keyring.U1_KEY_ATTR,
800+ secret, True)
801+
802+ result = keyring.Keyring(keyring.U1_APP_NAME).get_ubuntusso_attr()
803+
804+>>>>>>> MERGE-SOURCE
805 self.assertIn("token", result)
806 self.assertEqual(result["token"], sample_oauth_token)
807 self.assertIn("token_secret", result)
808@@ -244,8 +312,16 @@
809 "oauth_token": sample_oauth_token,
810 "oauth_token_secret": sample_oauth_secret,
811 }
812+<<<<<<< TREE
813 u1kr = common_keyring.UbuntuOneOAuthKeyring()
814 yield u1kr.set_credentials(keyring.U1_APP_NAME, old_creds)
815+=======
816+ secret = urllib.urlencode(old_creds)
817+ self.mgk.item_create_sync(None, None,
818+ keyring.U1_APP_NAME,
819+ keyring.U1_KEY_ATTR,
820+ secret, True)
821+>>>>>>> MERGE-SOURCE
822
823 kr = keyring.Keyring()
824 result = yield kr.get_credentials("Software Center")
825
826=== modified file 'ubuntu_sso/main/linux.py'
827--- ubuntu_sso/main/linux.py 2011-03-29 14:16:36 +0000
828+++ ubuntu_sso/main/linux.py 2011-03-29 17:35:28 +0000
829@@ -45,6 +45,279 @@
830
831
832 logger = setup_logging("ubuntu_sso.main")
833+<<<<<<< TREE
834+=======
835+PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
836+SERVICE_URL = "https://login.ubuntu.com/api/1.0"
837+NO_OP = lambda *args, **kwargs: None
838+
839+
840+class NoDefaultConfigError(Exception):
841+ """No default section in configuration file"""
842+
843+
844+class BadRealmError(Exception):
845+ """Realm must be a URL."""
846+
847+
848+class InvalidEmailError(Exception):
849+ """The email is not valid."""
850+
851+
852+class InvalidPasswordError(Exception):
853+ """The password is not valid.
854+
855+ Must provide at least 8 characters, one upper case, one number.
856+ """
857+
858+
859+class RegistrationError(Exception):
860+ """The registration failed."""
861+
862+
863+class AuthenticationError(Exception):
864+ """The authentication failed."""
865+
866+
867+class EmailTokenError(Exception):
868+ """The email token is not valid."""
869+
870+
871+class ResetPasswordTokenError(Exception):
872+ """The token for password reset could not be generated."""
873+
874+
875+class NewPasswordError(Exception):
876+ """The new password could not be set."""
877+
878+
879+def keyring_store_credentials(app_name, credentials, callback, *cb_args):
880+ """Store the credentials in the keyring."""
881+
882+ def _inner():
883+ """Store the credentials, and trigger the callback."""
884+ logger.info('keyring_store_credentials: app_name "%s".', app_name)
885+ Keyring(app_name).set_ubuntusso_attr(credentials)
886+ callback(*cb_args)
887+
888+ gobject.idle_add(_inner)
889+
890+
891+def keyring_get_credentials(app_name):
892+ """Get the credentials from the keyring or None if not there."""
893+ creds = Keyring(app_name).get_ubuntusso_attr()
894+ logger.info('keyring_get_credentials: app_name "%s", resulting credentials'
895+ ' is not None? %r', app_name, creds is not None)
896+ return creds
897+
898+
899+class SSOLoginProcessor(object):
900+ """Login and register users using the Ubuntu Single Sign On service."""
901+
902+ def __init__(self, sso_service_class=None):
903+ """Create a new SSO login processor."""
904+ if sso_service_class is None:
905+ self.sso_service_class = ServiceRoot
906+ else:
907+ self.sso_service_class = sso_service_class
908+
909+ self.service_url = os.environ.get('USSOC_SERVICE_URL', SERVICE_URL)
910+
911+ def _valid_email(self, email):
912+ """Validate the given email."""
913+ return email is not None and '@' in email
914+
915+ def _valid_password(self, password):
916+ """Validate the given password."""
917+ res = (len(password) > 7 and # at least 8 characters
918+ re.search('[A-Z]', password) and # one upper case
919+ re.search('\d+', password)) # one number
920+ return res
921+
922+ def _format_webservice_errors(self, errdict):
923+ """Turn each list of strings in the errdict into a LF separated str."""
924+ result = {}
925+ for k, v in errdict.iteritems():
926+ # workaround until bug #624955 is solved
927+ if isinstance(v, basestring):
928+ result[k] = v
929+ else:
930+ result[k] = "\n".join(v)
931+ return result
932+
933+ def generate_captcha(self, filename):
934+ """Generate a captcha using the SSO service."""
935+ logger.debug('generate_captcha: requesting captcha, filename: %r',
936+ filename)
937+ sso_service = self.sso_service_class(None, self.service_url)
938+ captcha = sso_service.captchas.new()
939+
940+ # download captcha and save to 'filename'
941+ logger.debug('generate_captcha: server answered: %r', captcha)
942+ try:
943+ res = urllib2.urlopen(captcha['image_url'])
944+ with open(filename, 'wb') as f:
945+ f.write(res.read())
946+ except:
947+ msg = 'generate_captcha crashed while downloading the image.'
948+ logger.exception(msg)
949+ raise
950+
951+ return captcha['captcha_id']
952+
953+ def register_with_name(self, email, password, displayname,
954+ captcha_id, captcha_solution):
955+ """Register a new user with 'email' and 'password'."""
956+ logger.debug('register_with_name: email: %r password: <hidden>, '
957+ 'displayname: %r, captcha_id: %r, captcha_solution: %r',
958+ email, displayname, captcha_id, captcha_solution)
959+ sso_service = self.sso_service_class(None, self.service_url)
960+ if not self._valid_email(email):
961+ logger.error('register_with_name: InvalidEmailError for email: %r',
962+ email)
963+ raise InvalidEmailError()
964+ if not self._valid_password(password):
965+ logger.error('register_with_name: InvalidPasswordError')
966+ raise InvalidPasswordError()
967+
968+ result = sso_service.registrations.register(
969+ email=email, password=password,
970+ displayname=displayname,
971+ captcha_id=captcha_id,
972+ captcha_solution=captcha_solution)
973+ logger.info('register_with_name: email: %r result: %r', email, result)
974+
975+ if result['status'] == 'error':
976+ errorsdict = self._format_webservice_errors(result['errors'])
977+ raise RegistrationError(errorsdict)
978+ elif result['status'] != 'ok':
979+ raise RegistrationError('Received unknown status: %s' % result)
980+ else:
981+ return email
982+
983+ def register_user(self, email, password,
984+ captcha_id, captcha_solution):
985+ """Register a new user with 'email' and 'password'."""
986+ logger.debug('register_user: email: %r password: <hidden>, '
987+ 'captcha_id: %r, captcha_solution: %r',
988+ email, captcha_id, captcha_solution)
989+ res = self.register_with_name(email, password, displayname='',
990+ captcha_id=captcha_id,
991+ captcha_solution=captcha_solution)
992+ return res
993+
994+ def login(self, email, password, token_name):
995+ """Login a user with 'email' and 'password'."""
996+ logger.debug('login: email: %r password: <hidden>, token_name: %r',
997+ email, token_name)
998+ basic = BasicHttpAuthorizer(email, password)
999+ sso_service = self.sso_service_class(basic, self.service_url)
1000+ service = sso_service.authentications.authenticate
1001+
1002+ try:
1003+ credentials = service(token_name=token_name)
1004+ except HTTPError:
1005+ logger.exception('login failed with:')
1006+ raise AuthenticationError()
1007+
1008+ logger.debug('login: authentication successful! consumer_key: %r, ' \
1009+ 'token_name: %r', credentials['consumer_key'], token_name)
1010+ return credentials
1011+
1012+ def is_validated(self, token, sso_service=None):
1013+ """Return if user with 'email' and 'password' is validated."""
1014+ logger.debug('is_validated: requesting accounts.me() info.')
1015+ if sso_service is None:
1016+ oauth_token = oauth.OAuthToken(token['token'],
1017+ token['token_secret'])
1018+ authorizer = OAuthAuthorizer(token['consumer_key'],
1019+ token['consumer_secret'],
1020+ oauth_token)
1021+ sso_service = self.sso_service_class(authorizer, self.service_url)
1022+
1023+ me_info = sso_service.accounts.me()
1024+ key = 'preferred_email'
1025+ result = key in me_info and me_info[key] != None
1026+
1027+ logger.info('is_validated: consumer_key: %r, result: %r.',
1028+ token['consumer_key'], result)
1029+ return result
1030+
1031+ def validate_email(self, email, password, email_token, token_name):
1032+ """Validate an email token for user with 'email' and 'password'."""
1033+ logger.debug('validate_email: email: %r password: <hidden>, '
1034+ 'email_token: %r, token_name: %r.',
1035+ email, email_token, token_name)
1036+ token = self.login(email=email, password=password,
1037+ token_name=token_name)
1038+
1039+ oauth_token = oauth.OAuthToken(token['token'], token['token_secret'])
1040+ authorizer = OAuthAuthorizer(token['consumer_key'],
1041+ token['consumer_secret'],
1042+ oauth_token)
1043+ sso_service = self.sso_service_class(authorizer, self.service_url)
1044+ result = sso_service.accounts.validate_email(email_token=email_token)
1045+ logger.info('validate_email: email: %r result: %r', email, result)
1046+ if 'errors' in result:
1047+ errorsdict = self._format_webservice_errors(result['errors'])
1048+ raise EmailTokenError(errorsdict)
1049+ elif 'email' in result:
1050+ return token
1051+ else:
1052+ raise EmailTokenError('Received invalid reply: %s' % result)
1053+
1054+ def request_password_reset_token(self, email):
1055+ """Request a token to reset the password for the account 'email'."""
1056+ sso_service = self.sso_service_class(None, self.service_url)
1057+ service = sso_service.registrations.request_password_reset_token
1058+ try:
1059+ result = service(email=email)
1060+ except HTTPError, e:
1061+ logger.exception('request_password_reset_token failed with:')
1062+ raise ResetPasswordTokenError(e.content.split('\n')[0])
1063+
1064+ if result['status'] == 'ok':
1065+ return email
1066+ else:
1067+ raise ResetPasswordTokenError('Received invalid reply: %s' %
1068+ result)
1069+
1070+ def set_new_password(self, email, token, new_password):
1071+ """Set a new password for the account 'email' to be 'new_password'.
1072+
1073+ The 'token' has to be the one resulting from a call to
1074+ 'request_password_reset_token'.
1075+
1076+ """
1077+ sso_service = self.sso_service_class(None, self.service_url)
1078+ service = sso_service.registrations.set_new_password
1079+ try:
1080+ result = service(email=email, token=token,
1081+ new_password=new_password)
1082+ except HTTPError, e:
1083+ logger.exception('set_new_password failed with:')
1084+ raise NewPasswordError(e.content.split('\n')[0])
1085+
1086+ if result['status'] == 'ok':
1087+ return email
1088+ else:
1089+ raise NewPasswordError('Received invalid reply: %s' % result)
1090+
1091+
1092+def except_to_errdict(e):
1093+ """Turn an exception into a dictionary to return thru DBus."""
1094+ result = {
1095+ "errtype": e.__class__.__name__,
1096+ }
1097+ if len(e.args) == 0:
1098+ result["message"] = e.__class__.__doc__
1099+ elif isinstance(e.args[0], dict):
1100+ result.update(e.args[0])
1101+ elif isinstance(e.args[0], basestring):
1102+ result["message"] = e.args[0]
1103+
1104+ return result
1105+>>>>>>> MERGE-SOURCE
1106
1107
1108 def blocking(f, app_name, result_cb, error_cb):
1109@@ -72,7 +345,13 @@
1110 """Initiate the Login object."""
1111 dbus.service.Object.__init__(self, object_path=object_path,
1112 bus_name=bus_name)
1113+<<<<<<< TREE
1114 self.root = SSOLoginRoot(sso_login_processor_class, sso_service_class)
1115+=======
1116+ self.sso_login_processor_class = sso_login_processor_class
1117+ self.processor = self.sso_login_processor_class(
1118+ sso_service_class=sso_service_class)
1119+>>>>>>> MERGE-SOURCE
1120
1121 # generate_capcha signals
1122 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1123@@ -91,9 +370,17 @@
1124 in_signature='ss')
1125 def generate_captcha(self, app_name, filename):
1126 """Call the matching method in the processor."""
1127+<<<<<<< TREE
1128 self.root.generate_captcha(app_name, filename, blocking,
1129 self.CaptchaGenerated,
1130 self.CaptchaGenerationError)
1131+=======
1132+ def f():
1133+ """Inner function that will be run in a thread."""
1134+ return self.processor.generate_captcha(filename)
1135+ blocking(f, app_name, self.CaptchaGenerated,
1136+ self.CaptchaGenerationError)
1137+>>>>>>> MERGE-SOURCE
1138
1139 # register_user signals
1140 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1141@@ -113,10 +400,29 @@
1142 def register_user(self, app_name, email, password, name,
1143 captcha_id, captcha_solution):
1144 """Call the matching method in the processor."""
1145+<<<<<<< TREE
1146 self.root.register_user(app_name, email, password, name, captcha_id,
1147 captcha_solution, blocking,
1148 self.UserRegistered,
1149 self.UserRegistrationError)
1150+=======
1151+ def f():
1152+ """Inner function that will be run in a thread."""
1153+ return self.processor.register_user(email, password,
1154+ captcha_id, captcha_solution)
1155+ blocking(f, app_name, self.UserRegistered, self.UserRegistrationError)
1156+
1157+ @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME,
1158+ in_signature='ssssss')
1159+ def register_with_name(self, app_name, email, password, name,
1160+ captcha_id, captcha_solution):
1161+ """Call the matching method in the processor."""
1162+ def f():
1163+ """Inner function that will be run in a thread."""
1164+ return self.processor.register_with_name(email, password, name,
1165+ captcha_id, captcha_solution)
1166+ blocking(f, app_name, self.UserRegistered, self.UserRegistrationError)
1167+>>>>>>> MERGE-SOURCE
1168
1169 # login signals
1170 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1171@@ -141,8 +447,31 @@
1172 in_signature='sss')
1173 def login(self, app_name, email, password):
1174 """Call the matching method in the processor."""
1175+<<<<<<< TREE
1176 self.root.login(app_name, email, password, blocking, self.LoggedIn,
1177 self.LoginError, self.UserNotValidated)
1178+=======
1179+ def f():
1180+ """Inner function that will be run in a thread."""
1181+ token_name = get_token_name(app_name)
1182+ logger.debug('login: token_name %r, email %r, password <hidden>.',
1183+ token_name, email)
1184+ credentials = self.processor.login(email, password, token_name)
1185+ logger.debug('login returned not None credentials? %r.',
1186+ credentials is not None)
1187+ return credentials
1188+
1189+ def success_cb(app_name, credentials):
1190+ """Login finished successfull."""
1191+ is_validated = self.processor.is_validated(credentials)
1192+ logger.debug('user is validated? %r.', is_validated)
1193+ if is_validated:
1194+ keyring_store_credentials(app_name, credentials,
1195+ self.LoggedIn, app_name, email)
1196+ else:
1197+ self.UserNotValidated(app_name, email)
1198+ blocking(f, app_name, success_cb, self.LoginError)
1199+>>>>>>> MERGE-SOURCE
1200
1201 # validate_email signals
1202 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1203@@ -161,9 +490,25 @@
1204 in_signature='ssss')
1205 def validate_email(self, app_name, email, password, email_token):
1206 """Call the matching method in the processor."""
1207+<<<<<<< TREE
1208 self.root.validate_email(app_name, email, password, email_token,
1209 blocking, self.EmailValidated,
1210 self.EmailValidationError)
1211+=======
1212+ def f():
1213+ """Inner function that will be run in a thread."""
1214+ token_name = get_token_name(app_name)
1215+ credentials = self.processor.validate_email(email, password,
1216+ email_token, token_name)
1217+
1218+ def _email_stored():
1219+ """The email was stored, so call the signal."""
1220+ self.EmailValidated(app_name, email)
1221+
1222+ keyring_store_credentials(app_name, credentials, _email_stored)
1223+
1224+ blocking(f, app_name, NO_OP, self.EmailValidationError)
1225+>>>>>>> MERGE-SOURCE
1226
1227 # request_password_reset_token signals
1228 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1229@@ -182,9 +527,17 @@
1230 in_signature='ss')
1231 def request_password_reset_token(self, app_name, email):
1232 """Call the matching method in the processor."""
1233+<<<<<<< TREE
1234 self.root.request_password_reset_token(app_name, email, blocking,
1235 self.PasswordResetTokenSent,
1236 self.PasswordResetError)
1237+=======
1238+ def f():
1239+ """Inner function that will be run in a thread."""
1240+ return self.processor.request_password_reset_token(email)
1241+ blocking(f, app_name, self.PasswordResetTokenSent,
1242+ self.PasswordResetError)
1243+>>>>>>> MERGE-SOURCE
1244
1245 # set_new_password signals
1246 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss")
1247@@ -203,9 +556,17 @@
1248 in_signature='ssss')
1249 def set_new_password(self, app_name, email, token, new_password):
1250 """Call the matching method in the processor."""
1251+<<<<<<< TREE
1252 self.root.set_new_password(app_name, email, token, new_password,
1253 blocking, self.PasswordChanged,
1254 self.PasswordChangeError)
1255+=======
1256+ def f():
1257+ """Inner function that will be run in a thread."""
1258+ return self.processor.set_new_password(email, token,
1259+ new_password)
1260+ blocking(f, app_name, self.PasswordChanged, self.PasswordChangeError)
1261+>>>>>>> MERGE-SOURCE
1262
1263
1264 class SSOCredentials(dbus.service.Object):
1265@@ -243,11 +604,92 @@
1266 '"%s" and error_message %r', app_name, error_message)
1267
1268 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
1269+<<<<<<< TREE
1270 in_signature="s", out_signature="a{ss}",
1271 async_callbacks=("callback", "errback"))
1272 def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):
1273 """Get the credentials from the keyring or {} if not there."""
1274 self.root.find_credentials(app_name, callback, errback)
1275+=======
1276+ in_signature="s", out_signature="a{ss}")
1277+ def find_credentials(self, app_name):
1278+ """Get the credentials from the keyring or '' if not there."""
1279+ token = keyring_get_credentials(app_name)
1280+ logger.info('find_credentials: app_name "%s", result is {}? %s',
1281+ app_name, token is None)
1282+ if token is None:
1283+ return {}
1284+ else:
1285+ return token
1286+
1287+ def _login_success_cb(self, dialog, app_name, email):
1288+ """Handles the response from the UI dialog."""
1289+ logger.info('Login successful for app %r, email %r. Still pending to '
1290+ 'ping server and send result signal.', app_name, email)
1291+ try:
1292+ creds = keyring_get_credentials(app_name)
1293+ self._ping_url(app_name, email, creds)
1294+ self.CredentialsFound(app_name, creds)
1295+ except: # pylint: disable=W0702
1296+ msg = "Problem getting the credentials from the keyring."
1297+ logger.exception(msg)
1298+ self.clear_token(app_name)
1299+ self.CredentialsError(app_name, msg, traceback.format_exc())
1300+
1301+ def _login_error_cb(self, dialog, app_name, error):
1302+ """Handles a problem in the UI."""
1303+ logger.info('Login unsuccessful for app %r, error %r', app_name, error)
1304+ msg = "Problem getting the credentials from the keyring."
1305+ self.CredentialsError(app_name, msg, "no more info available")
1306+
1307+ def _login_auth_denied_cb(self, dialog, app_name):
1308+ """The user decides not to allow the registration or login."""
1309+ self.AuthorizationDenied(app_name)
1310+
1311+ def _ping_url(self, app_name, email, credentials):
1312+ """Ping the given url."""
1313+ logger.info('Maybe pinging server for app_name "%s"', app_name)
1314+ if app_name == U1_APP_NAME:
1315+ url = self.ping_url + email
1316+ consumer = oauth.OAuthConsumer(credentials['consumer_key'],
1317+ credentials['consumer_secret'])
1318+ token = oauth.OAuthToken(credentials['token'],
1319+ credentials['token_secret'])
1320+ get_request = oauth.OAuthRequest.from_consumer_and_token
1321+ oauth_req = get_request(oauth_consumer=consumer, token=token,
1322+ http_method="GET", http_url=url)
1323+ oauth_req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),
1324+ consumer, token)
1325+ request = urllib2.Request(url, headers=oauth_req.to_header())
1326+ logger.debug('Opening the ping url %s with urllib2.urlopen. ' \
1327+ 'Request to: %s', PING_URL, request.get_full_url())
1328+ response = urllib2.urlopen(request)
1329+ logger.debug('Url opened. Response: %s.', response.code)
1330+ return response.code
1331+
1332+ def _show_login_or_register_ui(self, app_name, tc_url, help_text,
1333+ win_id, login_only=False):
1334+ """Shows the UI so the user can login or register."""
1335+ try:
1336+ # delay gui import to be able to function on non-graphical envs
1337+ from ubuntu_sso import gui
1338+ gui_app = gui.UbuntuSSOClientGUI(app_name, tc_url,
1339+ help_text, win_id, login_only)
1340+ gui_app.connect(gui.SIG_LOGIN_SUCCEEDED, self._login_success_cb)
1341+ gui_app.connect(gui.SIG_LOGIN_FAILED, self._login_error_cb)
1342+ gui_app.connect(gui.SIG_REGISTRATION_SUCCEEDED,
1343+ self._login_success_cb)
1344+ gui_app.connect(gui.SIG_REGISTRATION_FAILED, self._login_error_cb)
1345+ gui_app.connect(gui.SIG_USER_CANCELATION,
1346+ self._login_auth_denied_cb)
1347+ except: # pylint: disable=W0702
1348+ msg = '_show_login_or_register_ui failed when calling ' \
1349+ 'gui.UbuntuSSOClientGUI(%r, %r, %r, %r, %r)'
1350+ logger.exception(msg, app_name, tc_url, help_text,
1351+ win_id, login_only)
1352+ msg = "Problem opening the Ubuntu SSO user interface."
1353+ self.CredentialsError(app_name, msg, traceback.format_exc())
1354+>>>>>>> MERGE-SOURCE
1355
1356 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
1357 in_signature="sssx", out_signature="")
1358@@ -296,6 +738,7 @@
1359
1360 'app_name' is the name of the application.
1361 """
1362+<<<<<<< TREE
1363 self.root.clear_token(app_name, callback, errback)
1364
1365
1366@@ -452,3 +895,13 @@
1367 def login(self, app_name, args):
1368 """Get credentials if found else prompt GUI to login."""
1369 self.root.login(app_name, args)
1370+=======
1371+ logger.info('Clearing credentials for app %r.', app_name)
1372+ try:
1373+ creds = Keyring(app_name)
1374+ creds.delete_ubuntusso_attr()
1375+ except: # pylint: disable=W0702
1376+ logger.exception(
1377+ "problem removing credentials from keyring for %s",
1378+ app_name)
1379+>>>>>>> MERGE-SOURCE
1380
1381=== modified file 'ubuntu_sso/main/tests/test_linux.py'
1382--- ubuntu_sso/main/tests/test_linux.py 2011-03-29 14:16:36 +0000
1383+++ ubuntu_sso/main/tests/test_linux.py 2011-03-29 17:35:28 +0000
1384@@ -50,6 +50,369 @@
1385 # pylint: disable=W0212
1386
1387
1388+<<<<<<< TREE
1389+=======
1390+APP_NAME = 'The Coolest App Ever'
1391+CAPTCHA_PATH = os.path.abspath(os.path.join(os.curdir, 'ubuntu_sso', 'tests',
1392+ 'files', 'captcha.png'))
1393+CAPTCHA_ID = 'test'
1394+CAPTCHA_SOLUTION = 'william Byrd'
1395+CANT_RESET_PASSWORD_CONTENT = "CanNotResetPassowrdError: " \
1396+ "Can't reset password for this account"
1397+RESET_TOKEN_INVALID_CONTENT = "AuthToken matching query does not exist."
1398+EMAIL = 'test@example.com'
1399+EMAIL_ALREADY_REGISTERED = 'a@example.com'
1400+EMAIL_TOKEN = 'B2Pgtf'
1401+HELP = 'help text'
1402+NAME = 'Juanito Pérez'
1403+PASSWORD = 'be4tiFul'
1404+RESET_PASSWORD_TOKEN = '8G5Wtq'
1405+TOKEN = {u'consumer_key': u'xQ7xDAz',
1406+ u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
1407+ u'token_name': u'test',
1408+ u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
1409+ u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
1410+TOKEN_NAME = get_token_name(APP_NAME)
1411+STATUS_UNKNOWN = {'status': 'yadda-yadda'}
1412+STATUS_ERROR = {'status': 'error', 'errors': {'something': ['Bla', 'Ble']}}
1413+STATUS_OK = {'status': 'ok'}
1414+STATUS_EMAIL_UNKNOWN = {'status': 'yadda-yadda'}
1415+STATUS_EMAIL_ERROR = {'errors': {'email_token': ['Error1', 'Error2']}}
1416+STATUS_EMAIL_OK = {'email': EMAIL}
1417+TC_URL = 'tcurl'
1418+WINDOW_ID = 5
1419+
1420+NO_OP = lambda *args, **kwargs: None
1421+LOGIN_OR_REGISTER_ARGS = (APP_NAME, TC_URL, HELP, WINDOW_ID)
1422+LOGIN_OR_REGISTER_GUI_ARGS = LOGIN_OR_REGISTER_ARGS + (False,)
1423+LOGIN_ONLY_ARGS = (APP_NAME, HELP, WINDOW_ID)
1424+LOGIN_ONLY_GUI_ARGS = (APP_NAME, None, HELP, WINDOW_ID, True)
1425+
1426+
1427+class FakedResponse(object):
1428+ """Fake a urlopen response."""
1429+
1430+ def __init__(self, *args, **kwargs):
1431+ for k, val in kwargs.iteritems():
1432+ setattr(self, k, val)
1433+
1434+
1435+class FakedCaptchas(object):
1436+ """Fake the captcha generator."""
1437+
1438+ def new(self):
1439+ """Return a fix captcha)."""
1440+ return {'image_url': 'file://%s' % CAPTCHA_PATH,
1441+ 'captcha_id': CAPTCHA_ID}
1442+
1443+
1444+class FakedRegistrations(object):
1445+ """Fake the registrations service."""
1446+
1447+ def register(self, email, password, displayname,
1448+ captcha_id, captcha_solution):
1449+ """Fake registration. Return a fix result."""
1450+ if email == EMAIL_ALREADY_REGISTERED:
1451+ return {'status': 'error',
1452+ 'errors': {'email': 'Email already registered'}}
1453+ elif captcha_id is None and captcha_solution is None:
1454+ return STATUS_UNKNOWN
1455+ elif captcha_id != CAPTCHA_ID or captcha_solution != CAPTCHA_SOLUTION:
1456+ return STATUS_ERROR
1457+ else:
1458+ return STATUS_OK
1459+
1460+ def request_password_reset_token(self, email):
1461+ """Fake password reset token. Return a fix result."""
1462+ if email is None:
1463+ return STATUS_UNKNOWN
1464+ elif email != EMAIL:
1465+ raise HTTPError(response=None, content=CANT_RESET_PASSWORD_CONTENT)
1466+ else:
1467+ return STATUS_OK
1468+
1469+ def set_new_password(self, email, token, new_password):
1470+ """Fake the setting of new password. Return a fix result."""
1471+ if email is None and token is None and new_password is None:
1472+ return STATUS_UNKNOWN
1473+ elif email != EMAIL or token != RESET_PASSWORD_TOKEN:
1474+ raise HTTPError(response=None, content=RESET_TOKEN_INVALID_CONTENT)
1475+ else:
1476+ return STATUS_OK
1477+
1478+
1479+class FakedAuthentications(object):
1480+ """Fake the authentications service."""
1481+
1482+ def authenticate(self, token_name):
1483+ """Fake authenticate. Return a fix result."""
1484+ if not token_name.startswith(TOKEN_NAME):
1485+ raise HTTPError(response=None, content=None)
1486+ else:
1487+ return TOKEN
1488+
1489+
1490+class FakedAccounts(object):
1491+ """Fake the accounts service."""
1492+
1493+ def __init__(self):
1494+ self.preferred_email = EMAIL
1495+
1496+ def validate_email(self, email_token):
1497+ """Fake the email validation. Return a fix result."""
1498+ if email_token is None:
1499+ return STATUS_EMAIL_UNKNOWN
1500+ elif email_token == EMAIL_ALREADY_REGISTERED:
1501+ return {'status': 'error',
1502+ 'errors': {'email': 'Email already registered'}}
1503+ elif email_token != EMAIL_TOKEN:
1504+ return STATUS_EMAIL_ERROR
1505+ else:
1506+ return STATUS_EMAIL_OK
1507+
1508+ # pylint: disable=E0202, C0103
1509+
1510+ def me(self):
1511+ """Fake the 'me' information."""
1512+ return {u'username': u'Wh46bKY',
1513+ u'preferred_email': self.preferred_email,
1514+ u'displayname': u'',
1515+ u'unverified_emails': [u'aaaaaa@example.com'],
1516+ u'verified_emails': [],
1517+ u'openid_identifier': u'Wh46bKY'}
1518+
1519+
1520+class FakedSSOServer(object):
1521+ """Fake an SSO server."""
1522+
1523+ def __init__(self, authorizer, service_root):
1524+ self.captchas = FakedCaptchas()
1525+ self.registrations = FakedRegistrations()
1526+ self.authentications = FakedAuthentications()
1527+ self.accounts = FakedAccounts()
1528+
1529+
1530+class SSOLoginProcessorTestCase(TestCase, MockerTestCase):
1531+ """Test suite for the SSO login processor."""
1532+
1533+ def setUp(self):
1534+ """Init."""
1535+ self.processor = SSOLoginProcessor(sso_service_class=FakedSSOServer)
1536+ self.register_kwargs = dict(email=EMAIL, password=PASSWORD,
1537+ captcha_id=CAPTCHA_ID,
1538+ captcha_solution=CAPTCHA_SOLUTION)
1539+ self.login_kwargs = dict(email=EMAIL, password=PASSWORD,
1540+ token_name=TOKEN_NAME)
1541+
1542+ def tearDown(self):
1543+ """Clean up."""
1544+ self.processor = None
1545+
1546+ def test_generate_captcha(self):
1547+ """Captcha can be generated."""
1548+ filename = self.mktemp()
1549+ self.addCleanup(lambda: os.remove(filename))
1550+ captcha_id = self.processor.generate_captcha(filename)
1551+ self.assertEqual(CAPTCHA_ID, captcha_id, 'captcha id must be correct.')
1552+ self.assertTrue(os.path.isfile(filename), '%s must exist.' % filename)
1553+
1554+ with open(CAPTCHA_PATH) as f:
1555+ expected = f.read()
1556+ with open(filename) as f:
1557+ actual = f.read()
1558+ self.assertEqual(expected, actual, 'captcha image must be correct.')
1559+
1560+ def test_register_user_checks_valid_email(self):
1561+ """Email is validated."""
1562+ self.register_kwargs['email'] = 'notavalidemail'
1563+ self.assertRaises(InvalidEmailError,
1564+ self.processor.register_user, **self.register_kwargs)
1565+
1566+ def test_register_user_checks_valid_password(self):
1567+ """Password is validated."""
1568+ self.register_kwargs['password'] = ''
1569+ self.assertRaises(InvalidPasswordError,
1570+ self.processor.register_user, **self.register_kwargs)
1571+
1572+ # 7 chars, one less than expected
1573+ self.register_kwargs['password'] = 'tesT3it'
1574+ self.assertRaises(InvalidPasswordError,
1575+ self.processor.register_user, **self.register_kwargs)
1576+
1577+ self.register_kwargs['password'] = 'test3it!' # no upper case
1578+ self.assertRaises(InvalidPasswordError,
1579+ self.processor.register_user, **self.register_kwargs)
1580+
1581+ self.register_kwargs['password'] = 'testIt!!' # no number
1582+ self.assertRaises(InvalidPasswordError,
1583+ self.processor.register_user, **self.register_kwargs)
1584+
1585+ # register
1586+
1587+ def test_register_user_if_status_ok(self):
1588+ """A user is succesfuy registered into the SSO server."""
1589+ result = self.processor.register_user(**self.register_kwargs)
1590+ self.assertEqual(EMAIL, result, 'registration was successful.')
1591+
1592+ def test_register_user_if_status_error(self):
1593+ """Proper error is raised if register fails."""
1594+ self.register_kwargs['captcha_id'] = CAPTCHA_ID * 2 # incorrect
1595+ failure = self.assertRaises(RegistrationError,
1596+ self.processor.register_user,
1597+ **self.register_kwargs)
1598+ for k, val in failure.args[0].items():
1599+ self.assertIn(k, STATUS_ERROR['errors'])
1600+ self.assertEqual(val, "\n".join(STATUS_ERROR['errors'][k]))
1601+
1602+ def test_register_user_if_status_error_with_string_message(self):
1603+ """Proper error is raised if register fails."""
1604+ self.register_kwargs['email'] = EMAIL_ALREADY_REGISTERED
1605+ failure = self.assertRaises(RegistrationError,
1606+ self.processor.register_user,
1607+ **self.register_kwargs)
1608+ for k, val in failure.args[0].items():
1609+ self.assertIn(k, {'email': 'Email already registered'})
1610+ self.assertEqual(val, 'Email already registered')
1611+
1612+ def test_register_user_if_status_unknown(self):
1613+ """Proper error is raised if register returns an unknown status."""
1614+ self.register_kwargs['captcha_id'] = None
1615+ self.register_kwargs['captcha_solution'] = None
1616+ failure = self.assertRaises(RegistrationError,
1617+ self.processor.register_user,
1618+ **self.register_kwargs)
1619+ self.assertIn('Received unknown status: %s' % STATUS_UNKNOWN, failure)
1620+
1621+ # login
1622+
1623+ def test_login_if_http_error(self):
1624+ """Proper error is raised if authentication fails."""
1625+ self.login_kwargs['token_name'] = APP_NAME * 2 # invalid token name
1626+ self.assertRaises(AuthenticationError,
1627+ self.processor.login, **self.login_kwargs)
1628+
1629+ def test_login_if_no_error(self):
1630+ """A user can be succesfully logged in into the SSO service."""
1631+ result = self.processor.login(**self.login_kwargs)
1632+ self.assertEqual(TOKEN, result, 'authentication was successful.')
1633+
1634+ # is_validated
1635+
1636+ def test_is_validated(self):
1637+ """If preferred email is not None, user is validated."""
1638+ result = self.processor.is_validated(token=TOKEN)
1639+ self.assertTrue(result, 'user must be validated.')
1640+
1641+ def test_is_not_validated(self):
1642+ """If preferred email is None, user is not validated."""
1643+ service = FakedSSOServer(None, None)
1644+ service.accounts.preferred_email = None
1645+ result = self.processor.is_validated(sso_service=service,
1646+ token=TOKEN)
1647+ self.assertFalse(result, 'user must not be validated.')
1648+
1649+ def test_is_not_validated_empty_result(self):
1650+ """If preferred email is None, user is not validated."""
1651+ service = FakedSSOServer(None, None)
1652+ service.accounts.me = lambda: {}
1653+ result = self.processor.is_validated(sso_service=service,
1654+ token=TOKEN)
1655+ self.assertFalse(result, 'user must not be validated.')
1656+
1657+ # validate_email
1658+
1659+ def test_validate_email_if_status_ok(self):
1660+ """A email is succesfuy validated in the SSO server."""
1661+ self.login_kwargs['email_token'] = EMAIL_TOKEN # valid email token
1662+ result = self.processor.validate_email(**self.login_kwargs)
1663+ self.assertEqual(TOKEN, result, 'email validation was successful.')
1664+
1665+ def test_validate_email_if_status_error(self):
1666+ """Proper error is raised if email validation fails."""
1667+ self.login_kwargs['email_token'] = EMAIL_TOKEN * 2 # invalid token
1668+ failure = self.assertRaises(EmailTokenError,
1669+ self.processor.validate_email,
1670+ **self.login_kwargs)
1671+ for k, val in failure.args[0].items():
1672+ self.assertIn(k, STATUS_EMAIL_ERROR['errors'])
1673+ self.assertEqual(val, "\n".join(STATUS_EMAIL_ERROR['errors'][k]))
1674+
1675+ def test_validate_email_if_status_error_with_string_message(self):
1676+ """Proper error is raised if register fails."""
1677+ self.login_kwargs['email_token'] = EMAIL_ALREADY_REGISTERED
1678+ failure = self.assertRaises(EmailTokenError,
1679+ self.processor.validate_email,
1680+ **self.login_kwargs)
1681+ for k, val in failure.args[0].items():
1682+ self.assertIn(k, {'email': 'Email already registered'})
1683+ self.assertEqual(val, 'Email already registered')
1684+
1685+ def test_validate_email_if_status_unknown(self):
1686+ """Proper error is raised if email validation returns unknown."""
1687+ self.login_kwargs['email_token'] = None
1688+ failure = self.assertRaises(EmailTokenError,
1689+ self.processor.validate_email,
1690+ **self.login_kwargs)
1691+ self.assertIn('Received invalid reply: %s' % STATUS_UNKNOWN, failure)
1692+
1693+ # reset_password
1694+
1695+ def test_request_password_reset_token_if_status_ok(self):
1696+ """A reset password token is succesfuly sent."""
1697+ result = self.processor.request_password_reset_token(email=EMAIL)
1698+ self.assertEqual(EMAIL, result,
1699+ 'password reset token must be successful.')
1700+
1701+ def test_request_password_reset_token_if_http_error(self):
1702+ """Proper error is raised if password token request fails."""
1703+ exc = self.assertRaises(ResetPasswordTokenError,
1704+ self.processor.request_password_reset_token,
1705+ email=EMAIL * 2)
1706+ self.assertIn(CANT_RESET_PASSWORD_CONTENT, exc)
1707+
1708+ def test_request_password_reset_token_if_status_unknown(self):
1709+ """Proper error is raised if password token request returns unknown."""
1710+ exc = self.assertRaises(ResetPasswordTokenError,
1711+ self.processor.request_password_reset_token,
1712+ email=None)
1713+ self.assertIn('Received invalid reply: %s' % STATUS_UNKNOWN, exc)
1714+
1715+ def test_set_new_password_if_status_ok(self):
1716+ """A new password is succesfuy set."""
1717+ result = self.processor.set_new_password(email=EMAIL,
1718+ token=RESET_PASSWORD_TOKEN,
1719+ new_password=PASSWORD)
1720+ self.assertEqual(EMAIL, result,
1721+ 'new password must be set successfully.')
1722+
1723+ def test_set_new_password_if_http_error(self):
1724+ """Proper error is raised if setting a new password fails."""
1725+ exc = self.assertRaises(NewPasswordError,
1726+ self.processor.set_new_password,
1727+ email=EMAIL * 2,
1728+ token=RESET_PASSWORD_TOKEN * 2,
1729+ new_password=PASSWORD)
1730+ self.assertIn(RESET_TOKEN_INVALID_CONTENT, exc)
1731+
1732+ def test_set_new_password_if_status_unknown(self):
1733+ """Proper error is raised if setting a new password returns unknown."""
1734+ exc = self.assertRaises(NewPasswordError,
1735+ self.processor.set_new_password,
1736+ email=None, token=None, new_password=None)
1737+ self.assertIn('Received invalid reply: %s' % STATUS_UNKNOWN, exc)
1738+
1739+
1740+class SSORegistrationWithNameTestCase(SSOLoginProcessorTestCase):
1741+ """Test suite for the SSO login processor for register_with_name."""
1742+
1743+ def setUp(self):
1744+ """Init."""
1745+ super(SSORegistrationWithNameTestCase, self).setUp()
1746+ self.register_kwargs['displayname'] = NAME
1747+ self.processor.register_user = self.processor.register_with_name
1748+
1749+
1750+>>>>>>> MERGE-SOURCE
1751 class BlockingSampleException(Exception):
1752 """The exception that will be thrown by the fake blocking."""
1753
1754@@ -84,13 +447,21 @@
1755 mockbus._register_object_path(ARGS)
1756 self.mockprocessorclass = None
1757
1758+<<<<<<< TREE
1759 def ksc(keyring, k, val):
1760+=======
1761+ def ksc(k, val, callback, *cb_args):
1762+>>>>>>> MERGE-SOURCE
1763 """Assert over token and app_name."""
1764 self.assertEqual(k, APP_NAME)
1765 self.assertEqual(val, TOKEN)
1766 self.keyring_was_set = True
1767 self.keyring_values = k, val
1768+<<<<<<< TREE
1769 return defer.succeed(None)
1770+=======
1771+ callback(*cb_args)
1772+>>>>>>> MERGE-SOURCE
1773
1774 self.patch(ubuntu_sso.main.Keyring, "set_credentials", ksc)
1775 self.keyring_was_set = False
1776@@ -164,8 +535,13 @@
1777 """Test that the register_user method works ok."""
1778 d = Deferred()
1779 expected_result = "expected result"
1780+<<<<<<< TREE
1781 self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,
1782 CAPTCHA_ID, CAPTCHA_SOLUTION)
1783+=======
1784+ self.create_mock_processor().register_user(EMAIL, PASSWORD,
1785+ CAPTCHA_ID, CAPTCHA_SOLUTION)
1786+>>>>>>> MERGE-SOURCE
1787 self.mocker.result(expected_result)
1788 self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
1789 self.mocker.replay()
1790@@ -188,8 +564,13 @@
1791 """Test that the register_user method fails as expected."""
1792 d = Deferred()
1793 expected_result = "expected result"
1794+<<<<<<< TREE
1795 self.create_mock_processor().register_user(EMAIL, PASSWORD, NAME,
1796 CAPTCHA_ID, CAPTCHA_SOLUTION)
1797+=======
1798+ self.create_mock_processor().register_user(EMAIL, PASSWORD,
1799+ CAPTCHA_ID, CAPTCHA_SOLUTION)
1800+>>>>>>> MERGE-SOURCE
1801 self.mocker.result(expected_result)
1802 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
1803 self.mocker.replay()
1804@@ -208,15 +589,69 @@
1805 CAPTCHA_SOLUTION)
1806 return d
1807
1808+ def test_register_with_name(self):
1809+ """Test that the register_with_name method works ok."""
1810+ d = Deferred()
1811+ expected_result = "expected result"
1812+ self.create_mock_processor().register_with_name(EMAIL, PASSWORD, NAME,
1813+ CAPTCHA_ID, CAPTCHA_SOLUTION)
1814+ self.mocker.result(expected_result)
1815+ self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
1816+ self.mocker.replay()
1817+
1818+ def verify(app_name, result):
1819+ """The actual test."""
1820+ self.assertEqual(result, expected_result)
1821+ self.assertEqual(app_name, APP_NAME)
1822+ d.callback(result)
1823+
1824+ client = SSOLogin(self.mockbusname,
1825+ sso_login_processor_class=self.mockprocessorclass)
1826+ self.patch(client, "UserRegistered", verify)
1827+ self.patch(client, "UserRegistrationError", d.errback)
1828+ client.register_with_name(APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
1829+ CAPTCHA_SOLUTION)
1830+ return d
1831+
1832+ def test_register_with_name_error(self):
1833+ """Test that the register_with_name method fails as expected."""
1834+ d = Deferred()
1835+ expected_result = "expected result"
1836+ self.create_mock_processor().register_with_name(EMAIL, PASSWORD, NAME,
1837+ CAPTCHA_ID, CAPTCHA_SOLUTION)
1838+ self.mocker.result(expected_result)
1839+ self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking)
1840+ self.mocker.replay()
1841+
1842+ def verify(app_name, errdict):
1843+ """The actual test."""
1844+ self.assertEqual(errdict["errtype"], "BlockingSampleException")
1845+ self.assertEqual(app_name, APP_NAME)
1846+ d.callback("Ok")
1847+
1848+ client = SSOLogin(self.mockbusname,
1849+ sso_login_processor_class=self.mockprocessorclass)
1850+ self.patch(client, "UserRegistered", d.errback)
1851+ self.patch(client, "UserRegistrationError", verify)
1852+ client.register_with_name(APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID,
1853+ CAPTCHA_SOLUTION)
1854+ return d
1855+
1856 def test_login(self):
1857 """Test that the login method works ok."""
1858 d = Deferred()
1859 processor = self.create_mock_processor()
1860 processor.login(EMAIL, PASSWORD, TOKEN_NAME)
1861 self.mocker.result(TOKEN)
1862+<<<<<<< TREE
1863 processor.is_validated(TOKEN)
1864 self.mocker.result(True)
1865 self.patch(ubuntu_sso.main.linux, "blocking", fake_ok_blocking)
1866+=======
1867+ processor.is_validated(TOKEN)
1868+ self.mocker.result(True)
1869+ self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
1870+>>>>>>> MERGE-SOURCE
1871 self.mocker.replay()
1872
1873 def verify(app_name, result):
1874@@ -230,6 +665,7 @@
1875 sso_login_processor_class=self.mockprocessorclass)
1876 self.patch(client, "LoggedIn", verify)
1877 self.patch(client, "LoginError", d.errback)
1878+<<<<<<< TREE
1879 self.patch(client, "UserNotValidated", d.errback)
1880 client.login(APP_NAME, EMAIL, PASSWORD)
1881 return d
1882@@ -265,6 +701,43 @@
1883 d = Deferred()
1884 self.create_mock_processor()
1885 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
1886+=======
1887+ self.patch(client, "UserNotValidated", d.errback)
1888+ client.login(APP_NAME, EMAIL, PASSWORD)
1889+ return d
1890+
1891+ def test_login_user_not_validated(self):
1892+ """Test that the login sends EmailNotValidated signal."""
1893+ d = Deferred()
1894+ processor = self.create_mock_processor()
1895+ processor.login(EMAIL, PASSWORD, TOKEN_NAME)
1896+ self.mocker.result(TOKEN)
1897+ processor.is_validated(TOKEN)
1898+ self.mocker.result(False)
1899+ self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
1900+ self.mocker.replay()
1901+
1902+ def verify(app_name, email):
1903+ """The actual test."""
1904+ self.assertEqual(app_name, APP_NAME)
1905+ self.assertEqual(email, EMAIL)
1906+ self.assertFalse(self.keyring_was_set, "Keyring should not be set")
1907+ d.callback("Ok")
1908+
1909+ client = SSOLogin(self.mockbusname,
1910+ sso_login_processor_class=self.mockprocessorclass)
1911+ self.patch(client, "LoggedIn", d.errback)
1912+ self.patch(client, "LoginError", d.errback)
1913+ self.patch(client, "UserNotValidated", verify)
1914+ client.login(APP_NAME, EMAIL, PASSWORD)
1915+ return d
1916+
1917+ def test_login_error(self):
1918+ """Test that the login method fails as expected."""
1919+ d = Deferred()
1920+ self.create_mock_processor()
1921+ self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking)
1922+>>>>>>> MERGE-SOURCE
1923
1924 def fake_gtn(*args):
1925 """A fake get_token_name that fails."""
1926@@ -284,6 +757,7 @@
1927 sso_login_processor_class=self.mockprocessorclass)
1928 self.patch(client, "LoggedIn", d.errback)
1929 self.patch(client, "LoginError", verify)
1930+<<<<<<< TREE
1931 self.patch(client, "UserNotValidated", d.errback)
1932 client.login(APP_NAME, EMAIL, PASSWORD)
1933 return d
1934@@ -319,6 +793,9 @@
1935 self.patch(client, "LoggedIn", fail)
1936 self.patch(client, "LoginError", verify)
1937 self.patch(client, "UserNotValidated", fail)
1938+=======
1939+ self.patch(client, "UserNotValidated", d.errback)
1940+>>>>>>> MERGE-SOURCE
1941 client.login(APP_NAME, EMAIL, PASSWORD)
1942 return d
1943
1944@@ -348,8 +825,13 @@
1945 def test_validate_email_error(self):
1946 """Test that the validate_email method fails as expected."""
1947 d = Deferred()
1948+<<<<<<< TREE
1949 self.create_mock_processor()
1950 self.patch(ubuntu_sso.main.linux, "blocking", fake_err_blocking)
1951+=======
1952+ self.create_mock_processor()
1953+ self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking)
1954+>>>>>>> MERGE-SOURCE
1955
1956 def fake_gtn(*args):
1957 """A fake get_token_name that fails."""
1958@@ -559,6 +1041,58 @@
1959 self.assertEqual(result["errtype"], e.__class__.__name__)
1960
1961
1962+<<<<<<< TREE
1963+=======
1964+class KeyringCredentialsTestCase(TestCase, MockerTestCase):
1965+ """Check the functions that access the keyring."""
1966+
1967+ # Invalid name (should match ([a-z_][a-z0-9_]*|[A-Z_][A-Z0-9_]*)$)
1968+ # pylint: disable=C0103
1969+
1970+ def test_keyring_store_cred(self):
1971+ """Verify the method that stores credentials."""
1972+ idle_add = lambda f, *args, **kwargs: f(*args, **kwargs)
1973+ self.patch(gobject, "idle_add", idle_add)
1974+ token_value = TOKEN
1975+ mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
1976+ mockKeyringClass(APP_NAME)
1977+ mockKeyring = self.mocker.mock()
1978+ callback = self.mocker.mock()
1979+ self.mocker.result(mockKeyring)
1980+ mockKeyring.set_ubuntusso_attr(token_value)
1981+ callback(1, 2, 3)
1982+ self.mocker.replay()
1983+
1984+ keyring_store_credentials(APP_NAME, token_value, callback, 1, 2, 3)
1985+
1986+ def test_keyring_get_cred(self):
1987+ """The method returns the right token."""
1988+ mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
1989+ mockKeyringClass(APP_NAME)
1990+ mockKeyring = self.mocker.mock()
1991+ self.mocker.result(mockKeyring)
1992+ mockKeyring.get_ubuntusso_attr()
1993+ self.mocker.result(TOKEN)
1994+ self.mocker.replay()
1995+
1996+ token = keyring_get_credentials(APP_NAME)
1997+ self.assertEqual(token, TOKEN)
1998+
1999+ def test_keyring_get_cred_not_found(self):
2000+ """The method returns None when the token is not found."""
2001+ mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
2002+ mockKeyringClass(APP_NAME)
2003+ mockKeyring = self.mocker.mock()
2004+ self.mocker.result(mockKeyring)
2005+ mockKeyring.get_ubuntusso_attr()
2006+ self.mocker.result(None)
2007+ self.mocker.replay()
2008+
2009+ token = keyring_get_credentials(APP_NAME)
2010+ self.assertEqual(token, None)
2011+
2012+
2013+>>>>>>> MERGE-SOURCE
2014 class RegisterSampleException(Exception):
2015 """A mock exception thrown just when testing."""
2016
2017@@ -677,6 +1211,24 @@
2018 client = SSOCredentials(self.mocker.mock())
2019 client.clear_token(APP_NAME)
2020
2021+ def test_credentials_are_not_stored_if_ping_failed(self):
2022+ """Credentials are not stored if the ping fails."""
2023+
2024+ def fail(*args, **kwargs):
2025+ """Raise an exception."""
2026+ self.args = AssertionError((args, kwargs))
2027+ # pylint: disable=E0702
2028+ raise self.args
2029+
2030+ self.patch(self.client, '_ping_url', fail)
2031+ self._patch('clear_token')
2032+
2033+ self.client._login_success_cb(None, APP_NAME, EMAIL)
2034+
2035+ self.assertEqual(len(self.calls), 1)
2036+ self.assertEqual(self.calls[0][0], 'clear_token')
2037+ self.assertEqual(self.calls[0][1][0], APP_NAME)
2038+
2039
2040 class EnvironOverridesTestCase(TestCase):
2041 """Some URLs can be set from the environment for testing/QA purposes."""

Subscribers

People subscribed via source and target branches