~mvo/ubuntu-sso-client/strawman-lp711413

« back to all changes in this revision

Viewing changes to ubuntu_sso/qt/controllers.py

Added the way to reset the password.

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
    CAPTCHA_SOLUTION_ENTRY,
33
33
    EMAIL1_ENTRY,
34
34
    EMAIL2_ENTRY,
 
35
    EMAIL_LABEL,
35
36
    EMAIL_MISMATCH,
36
37
    EMAIL_INVALID,
37
38
    ERROR,
44
45
    PASSWORD_HELP,
45
46
    PASSWORD_MISMATCH,
46
47
    PASSWORD_TOO_WEAK,
 
48
    REQUEST_PASSWORD_TOKEN_LABEL,
 
49
    RESET_PASSWORD,
 
50
    RESET_CODE_ENTRY,
 
51
    REQUEST_PASSWORD_TOKEN_WRONG_EMAIL,
 
52
    REQUEST_PASSWORD_TOKEN_TECH_ERROR,
47
53
    SET_UP_ACCOUNT_CHOICE_BUTTON,
48
54
    SET_UP_ACCOUNT_BUTTON,
49
55
    SIGN_IN_BUTTON,
50
56
    SURNAME_ENTRY,
51
57
    SUCCESS,
52
58
    TC_BUTTON,
 
59
    TRY_AGAIN_BUTTON,
53
60
    VERIFY_EMAIL_TITLE,
54
61
    VERIFY_EMAIL_CONTENT,
55
62
    YES_TO_TC,
56
63
    get_password_strength,
57
 
    is_min_required_password)
 
64
    is_min_required_password,
 
65
    is_correct_email)
58
66
 
59
67
 
60
68
logger = setup_logging('ubuntu_sso.controllers')
142
150
class CurrentUserController(BackendController):
143
151
    """Controller used in the view that is used to allow the signin."""
144
152
 
145
 
    def __init__(self, backend=None, title='', app_name='', message_box=None):
 
153
    def __init__(self, backend=None, title='', message_box=None):
146
154
        """Create a new instance."""
147
155
        super(CurrentUserController, self).__init__()
148
156
        if message_box is None:
149
157
            message_box = QMessageBox
150
158
        self.message_box = message_box
151
159
        self._title = title
152
 
        self._app_name = app_name
153
160
 
154
161
    def _set_translated_strings(self, view):
155
162
        """Set the translated strings."""
175
182
        """Perform the login using the backend."""
176
183
        logger.debug('CurrentUserController.login')
177
184
        # grab the data from the view and call the backend
178
 
        d = backend.login(self._app_name, view.email, view.password)
179
 
        d.addErrback(lambda e: self.on_login_error(view, self._app_name, e))
 
185
        d = backend.login(view.wizard().app_name, view.email, view.password)
 
186
        d.addErrback(lambda e: self.on_login_error(view, e))
180
187
 
181
 
    def on_login_error(self, view, app_name, error):
 
188
    def on_login_error(self, view, error):
182
189
        """There was an error when login in."""
183
190
        # let the user know
184
191
        logger.error('Got error when login %s, error: %s', app_name, error)
185
 
        self.message_box.critical(view, app_name, str(error))
 
192
        self.message_box.critical(view, view.wizard().app_name, str(error))
186
193
 
187
194
    def on_logged_in(self, view, app_name, result):
188
195
        """We managed to log in."""
211
218
class SetUpAccountController(BackendController):
212
219
    """Conroller for the setup account view."""
213
220
 
214
 
    def __init__(self, app_name='', help_message='', message_box=None):
 
221
    def __init__(self,  message_box=None):
215
222
        """Create a new instance."""
216
223
        super(SetUpAccountController, self).__init__()
217
224
        if message_box is None:
218
225
            message_box = QMessageBox
219
226
        self.message_box = message_box
220
 
        self._app_name = app_name
221
 
        self._help_message = help_message
222
227
 
223
228
    def _set_translated_strings(self, view):
224
229
        """Set the different gettext translated strings."""
233
238
        view.password_info_label.setText(PASSWORD_HELP)
234
239
        view.captcha_solution_edit.setPlaceholderText(CAPTCHA_SOLUTION_ENTRY)
235
240
        view.terms_and_conditions_check_box.setText(
236
 
                                    YES_TO_TC % {'app_name': self._app_name})
 
241
                            YES_TO_TC % {'app_name': view.wizard().app_name})
237
242
        view.terms_and_conditions_button.setText(TC_BUTTON)
238
243
        view.set_up_button.setText(SET_UP_ACCOUNT_BUTTON)
239
244
 
240
245
    def _set_line_edits_validations(self, view):
241
246
        """Set the validations to be performed on the edits."""
242
247
        logger.debug('SetUpAccountController._set_line_edits_validations')
243
 
        view.set_line_edit_validation_rule(view.email_edit,
244
 
                                           self.is_correct_email)
 
248
        view.set_line_edit_validation_rule(view.email_edit, is_correct_email)
245
249
        # set the validation rule for the email confirmation
246
250
        view.set_line_edit_validation_rule(view.confirm_email_edit,
247
251
                         lambda x: self.is_correct_email_confirmation(x, view))
291
295
        fd = tempfile.TemporaryFile(mode='r')
292
296
        file_name = fd.name
293
297
        view.captcha_file = file_name
294
 
        d = backend.generate_captcha(self._app_name, file_name)
 
298
        d = backend.generate_captcha(view.wizard().app_name, file_name)
295
299
        if old_file:
296
300
            d.addCallback(lambda x: os.unlink(old_file))
297
 
        d.addErrback(lambda e: self.on_captcha_generation_error(view,
298
 
                                                                self._app_name,
299
 
                                                                e))
 
301
        d.addErrback(lambda e: self.on_captcha_generation_error(view, e))
300
302
 
301
303
    def _set_titles(self, view):
302
304
        """Set the diff titles of the view."""
303
305
        logger.debug('SetUpAccountController._set_titles')
304
 
        view.setTitle(JOIN_HEADER_LABEL % {'app_name': self._app_name})
305
 
        view.setSubTitle(self._help_message)
 
306
        view.setTitle(JOIN_HEADER_LABEL % {'app_name': view.wizard().app_name})
 
307
        view.setSubTitle(view.wizard().help_text)
306
308
 
307
309
    def on_captcha_generated(self, view, app_name, result):
308
310
        """A new image was generated."""
324
326
        pixmap_image.loadFromData(string_io.getvalue())
325
327
        view.captcha_image = pixmap_image
326
328
 
327
 
    def on_captcha_generation_error(self, view, app_name, error):
 
329
    def on_captcha_generation_error(self, view, error):
328
330
        """An error ocurred."""
329
331
        logger.debug('SetUpAccountController.on_captcha_generation_error')
330
 
        self.message_box.critical(view, app_name, str(error))
 
332
        self.message_box.critical(view, view.wizard().app_name, str(error))
331
333
 
332
334
    def on_user_registration_error(self, view, app_name, error):
333
335
        """Let the user know we could not register."""
349
351
    def validate_form(self, view):
350
352
        """Validate the info of the form and return an error."""
351
353
        logger.debug('SetUpAccountController.validate_form')
352
 
        if not self.is_correct_email(view.email):
353
 
            self.message_box.critical(view, self._app_name, EMAIL_INVALID)
 
354
        if not is_correct_email(view.email):
 
355
            self.message_box.critical(view, view.wizard().app_name,
 
356
                                      EMAIL_INVALID)
354
357
        if view.email_edit.text() != view.confirm_email_edit.text():
355
 
            self.message_box.critical(view, self._app_name, EMAIL_MISMATCH)
 
358
            self.message_box.critical(view, view.wizard().app_name,
 
359
                                      EMAIL_MISMATCH)
356
360
            return False
357
361
        if not is_min_required_password(str(view.password_edit.text())):
358
 
            self.message_box.critical(view, self._app_name, PASSWORD_TOO_WEAK)
 
362
            self.message_box.critical(view, view.wizard().app_name,
 
363
                                      PASSWORD_TOO_WEAK)
359
364
            return False
360
365
        if view.password_edit.text() != view.confirm_password_edit.text():
361
 
            self.message_box.critical(view, self._app_name, PASSWORD_MISMATCH)
 
366
            self.message_box.critical(view, view.wizard().app_name,
 
367
                                      PASSWORD_MISMATCH)
362
368
            return False
363
369
        return True
364
370
 
368
374
        # validate the current info of the form, try to perform the action
369
375
        # to register the user, and then move foward
370
376
        if self.validate_form(view):
371
 
            backend.register_user(self._app_name, view.email, view.password,
372
 
                                  view.captcha_id, view.captcha_solution)
 
377
            backend.register_user(view.wizard().app_name, view.email,
 
378
                                  view.password, view.captcha_id,
 
379
                                  view.captcha_solution)
373
380
            view.next = view.wizard().email_verification_page_id
374
381
            view.wizard().next()
375
382
 
381
388
        logger.info('Password strengh is %s', strengh)
382
389
        view.set_strenght_level(strengh, password)
383
390
 
384
 
    def is_correct_email(self, email_address):
385
 
        """Return if the email is correct."""
386
 
        logger.debug('SetUpAccountController.is_correct_email')
387
 
        return '@' in email_address
388
 
 
389
391
    def is_correct_email_confirmation(self, email_address, view):
390
392
        """Return that the email is the same."""
391
393
        logger.debug('SetUpAccountController.is_correct_email_confirmation')
413
415
class TosController(object):
414
416
    """Controller used for the tos page."""
415
417
 
416
 
    def __init__(self, title='', subtitle='', tos_url='http://www.ubuntu.com'):
 
418
    def __init__(self, title='', subtitle='', tos_url=''):
417
419
        """Create a new instance."""
418
420
        self._title = title
419
 
        self._subtitle = subtitle
 
421
        self._subtitle = subtitle   
420
422
        self._tos_url = tos_url
421
423
 
422
424
    #pylint: disable=C0103
476
478
    #pylint: enable=C0103
477
479
 
478
480
 
 
481
class ForgottenPasswordController(BackendController):
 
482
    """Controller used to deal with the forgotten pwd page."""
 
483
 
 
484
    def __init__(self):
 
485
        """Create a new instance."""
 
486
        super(ForgottenPasswordController, self).__init__()
 
487
 
 
488
    def _register_fields(self, view):
 
489
        """Register the fields of the wizard page."""
 
490
        view.registerField('email_address', view.email_address_line_edit)
 
491
 
 
492
    def _set_translated_strings(self, view):
 
493
        """Set the translated strings in the view."""
 
494
        view.forgotted_password_intro_label.setText(
 
495
                                    REQUEST_PASSWORD_TOKEN_LABEL % {'app_name':
 
496
                                    view.wizard().app_name})
 
497
        view.email_address_label.setText(EMAIL_LABEL)
 
498
        view.send_button.setText(RESET_PASSWORD)
 
499
        view.try_again_button.setText(TRY_AGAIN_BUTTON)
 
500
 
 
501
    def _set_enhanced_line_edir(self, view):
 
502
        """Set the extra logic to the line edits."""
 
503
        view.set_line_edit_validation_rule(view.email_address_line_edit,
 
504
                                           is_correct_email)
 
505
 
 
506
    def _connect_ui(self, view, backend):
 
507
        """Connect the diff signals from the Ui."""
 
508
        view.send_button.clicked.connect(
 
509
                            lambda: backend.request_password_reset_token(
 
510
                                                    view.wizard().app_name,
 
511
                                                    view.email_address))
 
512
        view.try_again_button.clicked.connect(lambda: self.on_try_again(view))
 
513
        # set the backend callbacks to be used
 
514
        backend.on_password_reset_token_sent_cb = lambda app, result:\
 
515
                                    self.on_password_reset_token_sent(view)
 
516
        backend.on_password_reset_error_cb = lambda app_name, error:\
 
517
                                    self.on_password_reset_error(app_name,
 
518
                                                                 error,
 
519
                                                                 view)
 
520
 
 
521
    def on_try_again(self, view):
 
522
        """Set back the widget to the initial state."""
 
523
        view.error_label.setVisible(False)
 
524
        view.try_again_widget.setVisible(False)
 
525
        view.email_widget.setVisible(True)
 
526
 
 
527
    def on_password_reset_token_sent(self, view):
 
528
        """Action taken when we managed to get the password reset done."""
 
529
        # ignore the result and move to the reset page
 
530
        view.next = view.wizard().reset_password_page_id
 
531
        view.wizard().next()
 
532
 
 
533
    def on_password_reset_error(self, app_name, error, view):
 
534
        """Action taken when there was an error requesting the reset."""
 
535
        if error['errtype'] == 'ResetPasswordTokenError':
 
536
            # the account provided is wrong, lets tell the user to try
 
537
            # again
 
538
            view.error_label.setText(REQUEST_PASSWORD_TOKEN_WRONG_EMAIL)
 
539
            view.error_label.setVisible(True)
 
540
        else:
 
541
            # ouch, I dont know what went wrong, lets tell the user to try later
 
542
            view.email_widget.setVisible(False)
 
543
            view.forgotted_password_intro_label.setVisible(False)
 
544
            view.try_again_wisget.setVisible(True)
 
545
            # set the error message
 
546
            view.error_label.setText(REQUEST_PASSWORD_TOKEN_TECH_ERROR)
 
547
            
 
548
    #pylint: disable=C0103
 
549
    @inlineCallbacks
 
550
    def setupUi(self, view):
 
551
        """Setup the view."""
 
552
        backend = yield self.get_backend()
 
553
        # hide the error label
 
554
        view.error_label.setVisible(False)
 
555
        view.try_again_widget.setVisible(False)
 
556
        self._set_translated_strings(view)
 
557
        self._connect_ui(view, backend)
 
558
        self._set_enhanced_line_edir(view)
 
559
        self._register_fields(view)
 
560
    #pylint: enable=C0103
 
561
 
 
562
 
 
563
class ResetPasswordController(BackendController):
 
564
    """Controller used to deal with reseintg the password."""
 
565
 
 
566
    def __init__(self):
 
567
        """Create a new instance."""
 
568
        super(ResetPasswordController, self).__init__()
 
569
 
 
570
    def _set_translated_strings(self, view):
 
571
        """Translate the diff strings used in the app."""
 
572
        view.reset_code_line_edit.setPlaceholderText(RESET_CODE_ENTRY)
 
573
        view.password_line_edit.setPlaceholderText(PASSWORD1_ENTRY)
 
574
        view.confirm_password_line_edit.setPlaceholderText(PASSWORD2_ENTRY)
 
575
        view.reset_password_button.setText(RESET_PASSWORD)
 
576
        view.setSubTitle(PASSWORD_HELP)
 
577
 
 
578
    def _connect_ui(self, view, backend):
 
579
        """Connect the different ui signals."""
 
580
        view.reset_password_button.clicked.connect(
 
581
                                lambda: self.set_new_password(view, backend))
 
582
        backend.on_password_changed_cb = lambda app, result:\
 
583
                                            self.on_password_changed(app,
 
584
                                                                     result,
 
585
                                                                     view)
 
586
        backend.on_password_change_error_cb = lambda app, error:\
 
587
                                        self.on_password_change_error(app,
 
588
                                                                      error,
 
589
                                                                      view)
 
590
 
 
591
    def _add_line_edits_validations(self, view):
 
592
        """Add the validations to be use by the line edits."""
 
593
        view.set_line_edit_validation_rule(view.password_line_edit,
 
594
                                           is_min_required_password)
 
595
        view.set_line_edit_validation_rule(view.confirm_password_line_edit,
 
596
                      lambda x: self.is_correct_password_confirmation(x, view))
 
597
        # same as the above case, lets connect a signal to a signal
 
598
        view.password_line_edit.textChanged.connect(
 
599
                              view.confirm_password_line_edit.textChanged.emit)
 
600
 
 
601
    def on_password_changed(self, app_name, result, view):
 
602
        """Let user know that the password was changed."""
 
603
    
 
604
    def on_password_change_error(self, app_name, error, view):
 
605
        """Let the user know that there was an error."""
 
606
 
 
607
    def set_new_password(self, view, backend):
 
608
        """Request a new password to be set."""
 
609
        app_name = view.wizard().app_name
 
610
        email = str(view.wizard().field('email_address').toString())
 
611
        code = view.reset_code
 
612
        logger.info('Settig new password for %s and email %s with code %s',
 
613
                    app_name, email, code)
 
614
        backend.set_new_password(app_name, email, code, view.password)
 
615
 
 
616
    def is_correct_password_confirmation(self, password, view):
 
617
        """Return if the password is correct."""
 
618
        return view.password_line_edit.text() == password
 
619
        
 
620
    #pylint: disable=C0103
 
621
    @inlineCallbacks
 
622
    def setupUi(self, view):
 
623
        """Setup the view."""
 
624
        backend = yield self.get_backend()
 
625
        self._set_translated_strings(view)
 
626
        self._connect_ui(view, backend)
 
627
        self._add_line_edits_validations(view)
 
628
    #pylint: enable=C0103
 
629
 
 
630
 
479
631
class SuccessController(object):
480
632
    """Controller used for the success page."""
481
633
 
493
645
class UbuntuSSOWizardController(object):
494
646
    """Controller used for the overall wizard."""
495
647
 
496
 
    def __init__(self, app_name='', login_success_callback=NO_OP,
 
648
    def __init__(self, login_success_callback=NO_OP,
497
649
                 registration_success_callback=NO_OP,
498
650
                 user_cancellation_callback=NO_OP):
499
651
        """Create a new instance."""
500
 
        self.app_name = app_name
501
652
        self.login_success_callback = login_success_callback
502
653
        self.registration_success_callback = registration_success_callback
503
654
        self.user_cancellation_callback = user_cancellation_callback
505
656
    def on_user_cancelation(self, view):
506
657
        """Process the cancel action."""
507
658
        logger.debug('UbuntuSSOWizardController.on_user_cancelation')
508
 
        self.user_cancellation_callback(self.app_name)
 
659
        self.user_cancellation_callback(view.app_name)
509
660
        view.close()
510
661
 
511
662
    @inlineCallbacks