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

« back to all changes in this revision

Viewing changes to ubuntu_sso/qt/controllers.py

  • Committer: Tarmac
  • Author(s): Manuel de la Pena, ralsina, Roberto Alsina
  • Date: 2011-04-14 17:01:56 UTC
  • mfrom: (705.2.2 forgotten_password)
  • Revision ID: tarmac-20110414170156-4k8s1708gzgj5oi2
Fixes lp:753281

Adds the required UI and backend to allow a windows user to reset his sso password from the Windows client. Tests have been added to ensure that the backend is correctly called.

Show diffs side-by-side

added added

removed removed

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