~ubuntu-branches/ubuntu/oneiric/ubuntu-sso-client/oneiric

« back to all changes in this revision

Viewing changes to ubuntu_sso/qt/controllers.py

  • Committer: Sebastien Bacher
  • Date: 2011-07-25 19:22:29 UTC
  • mfrom: (32.2.3 ubuntu-sso-client)
  • Revision ID: seb128@ubuntu.com-20110725192229-r2kxv3bo1mvk5fj7
* New upstream release.
  - Support new CreateCollection API as well (LP: #805244)
* cleanup leftover from the dh_python2 transition, 
  thanks to Stefano Rivera

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
# Author: Manuel de la Pena <manuel@canonical.com>
 
3
#
 
4
# Copyright 2011 Canonical Ltd.
 
5
#
 
6
# This program is free software: you can redistribute it and/or modify it
 
7
# under the terms of the GNU General Public License version 3, as published
 
8
# by the Free Software Foundation.
 
9
#
 
10
# This program is distributed in the hope that it will be useful, but
 
11
# WITHOUT ANY WARRANTY; without even the implied warranties of
 
12
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
13
# PURPOSE.  See the GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License along
 
16
# with this program.  If not, see <http://www.gnu.org/licenses/>.
 
17
"""Controllers with the logic of the UI."""
 
18
 
 
19
import os
 
20
import StringIO
 
21
import tempfile
 
22
 
 
23
try:
 
24
    from PIL import Image
 
25
except ImportError:
 
26
    import Image
 
27
 
 
28
from PyQt4.QtGui import QMessageBox, QWizard, QPixmap
 
29
from PyQt4.QtCore import QUrl
 
30
from twisted.internet.defer import inlineCallbacks, returnValue
 
31
 
 
32
from ubuntu_sso import NO_OP
 
33
from ubuntu_sso.logger import setup_logging
 
34
from ubuntu_sso.utils.ui import (
 
35
    CAPTCHA_LOAD_ERROR,
 
36
    CAPTCHA_SOLUTION_ENTRY,
 
37
    EMAIL1_ENTRY,
 
38
    EMAIL2_ENTRY,
 
39
    EMAIL_LABEL,
 
40
    EMAIL_MISMATCH,
 
41
    EMAIL_INVALID,
 
42
    ERROR,
 
43
    EXISTING_ACCOUNT_CHOICE_BUTTON,
 
44
    FORGOTTEN_PASSWORD_BUTTON,
 
45
    JOIN_HEADER_LABEL,
 
46
    LOGIN_PASSWORD_LABEL,
 
47
    NAME_ENTRY,
 
48
    PASSWORD1_ENTRY,
 
49
    PASSWORD2_ENTRY,
 
50
    PASSWORD_HELP,
 
51
    PASSWORD_MISMATCH,
 
52
    PASSWORD_TOO_WEAK,
 
53
    REQUEST_PASSWORD_TOKEN_LABEL,
 
54
    RESET_PASSWORD,
 
55
    RESET_CODE_ENTRY,
 
56
    REQUEST_PASSWORD_TOKEN_WRONG_EMAIL,
 
57
    REQUEST_PASSWORD_TOKEN_TECH_ERROR,
 
58
    SET_UP_ACCOUNT_CHOICE_BUTTON,
 
59
    SET_UP_ACCOUNT_BUTTON,
 
60
    SIGN_IN_BUTTON,
 
61
    SUCCESS,
 
62
    TC_BUTTON,
 
63
    TOS_LABEL,
 
64
    TRY_AGAIN_BUTTON,
 
65
    VERIFY_EMAIL_TITLE,
 
66
    VERIFY_EMAIL_CONTENT,
 
67
    YES_TO_TC,
 
68
    is_min_required_password,
 
69
    is_correct_email)
 
70
 
 
71
 
 
72
logger = setup_logging('ubuntu_sso.controllers')
 
73
FAKE_URL = '<a href="http://one.ubuntu.com">%s</a>'
 
74
 
 
75
# pylint: disable=W0511
 
76
# disabled warnings about TODO comments
 
77
 
 
78
 
 
79
class BackendController(object):
 
80
    """Represent a controller that talks with the sso self.backend."""
 
81
 
 
82
    def __init__(self):
 
83
        """Create a new instance."""
 
84
        self.root = None
 
85
        self.view = None
 
86
        self.backend = None
 
87
 
 
88
    def __del__(self):
 
89
        """Clean the resources."""
 
90
        logger.info('Unregistering %s backend', self.backend)
 
91
        self.backend.unregister_to_signals()
 
92
        if self.root is not None:
 
93
            logger.info('Disconnecting from root.')
 
94
            self.root.disconnect()
 
95
 
 
96
    @inlineCallbacks
 
97
    def get_backend(self):
 
98
        """Return the backend used by the controller."""
 
99
        # get the back end from the root
 
100
        if self.root is None:
 
101
            from ubuntu_sso.main.windows import UbuntuSSOClient
 
102
            self.root = UbuntuSSOClient()
 
103
            self.root = yield self.root.connect()
 
104
        self.backend = self.root.sso_login
 
105
        yield self.backend.register_to_signals()
 
106
        returnValue(self.backend)
 
107
 
 
108
 
 
109
class ChooseSignInController(object):
 
110
    """Controlled to the ChooseSignIn view/widget."""
 
111
 
 
112
    def __init__(self, title=''):
 
113
        """Create a new instance to manage the view."""
 
114
        self.view = None
 
115
        self._title = title
 
116
 
 
117
    # use an ugly name just so have a simlar api as found in PyQt
 
118
    #pylint: disable=C0103
 
119
    def setupUi(self, view):
 
120
        """Perform the required actions to set up the ui."""
 
121
        self.view = view
 
122
        self.view.setTitle(self._title)
 
123
        self._set_up_translated_strings()
 
124
        self._connect_buttons()
 
125
    #pylint: enable=C0103
 
126
 
 
127
    def _set_up_translated_strings(self):
 
128
        """Set the correct strings for the UI."""
 
129
        logger.debug('ChooseSignInController._set_up_translated_strings')
 
130
        self.view.ui.existing_account_button.setText(
 
131
                                        EXISTING_ACCOUNT_CHOICE_BUTTON)
 
132
        self.view.ui.setup_account_button.setText(
 
133
                                        SET_UP_ACCOUNT_CHOICE_BUTTON)
 
134
 
 
135
    def _connect_buttons(self):
 
136
        """Connect the buttons to the actions to perform."""
 
137
        logger.debug('ChooseSignInController._connect_buttons')
 
138
        self.view.ui.existing_account_button.clicked.connect(
 
139
                                                    self._set_next_existing)
 
140
        self.view.ui.setup_account_button.clicked.connect(self._set_next_new)
 
141
 
 
142
    def _set_next_existing(self):
 
143
        """Set the next id and fire signal."""
 
144
        logger.debug('ChooseSignInController._set_next_existing')
 
145
        self.view.next = self.view.wizard().current_user_page_id
 
146
        self.view.wizard().next()
 
147
 
 
148
    def _set_next_new(self):
 
149
        """Set the next id and fire signal."""
 
150
        logger.debug('ChooseSignInController._set_next_new')
 
151
        self.view.next = self.view.wizard().setup_account_page_id
 
152
        self.view.wizard().next()
 
153
 
 
154
 
 
155
class CurrentUserController(BackendController):
 
156
    """Controller used in the view that is used to allow the signin."""
 
157
 
 
158
    def __init__(self, backend=None, title='', subtitle='', message_box=None):
 
159
        """Create a new instance."""
 
160
        super(CurrentUserController, self).__init__()
 
161
        if message_box is None:
 
162
            message_box = QMessageBox
 
163
        self.message_box = message_box
 
164
        self._title = title
 
165
        self._subtitle = subtitle
 
166
 
 
167
    def _set_translated_strings(self):
 
168
        """Set the translated strings."""
 
169
        logger.debug('CurrentUserController._set_translated_strings')
 
170
        self.view.ui.email_label.setText(EMAIL_LABEL)
 
171
        self.view.ui.email_edit.setPlaceholderText(EMAIL1_ENTRY)
 
172
        self.view.ui.password_label.setText(LOGIN_PASSWORD_LABEL)
 
173
        self.view.ui.password_edit.setPlaceholderText(PASSWORD1_ENTRY)
 
174
        self.view.ui.forgot_password_label.setText(
 
175
                                    FAKE_URL % FORGOTTEN_PASSWORD_BUTTON)
 
176
        self.view.ui.sign_in_button.setText(SIGN_IN_BUTTON)
 
177
 
 
178
    def _connect_ui(self):
 
179
        """Connect the buttons to perform actions."""
 
180
        logger.debug('CurrentUserController._connect_buttons')
 
181
        self.view.ui.sign_in_button.clicked.connect(self.login)
 
182
        # lets add call backs to be execute for the calls we are interested
 
183
        self.backend.on_login_error_cb = lambda app, error:\
 
184
                                                    self.on_login_error(error)
 
185
        self.backend.on_logged_in_cb = self.on_logged_in
 
186
        self.view.ui.forgot_password_label.linkActivated.connect(
 
187
                                                    self.on_forgotten_password)
 
188
 
 
189
    def login(self):
 
190
        """Perform the login using the self.backend."""
 
191
        logger.debug('CurrentUserController.login')
 
192
        # grab the data from the view and call the backend
 
193
        email = str(self.view.ui.email_edit.text())
 
194
        password = str(self.view.ui.password_edit.text())
 
195
        d = self.backend.login(self.view.wizard().app_name, email, password)
 
196
        d.addErrback(self.on_login_error)
 
197
 
 
198
    def on_login_error(self, error):
 
199
        """There was an error when login in."""
 
200
        # let the user know
 
201
        logger.error('Got error when login %s, error: %s',
 
202
                     self.view.wizard().app_name, error)
 
203
        self.message_box.critical(self.view, self.view.wizard().app_name,
 
204
                                  error['message'])
 
205
 
 
206
    def on_logged_in(self, app_name, result):
 
207
        """We managed to log in."""
 
208
        logger.info('Logged in for %s', app_name)
 
209
        email = str(self.view.ui.email_edit.text())
 
210
        self.view.wizard().loginSuccess.emit(app_name, email)
 
211
        logger.debug('Wizard.loginSuccess emitted.')
 
212
 
 
213
    def on_forgotten_password(self):
 
214
        """Show the user the forgotten password page."""
 
215
        logger.info('Forgotten password')
 
216
        self.view.next = self.view.wizard().forgotten_password_page_id
 
217
        self.view.wizard().next()
 
218
 
 
219
    # use an ugly name just so have a simlar api as found in PyQt
 
220
    #pylint: disable=C0103
 
221
    @inlineCallbacks
 
222
    def setupUi(self, view):
 
223
        """Setup the view."""
 
224
        self.view = view
 
225
        self.backend = yield self.get_backend()
 
226
        self.view.setTitle(self._title)
 
227
        if self._subtitle:
 
228
            self.view.setSubTitle(self._subtitle)
 
229
        self._set_translated_strings()
 
230
        self._connect_ui()
 
231
    #pylint: enable=C0103
 
232
 
 
233
 
 
234
class SetUpAccountController(BackendController):
 
235
    """Conroller for the setup account view."""
 
236
 
 
237
    def __init__(self,  message_box=None):
 
238
        """Create a new instance."""
 
239
        super(SetUpAccountController, self).__init__()
 
240
        if message_box is None:
 
241
            message_box = QMessageBox
 
242
        self.message_box = message_box
 
243
 
 
244
    def _set_translated_strings(self):
 
245
        """Set the different gettext translated strings."""
 
246
        logger.debug('SetUpAccountController._set_translated_strings')
 
247
        # set the translated string
 
248
        self.view.ui.name_label.setText(NAME_ENTRY)
 
249
        self.view.ui.email_label.setText(EMAIL1_ENTRY)
 
250
        self.view.ui.confirm_email_label.setText(EMAIL2_ENTRY)
 
251
        self.view.ui.password_label.setText(PASSWORD1_ENTRY)
 
252
        self.view.ui.confirm_password_label.setText(PASSWORD2_ENTRY)
 
253
        self.view.ui.password_info_label.setText(PASSWORD_HELP)
 
254
        self.view.ui.captcha_solution_edit.setPlaceholderText(
 
255
                                                       CAPTCHA_SOLUTION_ENTRY)
 
256
        self.view.ui.terms_checkbox.setText(
 
257
                        YES_TO_TC % {'app_name': self.view.wizard().app_name})
 
258
        self.view.ui.terms_button.setText(TC_BUTTON)
 
259
        self.view.ui.set_up_button.setText(SET_UP_ACCOUNT_BUTTON)
 
260
 
 
261
    def _set_line_edits_validations(self):
 
262
        """Set the validations to be performed on the edits."""
 
263
        logger.debug('SetUpAccountController._set_line_edits_validations')
 
264
        self.view.set_line_edit_validation_rule(self.view.ui.email_edit,
 
265
                                                is_correct_email)
 
266
        # set the validation rule for the email confirmation
 
267
        self.view.set_line_edit_validation_rule(
 
268
                                            self.view.ui.confirm_email_edit,
 
269
                                            self.is_correct_email_confirmation)
 
270
        # connect the changed text of the password to trigger a changed text
 
271
        # in the confirm so that the validation is redone
 
272
        self.view.ui.email_edit.textChanged.connect(
 
273
                            self.view.ui.confirm_email_edit.textChanged.emit)
 
274
        self.view.set_line_edit_validation_rule(self.view.ui.password_edit,
 
275
                                                is_min_required_password)
 
276
        self.view.set_line_edit_validation_rule(
 
277
                                        self.view.ui.confirm_password_edit,
 
278
                                        self.is_correct_password_confirmation)
 
279
        # same as the above case, lets connect a signal to a signal
 
280
        self.view.ui.password_edit.textChanged.connect(
 
281
                        self.view.ui.confirm_password_edit.textChanged.emit)
 
282
 
 
283
    def _connect_ui_elements(self):
 
284
        """Set the connection of signals."""
 
285
        logger.debug('SetUpAccountController._connect_ui_elements')
 
286
        self.view.ui.terms_button.clicked.connect(self.set_next_tos)
 
287
        self.view.ui.set_up_button.clicked.connect(self.set_next_validation)
 
288
        self.view.ui.terms_checkbox.stateChanged.connect(
 
289
                                        self.view.ui.set_up_button.setEnabled)
 
290
        self.view.ui.refresh_label.linkActivated.connect(lambda url: \
 
291
                                          self._refresh_captcha())
 
292
        # set the callbacks for the captcha generation
 
293
        self.backend.on_captcha_generated_cb = self.on_captcha_generated
 
294
        self.backend.on_captcha_generation_error_cb = lambda app, error: \
 
295
                            self.on_captcha_generation_error(error)
 
296
        self.backend.on_user_registered_cb = self.on_user_registered
 
297
        self.backend.on_user_registration_error_cb = \
 
298
                                            self.on_user_registration_error
 
299
 
 
300
    def _refresh_captcha(self):
 
301
        """Refresh the captcha image shown in the ui."""
 
302
        logger.debug('SetUpAccountController._refresh_captcha')
 
303
        # lets clean behind us, do we have the old file arround?
 
304
        old_file = None
 
305
        if self.view.captcha_file and os.path.exists(self.view.captcha_file):
 
306
            old_file = self.view.captcha_file
 
307
        fd = tempfile.TemporaryFile(mode='r')
 
308
        file_name = fd.name
 
309
        self.view.captcha_file = file_name
 
310
        d = self.backend.generate_captcha(self.view.wizard().app_name,
 
311
                                          file_name)
 
312
        if old_file:
 
313
            d.addCallback(lambda x: os.unlink(old_file))
 
314
        d.addErrback(self.on_captcha_generation_error)
 
315
 
 
316
    def _set_titles(self):
 
317
        """Set the diff titles of the view."""
 
318
        logger.debug('SetUpAccountController._set_titles')
 
319
        wizard = self.view.wizard()
 
320
        self.view.setTitle(JOIN_HEADER_LABEL % {'app_name': wizard.app_name})
 
321
        self.view.setSubTitle(wizard.help_text)
 
322
 
 
323
    def _register_fields(self):
 
324
        """Register the diff fields of the Ui."""
 
325
        self.view.registerField('email_address', self.view.ui.email_edit)
 
326
        self.view.registerField('password', self.view.ui.password_edit)
 
327
 
 
328
    def on_captcha_generated(self, app_name, result):
 
329
        """A new image was generated."""
 
330
        logger.debug('SetUpAccountController.on_captcha_generated')
 
331
        self.view.captcha_id = result
 
332
        # HACK: First, let me apologize before hand, you can mention my mother
 
333
        # if needed I would do the same (mandel)
 
334
        # In an ideal world we could use the Qt plug-in for the images so that
 
335
        # we could load jpgs etc.. but this won't work when the app has been
 
336
        # brozen win py2exe using bundle_files=1
 
337
        # The main issue is that Qt will complain about the thread not being
 
338
        # the correct one when performing a moveToThread operation which is
 
339
        # done either by a setParent or something within the qtreactor, PIL
 
340
        # in this case does solve the issue. Sorry :(
 
341
        pil_image = Image.open(self.view.captcha_file)
 
342
        string_io = StringIO.StringIO()
 
343
        pil_image.save(string_io, format='png')
 
344
        pixmap_image = QPixmap()
 
345
        pixmap_image.loadFromData(string_io.getvalue())
 
346
        self.view.captcha_image = pixmap_image
 
347
 
 
348
    def on_captcha_generation_error(self, error):
 
349
        """An error ocurred."""
 
350
        logger.debug('SetUpAccountController.on_captcha_generation_error')
 
351
        self.message_box.critical(self.view, self.view.wizard().app_name,
 
352
            CAPTCHA_LOAD_ERROR)
 
353
 
 
354
    def on_user_registration_error(self, app_name, error):
 
355
        """Let the user know we could not register."""
 
356
        logger.debug('SetUpAccountController.on_user_registration_error')
 
357
        # errors are returned as a dict with the data we want to show.
 
358
        self._refresh_captcha()
 
359
        errors = [v for _, v in sorted(error.iteritems())]
 
360
        self.message_box.critical(
 
361
            self.view,
 
362
            self.view.wizard().app_name,
 
363
            '\n'.join(errors))
 
364
 
 
365
    def on_user_registered(self, app_name, result):
 
366
        """Execute when the user did register."""
 
367
        logger.debug('SetUpAccountController.on_user_registered')
 
368
        self.view.next = self.view.wizard().email_verification_page_id
 
369
        self.view.wizard().next()
 
370
 
 
371
    def set_next_tos(self):
 
372
        """Set the tos page as the next one."""
 
373
        logger.debug('SetUpAccountController.set_next_tos')
 
374
        self.view.next = self.view.wizard().tos_page_id
 
375
        self.view.wizard().next()
 
376
 
 
377
    def validate_form(self):
 
378
        """Validate the info of the form and return an error."""
 
379
        logger.debug('SetUpAccountController.validate_form')
 
380
        email = str(self.view.ui.email_edit.text())
 
381
        confirm_email = str(self.view.ui.confirm_email_edit.text())
 
382
        password = str(self.view.ui.password_edit.text())
 
383
        confirm_password = str(self.view.ui.confirm_password_edit.text())
 
384
        if not is_correct_email(email):
 
385
            self.message_box.critical(self.view, self.view.wizard().app_name,
 
386
                                      EMAIL_INVALID)
 
387
        if email != confirm_email:
 
388
            self.message_box.critical(self.view, self.view.wizard().app_name,
 
389
                                      EMAIL_MISMATCH)
 
390
            return False
 
391
        if not is_min_required_password(password):
 
392
            self.message_box.critical(self.view, self.view.wizard().app_name,
 
393
                                      PASSWORD_TOO_WEAK)
 
394
            return False
 
395
        if password != confirm_password:
 
396
            self.message_box.critical(self.view, self.view.wizard().app_name,
 
397
                                      PASSWORD_MISMATCH)
 
398
            return False
 
399
        return True
 
400
 
 
401
    def set_next_validation(self):
 
402
        """Set the validation as the next page."""
 
403
        logger.debug('SetUpAccountController.set_next_validation')
 
404
        email = str(self.view.ui.email_edit.text())
 
405
        password = str(self.view.ui.password_edit.text())
 
406
        name = str(self.view.ui.name_edit.text())
 
407
        captcha_id = self.view.captcha_id
 
408
        captcha_solution = str(self.view.ui.captcha_solution_edit.text())
 
409
        # validate the current info of the form, try to perform the action
 
410
        # to register the user, and then move foward
 
411
        if self.validate_form():
 
412
            self.backend.register_user(self.view.wizard().app_name, email,
 
413
                                       password, name, captcha_id,
 
414
                                       captcha_solution)
 
415
        # Update validation page's title, which contains the email
 
416
        p_id = self.view.wizard().email_verification_page_id
 
417
        self.view.wizard().page(p_id).controller.set_titles()
 
418
 
 
419
    def is_correct_email(self, email_address):
 
420
        """Return if the email is correct."""
 
421
        logger.debug('SetUpAccountController.is_correct_email')
 
422
        return '@' in email_address
 
423
 
 
424
    def is_correct_email_confirmation(self, email_address):
 
425
        """Return that the email is the same."""
 
426
        logger.debug('SetUpAccountController.is_correct_email_confirmation')
 
427
        return self.view.ui.email_edit.text() == email_address
 
428
 
 
429
    def is_correct_password_confirmation(self, password):
 
430
        """Return that the passwords are correct."""
 
431
        logger.debug('SetUpAccountController.is_correct_password_confirmation')
 
432
        return self.view.ui.password_edit.text() == password
 
433
 
 
434
    #pylint: disable=C0103
 
435
    @inlineCallbacks
 
436
    def setupUi(self, view):
 
437
        """Setup the view."""
 
438
        self.view = view
 
439
        # request the backend to be used with the ui
 
440
        self.backend = yield self.get_backend()
 
441
        self._connect_ui_elements()
 
442
        self._refresh_captcha()
 
443
        self._set_titles()
 
444
        self._set_translated_strings()
 
445
        self._set_line_edits_validations()
 
446
        self._register_fields()
 
447
    #pylint: enable=C0103
 
448
 
 
449
 
 
450
class TosController(object):
 
451
    """Controller used for the tos page."""
 
452
 
 
453
    def __init__(self, title='', subtitle='', tos_url=''):
 
454
        """Create a new instance."""
 
455
        self.view = None
 
456
        self._title = title
 
457
        self._subtitle = subtitle
 
458
        self._tos_url = tos_url
 
459
 
 
460
    #pylint: disable=C0103
 
461
    def setupUi(self, view):
 
462
        """Set up the ui."""
 
463
        self.view = view
 
464
        self.view.setTitle(self._title)
 
465
        self.view.setSubTitle(self._subtitle)
 
466
        # load the tos page
 
467
        self.view.ui.terms_webkit.load(QUrl(self._tos_url))
 
468
        self.view.ui.tos_link_label.setText(
 
469
            TOS_LABEL %
 
470
            {'url': self._tos_url})
 
471
    #pylint: enable=C0103
 
472
 
 
473
 
 
474
class EmailVerificationController(BackendController):
 
475
    """Controller used for the verification page."""
 
476
 
 
477
    def _set_translated_strings(self):
 
478
        """Set the trnaslated strings."""
 
479
        logger.debug('EmailVerificationController._set_translated_strings')
 
480
        self.view.ui.verification_code_edit.setPlaceholderText(
 
481
                                                        VERIFY_EMAIL_TITLE)
 
482
 
 
483
    def _connect_ui_elements(self):
 
484
        """Set the connection of signals."""
 
485
        logger.debug('EmailVerificationController._connect_ui_elements')
 
486
        self.view.next_button.clicked.connect(self.validate_email)
 
487
        self.backend.on_email_validated_cb = lambda app, result: \
 
488
                            self.on_email_validated(app)
 
489
        self.backend.on_email_validation_error_cb = \
 
490
                                                self.on_email_validation_error
 
491
 
 
492
    def _set_titles(self):
 
493
        """Set the different titles."""
 
494
        logger.debug('EmailVerificationController._set_titles')
 
495
        self.view.setTitle(VERIFY_EMAIL_TITLE)
 
496
        self.view.setSubTitle(VERIFY_EMAIL_CONTENT % {
 
497
            "app_name": self.view.wizard().app_name,
 
498
            "email": self.view.wizard().field("email_address").toString(),
 
499
            })
 
500
 
 
501
    def set_titles(self):
 
502
        """This class needs to have a public set_titles.
 
503
 
 
504
        Since the subtitle contains data that is only known after SetupAccount
 
505
        and _set_titles is only called on initialization.
 
506
        """
 
507
        self._set_titles()
 
508
 
 
509
    #pylint: disable=C0103
 
510
    @inlineCallbacks
 
511
    def setupUi(self, view):
 
512
        """Setup the view."""
 
513
        self.view = view
 
514
        self.backend = yield self.get_backend()
 
515
        self._set_titles()
 
516
        self._set_translated_strings()
 
517
        self._connect_ui_elements()
 
518
    #pylint: enable=C0103
 
519
 
 
520
    def validate_email(self):
 
521
        """Call the next action."""
 
522
        logger.debug('EmailVerificationController.validate_email')
 
523
        email = str(self.view.wizard().field('email_address').toString())
 
524
        password = str(self.view.wizard().field('password').toString())
 
525
        code = str(self.view.ui.verification_code_edit.text())
 
526
        self.backend.validate_email(self.view.wizard().app_name, email,
 
527
                                    password, code)
 
528
 
 
529
    def on_email_validated(self, app_name):
 
530
        """Signal thrown after the email is validated."""
 
531
        logger.info('EmailVerificationController.on_email_validated')
 
532
        email = self.view.wizard().field('email_address').toString()
 
533
        self.view.wizard().registrationSuccess.emit(app_name, email)
 
534
 
 
535
    def on_email_validation_error(self, app_name, raised_error):
 
536
        """Signal thrown when there's a problem validating the email."""
 
537
 
 
538
 
 
539
class ErrorController(object):
 
540
    """Controller used for the error page."""
 
541
 
 
542
    def __init__(self):
 
543
        """Create a new instance."""
 
544
        super(ErrorController, self).__init__()
 
545
        self.view = None
 
546
 
 
547
    #pylint: disable=C0103
 
548
    def setupUi(self, view):
 
549
        """Setup the view."""
 
550
        self.view = view
 
551
        self.view.next = -1
 
552
        self.view.ui.error_message_label.setText(ERROR)
 
553
    #pylint: enable=C0103
 
554
 
 
555
 
 
556
class ForgottenPasswordController(BackendController):
 
557
    """Controller used to deal with the forgotten pwd page."""
 
558
 
 
559
    def __init__(self):
 
560
        """Create a new instance."""
 
561
        super(ForgottenPasswordController, self).__init__()
 
562
 
 
563
    def _register_fields(self):
 
564
        """Register the fields of the wizard page."""
 
565
        self.view.registerField('email_address',
 
566
                                self.view.email_address_line_edit)
 
567
 
 
568
    def _set_translated_strings(self):
 
569
        """Set the translated strings in the view."""
 
570
        self.view.forgotted_password_intro_label.setText(
 
571
                                    REQUEST_PASSWORD_TOKEN_LABEL % {'app_name':
 
572
                                    self.view.wizard().app_name})
 
573
        self.view.email_address_label.setText(EMAIL_LABEL)
 
574
        self.view.send_button.setText(RESET_PASSWORD)
 
575
        self.view.try_again_button.setText(TRY_AGAIN_BUTTON)
 
576
 
 
577
    def _set_enhanced_line_edit(self):
 
578
        """Set the extra logic to the line edits."""
 
579
        self.view.set_line_edit_validation_rule(
 
580
                                           self.view.email_address_line_edit,
 
581
                                           is_correct_email)
 
582
 
 
583
    def _connect_ui(self):
 
584
        """Connect the diff signals from the Ui."""
 
585
        self.view.send_button.clicked.connect(
 
586
                            lambda: self.backend.request_password_reset_token(
 
587
                                                self.view.wizard().app_name,
 
588
                                                self.view.email_address))
 
589
        self.view.try_again_button.clicked.connect(self.on_try_again)
 
590
        # set the backend callbacks to be used
 
591
        self.backend.on_password_reset_token_sent_cb = lambda app, result:\
 
592
                                    self.on_password_reset_token_sent()
 
593
        self.backend.on_password_reset_error_cb = self.on_password_reset_error
 
594
 
 
595
    def on_try_again(self):
 
596
        """Set back the widget to the initial state."""
 
597
        self.view.error_label.setVisible(False)
 
598
        self.view.try_again_widget.setVisible(False)
 
599
        self.view.email_widget.setVisible(True)
 
600
 
 
601
    def on_password_reset_token_sent(self):
 
602
        """Action taken when we managed to get the password reset done."""
 
603
        # ignore the result and move to the reset page
 
604
        self.view.next = self.view.wizard().reset_password_page_id
 
605
        self.view.wizard().next()
 
606
 
 
607
    def on_password_reset_error(self, app_name, error):
 
608
        """Action taken when there was an error requesting the reset."""
 
609
        if error['errtype'] == 'ResetPasswordTokenError':
 
610
            # the account provided is wrong, lets tell the user to try
 
611
            # again
 
612
            self.view.error_label.setText(REQUEST_PASSWORD_TOKEN_WRONG_EMAIL)
 
613
            self.view.error_label.setVisible(True)
 
614
        else:
 
615
            # ouch, I dont know what went wrong, tell the user to try later
 
616
            self.view.email_widget.setVisible(False)
 
617
            self.view.forgotted_password_intro_label.setVisible(False)
 
618
            self.view.try_again_wisget.setVisible(True)
 
619
            # set the error message
 
620
            self.view.error_label.setText(REQUEST_PASSWORD_TOKEN_TECH_ERROR)
 
621
 
 
622
    #pylint: disable=C0103
 
623
    @inlineCallbacks
 
624
    def setupUi(self, view):
 
625
        """Setup the view."""
 
626
        self.view = view
 
627
        self.backend = yield self.get_backend()
 
628
        # hide the error label
 
629
        self.view.error_label.setVisible(False)
 
630
        self.view.try_again_widget.setVisible(False)
 
631
        self._set_translated_strings()
 
632
        self._connect_ui()
 
633
        self._set_enhanced_line_edit()
 
634
        self._register_fields()
 
635
    #pylint: enable=C0103
 
636
 
 
637
 
 
638
class ResetPasswordController(BackendController):
 
639
    """Controller used to deal with reseintg the password."""
 
640
 
 
641
    def __init__(self):
 
642
        """Create a new instance."""
 
643
        super(ResetPasswordController, self).__init__()
 
644
 
 
645
    def _set_translated_strings(self):
 
646
        """Translate the diff strings used in the app."""
 
647
        self.view.ui.reset_code_line_edit.setPlaceholderText(RESET_CODE_ENTRY)
 
648
        self.view.ui.password_line_edit.setPlaceholderText(PASSWORD1_ENTRY)
 
649
        self.view.ui.confirm_password_line_edit.setPlaceholderText(
 
650
                                                        PASSWORD2_ENTRY)
 
651
        self.view.ui.reset_password_button.setText(RESET_PASSWORD)
 
652
        self.view.setSubTitle(PASSWORD_HELP)
 
653
 
 
654
    def _connect_ui(self):
 
655
        """Connect the different ui signals."""
 
656
        self.view.ui.reset_password_button.clicked.connect(
 
657
                                                    self.set_new_password)
 
658
        self.backend.on_password_changed_cb = self.on_password_changed
 
659
        self.backend.on_password_change_error_cb = \
 
660
                                                self.on_password_change_error
 
661
 
 
662
    def _add_line_edits_validations(self):
 
663
        """Add the validations to be use by the line edits."""
 
664
        self.view.set_line_edit_validation_rule(
 
665
                                           self.view.ui.password_line_edit,
 
666
                                           is_min_required_password)
 
667
        self.view.set_line_edit_validation_rule(
 
668
                                    self.view.ui.confirm_password_line_edit,
 
669
                                    self.is_correct_password_confirmation)
 
670
        # same as the above case, lets connect a signal to a signal
 
671
        self.view.ui.password_line_edit.textChanged.connect(
 
672
                     self.view.ui.confirm_password_line_edit.textChanged.emit)
 
673
 
 
674
    def on_password_changed(self, app_name, result):
 
675
        """Let user know that the password was changed."""
 
676
 
 
677
    def on_password_change_error(self, app_name, error):
 
678
        """Let the user know that there was an error."""
 
679
 
 
680
    def set_new_password(self):
 
681
        """Request a new password to be set."""
 
682
        app_name = self.view.wizard().app_name
 
683
        email = str(self.view.wizard().field('email_address').toString())
 
684
        code = str(self.view.ui.reset_code_line_edit.text())
 
685
        password = str(self.view.ui.password_line_edit.text())
 
686
        logger.info('Settig new password for %s and email %s with code %s',
 
687
                    app_name, email, code)
 
688
        self.backend.set_new_password(app_name, email, code, password)
 
689
 
 
690
    def is_correct_password_confirmation(self, password):
 
691
        """Return if the password is correct."""
 
692
        return self.view.ui.password_line_edit.text() == password
 
693
 
 
694
    #pylint: disable=C0103
 
695
    @inlineCallbacks
 
696
    def setupUi(self, view):
 
697
        """Setup the view."""
 
698
        self.view = view
 
699
        self.backend = yield self.get_backend()
 
700
        self._set_translated_strings()
 
701
        self._connect_ui()
 
702
        self._add_line_edits_validations()
 
703
    #pylint: enable=C0103
 
704
 
 
705
 
 
706
class SuccessController(object):
 
707
    """Controller used for the success page."""
 
708
 
 
709
    def __init__(self):
 
710
        """Create a new instance."""
 
711
        self.view = None
 
712
 
 
713
    #pylint: disable=C0103
 
714
    def setupUi(self, view):
 
715
        """Setup the view."""
 
716
        self.view = view
 
717
        self.view.next = -1
 
718
        self.view.ui.success_message_label.setText(SUCCESS)
 
719
    #pylint: enable=C0103
 
720
 
 
721
 
 
722
class UbuntuSSOWizardController(object):
 
723
    """Controller used for the overall wizard."""
 
724
 
 
725
    def __init__(self, login_success_callback=NO_OP,
 
726
                 registration_success_callback=NO_OP,
 
727
                 user_cancellation_callback=NO_OP):
 
728
        """Create a new instance."""
 
729
        self.view = None
 
730
        self.login_success_callback = login_success_callback
 
731
        self.registration_success_callback = registration_success_callback
 
732
        self.user_cancellation_callback = user_cancellation_callback
 
733
 
 
734
    def on_user_cancelation(self):
 
735
        """Process the cancel action."""
 
736
        logger.debug('UbuntuSSOWizardController.on_user_cancelation')
 
737
        self.user_cancellation_callback(self.view.app_name)
 
738
        self.view.close()
 
739
 
 
740
    @inlineCallbacks
 
741
    def on_login_success(self, app_name, email):
 
742
        """Process the success of a login."""
 
743
        logger.debug('UbuntuSSOWizardController.on_login_success')
 
744
        result = yield self.login_success_callback(str(app_name), str(email))
 
745
        logger.debug('Result from callback is %s', result)
 
746
        if result == 0:
 
747
            logger.info('Success in calling the given success_callback')
 
748
            self.show_success_message()
 
749
        else:
 
750
            logger.info('Error in calling the given success_callback')
 
751
            self.show_error_message()
 
752
 
 
753
    @inlineCallbacks
 
754
    def on_registration_success(self, app_name, email):
 
755
        """Process the success of a registration."""
 
756
        logger.debug('UbuntuSSOWizardController.on_registration_success')
 
757
        result = yield self.registration_success_callback(str(app_name),
 
758
                                                          str(email))
 
759
        # TODO: what to do?
 
760
        logger.debug('Result from callback is %s', result)
 
761
        if result == 0:
 
762
            logger.info('Success in calling the given registration_callback')
 
763
            self.show_success_message()
 
764
        else:
 
765
            logger.info('Success in calling the given registration_callback')
 
766
            self.show_error_message()
 
767
 
 
768
    def show_success_message(self):
 
769
        """Show the success message in the view."""
 
770
        logger.info('Showing success message.')
 
771
        # get the id of the success page, set it as the next id of the
 
772
        # current page and let the wizard move to the next step
 
773
        self.view.currentPage().next = self.view.success_page_id
 
774
        self.view.next()
 
775
        # show the finish button but with a close message
 
776
        buttons_layout = []
 
777
        buttons_layout.append(QWizard.Stretch)
 
778
        buttons_layout.append(QWizard.FinishButton)
 
779
        self.view.setButtonLayout(buttons_layout)
 
780
 
 
781
    def show_error_message(self):
 
782
        """Show the error page in the view."""
 
783
        logger.info('Showing error message.')
 
784
        # similar to the success page but using the error id
 
785
        self.view.currentPage().next = self.view.error_page_id
 
786
        self.view.next()
 
787
        # show the finish button but with a close message
 
788
        buttons_layout = []
 
789
        buttons_layout.append(QWizard.Stretch)
 
790
        buttons_layout.append(QWizard.FinishButton)
 
791
        self.view.setButtonLayout(buttons_layout)
 
792
 
 
793
    #pylint: disable=C0103
 
794
    def setupUi(self, view):
 
795
        """Setup the view."""
 
796
        self.view = view
 
797
        self.view.setWizardStyle(QWizard.ModernStyle)
 
798
        self.view.button(QWizard.CancelButton).clicked.connect(
 
799
                                                    self.on_user_cancelation)
 
800
        self.view.loginSuccess.connect(self.on_login_success)
 
801
        self.view.registrationSuccess.connect(self.on_registration_success)
 
802
    #pylint: enable=C0103