~shanepatrickfagan/ubuntu-sso-client/refactoring_mandel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
# -*- coding: utf-8 -*-
# Author: Manuel de la Pena <manuel@canonical.com>
#
# Copyright 2011 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Controllers with the logic of the UI."""

import os
import StringIO
import tempfile

from PIL import Image
from PyQt4.QtGui import QMessageBox, QWizard, QPixmap
from PyQt4.QtCore import QUrl
from twisted.internet.defer import inlineCallbacks, returnValue

from ubuntu_sso import NO_OP
from ubuntu_sso.logger import setup_logging
from ubuntu_sso.main.windows import UbuntuSSOClient
from ubuntu_sso.utils.ui import (
    CAPTCHA_SOLUTION_ENTRY,
    EMAIL1_ENTRY,
    EMAIL2_ENTRY,
    EMAIL_LABEL,
    EMAIL_MISMATCH,
    EMAIL_INVALID,
    ERROR,
    EXISTING_ACCOUNT_CHOICE_BUTTON,
    FORGOTTEN_PASSWORD_BUTTON,
    JOIN_HEADER_LABEL,
    NAME_ENTRY,
    PASSWORD1_ENTRY,
    PASSWORD2_ENTRY,
    PASSWORD_HELP,
    PASSWORD_MISMATCH,
    PASSWORD_TOO_WEAK,
    REQUEST_PASSWORD_TOKEN_LABEL,
    RESET_PASSWORD,
    RESET_CODE_ENTRY,
    REQUEST_PASSWORD_TOKEN_WRONG_EMAIL,
    REQUEST_PASSWORD_TOKEN_TECH_ERROR,
    SET_UP_ACCOUNT_CHOICE_BUTTON,
    SET_UP_ACCOUNT_BUTTON,
    SIGN_IN_BUTTON,
    SURNAME_ENTRY,
    SUCCESS,
    TC_BUTTON,
    TRY_AGAIN_BUTTON,
    VERIFY_EMAIL_TITLE,
    VERIFY_EMAIL_CONTENT,
    YES_TO_TC,
    get_password_strength,
    is_min_required_password,
    is_correct_email)


logger = setup_logging('ubuntu_sso.controllers')

FAKE_URL = '<a href="http://one.ubuntu.com">%s</a>'
# pylint: disable=W0511
# disabled warnings about TODO comments

class InitView:
    def __init__(self, view):
        self.view = view

class BackendController(object):
    """Represent a controller that talks with the sso backend."""

    def __init__(self):
        """Create a new instance."""
        self.root = None
        self.backends = []
        self.backend = None

    def __del__(self):
        """Clean the resources."""
        logger.info('Unregistering %s backends', len(self.backends))
        for self.backend in self.backends:
            self.backend.unregister_to_signals()
        if self.root is not None:
            logger.info('Disconnecting from root.')
            self.root.disconnect()

    @inlineCallbacks
    def get_backend(self):
        """Return the backend used by the controller."""
        # get the back end from the root
        if self.root is None:
            self.root = UbuntuSSOClient()
            self.root = yield self.root.connect()
        self.backend = self.root.sso_login
        yield self.backend.register_to_signals()
        self.backends.append(self.backend)
        logger.debug('Number of current backends in the controller: %s',
                     len(self.backends))


class ChooseSignInController(InitView, object):
    """Controlled to the ChooseSignIn view/widget."""

    def __init__(self, title=''):
        """Create a new instance to manage the view."""
        self._title = title

    # use an ugly name just so have a simlar api as found in PyQt
    #pylint: disable=C0103
    def setupUi(self):
        """Perform the required actions to set up the ui."""
        self.view.setTitle(self._title)
        self._set_up_translated_strings()
        self._connect_buttons()
    #pylint: enable=C0103

    def _set_up_translated_strings(self):
        """Set the correct strings for the UI."""
        logger.debug('ChooseSignInController._set_up_translated_strings')
        self.view.ui.existing_account_button.setText(EXISTING_ACCOUNT_CHOICE_BUTTON)
        self.view.ui.setup_account_button.setText(SET_UP_ACCOUNT_CHOICE_BUTTON)

    def _connect_buttons(self):
        """Connect the buttons to the actions to perform."""
        logger.debug('ChooseSignInController._connect_buttons')
        self.view.ui.existing_account_button.clicked.connect(
                                             self._set_next_existing)
        self.view.ui.setup_account_button.clicked.connect(
                                             self._set_next_new)

    def _set_next_existing(self):
        """Set the next id and fire signal."""
        logger.debug('ChooseSignInController._set_next_existing')
        self.view.next = self.view.wizard().current_user_page_id
        self.view.wizard().next()

    def _set_next_new(self):
        """Set the next id and fire signal."""
        logger.debug('ChooseSignInController._set_next_new')
        self.view.next = self.view.wizard().setup_account_page_id
        self.view.wizard().next()


class CurrentUserController(InitView, BackendController):
    """Controller used in the view that is used to allow the signin."""

    def __init__(self, backend=None, title='', message_box=None):
        """Create a new instance."""
        super(CurrentUserController, self).__init__()
        if message_box is None:
            message_box = QMessageBox
        self.message_box = message_box
        self._title = title

    def _set_translated_strings(self):
        """Set the translated strings."""
        logger.debug('CurrentUserController._set_translated_strings')
        self.view.email_edit.setPlaceholderText(EMAIL1_ENTRY)
        self.view.ui.password_edit.setPlaceholderText(PASSWORD1_ENTRY)
        self.view.ui.forgot_label.setText(FAKE_URL % FORGOTTEN_PASSWORD_BUTTON)
        self.view.ui.sign_in_button.setText(SIGN_IN_BUTTON)

    def _connect_ui(self):
        """Connect the buttons to perform actions."""
        logger.debug('CurrentUserController._connect_buttons')
        self.view.ui.sign_in_button.clicked.connect(self.login)
        # lets add call backs to be execute for the calls we are interested
        self.backend.on_login_error_cb = self.on_login_error(app, error)
        self.backend.on_logged_in_cb = self.on_logged_in(app, result)
        self.view.ui.forgot_label.linkActivated.connect(self.on_forgotten_password())

    def login(self):
        """Perform the login using the backend."""
        logger.debug('CurrentUserController.login')
        # grab the data from the view and call the backend
        d = self.backend.login(self.view.wizard().app_name, str(
                                self.view.ui.email_edit.text()), str(
                                self.view.ui.password_edit.text()))
        d.addErrback(self.on_login_error(e))

    def on_login_error(self, error):
        """There was an error when login in."""
        # let the user know
        logger.error('Got error when login %s, error: %s', app_name, error)
        self.message_box.critical(self.view.wizard().app_name, str(error))

    def on_logged_in(self, app_name, result):
        """We managed to log in."""
        logger.info('Logged in for %s', app_name)
        self.view.wizard().loginSuccess.emit(app_name, str(
                                          self.view.ui.email_edit.text()))
        logger.debug('Wizard.loginSuccess emitted.')

    def on_forgotten_password(self):
        """Show the user the forgotten password page."""
        logger.info('Forgotten password')
        self.view.next = self.view.wizard().forgotten_password_page_id
        self.view.wizard().next()

    # use an ugly name just so have a simlar api as found in PyQt
    #pylint: disable=C0103
    @inlineCallbacks
    def setupUi(self):
        """Setup the view."""
        self.get_backend()
        self.view.setTitle(self._title)
        self._set_translated_strings()
        self._connect_ui(self.backend)
    #pylint: enable=C0103


class SetUpAccountController(InitView, BackendController):
    """Conroller for the setup account view."""

    def __init__(self,  message_box=None):
        """Create a new instance."""
        super(SetUpAccountController, self).__init__()
        if message_box is None:
            message_box = QMessageBox
        self.message_box = message_box

    def _set_translated_strings(self):
        """Set the different gettext translated strings."""
        logger.debug('SetUpAccountController._set_translated_strings')
        # set the translated string
        self.view.ui.first_name_edit.setPlaceholderText(NAME_ENTRY)
        self.view.ui.last_name_edit.setPlaceholderText(SURNAME_ENTRY)
        self.view.ui.email_edit.setPlaceholderText(EMAIL1_ENTRY)
        self.view.ui.confirm_email_edit.setPlaceholderText(EMAIL2_ENTRY)
        self.view.ui.password_edit.setPlaceholderText(PASSWORD1_ENTRY)
        self.view.ui.confirm_password_edit.setPlaceholderText(PASSWORD2_ENTRY)
        self.view.ui.password_info_label.setText(PASSWORD_HELP)
        self.view.ui.captcha_solution_edit.setPlaceholderText(CAPTCHA_SOLUTION_ENTRY)
        self.view.ui.terms_and_conditions_check_box.setText(
                            YES_TO_TC % {'app_name': self.view.wizard().app_name})
        self.view.ui.terms_and_conditions_button.setText(TC_BUTTON)
        self.view.ui.set_up_button.setText(SET_UP_ACCOUNT_BUTTON)

    def _set_line_edits_validations(self):
        """Set the validations to be performed on the edits."""
        logger.debug('SetUpAccountController._set_line_edits_validations')
        self.view.set_line_edit_validation_rule(self.view.email_edit, is_correct_email)
        # set the validation rule for the email confirmation
        self.view.set_line_edit_validation_rule(self.view.confirm_email_edit,
                                           self.is_correct_email_confirmation())
        # connect the changed text of the password to trigger a changed text
        # in the confirm so that the validation is redone
        self.view.email_edit.textChanged.connect(
                                      self.view.confirm_email_edit.textChanged.emit)
        self.view.set_line_edit_validation_rule(self.view.ui.password_edit,
                                           is_min_required_password)
        self.view.set_line_edit_validation_rule(self.view.ui.confirm_password_edit,
                                           self.is_correct_password_confirmation())
        # same as the above case, lets connect a signal to a signal
        self.view.ui.password_edit.textChanged.connect(
                                   self.view.ui.confirm_password_edit.textChanged.emit)

    def _connect_ui_elements(self):
        """Set the connection of signals."""
        logger.debug('SetUpAccountController._connect_ui_elements')
        self.view.ui.terms_and_conditions_button.clicked.connect(self.set_next_tos)
        self.view.ui.set_up_button.clicked.connect(self.set_next_validation)
        self.view.ui.password_edit.textChanged.connect(self.update_password_strength)
        self.view.ui.terms_and_conditions_check_box.stateChanged.connect(
                                                 self.view.ui.set_up_button.setEnabled)
        self.view.ui.captcha_refresh_label.linkActivated.connect(self._refresh_captcha)
        # set the callbacks for the captcha generation
        self.backend.on_captcha_generated_cb = self.on_captcha_generated
        self.backend.on_captcha_generation_error_cb = self.on_captcha_generation_error
        self.backend.on_user_registered_cb = self.on_user_registered
        self.backend.on_user_registration_error_cb = self.on_user_registration_error

    def _refresh_captcha(self):
        """Refresh the captcha image shown in the ui."""
        logger.debug('SetUpAccountController._refresh_captcha')
        # lets clean behind us, do we have the old file arround?
        old_file = None
        if self.view.captcha_file and os.path.exists(self.view.captcha_file):
            old_file = self.view.captcha_file
        fd = tempfile.TemporaryFile(mode='r')
        file_name = fd.name
        self.view.captcha_file = file_name
        d = self.backend.generate_captcha(self.view.wizard().app_name, file_name)
        if old_file:
            d.addCallback(os.unlink(old_file))
        d.addErrback(self.on_captcha_generation_error(e))

    def _set_titles(self):
        """Set the diff titles of the view."""
        logger.debug('SetUpAccountController._set_titles')
        self.view.setTitle(JOIN_HEADER_LABEL % {'app_name': self.view.wizard().app_name})
        self.view.setSubTitle(self.view.wizard().help_text)

    def on_captcha_generated(self):
        """A new image was generated."""
        logger.debug('SetUpAccountController.on_captcha_generated')
        self.view.captcha_id = result
        # HACK: First, let me apologize before hand, you can mention my mother
        # if needed I would do the same (mandel)
        # In an ideal world we could use the Qt plug-in for the images so that
        # we could load jpgs etc.. but this won't work when the app has been
        # brozen win py2exe using bundle_files=1
        # The main issue is that Qt will complain about the thread not being
        # the correct one when performing a moveToThread operation which is
        # done either by a setParent or something within the qtreactor, PIL
        # in this case does solve the issue. Sorry :(
        pil_image = Image.open(self.iew.captcha_file)
        string_io = StringIO.StringIO()
        pil_image.save(string_io, format='png')
        pixmap_image = QPixmap()
        pixmap_image.loadFromData(string_io.getvalue())
        self.view.captcha_image = pixmap_image

    def on_captcha_generation_error(self, error):
        """An error ocurred."""
        logger.debug('SetUpAccountController.on_captcha_generation_error')
        self.message_box.critical(self.view.wizard().app_name, str(error))

    def on_user_registration_error(self, error):
        """Let the user know we could not register."""
        logger.debug('SetUpAccountController.on_user_registration_error')
        # errors are returned as a dict with the data we want to show.
        self.message_box.critical(error['errtype'], error['email'])

    def on_user_registered(self, app_name, result):
        """Execute when the user did register."""
        logger.debug('SetUpAccountController.on_user_registered')
        self.view.wizard().registrationSuccess.emit(app_name, str(self.view.ui.email_edit.text()))

    def set_next_tos(self):
        """Set the tos page as the next one."""
        logger.debug('SetUpAccountController.set_next_tos')
        self.view.next = self.view.wizard().tos_page_id
        self.view.wizard().next()

    def validate_form(self):
        """Validate the info of the form and return an error."""
        logger.debug('SetUpAccountController.validate_form')
        if not is_correct_email(str(self.view.ui.email_edit.text())):
            self.message_box.critical(self.view.wizard().app_name,
                                      EMAIL_INVALID)
        if self.view.email_edit.text() != self.view.confirm_email_edit.text():
            self.message_box.critical(self.view.wizard().app_name,
                                      EMAIL_MISMATCH)
            return False
        if not is_min_required_password(str(self.view.ui.password_edit.text())):
            self.message_box.critical(self.view.wizard().app_name,
                                      PASSWORD_TOO_WEAK)
            return False
        if self.view.ui.password_edit.text() != self.view.ui.confirm_password_edit.text():
            self.message_box.critical(self.view.wizard().app_name,
                                      PASSWORD_MISMATCH)
            return False
        return True

    def set_next_validation(self):
        """Set the validation as the next page."""
        logger.debug('SetUpAccountController.set_next_validation')
        # validate the current info of the form, try to perform the action
        # to register the user, and then move foward
        if self.validate_form():
            self.backend.register_user(self.view.wizard().app_name, str(self.view.ui.email_edit.text()),
                                  str(self.view.ui.password_edit.text()), self.view.captcha_id,
                                  str(self.view.ui.captcha_solution))
            self.view.next = self.view.wizard().email_verification_page_id
            self.view.wizard().next()

    def update_password_strength(self, password):
        """Callback used to update the password strenght UI code."""
        logger.debug('SetUpAccountController.update_password_strength')
        # get the strengh and then according to it color the frames
        strengh = get_password_strength(password)
        logger.info('Password strengh is %s', strengh)
        self.view.set_strenght_level(strengh, password)

    def is_correct_email_confirmation(self):
        """Return that the email is the same."""
        logger.debug('SetUpAccountController.is_correct_email_confirmation')
        return self.view.email_line_edit.text() == self.email_address

    def is_correct_password_confirmation(self):
        """Return that the passwords are correct."""
        logger.debug('SetUpAccountController.is_correct_password_confirmation')
        return self.view.ui.password_edit.text() == self.password

    #pylint: disable=C0103
    @inlineCallbacks
    def setupUi(self):
        """Setup the view."""
        # request the backend to be used with the ui
        self.get_backend()
        self._connect_ui_elements()
        self._refresh_captcha()
        self._set_titles()
        self._set_translated_strings()
        self._set_line_edits_validations()
    #pylint: enable=C0103


class TosController(InitView, object):
    """Controller used for the tos page."""

    def __init__(self, title='', subtitle='', tos_url=''):
        """Create a new instance."""
        self._title = title
        self._subtitle = subtitle   
        self._tos_url = tos_url

    #pylint: disable=C0103
    def setupUi(self):
        """Set up the ui."""
        self.view.setTitle(self._title)
        self.view.setSubTitle(self._subtitle)
        # load the tos page
        self.view.ui.webkit.load(QUrl(self._tos_url))
    #pylint: enable=C0103


class EmailVerificationController(InitView, object):
    """Controller used for the verification page."""

    def _set_translated_strings(self):
        """Set the trnaslated strings."""
        logger.debug('EmailVerificationController._set_translated_strings')
        self.view.ui.verification_code_edit.setPlaceholderText(VERIFY_EMAIL_TITLE)

    def _connect_ui_elements(self):
        """Set the connection of signals."""
        logger.debug('EmailVerificationController._connect_ui_elements')
        self.view.ui.next_button.clicked.connect(self.next_page)

    def _set_titles(self):
        """Set the different titles."""
        logger.debug('EmailVerificationController._set_titles')
        self.view.setTitle(VERIFY_EMAIL_TITLE)
        self.view.setSubTitle(VERIFY_EMAIL_CONTENT)

    #pylint: disable=C0103
    def setupUi(self):
        """Setup the view."""
        self._set_titles()
        self._set_translated_strings()
        self._connect_ui_elements()
    #pylint: enable=C0103

    def next_page(self):
        """Call the next action."""
        logger.debug('EmailVerificationController.next_page')
        self.view.wizard().next()


class ErrorController(InitView, object):
    """Controller used for the error page."""

    def __init__(self):
        """Create a new instance."""

    #pylint: disable=C0103
    def setupUi(self):
        """Setup the view."""
        self.view.next = -1
        self.view.ui.error_message_label.setText(ERROR)
    #pylint: enable=C0103


class ForgottenPasswordController(InitView, BackendController):
    """Controller used to deal with the forgotten pwd page."""

    def __init__(self):
        """Create a new instance."""
        super(ForgottenPasswordController, self).__init__()

    def _register_fields(self):
        """Register the fields of the wizard page."""
        self.view.registerField('email_address', self.view.ui.email_address_line_edit)

    def _set_translated_strings(self):
        """Set the translated strings in the view."""
        self.view.ui.forgotten_password_intro_label.setText(
                                    REQUEST_PASSWORD_TOKEN_LABEL % {'app_name':
                                    self.view.wizard().app_name})
        self.view.ui.email_address_label.setText(EMAIL_LABEL)
        self.view.ui.send_button.setText(RESET_PASSWORD)
        self.view.ui.try_again_button.setText(TRY_AGAIN_BUTTON)

    def _set_enhanced_line_edir(self):
        """Set the extra logic to the line edits."""
        self.view.set_line_edit_validation_rule(self.view.ui.email_address_line_edit,
                                           is_correct_email)

    def _connect_ui(self):
        """Connect the diff signals from the Ui."""
        self.view.ui.send_button.clicked.connect(
                                    self.backend.request_password_reset_token(
                                                    self.view.wizard().app_name,
                                                    str(self.ui.email_line_edit.text())))
        self.view.ui.try_again_button.clicked.connect(self.on_try_again())
        # set the backend callbacks to be used
        self.backend.on_password_reset_token_sent_cb = self.on_password_reset_token_sent()
        self.backend.on_password_reset_error = self.on_password_reset_error(error)

    def on_try_again(self):
        """Set back the widget to the initial state."""
        self.view.ui.error_label.setVisible(False)
        self.view.ui.try_again_widget.setVisible(False)
        self.view.ui.email_widget.setVisible(True)

    def on_password_reset_token_sent(self):
        """Action taken when we managed to get the password reset done."""
        # ignore the result and move to the reset page
        self.view.next = self.view.wizard().reset_password_page_id
        self.view.wizard().next()

    def on_password_reset_error(self, error):
        """Action taken when there was an error requesting the reset."""
        if error['errtype'] == 'ResetPasswordTokenError':
            # the account provided is wrong, lets tell the user to try
            # again
            self.view.ui.error_label.setText(REQUEST_PASSWORD_TOKEN_WRONG_EMAIL)
            self.view.ui.error_label.setVisible(True)
        else:
            # ouch, I dont know what went wrong, lets tell the user to try later
            self.view.ui.email_widget.setVisible(False)
            self.view.ui.forgotten_password_intro_label.setVisible(False)
            self.view.try_again_wisget.setVisible(True)
            # set the error message
            self.view.ui.error_label.setText(REQUEST_PASSWORD_TOKEN_TECH_ERROR)
            
    #pylint: disable=C0103
    @inlineCallbacks
    def setupUi(self):
        """Setup the view."""
        self.get_backend()
        # hide the error label
        self.view.ui.error_label.setVisible(False)
        self.view.ui.try_again_widget.setVisible(False)
        self._set_translated_strings()
        self._connect_ui()
        self._set_enhanced_line_edir()
        self._register_fields()
    #pylint: enable=C0103


class ResetPasswordController(InitView, BackendController):
    """Controller used to deal with reseintg the password."""

    def __init__(self):
        """Create a new instance."""
        super(ResetPasswordController, self).__init__()

    def _set_translated_strings(self):
        """Translate the diff strings used in the app."""
        self.view.ui.reset_code_line_edit.setPlaceholderText(RESET_CODE_ENTRY)
        self.view.ui.password_line_edit.setPlaceholderText(PASSWORD1_ENTRY)
        self.view.ui.confirm_password_line_edit.setPlaceholderText(PASSWORD2_ENTRY)
        self.view.ui.reset_password_button.setText(RESET_PASSWORD)
        self.view.setSubTitle(PASSWORD_HELP)

    def _connect_ui(self):
        """Connect the different ui signals."""
        self.view.ui.reset_password_button.clicked.connect(self.set_new_password)
        self.backend.on_password_changed_cb = self.on_password_changed(app, result)
        self.backend.on_password_change_error_cb = self.on_password_change_error(app, error)

    def _add_line_edits_validations(self):
        """Add the validations to be use by the line edits."""
        self.view.set_line_edit_validation_rule(self.view.ui.password_line_edit,
                                           is_min_required_password)
        self.view.set_line_edit_validation_rule(self.view.ui.confirm_password_line_edit, 
                                           self.is_correct_password_confirmation)
        # same as the above case, lets connect a signal to a signal
        self.view.ui.password_line_edit.textChanged.connect(
                              self.view.ui.confirm_password_line_edit.textChanged.emit)

    def on_password_changed(self, app_name, result):
        """Let user know that the password was changed."""
    
    def on_password_change_error(self, app_name, error):
        """Let the user know that there was an error."""

    def set_new_password(self):
        """Request a new password to be set."""
        app_name = self.view.wizard().app_name
        email = str(self.view.wizard().field('email_address').toString())
        code = self.view.reset_code
        logger.info('Settig new password for %s and email %s with code %s',
                    app_name, email, code)
        self.backend.set_new_password(app_name, email, code, str(self.view.ui.password_edit.text()))

    def is_correct_password_confirmation(self, password):
        """Return if the password is correct."""
        return self.view.ui.password_line_edit.text() == password
        
    #pylint: disable=C0103
    @inlineCallbacks
    def setupUi(self):
        """Setup the view."""
        self.get_backend()
        self._set_translated_strings()
        self._connect_ui()
        self._add_line_edits_validations()
    #pylint: enable=C0103


class SuccessController(InitView, object):
    """Controller used for the success page."""

    def __init__(self):
        """Create a new instance."""

    #pylint: disable=C0103
    def setupUi(self):
        """Setup the view."""
        self.view.next = -1
        self.view.ui.success_message_label.setText(SUCCESS)
    #pylint: enable=C0103


class UbuntuSSOWizardController(InitView, object):
    """Controller used for the overall wizard."""

    def __init__(self, login_success_callback=NO_OP,
                 registration_success_callback=NO_OP,
                 user_cancellation_callback=NO_OP):
        """Create a new instance."""
        self.login_success_callback = login_success_callback
        self.registration_success_callback = registration_success_callback
        self.user_cancellation_callback = user_cancellation_callback

    def on_user_cancelation(self):
        """Process the cancel action."""
        logger.debug('UbuntuSSOWizardController.on_user_cancelation')
        self.user_cancellation_callback(self.view.app_name)
        self.view.close()

    @inlineCallbacks
    def on_login_success(self, app_name, email):
        """Process the success of a login."""
        logger.debug('UbuntuSSOWizardController.on_login_success')
        result = yield self.login_success_callback(app_name, email)
        logger.debug('Result from callback is %s', result)
        if result == 0:
            logger.info('Success in calling the given success_callback')
            self.show_success_message()
        else:
            logger.info('Error in calling the given success_callback')
            self.show_error_message()

    @inlineCallbacks
    def on_registration_success(self, app_name, email):
        """Process the success of a registration."""
        logger.debug('UbuntuSSOWizardController.on_registration_success')
        result = yield self.registration_success_callback(app_name, email)
        # TODO: what to do?
        logger.debug('Result from callback is %s', result)
        if result == 0:
            logger.info('Success in calling the given registration_callback')
            self.show_success_message()
        else:
            logger.info('Success in calling the given registration_callback')
            self.show_error_message()

    def show_success_message(self):
        """Show the success message in the view."""
        logger.info('Showing success message.')
        # get the id of the success page, set it as the next id of the
        # current page and let the wizard move to the next step
        self.view.currentPage().next = self.view.success_page_id
        self.view.next()
        # show the finish button but with a close message
        buttons_layout = []
        buttons_layout.append(QWizard.Stretch)
        buttons_layout.append(QWizard.FinishButton)
        self.view.setButtonLayout(buttons_layout)

    def show_error_message(self):
        """Show the error page in the view."""
        logger.info('Showing error message.')
        # similar to the success page but using the error id
        self.view.currentPage().next = self.view.error_page_id
        self.view.next()
        # show the finish button but with a close message
        buttons_layout = []
        buttons_layout.append(QWizard.Stretch)
        buttons_layout.append(QWizard.FinishButton)
        self.view.setButtonLayout(buttons_layout)

    #pylint: disable=C0103
    def setupUi(self):
        """Setup the view."""
        self.view.setWizardStyle(QWizard.ModernStyle)
        self.view.button(QWizard.CancelButton).clicked.connect(self.on_user_cancelation)
        self.view.loginSuccess.connect(self.on_login_success(app, email))
        self.view.registrationSuccess.connect(self.on_registration_success(app, email))
    #pylint: enable=C0103