Merge lp:~mandel/ubuntu-sso-client/windows_ui_4 into lp:ubuntu-sso-client

Proposed by Manuel de la Peña
Status: Merged
Approved by: Manuel de la Peña
Approved revision: 757
Merged at revision: 698
Proposed branch: lp:~mandel/ubuntu-sso-client/windows_ui_4
Merge into: lp:ubuntu-sso-client
Prerequisite: lp:~mandel/ubuntu-sso-client/windows_ui_3
Diff against target: 910 lines (+389/-89)
9 files modified
data/qt/setup_account.ui (+6/-0)
pylintrc (+3/-2)
setup.py (+6/-0)
ubuntu_sso/main/tests/test_windows.py (+45/-26)
ubuntu_sso/main/windows.py (+18/-14)
ubuntu_sso/qt/controllers.py (+154/-10)
ubuntu_sso/qt/gui.py (+18/-29)
ubuntu_sso/qt/tests/show_gui.py (+33/-0)
ubuntu_sso/qt/tests/test_windows.py (+106/-8)
To merge this branch: bzr merge lp:~mandel/ubuntu-sso-client/windows_ui_4
Reviewer Review Type Date Requested Status
Shane Fagan (community) Approve
Roberto Alsina (community) Approve
Review via email: mp+56308@code.launchpad.net

Commit message

Partial fix for lp:732203

Add SSO functionality to the Qt UI allowing to register and sign in users using the Ubuntu SSO client backend.

Description of the change

Partial fix for lp:732203

This branch adds SSO functionality to the Qt UI allowing to register and sign in users using the Ubuntu SSO client backend.

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) wrote :

+1

review: Approve
Revision history for this message
Shane Fagan (shanepatrickfagan) :
review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (46.0 KiB)

The attempt to merge lp:~mandel/ubuntu-sso-client/windows_ui_4 into lp:ubuntu-sso-client failed. Below is the output from the failed tests.

Running test suite for ubuntu_sso
Xlib: extension "RANDR" missing on display ":99".
ubuntu_sso.tests.test_account
  AccountTestCase
    test_generate_captcha ... [OK]
    test_is_not_validated ... [OK]
    test_is_not_validated_empty_result ... [OK]
    test_is_validated ... [OK]
    test_login_if_http_error ... [OK]
    test_login_if_no_error ... [OK]
    test_register_user_checks_valid_email ... [OK]
    test_register_user_checks_valid_password ... [OK]
    test_register_user_if_status_error ... [OK]
    test_register_user_if_status_error_with_string_message ... [OK]
    test_register_user_if_status_ok ... [OK]
    test_register_user_if_status_unknown ... [OK]
    test_request_password_reset_token_if_http_error ... [OK]
    test_request_password_reset_token_if_status_ok ... [OK]
    test_request_password_reset_token_if_status_unknown ... [OK]
    test_set_new_password_if_http_error ... [OK]
    test_set_new_password_if_status_ok ... [OK]
    test_set_new_password_if_status_unknown ... [OK]
    test_validate_email_if_status_error ... [OK]
    test_validate_email_if_status_error_with_string_message ... [OK]
    test_validate_email_if_status_ok ... [OK]
    test_validate_email_if_status_unknown ... [OK]
  EnvironOverridesTestCase
    test_no_override_service_url ... [OK]
    test_override_service_url ... [OK]
twisted.trial.unittest
  TestCase
    runTest ... [OK]
ubuntu_sso.tests.test_credentials
  BasicTestCase
    runTest ... [OK]
  ClearCredentialsTestCase
    test_clear_credentials ... [OK]
    test_keyring_failure ... [OK]
  CredentialsAuthDeniedTestCase
    test_auth_denial_cb ... [OK]
  CredentialsCallbacksTestCase
    test_callbacks_are_stored ... [OK]
    test_callbacks_default_to_no_op ... [OK]
    test_creation_parameters_are_stored ... [OK]
    test_error_cb ... [OK]
    test_help_text_defaults_to_empty_string ... [OK]
    t...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/qt/setup_account.ui'
2--- data/qt/setup_account.ui 2011-03-30 14:28:05 +0000
3+++ data/qt/setup_account.ui 2011-04-12 15:24:35 +0000
4@@ -290,6 +290,12 @@
5 </property>
6 <item>
7 <widget class="QLabel" name="captcha_view">
8+ <property name="minimumSize">
9+ <size>
10+ <width>0</width>
11+ <height>57</height>
12+ </size>
13+ </property>
14 <property name="frameShape">
15 <enum>QFrame::Box</enum>
16 </property>
17
18=== modified file 'pylintrc'
19--- pylintrc 2010-09-22 16:14:28 +0000
20+++ pylintrc 2011-04-12 15:24:35 +0000
21@@ -20,7 +20,7 @@
22
23 # Add <file or directory> to the black list. It should be a base name, not a
24 # path. You may set this option multiple times.
25-#ignore=<somedir>
26+#ignore=qt
27
28 # Pickle collected data for later comparisons.
29 persistent=no
30@@ -50,7 +50,8 @@
31 # W0142: Used * or ** magic
32 # W0613: Unused argument 'yyy'
33 # C0302: Too many lines in module
34-disable=R,I,W0142,W0613,C0302
35+# W0404: Reimport of module
36+disable=R,I,W0142,W0613,C0302,W0404
37
38
39 [REPORTS]
40
41=== modified file 'setup.py'
42--- setup.py 2011-03-31 13:55:42 +0000
43+++ setup.py 2011-04-12 15:24:35 +0000
44@@ -49,6 +49,9 @@
45
46 description = 'build extra files needed by ubuntu-sso-client'
47
48+ def __init__(self):
49+ build_extra.build_extra.__init__(self)
50+
51 def run(self):
52 """Do the build."""
53 sed = find_executable('sed')
54@@ -93,6 +96,9 @@
55
56 description = "build PyQt GUIs (.ui) and resources (.qrc)"
57
58+ def __init__(self):
59+ build_extra.build_extra.__init__(self)
60+
61 def compile_ui(self, ui_file, py_file=None):
62 """Compile the .ui files to python modules."""
63 # Search for pyuic4 in python bin dir, then in the $Path.
64
65=== modified file 'ubuntu_sso/main/tests/test_windows.py'
66--- ubuntu_sso/main/tests/test_windows.py 2011-03-30 18:50:04 +0000
67+++ ubuntu_sso/main/tests/test_windows.py 2011-04-12 15:24:35 +0000
68@@ -40,8 +40,9 @@
69 SSOLoginClient,
70 UbuntuSSORoot)
71
72-# because we are using twisted we have java like names C0103
73-# pylint: disable=C0103
74+# because we are using twisted we have java like names C0103 and
75+# the issues that mocker brings with it like W0104
76+# pylint: disable=C0103,W0104
77
78
79 class SaveProtocolServerFactory(PBServerFactory):
80@@ -141,14 +142,17 @@
81 def test_emit_captcha_generation_error(self):
82 """Test that the cb was called."""
83 app_name = 'app'
84- raised_error = 'error'
85+ error_value = 'error'
86+ raised_error = self.mocker.mock()
87
88 @defer.inlineCallbacks
89 def test_emit(client):
90 """Actual test."""
91- self.except_to_errdict(raised_error)
92- self.mocker.result(raised_error)
93- client.on_captcha_generation_error_cb(app_name, raised_error)
94+ raised_error.value
95+ self.mocker.result(error_value)
96+ self.except_to_errdict(error_value)
97+ self.mocker.result(error_value)
98+ client.on_captcha_generation_error_cb(app_name, error_value)
99 self.mocker.replay()
100 self.login.emit_captcha_generation_error(app_name, raised_error)
101 yield client.unregister_to_signals()
102@@ -205,14 +209,17 @@
103 def test_emit_user_registration_error(self):
104 """Test that the cb was called."""
105 app_name = 'app'
106- raised_error = 'error'
107+ error_value = 'error'
108+ raised_error = self.mocker.mock()
109
110 @defer.inlineCallbacks
111 def test_emit(client):
112 """Actual test."""
113- self.except_to_errdict(raised_error)
114- self.mocker.result(raised_error)
115- client.on_user_registration_error_cb(app_name, raised_error)
116+ raised_error.value
117+ self.mocker.result(error_value)
118+ self.except_to_errdict(error_value)
119+ self.mocker.result(error_value)
120+ client.on_user_registration_error_cb(app_name, error_value)
121 self.mocker.replay()
122 self.login.emit_user_registration_error(app_name, raised_error)
123 yield client.unregister_to_signals()
124@@ -275,14 +282,17 @@
125 def test_emit_login_error(self):
126 """Test that the db was called."""
127 app_name = 'app'
128- raised_error = 'error'
129+ error_value = 'error'
130+ raised_error = self.mocker.mock()
131
132 @defer.inlineCallbacks
133 def test_emit(client):
134 """Actual test."""
135- self.except_to_errdict(raised_error)
136- self.mocker.result(raised_error)
137- client.on_login_error_cb(app_name, raised_error)
138+ raised_error.value
139+ self.mocker.result(error_value)
140+ self.except_to_errdict(error_value)
141+ self.mocker.result(error_value)
142+ client.on_login_error_cb(app_name, error_value)
143 self.mocker.replay()
144 self.login.emit_login_error(app_name, raised_error)
145 yield client.unregister_to_signals()
146@@ -361,14 +371,17 @@
147 def test_emit_email_validation_error(self):
148 """Test the cb was called."""
149 app_name = 'app'
150- raised_error = 'error'
151+ error_value = 'error'
152+ raised_error = self.mocker.mock()
153
154 @defer.inlineCallbacks
155 def test_emit(client):
156 """Actual test."""
157- self.except_to_errdict(raised_error)
158- self.mocker.result(raised_error)
159- client.on_email_validation_error_cb(app_name, raised_error)
160+ raised_error.value
161+ self.mocker.result(error_value)
162+ self.except_to_errdict(error_value)
163+ self.mocker.result(error_value)
164+ client.on_email_validation_error_cb(app_name, error_value)
165 self.mocker.replay()
166 self.login.emit_email_validation_error(app_name, raised_error)
167 yield client.unregister_to_signals()
168@@ -427,14 +440,17 @@
169 def test_emit_password_reset_error(self):
170 """Test the cb was called."""
171 app_name = 'app'
172- raised_error = 'error'
173+ error_value = 'error'
174+ raised_error = self.mocker.mock()
175
176 @defer.inlineCallbacks
177 def test_emit(client):
178 """Actual test."""
179- self.except_to_errdict(raised_error)
180- self.mocker.result(raised_error)
181- client.on_password_reset_error_cb(app_name, raised_error)
182+ raised_error.value
183+ self.mocker.result(error_value)
184+ self.except_to_errdict(error_value)
185+ self.mocker.result(error_value)
186+ client.on_password_reset_error_cb(app_name, error_value)
187 self.mocker.replay()
188 self.login.emit_password_reset_error(app_name, raised_error)
189 yield client.unregister_to_signals()
190@@ -491,14 +507,17 @@
191 def test_emit_password_change_error(self):
192 """Test the cb was called."""
193 app_name = 'app'
194- raised_error = 'error'
195+ error_value = 'error'
196+ raised_error = self.mocker.mock()
197
198 @defer.inlineCallbacks
199 def test_emit(client):
200 """Actual test."""
201- self.except_to_errdict(raised_error)
202- self.mocker.result(raised_error)
203- client.on_password_change_error(app_name, raised_error)
204+ raised_error.value
205+ self.mocker.result(error_value)
206+ self.except_to_errdict(error_value)
207+ self.mocker.result(error_value)
208+ client.on_password_change_error(app_name, error_value)
209 self.mocker.replay()
210 self.login.emit_password_change_error(app_name, raised_error)
211 yield client.unregister_to_signals()
212
213=== modified file 'ubuntu_sso/main/windows.py'
214--- ubuntu_sso/main/windows.py 2011-03-30 19:27:48 +0000
215+++ ubuntu_sso/main/windows.py 2011-04-12 15:24:35 +0000
216@@ -15,7 +15,6 @@
217 # You should have received a copy of the GNU General Public License along
218 # with this program. If not, see <http://www.gnu.org/licenses/>.
219 """Main implementation on windows."""
220-
221 from functools import wraps
222 from twisted.internet import defer, reactor
223 from twisted.internet.threads import deferToThread
224@@ -61,7 +60,7 @@
225 from ubuntu_sso.main import (CredentialsManagementRoot, SSOLoginRoot,
226 SSOCredentialsRoot, except_to_errdict)
227
228-logger = setup_logging("ubuntu_sso.main")
229+logger = setup_logging("ubuntu_sso.main.windows")
230 NAMED_PIPE_URL = '\\\\.\\pipe\\ubuntu_sso\\%s'
231
232
233@@ -74,9 +73,11 @@
234
235 def blocking(f, app_name, result_cb, error_cb):
236 """Run f in a thread; return or throw an exception thru the callbacks."""
237- d = deferToThread(f, app_name)
238- d.addCallbacks(result_cb, error_cb, callbackArgs=(app_name,),
239- errbackArgs=(app_name,))
240+ d = deferToThread(f)
241+ # the calls in twisted will be called with the args in a diff order,
242+ # in order to follow the linux api, we swap them around with a lambda
243+ d.addCallback(lambda result, app: result_cb(app, result), app_name)
244+ d.addErrback(lambda err, app: error_cb(app, err), app_name)
245
246
247 class RemoteMeta(type):
248@@ -164,7 +165,7 @@
249 logger.debug('SSOLogin: emitting CaptchaGenerationError with '
250 'app_name "%s" and error %r', app_name, raised_error)
251 self.emit_signal('on_captcha_generation_error', app_name,
252- except_to_errdict(raised_error))
253+ except_to_errdict(raised_error.value))
254
255 def generate_captcha(self, app_name, filename):
256 """Call the matching method in the processor."""
257@@ -184,7 +185,7 @@
258 logger.debug('SSOLogin: emitting UserRegistrationError with '
259 'app_name "%s" and error %r', app_name, raised_error)
260 self.emit_signal('on_user_registration_error', app_name,
261- except_to_errdict(raised_error))
262+ except_to_errdict(raised_error.value))
263
264 def register_user(self, app_name, email, password, displayname,
265 captcha_id, captcha_solution):
266@@ -206,7 +207,7 @@
267 logger.debug('SSOLogin: emitting LoginError with '
268 'app_name "%s" and error %r', app_name, raised_error)
269 self.emit_signal('on_login_error', app_name,
270- except_to_errdict(raised_error))
271+ except_to_errdict(raised_error.value))
272
273 def emit_user_not_validated(self, app_name, result):
274 """Signal thrown when the user is not validated."""
275@@ -232,7 +233,7 @@
276 logger.debug('SSOLogin: emitting EmailValidationError with '
277 'app_name "%s" and error %r', app_name, raised_error)
278 self.emit_signal('on_email_validation_error', app_name,
279- except_to_errdict(raised_error))
280+ except_to_errdict(raised_error.value))
281
282 def validate_email(self, app_name, email, password, email_token):
283 """Call the matching method in the processor."""
284@@ -252,7 +253,7 @@
285 logger.debug('SSOLogin: emitting PasswordResetError with '
286 'app_name "%s" and error %r', app_name, raised_error)
287 self.emit_signal('on_password_reset_error', app_name,
288- except_to_errdict(raised_error))
289+ except_to_errdict(raised_error.value))
290
291 def request_password_reset_token(self, app_name, email):
292 """Call the matching method in the processor."""
293@@ -272,7 +273,7 @@
294 logger.debug('SSOLogin: emitting PasswordChangeError with '
295 'app_name "%s" and error %r', app_name, raised_error)
296 self.emit_signal('on_password_change_error', app_name,
297- except_to_errdict(raised_error))
298+ except_to_errdict(raised_error.value))
299
300 def set_new_password(self, app_name, email, token, new_password):
301 """Call the matching method in the processor."""
302@@ -918,9 +919,12 @@
303 @defer.inlineCallbacks
304 def _request_remote_objects(self, root):
305 """Get the status remote object."""
306- self.sso_login = yield root.callRemote('get_sso_login')
307- self.sso_cred = yield root.callRemote('get_sso_credentials')
308- self.cred_management = yield root.callRemote('get_cred_manager')
309+ sso_login = yield root.callRemote('get_sso_login')
310+ self.sso_login = SSOLoginClient(sso_login)
311+ sso_cred = yield root.callRemote('get_sso_credentials')
312+ self.sso_cred = SSOCredentialsClient(sso_cred)
313+ cred_management = yield root.callRemote('get_cred_manager')
314+ self.cred_management = CredentialsManagementClient(cred_management)
315 defer.returnValue(self)
316
317 def connect(self):
318
319=== modified file 'ubuntu_sso/qt/controllers.py'
320--- ubuntu_sso/qt/controllers.py 2011-04-05 13:37:33 +0000
321+++ ubuntu_sso/qt/controllers.py 2011-04-12 15:24:35 +0000
322@@ -16,13 +16,21 @@
323 # with this program. If not, see <http://www.gnu.org/licenses/>.
324 """Controllers with the logic of the UI."""
325
326+import os
327+import tempfile
328+
329+from PyQt4.QtGui import QMessageBox
330 from PyQt4.QtCore import QUrl
331+from twisted.internet.defer import inlineCallbacks, returnValue
332
333 from ubuntu_sso.logger import setup_logging
334+from ubuntu_sso.main.windows import UbuntuSSOClient
335 from ubuntu_sso.utils.ui import (
336 CAPTCHA_SOLUTION_ENTRY,
337 EMAIL1_ENTRY,
338 EMAIL2_ENTRY,
339+ EMAIL_MISMATCH,
340+ EMAIL_INVALID,
341 EXISTING_ACCOUNT_CHOICE_BUTTON,
342 FORGOTTEN_PASSWORD_BUTTON,
343 JOIN_HEADER_LABEL,
344@@ -30,9 +38,12 @@
345 PASSWORD1_ENTRY,
346 PASSWORD2_ENTRY,
347 PASSWORD_HELP,
348+ PASSWORD_MISMATCH,
349+ PASSWORD_TOO_WEAK,
350 SET_UP_ACCOUNT_CHOICE_BUTTON,
351 SET_UP_ACCOUNT_BUTTON,
352 SIGN_IN_BUTTON,
353+ SUCCESS,
354 SURNAME_ENTRY,
355 TC_BUTTON,
356 VERIFY_EMAIL_TITLE,
357@@ -45,6 +56,36 @@
358 logger = setup_logging('ubuntu_sso.controllers')
359
360
361+class BackendController(object):
362+ """Represent a controller that talks with the sso backend."""
363+
364+ def __init__(self):
365+ """Create a new instance."""
366+ self.root = None
367+ self.backends = []
368+
369+ def __del__(self):
370+ """Clean the resources."""
371+ logger.info('Unregistering %s backends', len(self.backends))
372+ for backend in self.backends:
373+ backend.unregister_to_signals()
374+ if self.root is not None:
375+ logger.info('Disconnecting from root.')
376+ self.root.disconnect()
377+
378+ @inlineCallbacks
379+ def get_backend(self):
380+ """Return the backend used by the controller."""
381+ # get the back end from the root
382+ if self.root is None:
383+ self.root = UbuntuSSOClient()
384+ self.root = yield self.root.connect()
385+ backend = self.root.sso_login
386+ yield backend.register_to_signals()
387+ self.backends.append(backend)
388+ returnValue(backend)
389+
390+
391 class ChooseSignInController(object):
392 """Controlled to the ChooseSignIn view/widget."""
393
394@@ -87,12 +128,17 @@
395 view.wizard().next()
396
397
398-class CurrentUserController(object):
399+class CurrentUserController(BackendController):
400 """Controller used in the view that is used to allow the signin."""
401
402- def __init__(self, title=''):
403+ def __init__(self, backend=None, title='', app_name='', message_box=None):
404 """Create a new instance."""
405+ super(CurrentUserController, self).__init__()
406+ if message_box is None:
407+ message_box = QMessageBox
408+ self.message_box = message_box
409 self._title = title
410+ self._app_name = app_name
411
412 def _set_translated_strings(self, view):
413 """Set the translated strings."""
414@@ -102,21 +148,56 @@
415 view.forgot_password_label.setText(FORGOTTEN_PASSWORD_BUTTON)
416 view.sign_in_button.setText(SIGN_IN_BUTTON)
417
418+ def _connect_buttons(self, view, backend):
419+ """Connect the buttons to perform actions."""
420+ view.sign_in_button.clicked.connect(lambda: self.login(view, backend))
421+ # lets add call backs to be execute for the calls we are interested
422+ backend.on_login_error_cb = lambda app, error:\
423+ self.on_login_error(view, app, error)
424+ backend.on_logged_in_cb = lambda app, result:\
425+ self.on_logged_in(view, app, result)
426+
427+ def login(self, view, backend):
428+ """Perform the login using the backend."""
429+ logger.debug('Trying to log in using %s', backend)
430+ # grab the data from the view and call the backend
431+ d = backend.login(self._app_name, view.email, view.password)
432+ d.addErrback(lambda e: self.on_login_error(view, self._app_name, e))
433+
434+ def on_login_error(self, view, app_name, error):
435+ """There was an error when login in."""
436+ # let the user know
437+ logger.error('Got error when login %s, error: %s', app_name, error)
438+ self.message_box.critical(view, app_name, error)
439+
440+ def on_logged_in(self, view, app_name, result):
441+ """We managed to log in."""
442+ logger.info('Logged in for %s', app_name)
443+ self.message_box.information(view, app_name, SUCCESS)
444+ view.wizard.close()
445+
446 # use an ugly name just so have a simlar api as found in PyQt
447 #pylint: disable=C0103
448+ @inlineCallbacks
449 def setupUi(self, view):
450 """Setup the view."""
451+ backend = yield self.get_backend()
452 view.setTitle(self._title)
453 self._set_translated_strings(view)
454+ self._connect_buttons(view, backend)
455 #pylint: enable=C0103
456
457
458-class SetUpAccountController(object):
459+class SetUpAccountController(BackendController):
460 """Conroller for the setup account view."""
461
462 def __init__(self, tos_id=0, validation_id=1, app_name='',
463- help_message=''):
464+ help_message='', message_box=None):
465 """Create a new instance."""
466+ super(SetUpAccountController, self).__init__()
467+ if message_box is None:
468+ message_box = QMessageBox
469+ self.message_box = message_box
470 self._tos_id = tos_id
471 self._validation_id = validation_id
472 self._app_name = app_name
473@@ -157,31 +238,90 @@
474 view.password_edit.textChanged.connect(
475 view.confirm_password_edit.textChanged.emit)
476
477- def _connect_ui_elements(self, view):
478+ def _connect_ui_elements(self, view, backend):
479 """Set the connection of signals."""
480 view.terms_and_conditions_button.clicked.connect(
481 lambda: self.set_next_tos(view))
482 view.set_up_button.clicked.connect(lambda: self.set_next_validation(
483- view))
484+ view, backend))
485 view.password_edit.textChanged.connect(
486 lambda x: self.update_password_strength(x, view))
487 view.terms_and_conditions_check_box.stateChanged.connect(
488 view.set_up_button.setEnabled)
489+ view.captcha_refresh_label.linkActivated.connect(lambda url:\
490+ self._refresh_captcha(view, backend))
491+ # set the callbacks for the captcha generation
492+ backend.on_captcha_generated_cb = lambda app, result:\
493+ self.on_captcha_generated(view, app, result)
494+ backend.on_captcha_generation_error_cb = lambda app, error:\
495+ self.on_captcha_generation_error(view, app, error)
496+ backend.on_user_registration_error_cb = lambda app, error:\
497+ self.on_user_registration_error(view, app, error)
498+ return backend
499+
500+ def _refresh_captcha(self, view, backend):
501+ """Refresh the captcha image shown in the ui."""
502+ # lets clean behind us, do we have the old file arround?
503+ old_file = None
504+ if view.captcha_file and os.path.exists(view.captcha_file):
505+ old_file = view.captcha_file
506+ fd = tempfile.TemporaryFile(mode='r')
507+ file_name = fd.name
508+ view.captcha_file = file_name
509+ d = backend.generate_captcha(self._app_name, file_name)
510+ if old_file:
511+ d.addCallback(lambda x: os.unlink(old_file))
512+ d.addErrback(lambda e: self.on_captcha_generation_error(view,
513+ self._app_name,
514+ e))
515
516 def _set_titles(self, view):
517 """Set the diff titles of the view."""
518 view.setTitle(JOIN_HEADER_LABEL % {'app_name': self._app_name})
519 view.setSubTitle(self._help_message)
520
521+ def on_captcha_generated(self, view, app_name, result):
522+ """A new image was generated."""
523+ view.captcha_image = result
524+
525+ def on_captcha_generation_error(self, view, app_name, error):
526+ """An error ocurred."""
527+ self.message_box.critical(view, app_name, str(error))
528+
529+ def on_user_registration_error(self, view, app_name, error):
530+ """Let the user know we could not register."""
531+ # error are returned as a dict with the data we want to show.
532+ self.message_box.critical(view, error['errtype'], error['email'])
533+
534 def set_next_tos(self, view):
535 """Set the tos page as the next one."""
536 view.next = self._tos_id
537 view.wizard().next()
538
539- def set_next_validation(self, view):
540+ def validate_form(self, view):
541+ """Validate the info of the form and return an error."""
542+ if not self.is_correct_email(view.email):
543+ self.message_box.critical(view, self._app_name, EMAIL_INVALID)
544+ if view.email_edit.text() != view.confirm_email_edit.text():
545+ self.message_box.critical(view, self._app_name, EMAIL_MISMATCH)
546+ return False
547+ if not is_min_required_password(str(view.password_edit.text())):
548+ self.message_box.critical(view, self._app_name, PASSWORD_TOO_WEAK)
549+ return False
550+ if view.password_edit.text() != view.confirm_password_edit.text():
551+ self.message_box.critical(view, self._app_name, PASSWORD_MISMATCH)
552+ return False
553+ return True
554+
555+ def set_next_validation(self, view, backend):
556 """Set the validation as the next page."""
557- view.next = self._validation_id
558- view.wizard().next()
559+ # validate the current info of the form, try to perform the action
560+ # to register the user, and then move foward
561+ if self.validate_form(view):
562+ backend.register_user(self._app_name, view.email, view.password,
563+ view.captcha_id, view.captcha_solution)
564+ view.next = self._validation_id
565+ view.wizard().next()
566
567 def update_password_strength(self, password, view):
568 """Callback used to update the password strenght UI code."""
569@@ -203,12 +343,16 @@
570 return view.password_edit.text() == password
571
572 #pylint: disable=C0103
573+ @inlineCallbacks
574 def setupUi(self, view):
575 """Setup the view."""
576+ # request the backend to be used with the ui
577+ backend = yield self.get_backend()
578+ self._connect_ui_elements(view, backend)
579+ self._refresh_captcha(view, backend)
580 self._set_titles(view)
581 self._set_translated_strings(view)
582 self._set_line_edits_validations(view)
583- self._connect_ui_elements(view)
584 #pylint: enable=C0103
585
586
587
588=== modified file 'ubuntu_sso/qt/gui.py'
589--- ubuntu_sso/qt/gui.py 2011-04-05 13:37:33 +0000
590+++ ubuntu_sso/qt/gui.py 2011-04-12 15:24:35 +0000
591@@ -16,7 +16,6 @@
592 # with this program. If not, see <http://www.gnu.org/licenses/>.
593 """Qt implementation of the UI."""
594
595-from PyQt4 import Qt
596 from PyQt4.QtGui import (
597 QApplication,
598 QColor,
599@@ -27,8 +26,7 @@
600 QPushButton,
601 QStyle,
602 QWizard,
603- QWizardPage,
604- QGraphicsScene)
605+ QWizardPage)
606
607 from ubuntu_sso.logger import setup_logging
608 # pylint: disable=F0401,E0611
609@@ -50,7 +48,7 @@
610
611 # colors used to define the password stenght
612 WEAK_COLOR = QColor(220, 20, 60)
613-MEDIUN_COLOR = QColor(255, 215, 0)
614+MEDIUM_COLOR = QColor(255, 215, 0)
615 STRONG_COLOR = QColor(50, 205, 50)
616
617
618@@ -224,13 +222,11 @@
619 # palettes that will be used to set the colors of the password strenght
620 original_palette = self.ui.strenght_frame.palette()
621 self._original_color = original_palette.background().color()
622+ self.next = -1
623+ self.captcha_id = None
624+ self.captcha_file = None
625 self.controller = controller
626 self.controller.setupUi(self)
627- self.next = -1
628- # create the scene to be used to show the captcha
629- self.image = None
630- self.image_file = None
631- self.scene = QGraphicsScene(self.ui.captcha_view)
632
633 def set_strenght_level(self, level, password):
634 """Set the strenght level colors."""
635@@ -348,6 +344,11 @@
636 return self.ui.confirm_password_edit
637
638 @property
639+ def captcha_refresh_label(self):
640+ """Return the refresh label."""
641+ return self.ui.refresh_label
642+
643+ @property
644 def captcha_solution(self):
645 """Return the provided captcha solution."""
646 return str(self.ui.captcha_solution_edit.text())
647@@ -359,15 +360,14 @@
648
649 def get_captcha_image(self):
650 """Return the path to the captcha image."""
651- return self.image_file
652+ return self.captcha_id
653
654- def set_captcha_image(self, file_path):
655+ def set_captcha_image(self, data):
656 """Set the new image of the captcha."""
657- if self.image:
658- self.scene.removeItem(self.image)
659- self.image_file = file_path
660- pix = QPixmap(self.image_file)
661- self.image = self.scene.addPixmap(pix)
662+ self.captcha_id = data
663+ # lets set the QPixmap for the label
664+ pic = QPixmap(self.captcha_file)
665+ self.ui.captcha_view.setPixmap(pic)
666
667 captcha_image = property(get_captcha_image, set_captcha_image)
668
669@@ -416,7 +416,8 @@
670 self.email_verification = EmailVerificationPage(
671 Ui_EmailVerificationPage(),
672 EmailVerificationController())
673- self.current_user_controller = CurrentUserController(title='Sign in')
674+ self.current_user_controller = CurrentUserController(title='Sign in',
675+ app_name=app_name)
676 self.current_user = CurrentUserSignInPage(Ui_CurrentUserSignInPage(),
677 self.current_user_controller,
678 parent=self)
679@@ -445,15 +446,3 @@
680
681 class UbuntuSSOClientGUI(object):
682 """Ubuntu single sign-on GUI."""
683-
684-if __name__ == '__main__':
685- # pylint: disable=C0103
686- # show the ui
687- import sys
688-
689- app = Qt.QApplication(sys.argv)
690-
691- wizard = UbuntuSSOWizard()
692- wizard.show()
693- # Now we can start it.
694- app.exec_()
695
696=== added file 'ubuntu_sso/qt/tests/show_gui.py'
697--- ubuntu_sso/qt/tests/show_gui.py 1970-01-01 00:00:00 +0000
698+++ ubuntu_sso/qt/tests/show_gui.py 2011-04-12 15:24:35 +0000
699@@ -0,0 +1,33 @@
700+# -*- coding: utf-8 -*-
701+# Author: Manuel de la Pena <manuel@canonical.com>
702+#
703+# Copyright 2011 Canonical Ltd.
704+#
705+# This program is free software: you can redistribute it and/or modify it
706+# under the terms of the GNU General Public License version 3, as published
707+# by the Free Software Foundation.
708+#
709+# This program is distributed in the hope that it will be useful, but
710+# WITHOUT ANY WARRANTY; without even the implied warranties of
711+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
712+# PURPOSE. See the GNU General Public License for more details.
713+#
714+# You should have received a copy of the GNU General Public License along
715+# with this program. If not, see <http://www.gnu.org/licenses/>.
716+"""Script that shows the qt gui."""
717+
718+
719+if __name__ == '__main__':
720+ # pylint: disable=C0103,E1101,F0401
721+ # show the ui
722+ import sys
723+ from PyQt4 import Qt
724+ app = Qt.QApplication(sys.argv)
725+ from qtreactor import qt4reactor
726+ qt4reactor.install()
727+ from twisted.internet import reactor
728+ from ubuntu_sso.qt.gui import UbuntuSSOWizard
729+
730+ wizard = UbuntuSSOWizard(app_name='test')
731+ wizard.show()
732+ reactor.run()
733
734=== renamed file 'ubuntu_sso/qt/tests/test_controllers.py' => 'ubuntu_sso/qt/tests/test_windows.py'
735--- ubuntu_sso/qt/tests/test_controllers.py 2011-04-05 13:45:31 +0000
736+++ ubuntu_sso/qt/tests/test_windows.py 2011-04-12 15:24:35 +0000
737@@ -28,6 +28,7 @@
738 CAPTCHA_SOLUTION_ENTRY,
739 EMAIL1_ENTRY,
740 EMAIL2_ENTRY,
741+ EMAIL_MISMATCH,
742 EXISTING_ACCOUNT_CHOICE_BUTTON,
743 FORGOTTEN_PASSWORD_BUTTON,
744 JOIN_HEADER_LABEL,
745@@ -35,6 +36,8 @@
746 PASSWORD1_ENTRY,
747 PASSWORD2_ENTRY,
748 PASSWORD_HELP,
749+ PASSWORD_TOO_WEAK,
750+ PASSWORD_MISMATCH,
751 SET_UP_ACCOUNT_CHOICE_BUTTON,
752 SET_UP_ACCOUNT_BUTTON,
753 SIGN_IN_BUTTON,
754@@ -95,17 +98,28 @@
755 """Setup tests."""
756 super(CurrentUserControllerTestCase, self).setUp()
757 self.view = self.mocker.mock()
758- self.controller = CurrentUserController(title='the title')
759+ self.backend = self.mocker.mock()
760+ self.connect = self.mocker.mock()
761+ self.signal = self.mocker.replace('PyQt4.QtCore.SIGNAL')
762+ self.controller = CurrentUserController(title='the title',
763+ backend=self.backend)
764
765- def test_setup_ui(self):
766+ def test_translated_strings(self):
767 """test that the ui is correctly set up."""
768- self.view.setTitle(self.controller._title)
769 self.view.email_edit.setPlaceholderText(EMAIL1_ENTRY)
770 self.view.password_edit.setPlaceholderText(PASSWORD1_ENTRY)
771 self.view.forgot_password_label.setText(FORGOTTEN_PASSWORD_BUTTON)
772 self.view.sign_in_button.setText(SIGN_IN_BUTTON)
773 self.mocker.replay()
774- self.controller.setupUi(self.view)
775+ self.controller._set_translated_strings(self.view)
776+
777+ def test_connect_ui(self):
778+ """test that the ui is correctly set up."""
779+ self.view.sign_in_button.clicked.connect(MATCH(callable))
780+ self.backend.on_login_error_cb = MATCH(callable)
781+ self.backend.on_logged_in_cb = MATCH(callable)
782+ self.mocker.replay()
783+ self.controller._connect_buttons(self.view, self.backend)
784
785
786 class SetUpAccountControllerTestCase(MockerTestCase):
787@@ -119,8 +133,10 @@
788 'ubuntu_sso.utils.ui.get_password_strength')
789 self.app_name = 'test'
790 self.help = 'help'
791+ self.message_box = self.mocker.mock()
792 self.controller = SetUpAccountController(app_name=self.app_name,
793- help_message=self.help)
794+ help_message=self.help,
795+ message_box=self.message_box)
796
797 def test_set_translated_strings(self):
798 """Ensure all the strings are set."""
799@@ -148,15 +164,21 @@
800
801 def test_connect_ui_elements(self):
802 """Test that the ui elements are correctly connect."""
803+ backend = self.mocker.mock()
804 self.view.terms_and_conditions_button.clicked.connect(MATCH(callable))
805 self.view.set_up_button.clicked.connect(MATCH(callable))
806 self.view.password_edit.textChanged.connect(MATCH(callable))
807 self.view.set_up_button.setEnabled
808- self.mocker.result(lambda:None)
809+ self.mocker.result(lambda: None)
810 self.view.terms_and_conditions_check_box.stateChanged.connect(
811 MATCH(callable))
812+ self.view.captcha_refresh_label.linkActivated.connect(MATCH(callable))
813+ # set the callbacks for the captcha generatio
814+ backend.on_captcha_generated_cb = MATCH(callable)
815+ backend.on_captcha_generation_error_cb = MATCH(callable)
816+ backend.on_user_registration_error_cb = MATCH(callable)
817 self.mocker.replay()
818- self.controller._connect_ui_elements(self.view)
819+ self.controller._connect_ui_elements(self.view, backend)
820
821 def test_is_correct_password_confirmation_false(self):
822 """Test when the password is not correct."""
823@@ -219,10 +241,86 @@
824
825 def test_set_next_validation(self):
826 """Test the callback."""
827+ email = 'email@example.com'
828+ password = 'Qwerty9923'
829+ captcha_id = 'captcha_id'
830+ captcha_solution = 'captcha_solution'
831+ self.view.email
832+ self.mocker.result(email)
833+ self.view.email_edit.text()
834+ self.mocker.result(email)
835+ self.view.confirm_email_edit.text()
836+ self.mocker.result(email)
837+ self.view.password_edit.text()
838+ self.mocker.result(password)
839+ self.view.password_edit.text()
840+ self.mocker.result(password)
841+ self.view.confirm_password_edit.text()
842+ self.mocker.result(password)
843+ self.view.email
844+ self.mocker.result(email)
845+ self.view.password
846+ self.mocker.result(password)
847+ self.view.captcha_id
848+ self.mocker.result(captcha_id)
849+ self.view.captcha_solution
850+ self.mocker.result(captcha_solution)
851+ backend = self.mocker.mock()
852+ backend.register_user(self.app_name, email, password, captcha_id,
853+ captcha_solution)
854 self.view.next = self.controller._validation_id
855 self.view.wizard().next()
856 self.mocker.replay()
857- self.controller.set_next_validation(self.view)
858+ self.controller.set_next_validation(self.view, backend)
859+
860+ def test_set_next_validation_wrong_email(self):
861+ """Test the callback when there is a wrong email."""
862+ email = 'email@example.com'
863+ self.view.email
864+ self.mocker.result(email)
865+ self.view.email_edit.text()
866+ self.mocker.result('email')
867+ self.view.confirm_email_edit.text()
868+ self.mocker.result('email2')
869+ self.message_box.critical(self.view, self.app_name, EMAIL_MISMATCH)
870+ self.mocker.replay()
871+ self.assertFalse(self.controller.validate_form(self.view))
872+
873+ def test_set_next_validation_not_min_password(self):
874+ """Test the callback with a weak password."""
875+ weak_password = 'weak'
876+ email = 'email@example.com'
877+ self.view.email
878+ self.mocker.result(email)
879+ self.view.email_edit.text()
880+ self.mocker.result(email)
881+ self.view.confirm_email_edit.text()
882+ self.mocker.result(email)
883+ self.view.password_edit.text()
884+ self.mocker.result(weak_password)
885+ self.message_box.critical(self.view, self.app_name, PASSWORD_TOO_WEAK)
886+ self.mocker.replay()
887+ self.assertFalse(self.controller.validate_form(self.view))
888+
889+ def test_set_next_validation_wrong_password(self):
890+ """Test the callback where the password is wrong."""
891+ password = 'Qwerty9923'
892+ email = 'email@example.com'
893+ self.view.email
894+ self.mocker.result(email)
895+ self.view.email_edit.text()
896+ self.mocker.result(email)
897+ self.view.confirm_email_edit.text()
898+ self.mocker.result(email)
899+ self.view.password_edit.text()
900+ self.mocker.result(password)
901+ self.view.password_edit.text()
902+ self.mocker.result(password)
903+ self.view.confirm_password_edit.text()
904+ self.mocker.result('other_password')
905+ self.message_box.critical(self.view, self.app_name, PASSWORD_MISMATCH)
906+ self.mocker.replay()
907+ self.assertFalse(self.controller.validate_form(self.view))
908
909 def test_update_password_strength(self):
910 """Test the callback."""

Subscribers

People subscribed via source and target branches