From ad16a72f60ecc84524c22e8912df4eb8a48a2a42 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 6 Nov 2012 16:26:10 +0900 Subject: split wizard into separate files so we don't go nuts yet. --- src/leap/gui/firstrun/__init__.py | 24 +++ src/leap/gui/firstrun/connect.py | 218 ++++++++++++++++++++++++++ src/leap/gui/firstrun/intro.py | 68 ++++++++ src/leap/gui/firstrun/last.py | 89 +++++++++++ src/leap/gui/firstrun/login.py | 132 ++++++++++++++++ src/leap/gui/firstrun/mixins.py | 18 +++ src/leap/gui/firstrun/providerinfo.py | 67 ++++++++ src/leap/gui/firstrun/providersetup.py | 122 +++++++++++++++ src/leap/gui/firstrun/register.py | 211 +++++++++++++++++++++++++ src/leap/gui/firstrun/selectprovider.py | 216 +++++++++++++++++++++++++ src/leap/gui/firstrun/wizard.py | 269 ++++++++++++++++++++++++++++++++ 11 files changed, 1434 insertions(+) create mode 100644 src/leap/gui/firstrun/__init__.py create mode 100644 src/leap/gui/firstrun/connect.py create mode 100644 src/leap/gui/firstrun/intro.py create mode 100644 src/leap/gui/firstrun/last.py create mode 100644 src/leap/gui/firstrun/login.py create mode 100644 src/leap/gui/firstrun/mixins.py create mode 100644 src/leap/gui/firstrun/providerinfo.py create mode 100644 src/leap/gui/firstrun/providersetup.py create mode 100644 src/leap/gui/firstrun/register.py create mode 100644 src/leap/gui/firstrun/selectprovider.py create mode 100755 src/leap/gui/firstrun/wizard.py (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/__init__.py b/src/leap/gui/firstrun/__init__.py new file mode 100644 index 00000000..bf8d0df9 --- /dev/null +++ b/src/leap/gui/firstrun/__init__.py @@ -0,0 +1,24 @@ +import sip +sip.setapi('QString', 2) +sip.setapi('QVariant', 2) + +import connect +import intro +import last +import login +import mixins +import providerinfo +import providersetup +import register +import selectprovider + +__all__ = [ + 'connect', + 'intro', + 'last', + 'login', + 'mixins', + 'providerinfo', + 'providersetup', + 'register', + 'selectprovider'] diff --git a/src/leap/gui/firstrun/connect.py b/src/leap/gui/firstrun/connect.py new file mode 100644 index 00000000..3172a526 --- /dev/null +++ b/src/leap/gui/firstrun/connect.py @@ -0,0 +1,218 @@ +""" +Connecting Page, used in First Run Wizard +""" +import logging + +from PyQt4 import QtGui + +logger = logging.getLogger(__name__) + +from leap.base import auth + +from leap.gui.constants import APP_LOGO +from leap.gui.styles import ErrorLabelStyleSheet + + +class ConnectingPage(QtGui.QWizardPage): + + # XXX change to a ValidationPage + + def __init__(self, parent=None): + super(ConnectingPage, self).__init__(parent) + + self.setTitle("Connecting") + self.setSubTitle('Connecting to provider.') + + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + self.status = QtGui.QLabel("") + self.status.setWordWrap(True) + self.progress = QtGui.QProgressBar() + self.progress.setMaximum(100) + self.progress.hide() + + # for pre-checks + self.status_line_1 = QtGui.QLabel() + self.status_line_2 = QtGui.QLabel() + self.status_line_3 = QtGui.QLabel() + self.status_line_4 = QtGui.QLabel() + + # for connecting signals... + self.status_line_5 = QtGui.QLabel() + + layout = QtGui.QGridLayout() + layout.addWidget(self.status, 0, 1) + layout.addWidget(self.progress, 5, 1) + layout.addWidget(self.status_line_1, 8, 1) + layout.addWidget(self.status_line_2, 9, 1) + layout.addWidget(self.status_line_3, 10, 1) + layout.addWidget(self.status_line_4, 11, 1) + + # XXX to be used? + #self.validation_status = QtGui.QLabel("") + #self.validation_status.setStyleSheet( + #ErrorLabelStyleSheet) + #self.validation_msg = QtGui.QLabel("") + + self.setLayout(layout) + + self.goto_login_again = False + + def set_status(self, status): + self.status.setText(status) + self.status.setWordWrap(True) + + def set_status_line(self, line, status): + line = getattr(self, 'status_line_%s' % line) + if line: + line.setText(status) + + def set_validation_status(self, status): + # Do not remember if we're using + # status lines > 3 now... + # if we are, move below + self.status_line_3.setStyleSheet( + ErrorLabelStyleSheet) + self.status_line_3.setText(status) + + def set_validation_message(self, message): + self.status_line_4.setText(message) + self.status_line_4.setWordWrap(True) + + def get_donemsg(self, msg): + return "%s ... done" % msg + + def run_eip_checks_for_provider_and_connect(self, domain): + wizard = self.wizard() + conductor = wizard.conductor + start_eip_signal = getattr( + wizard, + 'start_eipconnection_signal', None) + + conductor.set_provider_domain(domain) + conductor.run_checks() + self.conductor = conductor + errors = self.eip_error_check() + if not errors and start_eip_signal: + start_eip_signal.emit() + + def eip_error_check(self): + """ + a version of the main app error checker, + but integrated within the connecting page of the wizard. + consumes the conductor error queue. + pops errors, and add those to the wizard page + """ + logger.debug('eip error check from connecting page') + errq = self.conductor.error_queue + # XXX missing! + + def fetch_and_validate(self): + import time + domain = self.field('provider_domain') + wizard = self.wizard() + #pconfig = wizard.providerconfig + eipconfigchecker = wizard.eipconfigchecker() + pCertChecker = wizard.providercertchecker( + domain=domain) + + # username and password are in different fields + # if they were stored in log_in or sign_up pages. + from_login = self.wizard().from_login + unamek_base = 'userName' + passwk_base = 'userPassword' + unamek = 'login_%s' % unamek_base if from_login else unamek_base + passwk = 'login_%s' % passwk_base if from_login else passwk_base + + username = self.field(unamek) + password = self.field(passwk) + credentials = username, password + + self.progress.show() + + fetching_eip_conf_msg = 'Fetching eip service configuration' + self.set_status(fetching_eip_conf_msg) + self.progress.setValue(30) + + # Fetching eip service + eipconfigchecker.fetch_eip_service_config( + domain=domain) + + self.status_line_1.setText( + self.get_donemsg(fetching_eip_conf_msg)) + + getting_client_cert_msg = 'Getting client certificate' + self.set_status(getting_client_cert_msg) + self.progress.setValue(66) + + # Download cert + try: + pCertChecker.download_new_client_cert( + credentials=credentials) + except auth.SRPAuthenticationError: + self.set_validation_status("Authentication error") + #self.set_validation_message( + #"Click next to introduce your " + #"credentials again") + self.goto_login_again = True + # We should do something here + # but it's broken + return False + + time.sleep(2) + self.status_line_2.setText( + self.get_donemsg(getting_client_cert_msg)) + + validating_clientcert_msg = 'Validating client certificate' + self.set_status(validating_clientcert_msg) + self.progress.setValue(90) + time.sleep(2) + self.status_line_3.setText( + self.get_donemsg(validating_clientcert_msg)) + + self.progress.setValue(100) + time.sleep(3) + + # here we go! :) + self.run_eip_checks_for_provider_and_connect(domain) + + #self.validation_block = self.wait_for_validation_block() + + # XXX signal timeout! + return True + + # + # wizardpage methods + # + + def nextId(self): + wizard = self.wizard() + # XXX this does not work because + # page login has already been met + #if self.goto_login_again: + #next_ = "login" + #else: + #next_ = "lastpage" + next_ = "lastpage" + return wizard.get_page_index(next_) + + def initializePage(self): + # XXX if we're coming from signup page + # we could say something like + # 'registration successful!' + self.status.setText( + "We have " + "all we need to connect with the provider.

" + "Click next to continue. ") + self.progress.setValue(0) + self.progress.hide() + self.status_line_1.setText('') + self.status_line_2.setText('') + self.status_line_3.setText('') + + def validatePage(self): + # XXX remove + validated = self.fetch_and_validate() + return validated diff --git a/src/leap/gui/firstrun/intro.py b/src/leap/gui/firstrun/intro.py new file mode 100644 index 00000000..4bb008c7 --- /dev/null +++ b/src/leap/gui/firstrun/intro.py @@ -0,0 +1,68 @@ +""" +Intro page used in first run wizard +""" + +from PyQt4 import QtGui + +from leap.gui.constants import APP_LOGO + + +class IntroPage(QtGui.QWizardPage): + def __init__(self, parent=None): + super(IntroPage, self).__init__(parent) + + self.setTitle("First run wizard.") + + #self.setPixmap( + #QtGui.QWizard.WatermarkPixmap, + #QtGui.QPixmap(':/images/watermark1.png')) + + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + label = QtGui.QLabel( + "Now we will guide you through " + "some configuration that is needed before you " + "can connect for the first time.

" + "If you ever need to modify these options again, " + "you can find the wizard in the 'Settings' menu from the " + "main window.

" + "Do you want to sign up for a new account, or log " + "in with an already existing username?
") + label.setWordWrap(True) + + radiobuttonGroup = QtGui.QGroupBox() + + self.sign_up = QtGui.QRadioButton( + "Sign up for a new account.") + self.sign_up.setChecked(True) + self.log_in = QtGui.QRadioButton( + "Log In with my credentials.") + + radiobLayout = QtGui.QVBoxLayout() + radiobLayout.addWidget(self.sign_up) + radiobLayout.addWidget(self.log_in) + radiobuttonGroup.setLayout(radiobLayout) + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) + layout.addWidget(radiobuttonGroup) + self.setLayout(layout) + + self.registerField('is_signup', self.sign_up) + + def validatePage(self): + return True + + def nextId(self): + """ + returns next id + in a non-linear wizard + """ + if self.sign_up.isChecked(): + next_ = 'providerselection' + if self.log_in.isChecked(): + next_ = 'login' + wizard = self.wizard() + return wizard.get_page_index(next_) diff --git a/src/leap/gui/firstrun/last.py b/src/leap/gui/firstrun/last.py new file mode 100644 index 00000000..6f9abbb5 --- /dev/null +++ b/src/leap/gui/firstrun/last.py @@ -0,0 +1,89 @@ +""" +Last Page, used in First Run Wizard +""" +import logging + +from PyQt4 import QtGui + +from leap.util.coroutines import coroutine +from leap.gui.constants import APP_LOGO + +logger = logging.getLogger(__name__) + + +class LastPage(QtGui.QWizardPage): + def __init__(self, parent=None): + super(LastPage, self).__init__(parent) + + self.setTitle("Connecting to Encrypted Internet Proxy service...") + + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + #self.setPixmap( + #QtGui.QWizard.WatermarkPixmap, + #QtGui.QPixmap(':/images/watermark2.png')) + + self.label = QtGui.QLabel() + self.label.setWordWrap(True) + + self.status_line_1 = QtGui.QLabel() + self.status_line_2 = QtGui.QLabel() + self.status_line_3 = QtGui.QLabel() + self.status_line_4 = QtGui.QLabel() + + layout = QtGui.QVBoxLayout() + layout.addWidget(self.label) + + # make loop + layout.addWidget(self.status_line_1) + layout.addWidget(self.status_line_2) + layout.addWidget(self.status_line_3) + layout.addWidget(self.status_line_4) + + self.setLayout(layout) + + def set_status_line(self, line, status): + statusline = getattr(self, 'status_line_%s' % line) + if statusline: + statusline.setText(status) + + def set_finished_status(self): + self.setTitle('You are now using an encrypted connection!') + finishText = self.wizard().buttonText( + QtGui.QWizard.FinishButton) + finishText = finishText.replace('&', '') + self.label.setText( + "Click '%s' to end the wizard and " + "save your settings." % finishText) + + @coroutine + def eip_status_handler(self): + # XXX this can be changed to use + # signals. See progress.py + logger.debug('logging status in last page') + self.validation_done = False + status_count = 0 + try: + while True: + status = (yield) + status_count += 1 + # XXX add to line... + logger.debug('status --> %s', status) + self.set_status_line(status_count, status) + if status == "connected": + self.set_finished_status() + break + except GeneratorExit: + pass + + def initializePage(self): + wizard = self.wizard() + if not wizard: + return + eip_status_handler = self.eip_status_handler() + eip_statuschange_signal = wizard.eip_statuschange_signal + if eip_statuschange_signal: + eip_statuschange_signal.connect( + lambda status: eip_status_handler.send(status)) diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py new file mode 100644 index 00000000..ae4b23c6 --- /dev/null +++ b/src/leap/gui/firstrun/login.py @@ -0,0 +1,132 @@ +""" +LogIn Page, used inf First Run Wizard +""" +from PyQt4 import QtCore +from PyQt4 import QtGui + +import requests + +from leap.gui.firstrun.mixins import UserFormMixIn + +from leap.gui.constants import APP_LOGO, FULL_USERNAME_REGEX +from leap.gui.styles import ErrorLabelStyleSheet + + +class LogInPage(QtGui.QWizardPage, UserFormMixIn): + def __init__(self, parent=None): + super(LogInPage, self).__init__(parent) + + self.setTitle("Log In") + self.setSubTitle("Log in with your credentials.") + + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + userNameLabel = QtGui.QLabel("User &name:") + userNameLineEdit = QtGui.QLineEdit() + userNameLineEdit.cursorPositionChanged.connect( + self.reset_validation_status) + userNameLabel.setBuddy(userNameLineEdit) + + # let's add regex validator + usernameRe = QtCore.QRegExp(FULL_USERNAME_REGEX) + userNameLineEdit.setValidator( + QtGui.QRegExpValidator(usernameRe, self)) + self.userNameLineEdit = userNameLineEdit + + userPasswordLabel = QtGui.QLabel("&Password:") + self.userPasswordLineEdit = QtGui.QLineEdit() + self.userPasswordLineEdit.setEchoMode( + QtGui.QLineEdit.Password) + userPasswordLabel.setBuddy(self.userPasswordLineEdit) + + self.registerField('login_userName*', self.userNameLineEdit) + self.registerField('login_userPassword*', self.userPasswordLineEdit) + + layout = QtGui.QGridLayout() + layout.setColumnMinimumWidth(0, 20) + + validationMsg = QtGui.QLabel("") + validationMsg.setStyleSheet(ErrorLabelStyleSheet) + self.validationMsg = validationMsg + + layout.addWidget(validationMsg, 0, 3) + layout.addWidget(userNameLabel, 1, 0) + layout.addWidget(self.userNameLineEdit, 1, 3) + layout.addWidget(userPasswordLabel, 2, 0) + layout.addWidget(self.userPasswordLineEdit, 2, 3) + + self.setLayout(layout) + + #self.registerField('is_login_wizard') + + def onUserNameEdit(self, *args): + if self.initial_username_sample: + self.userNameLineEdit.setText('') + self.initial_username_sample = None + + # pagewizard methods + + def nextId(self): + wizard = self.wizard() + if not wizard: + return + if wizard.is_provider_setup is True: + next_ = 'connecting' + if wizard.is_provider_setup is False: + next_ = 'providersetup' + return wizard.get_page_index(next_) + + def initializePage(self): + self.userNameLineEdit.setText('username@provider.example.org') + self.userNameLineEdit.cursorPositionChanged.connect( + self.onUserNameEdit) + self.initial_username_sample = True + + def validatePage(self): + wizard = self.wizard() + eipconfigchecker = wizard.eipconfigchecker() + + full_username = self.userNameLineEdit.text() + password = self.userPasswordLineEdit.text() + if full_username.count('@') != 1: + self.set_validation_status( + "Username must be in the username@provider form.") + return False + + username, domain = full_username.split('@') + self.setField('provider_domain', domain) + self.setField('login_userName', username) + self.setField('login_userPassword', password) + + # Able to contact domain? + # can get definition? + # two-by-one + try: + eipconfigchecker.fetch_definition(domain=domain) + + # we're using requests here for all + # the possible error cases that it catches. + except requests.exceptions.ConnectionError as exc: + self.set_validation_status(exc.message[1]) + return False + except requests.exceptions.HTTPError as exc: + self.set_validation_status(exc.message) + return False + wizard.set_providerconfig( + eipconfigchecker.defaultprovider.config) + + # XXX validate user? or we leave that for later? + # I think the best thing to do for that is + # continue to provider setup page, and if + # we catch authentication error there, redirect + # again to this page (by clicking "next" to + # come here). + # Rationale is that we need to verify server certs + # and so on. + + # mark that we came from login page. + self.wizard().from_login = True + + return True diff --git a/src/leap/gui/firstrun/mixins.py b/src/leap/gui/firstrun/mixins.py new file mode 100644 index 00000000..c4731893 --- /dev/null +++ b/src/leap/gui/firstrun/mixins.py @@ -0,0 +1,18 @@ +""" +mixins used in First Run Wizard +""" + + +class UserFormMixIn(object): + + def reset_validation_status(self): + """ + empty the validation msg + """ + self.validationMsg.setText('') + + def set_validation_status(self, msg): + """ + set generic validation status + """ + self.validationMsg.setText(msg) diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py new file mode 100644 index 00000000..f5f5518a --- /dev/null +++ b/src/leap/gui/firstrun/providerinfo.py @@ -0,0 +1,67 @@ +""" +Provider Info Page, used in First run Wizard +""" + +from PyQt4 import QtGui + +from leap.gui.constants import APP_LOGO + + +class ProviderInfoPage(QtGui.QWizardPage): + def __init__(self, parent=None): + super(ProviderInfoPage, self).__init__(parent) + + self.setTitle("Provider Info") + self.setSubTitle("Available information about chosen provider.") + + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + displayName = QtGui.QLabel("") + description = QtGui.QLabel("") + enrollment_policy = QtGui.QLabel("") + # XXX set stylesheet... + # prettify a little bit. + # bigger fonts and so on... + self.displayName = displayName + self.description = description + self.enrollment_policy = enrollment_policy + + layout = QtGui.QGridLayout() + layout.addWidget(displayName, 0, 1) + layout.addWidget(description, 1, 1) + layout.addWidget(enrollment_policy, 2, 1) + + self.setLayout(layout) + + def initializePage(self): + # XXX move to show info... + + # XXX get multilingual objects + # directly from the config object + + lang = "en" + pconfig = self.wizard().providerconfig + + dn = pconfig.get('display_name') + display_name = dn[lang] if dn else '' + self.displayName.setText( + "%s" % display_name) + + desc = pconfig.get('description') + description_text = desc[lang] if desc else '' + self.description.setText( + "%s" % description_text) + + enroll = pconfig.get('enrollment_policy') + if enroll: + self.enrollment_policy.setText( + 'enrollment policy: %s' % enroll) + + def nextId(self): + wizard = self.wizard() + next_ = "providersetupvalidation" + return wizard.get_page_index(next_) + + diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py new file mode 100644 index 00000000..63f55d00 --- /dev/null +++ b/src/leap/gui/firstrun/providersetup.py @@ -0,0 +1,122 @@ +""" +Provider Setup Validation Page, +used if First Run Wizard +""" + +from PyQt4 import QtGui + +from leap.gui.progress import ValidationPage + +from leap.gui.constants import APP_LOGO + + +class ProviderSetupValidationPage(ValidationPage): + def __init__(self, parent=None): + super(ProviderSetupValidationPage, self).__init__(parent) + self.setTitle("Setting up provider") + #self.setSubTitle( + #"auto configuring provider...") + + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + def _do_checks(self, signal=None): + """ + executes actual checks in a separate thread + """ + import time + domain = self.field('provider_domain') + wizard = self.wizard() + pconfig = wizard.providerconfig + + pCertChecker = wizard.providercertchecker + certchecker = pCertChecker(domain=domain) + + signal.emit('Fetching CA certificate') + self.progress.setValue(30) + + if pconfig: + ca_cert_uri = pconfig.get('ca_cert_uri').geturl() + else: + ca_cert_uri = None + + # XXX check scheme == "https" + # XXX passing verify == False because + # we have trusted right before. + # We should check it's the same domain!!! + # (Check with the trusted fingerprints dict + # or something smart) + + #certchecker.download_ca_cert( + #uri=ca_cert_uri, + #verify=False) + + time.sleep(2) + + signal.emit('Checking CA fingerprint') + self.progress.setValue(66) + #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None) + + # XXX get fingerprint dict (types) + #sha256_fpr = ca_cert_fingerprint.split('=')[1] + + #validate_fpr = certchecker.check_ca_cert_fingerprint( + #fingerprint=sha256_fpr) + time.sleep(0.5) + #if not validate_fpr: + # XXX update validationMsg + # should catch exception + #return False + + signal.emit('Validating api certificate') + self.progress.setValue(90) + + #api_uri = pconfig.get('api_uri', None) + #try: + #api_cert_verified = certchecker.verify_api_https(api_uri) + #except requests.exceptions.SSLError as exc: + #logger.error('BUG #638. %s' % exc.message) + # XXX RAISE! See #638 + # bypassing until the hostname is fixed. + # We probably should raise yet-another-warning + # here saying user that the hostname "XX.XX.XX.XX' does not + # match 'foo.bar.baz' + #api_cert_verified = True + + #if not api_cert_verified: + # XXX update validationMsg + # should catch exception + #return False + time.sleep(0.5) + #ca_cert_path = checker.ca_cert_path + + self.progress.setValue(100) + signal.emit('end_sentinel') + time.sleep(1) + + def _do_validation(self): + """ + called after _do_checks has finished + (connected to checker thread finished signal) + """ + wizard = self.wizard() + if self.errors: + print 'going back with errors' + wizard.set_validation_error( + 'signup', 'that name is taken') + self.go_back() + else: + print 'going next' + self.go_next() + + def nextId(self): + wizard = self.wizard() + if not wizard: + return + is_signup = self.field('is_signup') + if is_signup is True: + next_ = 'signup' + if is_signup is False: + next_ = 'connecting' + return wizard.get_page_index(next_) diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py new file mode 100644 index 00000000..b04844bf --- /dev/null +++ b/src/leap/gui/firstrun/register.py @@ -0,0 +1,211 @@ +""" +Register User Page, used in First Run Wizard +""" +import json +import logging +import socket + +import requests + +from PyQt4 import QtCore +from PyQt4 import QtGui + +from leap.base import auth +from leap.gui.firstrun.mixins import UserFormMixIn + +logger = logging.getLogger(__name__) + +from leap.gui.constants import APP_LOGO, BARE_USERNAME_REGEX +from leap.gui.styles import ErrorLabelStyleSheet + + +class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): + setSigningUpStatus = QtCore.pyqtSignal([]) + + def __init__(self, parent=None): + super(RegisterUserPage, self).__init__(parent) + + # bind wizard page signals + self.setSigningUpStatus.connect( + lambda: self.set_validation_status( + 'validating')) + + self.setTitle("Sign Up") + + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + userNameLabel = QtGui.QLabel("User &name:") + userNameLineEdit = QtGui.QLineEdit() + userNameLineEdit.cursorPositionChanged.connect( + self.reset_validation_status) + userNameLabel.setBuddy(userNameLineEdit) + + # let's add regex validator + usernameRe = QtCore.QRegExp(BARE_USERNAME_REGEX) + userNameLineEdit.setValidator( + QtGui.QRegExpValidator(usernameRe, self)) + self.userNameLineEdit = userNameLineEdit + + userPasswordLabel = QtGui.QLabel("&Password:") + self.userPasswordLineEdit = QtGui.QLineEdit() + self.userPasswordLineEdit.setEchoMode( + QtGui.QLineEdit.Password) + userPasswordLabel.setBuddy(self.userPasswordLineEdit) + + userPassword2Label = QtGui.QLabel("Password (again):") + self.userPassword2LineEdit = QtGui.QLineEdit() + self.userPassword2LineEdit.setEchoMode( + QtGui.QLineEdit.Password) + userPassword2Label.setBuddy(self.userPassword2LineEdit) + + rememberPasswordCheckBox = QtGui.QCheckBox( + "&Remember username and password.") + rememberPasswordCheckBox.setChecked(True) + + self.registerField('userName*', self.userNameLineEdit) + self.registerField('userPassword*', self.userPasswordLineEdit) + + # XXX missing password confirmation + # XXX validator! + + self.registerField('rememberPassword', rememberPasswordCheckBox) + + layout = QtGui.QGridLayout() + layout.setColumnMinimumWidth(0, 20) + + validationMsg = QtGui.QLabel("") + validationMsg.setStyleSheet(ErrorLabelStyleSheet) + + self.validationMsg = validationMsg + + layout.addWidget(validationMsg, 0, 3) + layout.addWidget(userNameLabel, 1, 0) + layout.addWidget(self.userNameLineEdit, 1, 3) + layout.addWidget(userPasswordLabel, 2, 0) + layout.addWidget(userPassword2Label, 3, 0) + layout.addWidget(self.userPasswordLineEdit, 2, 3) + layout.addWidget(self.userPassword2LineEdit, 3, 3) + layout.addWidget(rememberPasswordCheckBox, 4, 3, 4, 4) + self.setLayout(layout) + + # overwritten methods + + def initializePage(self): + """ + inits wizard page + """ + provider = self.field('provider_domain') + self.setSubTitle( + "Register a new user with provider %s." % + provider) + self.validationMsg.setText('') + + def validatePage(self): + """ + validation + we initialize the srp protocol register + and try to register user. if error + returned we write validation error msg + above the form. + """ + # the slot for this signal is not doing + # what's expected. Investigate why, + # right now we're not giving any feedback + # to the user re. what's going on. The only + # thing I can see as a workaround is setting + # a low timeout. + wizard = self.wizard() + + self.setSigningUpStatus.emit() + + username = self.userNameLineEdit.text() + password = self.userPasswordLineEdit.text() + password2 = self.userPassword2LineEdit.text() + + # have some call to a password checker... + + if password != password2: + self.set_validation_status('Password does not match.') + return False + + if len(password) < 6: + self.set_validation_status('Password too short.') + return False + + if password == "123456": + # joking + self.set_validation_status('Password too obvious.') + return False + + domain = self.field('provider_domain') + + if wizard and wizard.debug_server: + # We're debugging + dbgsrv = wizard.debug_server + schema = dbgsrv.scheme + netloc = dbgsrv.netloc + port = None + netloc_split = netloc.split(':') + if len(netloc_split) > 1: + provider, port = netloc_split + else: + provider = netloc + + signup = auth.LeapSRPRegister( + scheme=schema, + provider=provider, + port=port) + + else: + # this is the real thing + signup = auth.LeapSRPRegister( + # XXX FIXME 0 Force HTTPS + #schema="https", + schema="http", + provider=domain) + try: + ok, req = signup.register_user(username, password) + except socket.timeout: + self.set_validation_status( + "Error connecting to provider (timeout)") + return False + + except requests.exceptions.ConnectionError as exc: + logger.error(exc) + self.set_validation_status( + "Error connecting to provider " + "(connection error)") + return False + + if ok: + return True + + # something went wrong. + # not registered, let's catch what. + # get timeout + # ... + if req.status_code == 500: + self.set_validation_status( + "Error during registration (500)") + return False + + validation_msgs = json.loads(req.content) + logger.debug('validation errors: %s' % validation_msgs) + errors = validation_msgs.get('errors', None) + if errors and errors.get('login', None): + # XXX this sometimes catch the blank username + # but we're not allowing that (soon) + self.set_validation_status( + 'Username not available.') + else: + self.set_validation_status( + "Error during sign up") + return False + + def nextId(self): + wizard = self.wizard() + if not wizard: + return + return wizard.get_page_index('connecting') diff --git a/src/leap/gui/firstrun/selectprovider.py b/src/leap/gui/firstrun/selectprovider.py new file mode 100644 index 00000000..d26fdbbb --- /dev/null +++ b/src/leap/gui/firstrun/selectprovider.py @@ -0,0 +1,216 @@ +""" +Select Provider Page, used in First Run Wizard +""" +from PyQt4 import QtCore +from PyQt4 import QtGui + +from leap.base import exceptions as baseexceptions +from leap.crypto import certs +from leap.eip import exceptions as eipexceptions + +from leap.gui.constants import APP_LOGO +from leap.gui.styles import ErrorLabelStyleSheet + + +class SelectProviderPage(QtGui.QWizardPage): + def __init__(self, parent=None, providers=None): + super(SelectProviderPage, self).__init__(parent) + + self.setTitle("Enter Provider") + self.setSubTitle( + "Please enter the domain of the provider you want " + "to use for your connection." + ) + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + self.did_cert_check = False + + providerNameLabel = QtGui.QLabel("h&ttps://") + # note that we expect the bare domain name + # we will add the scheme later + providerNameEdit = QtGui.QLineEdit() + providerNameEdit.cursorPositionChanged.connect( + self.reset_validation_status) + providerNameLabel.setBuddy(providerNameEdit) + + # add regex validator + providerDomainRe = QtCore.QRegExp(r"^[a-z\d_-.]+$") + providerNameEdit.setValidator( + QtGui.QRegExpValidator(providerDomainRe, self)) + self.providerNameEdit = providerNameEdit + + # Eventually we will seed a list of + # well known providers here. + + #providercombo = QtGui.QComboBox() + #if providers: + #for provider in providers: + #providercombo.addItem(provider) + #providerNameSelect = providercombo + + self.registerField('provider_domain*', self.providerNameEdit) + #self.registerField('provider_name_index', providerNameSelect) + + validationMsg = QtGui.QLabel("") + validationMsg.setStyleSheet(ErrorLabelStyleSheet) + self.validationMsg = validationMsg + + # cert info + + # this is used in the callback + # for the checkbox changes. + # tricky, since the first time came + # from the exception message. + # should get string from exception too! + self.bad_cert_status = "Server certificate could not be verified." + + self.certInfo = QtGui.QLabel("") + self.certInfo.setWordWrap(True) + self.certWarning = QtGui.QLabel("") + self.trustProviderCertCheckBox = QtGui.QCheckBox( + "&Trust this provider certificate.") + + self.trustProviderCertCheckBox.stateChanged.connect( + self.onTrustCheckChanged) + + layout = QtGui.QGridLayout() + layout.addWidget(validationMsg, 0, 2) + layout.addWidget(providerNameLabel, 1, 1) + layout.addWidget(providerNameEdit, 1, 2) + + # XXX get a groupbox or something.... + certinfoGroup = QtGui.QGroupBox("Certificate validation") + certinfoLayout = QtGui.QVBoxLayout() + certinfoLayout.addWidget(self.certInfo) + certinfoLayout.addWidget(self.certWarning) + certinfoLayout.addWidget(self.trustProviderCertCheckBox) + certinfoGroup.setLayout(certinfoLayout) + + layout.addWidget(certinfoGroup, 4, 1, 4, 2) + self.certinfoGroup = certinfoGroup + self.certinfoGroup.hide() + + self.setLayout(layout) + + def is_insecure_cert_trusted(self): + return self.trustProviderCertCheckBox.isChecked() + + def onTrustCheckChanged(self, state): + checked = False + if state == 2: + checked = True + + if checked: + self.reset_validation_status() + else: + self.set_validation_status(self.bad_cert_status) + + # trigger signal to redraw next button + self.completeChanged.emit() + + def reset_validation_status(self): + """ + empty the validation msg + """ + self.validationMsg.setText('') + + def set_validation_status(self, status): + self.validationMsg.setText(status) + + def add_cert_info(self, certinfo): + self.certWarning.setText( + "Do you want to trust this provider certificate?") + self.certInfo.setText( + 'SHA-256 fingerprint: %s
' % certinfo) + self.certInfo.setWordWrap(True) + self.certinfoGroup.show() + + # pagewizard methods + + def isComplete(self): + if not self.did_cert_check: + return True + if self.is_insecure_cert_trusted(): + return True + return False + + def initializePage(self): + self.certinfoGroup.hide() + + def validatePage(self): + ################################## + # XXX FIXME! + ################################## + ################################## + ################################## + ################################## + ##### validation skipped !!! ##### + ################################## + ################################## + return True + ################################## + ################################## + ################################## + + # XXX move to ProviderInfo... + + wizard = self.wizard() + netchecker = wizard.netchecker() + providercertchecker = wizard.providercertchecker() + eipconfigchecker = wizard.eipconfigchecker() + + domain = self.providerNameEdit.text() + + # try name resolution + try: + netchecker.check_name_resolution( + domain) + + except baseexceptions.LeapException as exc: + self.set_validation_status(exc.usermessage) + return False + + # try https connection + try: + providercertchecker.is_https_working( + "https://%s" % domain, + verify=True) + + except eipexceptions.HttpsBadCertError as exc: + if self.trustProviderCertCheckBox.isChecked(): + pass + else: + self.set_validation_status(exc.usermessage) + fingerprint = certs.get_cert_fingerprint( + domain=domain, sep=" ") + + # it's ok if we've trusted this fgprt before + trustedcrts = self.wizard().trusted_certs + if trustedcrts and fingerprint.replace(' ', '') in trustedcrts: + pass + else: + # let your user face panick :P + self.add_cert_info(fingerprint) + self.did_cert_check = True + self.completeChanged.emit() + return False + + except baseexceptions.LeapException as exc: + self.set_validation_status(exc.usermessage) + return False + + # try download provider info... + eipconfigchecker.fetch_definition(domain=domain) + wizard.set_providerconfig( + eipconfigchecker.defaultprovider.config) + + # all ok, go on... + return True + + def nextId(self): + wizard = self.wizard() + if not wizard: + return + return wizard.get_page_index('providerinfo') diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py new file mode 100755 index 00000000..fb601714 --- /dev/null +++ b/src/leap/gui/firstrun/wizard.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python +import logging + +import sip +sip.setapi('QString', 2) +sip.setapi('QVariant', 2) + +from PyQt4 import QtCore +from PyQt4 import QtGui + +from leap.base import checks as basechecks +from leap.crypto import leapkeyring +from leap.eip import checks as eipchecks + +from leap.gui import firstrun + +from leap.gui import mainwindow_rc + +try: + from collections import OrderedDict +except ImportError: + # We must be in 2.6 + from leap.util.dicts import OrderedDict + +logger = logging.getLogger(__name__) + +""" +~~~~~~~~~~~~~~~~~~~~~~~~~~ +Work in progress! +~~~~~~~~~~~~~~~~~~~~~~~~~~ +This wizard still needs to be refactored out. + +TODO-ish: + +[X] Break file in wizard / pages files (and its own folder). +[ ] Separate presentation from logic. +[ ] Have a "manager" class for connections, that can be + dep-injected for testing. +[ ] Document signals used / expected. +[ ] Separate style from widgets. +[ ] Fix TOFU Widget for provider cert. +[ ] Refactor widgets out. +[ ] Follow more MVC style. +[ ] Maybe separate "first run wizard" into different wizards + that share some of the pages? +""" + + +class FirstRunWizard(QtGui.QWizard): + + def __init__( + self, + conductor_instance, + parent=None, + eip_username=None, + providers=None, + success_cb=None, is_provider_setup=False, + trusted_certs=None, + netchecker=basechecks.LeapNetworkChecker, + providercertchecker=eipchecks.ProviderCertChecker, + eipconfigchecker=eipchecks.EIPConfigChecker, + start_eipconnection_signal=None, + eip_statuschange_signal=None, + debug_server=None): + super(FirstRunWizard, self).__init__( + parent, + QtCore.Qt.WindowStaysOnTopHint) + + # we keep a reference to the conductor + # to be able to launch eip checks and connection + # in the connection page, before the wizard has ended. + self.conductor = conductor_instance + + self.eip_username = eip_username + self.providers = providers + + # success callback + self.success_cb = success_cb + + # is provider setup? + self.is_provider_setup = is_provider_setup + + # a dict with trusted fingerprints + # in the form {'nospacesfingerprint': ['host1', 'host2']} + self.trusted_certs = trusted_certs + + # Checkers + self.netchecker = netchecker + self.providercertchecker = providercertchecker + self.eipconfigchecker = eipconfigchecker + + # debug server + self.debug_server = debug_server + + # Signals + # will be emitted in connecting page + self.start_eipconnection_signal = start_eipconnection_signal + self.eip_statuschange_signal = eip_statuschange_signal + + self.providerconfig = None + # previously registered + # if True, jumps to LogIn page. + # by setting 1st page?? + #self.is_previously_registered = is_previously_registered + # XXX ??? ^v + self.is_previously_registered = bool(self.eip_username) + self.from_login = False + + pages_dict = OrderedDict(( + ('intro', firstrun.intro.IntroPage), + ('providerselection', + firstrun.selectprovider.SelectProviderPage), + ('login', firstrun.login.LogInPage), + ('providerinfo', firstrun.providerinfo.ProviderInfoPage), + ('providersetupvalidation', + firstrun.providersetup.ProviderSetupValidationPage), + ('signup', firstrun.register.RegisterUserPage), + ('connecting', firstrun.connect.ConnectingPage), + ('lastpage', firstrun.last.LastPage) + )) + self.add_pages_from_dict(pages_dict) + + self.validation_errors = {} + + self.setPixmap( + QtGui.QWizard.BannerPixmap, + QtGui.QPixmap(':/images/banner.png')) + self.setPixmap( + QtGui.QWizard.BackgroundPixmap, + QtGui.QPixmap(':/images/background.png')) + + self.setWindowTitle("First Run Wizard") + + # TODO: set style for MAC / windows ... + #self.setWizardStyle() + + def add_pages_from_dict(self, pages_dict): + """ + @param pages_dict: the dictionary with pages, where + values are a tuple of InstanceofWizardPage, kwargs. + @type pages_dict: dict + """ + for name, page in pages_dict.items(): + # XXX check for is_previously registered + # and skip adding the signup branch if so + self.addPage(page()) + self.pages_dict = pages_dict + + def get_page_index(self, page_name): + """ + returns the index of the given page + @param page_name: the name of the desired page + @type page_name: str + @rparam: index of page in wizard + @rtype: int + """ + return self.pages_dict.keys().index(page_name) + + def set_validation_error(self, pagename, error): + self.validation_errors[pagename] = error + + def get_validation_error(self, pagename): + return self.validation_errors.get(pagename, None) + + def set_providerconfig(self, providerconfig): + self.providerconfig = providerconfig + + def setWindowFlags(self, flags): + logger.debug('setting window flags') + QtGui.QWizard.setWindowFlags(self, flags) + + def focusOutEvent(self, event): + # needed ? + self.setFocus(True) + self.activateWindow() + self.raise_() + self.show() + + def accept(self): + """ + final step in the wizard. + gather the info, update settings + and call the success callback if any has been passed. + """ + super(FirstRunWizard, self).accept() + + # username and password are in different fields + # if they were stored in log_in or sign_up pages. + from_login = self.from_login + unamek_base = 'userName' + passwk_base = 'userPassword' + unamek = 'login_%s' % unamek_base if from_login else unamek_base + passwk = 'login_%s' % passwk_base if from_login else passwk_base + + username = self.field(unamek) + password = self.field(passwk) + provider = self.field('provider_domain') + remember_pass = self.field('rememberPassword') + + logger.debug('chosen provider: %s', provider) + logger.debug('username: %s', username) + logger.debug('remember password: %s', remember_pass) + + # we are assuming here that we only remember one username + # in the form username@provider.domain + # We probably could extend this to support some form of + # profiles. + + settings = QtCore.QSettings() + + settings.setValue("FirstRunWizardDone", True) + settings.setValue("provider_domain", provider) + full_username = "%s@%s" % (username, provider) + + settings.setValue("remember_user_and_pass", remember_pass) + + if remember_pass: + settings.setValue("eip_username", full_username) + seed = self.get_random_str(10) + settings.setValue("%s_seed" % provider, seed) + + # XXX #744: comment out for 0.2.0 release + # if we need to have a version of python-keyring < 0.9 + leapkeyring.leap_set_password( + full_username, password, seed=seed) + + logger.debug('First Run Wizard Done.') + cb = self.success_cb + if cb and callable(cb): + self.success_cb() + + def get_provider_by_index(self): + provider = self.field('provider_index') + return self.providers[provider] + + def get_random_str(self, n): + from string import (ascii_uppercase, ascii_lowercase, digits) + from random import choice + return ''.join(choice( + ascii_uppercase + + ascii_lowercase + + digits) for x in range(n)) + + +if __name__ == '__main__': + # standalone test + # it can be (somehow) run against + # gui/tests/integration/fake_user_signup.py + + import sys + import logging + logging.basicConfig() + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + + app = QtGui.QApplication(sys.argv) + server = sys.argv[1] if len(sys.argv) > 1 else None + + trusted_certs = { + "3DF83F316BFA0186" + "0A11A5C9C7FC24B9" + "18C62B941192CC1A" + "49AE62218B2A4B7C": ['springbok']} + + wizard = FirstRunWizard( + None, trusted_certs=trusted_certs, + debug_server=server) + wizard.show() + sys.exit(app.exec_()) -- cgit v1.2.3 From 00009f8a7de957c4d53c10855cae1396108203c3 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 6 Nov 2012 16:35:48 +0900 Subject: add 404 catch to register --- src/leap/gui/firstrun/register.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index b04844bf..b169f45b 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -101,6 +101,7 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): "Register a new user with provider %s." % provider) self.validationMsg.setText('') + self.userPassword2LineEdit.setText('') def validatePage(self): """ @@ -110,12 +111,6 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): returned we write validation error msg above the form. """ - # the slot for this signal is not doing - # what's expected. Investigate why, - # right now we're not giving any feedback - # to the user re. what's going on. The only - # thing I can see as a workaround is setting - # a low timeout. wizard = self.wizard() self.setSigningUpStatus.emit() @@ -124,7 +119,9 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): password = self.userPasswordLineEdit.text() password2 = self.userPassword2LineEdit.text() - # have some call to a password checker... + # we better have here + # some call to a password checker... + # to assess strenght and avoid silly stuff. if password != password2: self.set_validation_status('Password does not match.') @@ -161,7 +158,9 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): else: # this is the real thing signup = auth.LeapSRPRegister( - # XXX FIXME 0 Force HTTPS + # XXX FIXME FIXME FIXME FIXME + # XXX FIXME 0 Force HTTPS !!! + # XXX FIXME FIXME FIXME FIXME #schema="https", schema="http", provider=domain) @@ -191,6 +190,11 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): "Error during registration (500)") return False + if req.status_code == 404: + self.set_validation_status( + "Error during registration (404)") + return False + validation_msgs = json.loads(req.content) logger.debug('validation errors: %s' % validation_msgs) errors = validation_msgs.get('errors', None) -- cgit v1.2.3 From 6032f776cabcd04aa7f4e1f55a34ecfec2775e85 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 7 Nov 2012 04:48:12 +0900 Subject: changes to update_signal + Fix update + Rename + Changed signature (we update progress bar from slot now) --- src/leap/gui/firstrun/providerinfo.py | 42 +++++++++++++++++++++++++++++----- src/leap/gui/firstrun/providersetup.py | 14 ++++-------- 2 files changed, 41 insertions(+), 15 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index f5f5518a..52b4873f 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -2,22 +2,26 @@ Provider Info Page, used in First run Wizard """ +from PyQt4 import QtCore from PyQt4 import QtGui +from leap.gui.progress import ValidationPage + from leap.gui.constants import APP_LOGO -class ProviderInfoPage(QtGui.QWizardPage): +class ProviderInfoPage(ValidationPage): def __init__(self, parent=None): super(ProviderInfoPage, self).__init__(parent) self.setTitle("Provider Info") - self.setSubTitle("Available information about chosen provider.") + #self.setSubTitle("Available information about chosen provider.") self.setPixmap( QtGui.QWizard.LogoPixmap, QtGui.QPixmap(APP_LOGO)) + def create_info_panel(self): displayName = QtGui.QLabel("") description = QtGui.QLabel("") enrollment_policy = QtGui.QLabel("") @@ -28,15 +32,18 @@ class ProviderInfoPage(QtGui.QWizardPage): self.description = description self.enrollment_policy = enrollment_policy + # this trick allows us to reparent + QtCore.QObjectCleanupHandler().add(self.layout) layout = QtGui.QGridLayout() + layout.addWidget(displayName, 0, 1) layout.addWidget(description, 1, 1) layout.addWidget(enrollment_policy, 2, 1) self.setLayout(layout) + self.update() - def initializePage(self): - # XXX move to show info... + def show_provider_info(self): # XXX get multilingual objects # directly from the config object @@ -59,9 +66,32 @@ class ProviderInfoPage(QtGui.QWizardPage): self.enrollment_policy.setText( 'enrollment policy: %s' % enroll) + def _do_checks(self, update_signal=None): + """ + executes actual checks in a separate thread + """ + import time + update_signal.emit("head_sentinel", 0) + time.sleep(0.5) + update_signal.emit("something", 10) + time.sleep(0.5) + update_signal.emit("done", 90) + time.sleep(1) + update_signal.emit("end_sentinel", 100) + time.sleep(1) + + def _do_validation(self): + """ + called after _do_checks has finished + (connected to checker thread finished signal) + """ + print 'validation...' + self.progress.hide() + self.stepsTableWidget.hide() + self.create_info_panel() + self.show_provider_info() + def nextId(self): wizard = self.wizard() next_ = "providersetupvalidation" return wizard.get_page_index(next_) - - diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index 63f55d00..c039dfc5 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -21,7 +21,7 @@ class ProviderSetupValidationPage(ValidationPage): QtGui.QWizard.LogoPixmap, QtGui.QPixmap(APP_LOGO)) - def _do_checks(self, signal=None): + def _do_checks(self, update_signal=None): """ executes actual checks in a separate thread """ @@ -33,8 +33,7 @@ class ProviderSetupValidationPage(ValidationPage): pCertChecker = wizard.providercertchecker certchecker = pCertChecker(domain=domain) - signal.emit('Fetching CA certificate') - self.progress.setValue(30) + update_signal.emit('Fetching CA certificate', 30) if pconfig: ca_cert_uri = pconfig.get('ca_cert_uri').geturl() @@ -54,8 +53,7 @@ class ProviderSetupValidationPage(ValidationPage): time.sleep(2) - signal.emit('Checking CA fingerprint') - self.progress.setValue(66) + update_signal.emit('Checking CA fingerprint', 66) #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None) # XXX get fingerprint dict (types) @@ -69,8 +67,7 @@ class ProviderSetupValidationPage(ValidationPage): # should catch exception #return False - signal.emit('Validating api certificate') - self.progress.setValue(90) + update_signal.emit('Validating api certificate', 90) #api_uri = pconfig.get('api_uri', None) #try: @@ -91,8 +88,7 @@ class ProviderSetupValidationPage(ValidationPage): time.sleep(0.5) #ca_cert_path = checker.ca_cert_path - self.progress.setValue(100) - signal.emit('end_sentinel') + update_signal.emit('end_sentinel', 100) time.sleep(1) def _do_validation(self): -- cgit v1.2.3 From 63fc5b19e39072f4bda26f2649e83832d68b5000 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 7 Nov 2012 06:06:40 +0900 Subject: fix issue with refresh timing using end_sentinel as a workaround for the lack of refresh. doing a pause so user gets some feedback about what's going on. error messages on select provider page should be all covered now. --- src/leap/gui/firstrun/providerinfo.py | 126 +++++++++++++++++++++++++++++--- src/leap/gui/firstrun/selectprovider.py | 95 +++++++----------------- 2 files changed, 142 insertions(+), 79 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index 52b4873f..5cc34927 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -1,14 +1,27 @@ """ Provider Info Page, used in First run Wizard """ +import logging +import time from PyQt4 import QtCore from PyQt4 import QtGui +import requests + +from leap.base import exceptions as baseexceptions +from leap.crypto import certs +from leap.eip import exceptions as eipexceptions + from leap.gui.progress import ValidationPage from leap.gui.constants import APP_LOGO +logger = logging.getLogger(__name__) + +GUI_PAUSE_FOR_USER_SECONDS = 1 +pause_for_user = lambda: time.sleep(GUI_PAUSE_FOR_USER_SECONDS) + class ProviderInfoPage(ValidationPage): def __init__(self, parent=None): @@ -70,15 +83,95 @@ class ProviderInfoPage(ValidationPage): """ executes actual checks in a separate thread """ - import time + def pause_and_finish(): + update_signal.emit("end_sentinel", 100) + pause_for_user() + + wizard = self.wizard() + prevpage = "providerselection" + netchecker = wizard.netchecker() + providercertchecker = wizard.providercertchecker() + eipconfigchecker = wizard.eipconfigchecker() + + domain = self.field('provider_domain') + update_signal.emit("head_sentinel", 0) - time.sleep(0.5) - update_signal.emit("something", 10) - time.sleep(0.5) - update_signal.emit("done", 90) - time.sleep(1) - update_signal.emit("end_sentinel", 100) - time.sleep(1) + pause_for_user() + + # 1) try name resolution + update_signal.emit("Checking that server is reachable", 20) + logger.debug('checking name resolution') + try: + netchecker.check_name_resolution( + domain) + + except baseexceptions.LeapException as exc: + logger.debug('exception') + wizard.set_validation_error( + prevpage, exc.usermessage) + pause_and_finish() + return False + + # 2) try https connection + update_signal.emit("Checking secure connection to provider", 40) + logger.debug('checking https connection') + try: + providercertchecker.is_https_working( + "https://%s" % domain, + verify=True) + + except eipexceptions.HttpsBadCertError as exc: + logger.debug('exception') + # XXX skipping for now... + ############################################## + # We had this validation logic + # in the provider selection page before + ############################################## + #if self.trustProviderCertCheckBox.isChecked(): + #pass + #else: + wizard.set_validation_error( + prevpage, exc.usermessage) + #fingerprint = certs.get_cert_fingerprint( + #domain=domain, sep=" ") + + # it's ok if we've trusted this fgprt before + #trustedcrts = wizard.trusted_certs + #if trustedcrts and fingerprint.replace(' ', '') in trustedcrts: + #pass + #else: + # let your user face panick :P + #self.add_cert_info(fingerprint) + #self.did_cert_check = True + #self.completeChanged.emit() + #return False + pause_and_finish() + return False + + except baseexceptions.LeapException as exc: + wizard.set_validation_error( + prevpage, exc.usermessage) + pause_and_finish() + return False + + # try download provider info... + update_signal.emit("Downloading provider info", 70) + try: + eipconfigchecker.fetch_definition(domain=domain) + wizard.set_providerconfig( + eipconfigchecker.defaultprovider.config) + # XXX catch errors... + except requests.exceptions.SSLError: + # XXX we should have catched this before. + # but cert checking is broken. + wizard.set_validation_error( + prevpage, + "Could not get info from provider.") + pause_and_finish() + return False + + # We're done + pause_and_finish() def _do_validation(self): """ @@ -86,10 +179,19 @@ class ProviderInfoPage(ValidationPage): (connected to checker thread finished signal) """ print 'validation...' - self.progress.hide() - self.stepsTableWidget.hide() - self.create_info_panel() - self.show_provider_info() + prevpage = "providerselection" + errors = self.wizard().get_validation_error(prevpage) + + if not errors: + self.progress.hide() + self.stepsTableWidget.hide() + self.create_info_panel() + self.show_provider_info() + + else: + logger.debug('going back with errors') + logger.debug('ERRORS: %s' % errors) + self.go_back() def nextId(self): wizard = self.wizard() diff --git a/src/leap/gui/firstrun/selectprovider.py b/src/leap/gui/firstrun/selectprovider.py index d26fdbbb..e95a4af2 100644 --- a/src/leap/gui/firstrun/selectprovider.py +++ b/src/leap/gui/firstrun/selectprovider.py @@ -1,6 +1,8 @@ """ Select Provider Page, used in First Run Wizard """ +import logging + from PyQt4 import QtCore from PyQt4 import QtGui @@ -11,6 +13,8 @@ from leap.eip import exceptions as eipexceptions from leap.gui.constants import APP_LOGO from leap.gui.styles import ErrorLabelStyleSheet +logger = logging.getLogger(__name__) + class SelectProviderPage(QtGui.QWizardPage): def __init__(self, parent=None, providers=None): @@ -136,77 +140,34 @@ class SelectProviderPage(QtGui.QWizardPage): return True return False + def populateErrors(self): + # XXX could move this to ValidationMixin + + #logger.debug('getting errors') + errors = self.wizard().get_validation_error('providerselection') + if errors: + #logger.debug('errors! -> %s', errors) + self.validationMsg.setText(errors) + + def paintEvent(self, event): + """ + we hook our populate errors + on paintEvent because we need it to catch + when user enters the page coming from next, + and initializePage does not cover that case. + Maybe there's a better event to hook upon. + """ + super(SelectProviderPage, self).paintEvent(event) + self.populateErrors() + def initializePage(self): + self.validationMsg.setText('') self.certinfoGroup.hide() def validatePage(self): - ################################## - # XXX FIXME! - ################################## - ################################## - ################################## - ################################## - ##### validation skipped !!! ##### - ################################## - ################################## - return True - ################################## - ################################## - ################################## - - # XXX move to ProviderInfo... - - wizard = self.wizard() - netchecker = wizard.netchecker() - providercertchecker = wizard.providercertchecker() - eipconfigchecker = wizard.eipconfigchecker() - - domain = self.providerNameEdit.text() - - # try name resolution - try: - netchecker.check_name_resolution( - domain) - - except baseexceptions.LeapException as exc: - self.set_validation_status(exc.usermessage) - return False - - # try https connection - try: - providercertchecker.is_https_working( - "https://%s" % domain, - verify=True) - - except eipexceptions.HttpsBadCertError as exc: - if self.trustProviderCertCheckBox.isChecked(): - pass - else: - self.set_validation_status(exc.usermessage) - fingerprint = certs.get_cert_fingerprint( - domain=domain, sep=" ") - - # it's ok if we've trusted this fgprt before - trustedcrts = self.wizard().trusted_certs - if trustedcrts and fingerprint.replace(' ', '') in trustedcrts: - pass - else: - # let your user face panick :P - self.add_cert_info(fingerprint) - self.did_cert_check = True - self.completeChanged.emit() - return False - - except baseexceptions.LeapException as exc: - self.set_validation_status(exc.usermessage) - return False - - # try download provider info... - eipconfigchecker.fetch_definition(domain=domain) - wizard.set_providerconfig( - eipconfigchecker.defaultprovider.config) - - # all ok, go on... + """ + we are doing validation in next page + """ return True def nextId(self): -- cgit v1.2.3 From ee5928e4e066ac9f72a7dec15972817746dbc058 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 8 Nov 2012 05:10:22 +0900 Subject: allow user to specify host:port for provider selection --- src/leap/gui/firstrun/providerinfo.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index 5cc34927..06e00682 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -23,6 +23,21 @@ GUI_PAUSE_FOR_USER_SECONDS = 1 pause_for_user = lambda: time.sleep(GUI_PAUSE_FOR_USER_SECONDS) +def get_https_domain_and_port(full_domain): + """ + returns a tuple with domain and port + from a full_domain string that can + contain a colon + """ + domain_split = full_domain.split(':') + _len = len(domain_split) + if _len == 1: + domain, port = full_domain, 443 + if _len == 2: + domain, port = domain_split + return domain, port + + class ProviderInfoPage(ValidationPage): def __init__(self, parent=None): super(ProviderInfoPage, self).__init__(parent) @@ -93,12 +108,18 @@ class ProviderInfoPage(ValidationPage): providercertchecker = wizard.providercertchecker() eipconfigchecker = wizard.eipconfigchecker() - domain = self.field('provider_domain') + full_domain = self.field('provider_domain') + + # we check if we have a port in the domain string. + domain, port = get_https_domain_and_port(full_domain) + _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain) update_signal.emit("head_sentinel", 0) pause_for_user() + ######################## # 1) try name resolution + ######################## update_signal.emit("Checking that server is reachable", 20) logger.debug('checking name resolution') try: @@ -112,12 +133,14 @@ class ProviderInfoPage(ValidationPage): pause_and_finish() return False + ######################### # 2) try https connection + ######################### update_signal.emit("Checking secure connection to provider", 40) logger.debug('checking https connection') try: providercertchecker.is_https_working( - "https://%s" % domain, + "https://%s" % _domain, verify=True) except eipexceptions.HttpsBadCertError as exc: -- cgit v1.2.3 From 6a4d0b8298e24968106007fabef16238300742cd Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 8 Nov 2012 05:15:27 +0900 Subject: catch conn error while fetching provider info --- src/leap/gui/firstrun/providerinfo.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index 06e00682..9b959602 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -177,13 +177,15 @@ class ProviderInfoPage(ValidationPage): pause_and_finish() return False - # try download provider info... + ################################## + # 3) try download provider info... + ################################## + update_signal.emit("Downloading provider info", 70) try: - eipconfigchecker.fetch_definition(domain=domain) + eipconfigchecker.fetch_definition(domain=_domain) wizard.set_providerconfig( eipconfigchecker.defaultprovider.config) - # XXX catch errors... except requests.exceptions.SSLError: # XXX we should have catched this before. # but cert checking is broken. @@ -192,8 +194,16 @@ class ProviderInfoPage(ValidationPage): "Could not get info from provider.") pause_and_finish() return False + except requests.exceptions.ConnectionError: + wizard.set_validation_error( + prevpage, + "Could not download provider info " + "(refused conn.).") + pause_and_finish() + return False + # XXX catch more errors... - # We're done + # We're done! pause_and_finish() def _do_validation(self): -- cgit v1.2.3 From b3c0634dc6e01656422a6c70297fc46d808f303f Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 8 Nov 2012 06:02:36 +0900 Subject: selectprovider rename + fix onComplete function for provider entry --- src/leap/gui/firstrun/__init__.py | 6 +- src/leap/gui/firstrun/providerselect.py | 181 ++++++++++++++++++++++++++++++++ src/leap/gui/firstrun/register.py | 95 ++--------------- src/leap/gui/firstrun/regvalidation.py | 140 ++++++++++++++++++++++++ src/leap/gui/firstrun/selectprovider.py | 177 ------------------------------- src/leap/gui/firstrun/wizard.py | 4 +- 6 files changed, 339 insertions(+), 264 deletions(-) create mode 100644 src/leap/gui/firstrun/providerselect.py create mode 100644 src/leap/gui/firstrun/regvalidation.py delete mode 100644 src/leap/gui/firstrun/selectprovider.py (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/__init__.py b/src/leap/gui/firstrun/__init__.py index bf8d0df9..477e7269 100644 --- a/src/leap/gui/firstrun/__init__.py +++ b/src/leap/gui/firstrun/__init__.py @@ -8,9 +8,10 @@ import last import login import mixins import providerinfo +import providerselect import providersetup import register -import selectprovider +import regvalidation __all__ = [ 'connect', @@ -19,6 +20,7 @@ __all__ = [ 'login', 'mixins', 'providerinfo', + 'providerselect', 'providersetup', 'register', - 'selectprovider'] + 'regvalidation'] diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py new file mode 100644 index 00000000..c282e3ab --- /dev/null +++ b/src/leap/gui/firstrun/providerselect.py @@ -0,0 +1,181 @@ +""" +Select Provider Page, used in First Run Wizard +""" +import logging + +from PyQt4 import QtCore +from PyQt4 import QtGui + +from leap.base import exceptions as baseexceptions +from leap.crypto import certs +from leap.eip import exceptions as eipexceptions + +from leap.gui.constants import APP_LOGO +from leap.gui.styles import ErrorLabelStyleSheet + +logger = logging.getLogger(__name__) + + +class SelectProviderPage(QtGui.QWizardPage): + def __init__(self, parent=None, providers=None): + super(SelectProviderPage, self).__init__(parent) + + self.setTitle("Enter Provider") + self.setSubTitle( + "Please enter the domain of the provider you want " + "to use for your connection." + ) + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + self.did_cert_check = False + + providerNameLabel = QtGui.QLabel("h&ttps://") + # note that we expect the bare domain name + # we will add the scheme later + providerNameEdit = QtGui.QLineEdit() + providerNameEdit.cursorPositionChanged.connect( + self.reset_validation_status) + providerNameLabel.setBuddy(providerNameEdit) + + # add regex validator + providerDomainRe = QtCore.QRegExp(r"^[a-z\d_-.]+$") + providerNameEdit.setValidator( + QtGui.QRegExpValidator(providerDomainRe, self)) + self.providerNameEdit = providerNameEdit + + # Eventually we will seed a list of + # well known providers here. + + #providercombo = QtGui.QComboBox() + #if providers: + #for provider in providers: + #providercombo.addItem(provider) + #providerNameSelect = providercombo + + self.registerField("provider_domain*", self.providerNameEdit) + #self.registerField('provider_name_index', providerNameSelect) + + validationMsg = QtGui.QLabel("") + validationMsg.setStyleSheet(ErrorLabelStyleSheet) + self.validationMsg = validationMsg + + # cert info + + # this is used in the callback + # for the checkbox changes. + # tricky, since the first time came + # from the exception message. + # should get string from exception too! + self.bad_cert_status = "Server certificate could not be verified." + + self.certInfo = QtGui.QLabel("") + self.certInfo.setWordWrap(True) + self.certWarning = QtGui.QLabel("") + self.trustProviderCertCheckBox = QtGui.QCheckBox( + "&Trust this provider certificate.") + + self.trustProviderCertCheckBox.stateChanged.connect( + self.onTrustCheckChanged) + self.providerNameEdit.textChanged.connect( + self.onProviderChanged) + + layout = QtGui.QGridLayout() + layout.addWidget(validationMsg, 0, 2) + layout.addWidget(providerNameLabel, 1, 1) + layout.addWidget(providerNameEdit, 1, 2) + + # XXX get a groupbox or something.... + certinfoGroup = QtGui.QGroupBox("Certificate validation") + certinfoLayout = QtGui.QVBoxLayout() + certinfoLayout.addWidget(self.certInfo) + certinfoLayout.addWidget(self.certWarning) + certinfoLayout.addWidget(self.trustProviderCertCheckBox) + certinfoGroup.setLayout(certinfoLayout) + + layout.addWidget(certinfoGroup, 4, 1, 4, 2) + self.certinfoGroup = certinfoGroup + self.certinfoGroup.hide() + + self.setLayout(layout) + + def is_insecure_cert_trusted(self): + return self.trustProviderCertCheckBox.isChecked() + + def onTrustCheckChanged(self, state): + checked = False + if state == 2: + checked = True + + if checked: + self.reset_validation_status() + else: + self.set_validation_status(self.bad_cert_status) + + # trigger signal to redraw next button + self.completeChanged.emit() + + def onProviderChanged(self, text): + self.completeChanged.emit() + + def reset_validation_status(self): + """ + empty the validation msg + """ + self.validationMsg.setText('') + + #def set_validation_status(selF, STATUS): + #self.validationMsg.setText(status) + + def add_cert_info(self, certinfo): + self.certWarning.setText( + "Do you want to trust this provider certificate?") + self.certInfo.setText( + 'SHA-256 fingerprint: %s
' % certinfo) + self.certInfo.setWordWrap(True) + self.certinfoGroup.show() + + # pagewizard methods + + def isComplete(self): + provider = self.providerNameEdit.text() + + if not provider: + return False + else: + if self.is_insecure_cert_trusted(): + return True + if not self.did_cert_check: + return True + return False + + def populateErrors(self): + # XXX could move this to ValidationMixin + + #logger.debug('getting errors') + errors = self.wizard().get_validation_error('providerselection') + if errors: + #logger.debug('errors! -> %s', errors) + self.validationMsg.setText(errors) + + def paintEvent(self, event): + """ + we hook our populate errors + on paintEvent because we need it to catch + when user enters the page coming from next, + and initializePage does not cover that case. + Maybe there's a better event to hook upon. + """ + super(SelectProviderPage, self).paintEvent(event) + self.populateErrors() + + def initializePage(self): + self.validationMsg.setText('') + self.certinfoGroup.hide() + + def nextId(self): + wizard = self.wizard() + if not wizard: + return + return wizard.get_page_index('providerinfo') diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index b169f45b..d7e8db0e 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -1,16 +1,12 @@ """ Register User Page, used in First Run Wizard """ -import json import logging -import socket -import requests from PyQt4 import QtCore from PyQt4 import QtGui -from leap.base import auth from leap.gui.firstrun.mixins import UserFormMixIn logger = logging.getLogger(__name__) @@ -20,9 +16,11 @@ from leap.gui.styles import ErrorLabelStyleSheet class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): + setSigningUpStatus = QtCore.pyqtSignal([]) def __init__(self, parent=None): + super(RegisterUserPage, self).__init__(parent) # bind wizard page signals @@ -105,17 +103,16 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): def validatePage(self): """ - validation - we initialize the srp protocol register - and try to register user. if error - returned we write validation error msg - above the form. + we only pre-validate here password weakness + stuff, or any other client side validation + that we think of. + real server validation is made on next page, + and if any errors are thrown there we come back + and re-display the validation label. """ - wizard = self.wizard() - self.setSigningUpStatus.emit() - username = self.userNameLineEdit.text() + #username = self.userNameLineEdit.text() password = self.userPasswordLineEdit.text() password2 = self.userPassword2LineEdit.text() @@ -136,80 +133,10 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): self.set_validation_status('Password too obvious.') return False - domain = self.field('provider_domain') - - if wizard and wizard.debug_server: - # We're debugging - dbgsrv = wizard.debug_server - schema = dbgsrv.scheme - netloc = dbgsrv.netloc - port = None - netloc_split = netloc.split(':') - if len(netloc_split) > 1: - provider, port = netloc_split - else: - provider = netloc - - signup = auth.LeapSRPRegister( - scheme=schema, - provider=provider, - port=port) - - else: - # this is the real thing - signup = auth.LeapSRPRegister( - # XXX FIXME FIXME FIXME FIXME - # XXX FIXME 0 Force HTTPS !!! - # XXX FIXME FIXME FIXME FIXME - #schema="https", - schema="http", - provider=domain) - try: - ok, req = signup.register_user(username, password) - except socket.timeout: - self.set_validation_status( - "Error connecting to provider (timeout)") - return False - - except requests.exceptions.ConnectionError as exc: - logger.error(exc) - self.set_validation_status( - "Error connecting to provider " - "(connection error)") - return False - - if ok: - return True - - # something went wrong. - # not registered, let's catch what. - # get timeout - # ... - if req.status_code == 500: - self.set_validation_status( - "Error during registration (500)") - return False - - if req.status_code == 404: - self.set_validation_status( - "Error during registration (404)") - return False - - validation_msgs = json.loads(req.content) - logger.debug('validation errors: %s' % validation_msgs) - errors = validation_msgs.get('errors', None) - if errors and errors.get('login', None): - # XXX this sometimes catch the blank username - # but we're not allowing that (soon) - self.set_validation_status( - 'Username not available.') - else: - self.set_validation_status( - "Error during sign up") - return False + return True def nextId(self): wizard = self.wizard() if not wizard: return - return wizard.get_page_index('connecting') + return wizard.get_page_index('signupvalidation') diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py new file mode 100644 index 00000000..42b9ccd5 --- /dev/null +++ b/src/leap/gui/firstrun/regvalidation.py @@ -0,0 +1,140 @@ +""" +Provider Setup Validation Page, +used if First Run Wizard +""" +import logging +import json +import socket +import time + +from PyQt4 import QtGui + +import requests + +from leap.gui.progress import ValidationPage + +from leap.base import auth +from leap.gui.constants import APP_LOGO + +logger = logging.getLogger(__name__) + + +class RegisterUserValidationPage(ValidationPage): + + def __init__(self, parent=None): + + super(RegisterUserValidationPage, self).__init__(parent) + self.setTitle("User Creation") + self.setSubTitle( + "Registering account with provider.") + + self.setPixmap( + QtGui.QWizard.LogoPixmap, + QtGui.QPixmap(APP_LOGO)) + + def _do_checks(self, update_signal=None): + """ + executes actual checks in a separate thread + + we initialize the srp protocol register + and try to register user. + """ + wizard = self.wizard() + domain = self.field('provider_domain') + username = self.field('userName') + password = self.field('userPassword') + + update_signal.emit("head_sentinel") + update_signal.emit("registering with provider", 40) + time.sleep(4) + + if wizard and wizard.debug_server: + # We're debugging + dbgsrv = wizard.debug_server + schema = dbgsrv.scheme + netloc = dbgsrv.netloc + port = None + netloc_split = netloc.split(':') + if len(netloc_split) > 1: + provider, port = netloc_split + else: + provider = netloc + + signup = auth.LeapSRPRegister( + scheme=schema, + provider=provider, + port=port) + + else: + # this is the real thing + signup = auth.LeapSRPRegister( + # XXX FIXME FIXME FIXME FIXME + # XXX FIXME 0 Force HTTPS !!! + # XXX FIXME FIXME FIXME FIXME + #schema="https", + schema="http", + provider=domain) + try: + ok, req = signup.register_user(username, password) + except socket.timeout: + self.set_validation_status( + "Error connecting to provider (timeout)") + return False + + except requests.exceptions.ConnectionError as exc: + logger.error(exc) + self.set_validation_status( + "Error connecting to provider " + "(connection error)") + return False + + if ok: + return True + + # something went wrong. + # not registered, let's catch what. + # get timeout + # ... + if req.status_code == 500: + self.set_validation_status( + "Error during registration (500)") + return False + + if req.status_code == 404: + self.set_validation_status( + "Error during registration (404)") + return False + + validation_msgs = json.loads(req.content) + logger.debug('validation errors: %s' % validation_msgs) + errors = validation_msgs.get('errors', None) + if errors and errors.get('login', None): + # XXX this sometimes catch the blank username + # but we're not allowing that (soon) + self.set_validation_status( + 'Username not available.') + else: + self.set_validation_status( + "Error during sign up") + return False + + def _do_validation(self): + """ + called after _do_checks has finished + (connected to checker thread finished signal) + """ + wizard = self.wizard() + if self.errors: + print 'going back with errors' + wizard.set_validation_error( + 'signup', 'that name is taken') + self.go_back() + else: + print 'going next' + self.go_next() + + def nextId(self): + wizard = self.wizard() + if not wizard: + return + return wizard.get_page_index('connecting') diff --git a/src/leap/gui/firstrun/selectprovider.py b/src/leap/gui/firstrun/selectprovider.py deleted file mode 100644 index e95a4af2..00000000 --- a/src/leap/gui/firstrun/selectprovider.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -Select Provider Page, used in First Run Wizard -""" -import logging - -from PyQt4 import QtCore -from PyQt4 import QtGui - -from leap.base import exceptions as baseexceptions -from leap.crypto import certs -from leap.eip import exceptions as eipexceptions - -from leap.gui.constants import APP_LOGO -from leap.gui.styles import ErrorLabelStyleSheet - -logger = logging.getLogger(__name__) - - -class SelectProviderPage(QtGui.QWizardPage): - def __init__(self, parent=None, providers=None): - super(SelectProviderPage, self).__init__(parent) - - self.setTitle("Enter Provider") - self.setSubTitle( - "Please enter the domain of the provider you want " - "to use for your connection." - ) - self.setPixmap( - QtGui.QWizard.LogoPixmap, - QtGui.QPixmap(APP_LOGO)) - - self.did_cert_check = False - - providerNameLabel = QtGui.QLabel("h&ttps://") - # note that we expect the bare domain name - # we will add the scheme later - providerNameEdit = QtGui.QLineEdit() - providerNameEdit.cursorPositionChanged.connect( - self.reset_validation_status) - providerNameLabel.setBuddy(providerNameEdit) - - # add regex validator - providerDomainRe = QtCore.QRegExp(r"^[a-z\d_-.]+$") - providerNameEdit.setValidator( - QtGui.QRegExpValidator(providerDomainRe, self)) - self.providerNameEdit = providerNameEdit - - # Eventually we will seed a list of - # well known providers here. - - #providercombo = QtGui.QComboBox() - #if providers: - #for provider in providers: - #providercombo.addItem(provider) - #providerNameSelect = providercombo - - self.registerField('provider_domain*', self.providerNameEdit) - #self.registerField('provider_name_index', providerNameSelect) - - validationMsg = QtGui.QLabel("") - validationMsg.setStyleSheet(ErrorLabelStyleSheet) - self.validationMsg = validationMsg - - # cert info - - # this is used in the callback - # for the checkbox changes. - # tricky, since the first time came - # from the exception message. - # should get string from exception too! - self.bad_cert_status = "Server certificate could not be verified." - - self.certInfo = QtGui.QLabel("") - self.certInfo.setWordWrap(True) - self.certWarning = QtGui.QLabel("") - self.trustProviderCertCheckBox = QtGui.QCheckBox( - "&Trust this provider certificate.") - - self.trustProviderCertCheckBox.stateChanged.connect( - self.onTrustCheckChanged) - - layout = QtGui.QGridLayout() - layout.addWidget(validationMsg, 0, 2) - layout.addWidget(providerNameLabel, 1, 1) - layout.addWidget(providerNameEdit, 1, 2) - - # XXX get a groupbox or something.... - certinfoGroup = QtGui.QGroupBox("Certificate validation") - certinfoLayout = QtGui.QVBoxLayout() - certinfoLayout.addWidget(self.certInfo) - certinfoLayout.addWidget(self.certWarning) - certinfoLayout.addWidget(self.trustProviderCertCheckBox) - certinfoGroup.setLayout(certinfoLayout) - - layout.addWidget(certinfoGroup, 4, 1, 4, 2) - self.certinfoGroup = certinfoGroup - self.certinfoGroup.hide() - - self.setLayout(layout) - - def is_insecure_cert_trusted(self): - return self.trustProviderCertCheckBox.isChecked() - - def onTrustCheckChanged(self, state): - checked = False - if state == 2: - checked = True - - if checked: - self.reset_validation_status() - else: - self.set_validation_status(self.bad_cert_status) - - # trigger signal to redraw next button - self.completeChanged.emit() - - def reset_validation_status(self): - """ - empty the validation msg - """ - self.validationMsg.setText('') - - def set_validation_status(self, status): - self.validationMsg.setText(status) - - def add_cert_info(self, certinfo): - self.certWarning.setText( - "Do you want to trust this provider certificate?") - self.certInfo.setText( - 'SHA-256 fingerprint: %s
' % certinfo) - self.certInfo.setWordWrap(True) - self.certinfoGroup.show() - - # pagewizard methods - - def isComplete(self): - if not self.did_cert_check: - return True - if self.is_insecure_cert_trusted(): - return True - return False - - def populateErrors(self): - # XXX could move this to ValidationMixin - - #logger.debug('getting errors') - errors = self.wizard().get_validation_error('providerselection') - if errors: - #logger.debug('errors! -> %s', errors) - self.validationMsg.setText(errors) - - def paintEvent(self, event): - """ - we hook our populate errors - on paintEvent because we need it to catch - when user enters the page coming from next, - and initializePage does not cover that case. - Maybe there's a better event to hook upon. - """ - super(SelectProviderPage, self).paintEvent(event) - self.populateErrors() - - def initializePage(self): - self.validationMsg.setText('') - self.certinfoGroup.hide() - - def validatePage(self): - """ - we are doing validation in next page - """ - return True - - def nextId(self): - wizard = self.wizard() - if not wizard: - return - return wizard.get_page_index('providerinfo') diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py index fb601714..7a3d6973 100755 --- a/src/leap/gui/firstrun/wizard.py +++ b/src/leap/gui/firstrun/wizard.py @@ -109,12 +109,14 @@ class FirstRunWizard(QtGui.QWizard): pages_dict = OrderedDict(( ('intro', firstrun.intro.IntroPage), ('providerselection', - firstrun.selectprovider.SelectProviderPage), + firstrun.providerselect.SelectProviderPage), ('login', firstrun.login.LogInPage), ('providerinfo', firstrun.providerinfo.ProviderInfoPage), ('providersetupvalidation', firstrun.providersetup.ProviderSetupValidationPage), ('signup', firstrun.register.RegisterUserPage), + ('signupvalidation', + firstrun.regvalidation.RegisterUserValidationPage), ('connecting', firstrun.connect.ConnectingPage), ('lastpage', firstrun.last.LastPage) )) -- cgit v1.2.3 From b84007d8fec8c949ba4ac1d26695c710a210d797 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 8 Nov 2012 08:37:24 +0900 Subject: more careful error catching during registration. added a twisted server that fakes some of the provider interaction. --- src/leap/gui/firstrun/connect.py | 38 +++--- src/leap/gui/firstrun/providersetup.py | 3 +- src/leap/gui/firstrun/regvalidation.py | 12 +- .../firstrun/tests/integration/fake_provider.py | 132 +++++++++++++++++++++ 4 files changed, 167 insertions(+), 18 deletions(-) create mode 100755 src/leap/gui/firstrun/tests/integration/fake_provider.py (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/connect.py b/src/leap/gui/firstrun/connect.py index 3172a526..283e81b2 100644 --- a/src/leap/gui/firstrun/connect.py +++ b/src/leap/gui/firstrun/connect.py @@ -91,12 +91,19 @@ class ConnectingPage(QtGui.QWizardPage): wizard, 'start_eipconnection_signal', None) - conductor.set_provider_domain(domain) - conductor.run_checks() - self.conductor = conductor - errors = self.eip_error_check() - if not errors and start_eip_signal: - start_eip_signal.emit() + if conductor: + conductor.set_provider_domain(domain) + conductor.run_checks() + self.conductor = conductor + errors = self.eip_error_check() + if not errors and start_eip_signal: + start_eip_signal.emit() + + else: + logger.warning( + "No conductor found. This means that " + "probably the wizard has been launched " + "in an stand-alone way") def eip_error_check(self): """ @@ -110,6 +117,7 @@ class ConnectingPage(QtGui.QWizardPage): # XXX missing! def fetch_and_validate(self): + # XXX MOVE TO validate function in register-validation import time domain = self.field('provider_domain') wizard = self.wizard() @@ -150,15 +158,15 @@ class ConnectingPage(QtGui.QWizardPage): # Download cert try: pCertChecker.download_new_client_cert( - credentials=credentials) - except auth.SRPAuthenticationError: - self.set_validation_status("Authentication error") - #self.set_validation_message( - #"Click next to introduce your " - #"credentials again") - self.goto_login_again = True - # We should do something here - # but it's broken + credentials=credentials, + # FIXME FIXME FIXME + # XXX FIX THIS!!!!! + # BUG #638. remove verify + # FIXME FIXME FIXME + verify=False) + except auth.SRPAuthenticationError as exc: + self.set_validation_status( + "Authentication error: %s" % exc.message) return False time.sleep(2) diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index c039dfc5..2609629a 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -100,7 +100,8 @@ class ProviderSetupValidationPage(ValidationPage): if self.errors: print 'going back with errors' wizard.set_validation_error( - 'signup', 'that name is taken') + 'providerselection', + 'error on provider setup') self.go_back() else: print 'going next' diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index 42b9ccd5..6cf150b6 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -39,14 +39,22 @@ class RegisterUserValidationPage(ValidationPage): we initialize the srp protocol register and try to register user. """ + print 'register user checks' + wizard = self.wizard() domain = self.field('provider_domain') username = self.field('userName') password = self.field('userPassword') - update_signal.emit("head_sentinel") + # XXX use pause_for_user from providerinfo + update_signal.emit("head_sentinel", 0) update_signal.emit("registering with provider", 40) - time.sleep(4) + time.sleep(0.5) + update_signal.emit("registering 2", 60) + time.sleep(1) + update_signal.emit("end_sentinel", 100) + time.sleep(0.5) + return if wizard and wizard.debug_server: # We're debugging diff --git a/src/leap/gui/firstrun/tests/integration/fake_provider.py b/src/leap/gui/firstrun/tests/integration/fake_provider.py new file mode 100755 index 00000000..27886d3b --- /dev/null +++ b/src/leap/gui/firstrun/tests/integration/fake_provider.py @@ -0,0 +1,132 @@ +#/usr/bin/env python +"""A server faking some of the provider resources and apis, +used for testing Leap Client requests. + +Right needs that you create a subfolder named 'certs', +and that you place the following files: + +[ ] certs/leaptestscert.pem +[ ] certs/leaptestskey.pem +[ ] certs/cacert.pem +[ ] certs/openvpn.pem + +[ ] provider.json +[ ] eip-service.json + +""" +import json +import os +import sys + +# GnuTLS Example -- is not working as expected +from gnutls import crypto +from gnutls.constants import COMP_LZO, COMP_DEFLATE, COMP_NULL +from gnutls.interfaces.twisted import X509Credentials + +# Going with OpenSSL as a workaround instead +# But we DO NOT want to introduce this dependency. +from OpenSSL import SSL + +from twisted.web.server import Site +from twisted.web.static import File +from twisted.web.resource import Resource +from twisted.internet import reactor + +# See +# http://twistedmatrix.com/documents/current/web/howto/web-in-60/index.htmln +# for more examples + + +class FakeSession(Resource): + def __init__(self, name): + self.name = name + + def render_GET(self, request): + return json.dumps({'errors': None}) + + def render_POST(self, request): + return json.dumps( + {'salt': 'deadbeef', 'B': 'deadbeef', 'errors': None}) + + def render_PUT(self, request): + return json.dumps( + {'M2': 'deadbeef', 'errors': None}) + + +class API_Sessions(Resource): + def getChild(self, name, request): + return FakeSession(name) + + +def get_certs_path(): + script_path = os.path.realpath(os.path.dirname(sys.argv[0])) + certs_path = os.path.join(script_path, 'certs') + return certs_path + + +def get_TLS_credentials(): + # XXX this is giving errors + # XXX REview! We want to use gnutls! + certs_path = get_certs_path() + + cert = crypto.X509Certificate( + open(certs_path + '/leaptestscert.pem').read()) + key = crypto.X509PrivateKey( + open(certs_path + '/leaptestskey.pem').read()) + ca = crypto.X509Certificate( + open(certs_path + '/cacert.pem').read()) + #crl = crypto.X509CRL(open(certs_path + '/crl.pem').read()) + #cred = crypto.X509Credentials(cert, key, [ca], [crl]) + cred = X509Credentials(cert, key, [ca]) + cred.verify_peer = True + cred.session_params.compressions = (COMP_LZO, COMP_DEFLATE, COMP_NULL) + return cred + + +class OpenSSLServerContextFactory: + # XXX workaround for broken TLS interface + # from gnuTLS. + + def getContext(self): + """Create an SSL context. + This is a sample implementation that loads a certificate from a file + called 'server.pem'.""" + certs_path = get_certs_path() + + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_certificate_file(certs_path + '/leaptestscert.pem') + ctx.use_privatekey_file(certs_path + '/leaptestskey.pem') + return ctx + + +if __name__ == "__main__": + + from twisted.python import log + log.startLogging(sys.stdout) + + root = Resource() + root.putChild("provider.json", File("./provider.json")) + config = Resource() + config.putChild( + "eip-service.json", + File("./eip-service.json")) + apiv1 = Resource() + apiv1.putChild("config", config) + apiv1.putChild("sessions.json", API_Sessions()) + apiv1.putChild("cert", File(get_certs_path() + '/openvpn.pem')) + root.putChild("1", apiv1) + + cred = get_TLS_credentials() + + factory = Site(root) + + # regular http + reactor.listenTCP(8000, factory) + + # TLS with gnutls --- seems broken :( + #reactor.listenTLS(8003, factory, cred) + + # OpenSSL + reactor.listenSSL(8443, factory, OpenSSLServerContextFactory()) + + reactor.run() -- cgit v1.2.3 From 8118056a244ca74d16380ad26a70e3da40e7e401 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 9 Nov 2012 11:21:40 +0900 Subject: connect page merged into regvalidation. Flow nearly working with fake provider, except for authentication. --- src/leap/gui/firstrun/constants.py | 0 src/leap/gui/firstrun/providerinfo.py | 24 +---- src/leap/gui/firstrun/providerselect.py | 10 +- src/leap/gui/firstrun/register.py | 49 ++++++--- src/leap/gui/firstrun/regvalidation.py | 186 +++++++++++++++++++++++--------- 5 files changed, 178 insertions(+), 91 deletions(-) create mode 100644 src/leap/gui/firstrun/constants.py (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/constants.py b/src/leap/gui/firstrun/constants.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index 9b959602..4df477a7 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -2,7 +2,6 @@ Provider Info Page, used in First run Wizard """ import logging -import time from PyQt4 import QtCore from PyQt4 import QtGui @@ -14,29 +13,12 @@ from leap.crypto import certs from leap.eip import exceptions as eipexceptions from leap.gui.progress import ValidationPage +from leap.util.web import get_https_domain_and_port -from leap.gui.constants import APP_LOGO +from leap.gui.constants import APP_LOGO, pause_for_user logger = logging.getLogger(__name__) -GUI_PAUSE_FOR_USER_SECONDS = 1 -pause_for_user = lambda: time.sleep(GUI_PAUSE_FOR_USER_SECONDS) - - -def get_https_domain_and_port(full_domain): - """ - returns a tuple with domain and port - from a full_domain string that can - contain a colon - """ - domain_split = full_domain.split(':') - _len = len(domain_split) - if _len == 1: - domain, port = full_domain, 443 - if _len == 2: - domain, port = domain_split - return domain, port - class ProviderInfoPage(ValidationPage): def __init__(self, parent=None): @@ -127,7 +109,7 @@ class ProviderInfoPage(ValidationPage): domain) except baseexceptions.LeapException as exc: - logger.debug('exception') + logger.error(exc.message) wizard.set_validation_error( prevpage, exc.usermessage) pause_and_finish() diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index c282e3ab..a20f40b3 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -6,9 +6,9 @@ import logging from PyQt4 import QtCore from PyQt4 import QtGui -from leap.base import exceptions as baseexceptions -from leap.crypto import certs -from leap.eip import exceptions as eipexceptions +#from leap.base import exceptions as baseexceptions +#from leap.crypto import certs +#from leap.eip import exceptions as eipexceptions from leap.gui.constants import APP_LOGO from leap.gui.styles import ErrorLabelStyleSheet @@ -30,6 +30,7 @@ class SelectProviderPage(QtGui.QWizardPage): QtGui.QPixmap(APP_LOGO)) self.did_cert_check = False + self.current_page = 'providerselection' providerNameLabel = QtGui.QLabel("h&ttps://") # note that we expect the bare domain name @@ -154,7 +155,8 @@ class SelectProviderPage(QtGui.QWizardPage): # XXX could move this to ValidationMixin #logger.debug('getting errors') - errors = self.wizard().get_validation_error('providerselection') + errors = self.wizard().get_validation_error( + self.current_page) if errors: #logger.debug('errors! -> %s', errors) self.validationMsg.setText(errors) diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index d7e8db0e..0a7ba34b 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -17,23 +17,18 @@ from leap.gui.styles import ErrorLabelStyleSheet class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): - setSigningUpStatus = QtCore.pyqtSignal([]) - def __init__(self, parent=None): super(RegisterUserPage, self).__init__(parent) - # bind wizard page signals - self.setSigningUpStatus.connect( - lambda: self.set_validation_status( - 'validating')) - self.setTitle("Sign Up") self.setPixmap( QtGui.QWizard.LogoPixmap, QtGui.QPixmap(APP_LOGO)) + self.current_page = "signup" + userNameLabel = QtGui.QLabel("User &name:") userNameLineEdit = QtGui.QLineEdit() userNameLineEdit.cursorPositionChanged.connect( @@ -88,18 +83,28 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): layout.addWidget(rememberPasswordCheckBox, 4, 3, 4, 4) self.setLayout(layout) - # overwritten methods + # pagewizard methods - def initializePage(self): + def populateErrors(self): + # XXX could move this to ValidationMixin + + #logger.debug('getting errors') + errors = self.wizard().get_validation_error( + self.current_page) + if errors: + #logger.debug('errors! -> %s', errors) + self.validationMsg.setText(errors) + + def paintEvent(self, event): """ - inits wizard page + we hook our populate errors + on paintEvent because we need it to catch + when user enters the page coming from next, + and initializePage does not cover that case. + Maybe there's a better event to hook upon. """ - provider = self.field('provider_domain') - self.setSubTitle( - "Register a new user with provider %s." % - provider) - self.validationMsg.setText('') - self.userPassword2LineEdit.setText('') + super(RegisterUserPage, self).paintEvent(event) + self.populateErrors() def validatePage(self): """ @@ -110,7 +115,6 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): and if any errors are thrown there we come back and re-display the validation label. """ - self.setSigningUpStatus.emit() #username = self.userNameLineEdit.text() password = self.userPasswordLineEdit.text() @@ -135,6 +139,17 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): return True + def initializePage(self): + """ + inits wizard page + """ + provider = self.field('provider_domain') + self.setSubTitle( + "Register a new user with provider %s." % + provider) + self.validationMsg.setText('') + self.userPassword2LineEdit.setText('') + def nextId(self): wizard = self.wizard() if not wizard: diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index 6cf150b6..f6d69a60 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -5,16 +5,16 @@ used if First Run Wizard import logging import json import socket -import time from PyQt4 import QtGui import requests from leap.gui.progress import ValidationPage +from leap.util.web import get_https_domain_and_port from leap.base import auth -from leap.gui.constants import APP_LOGO +from leap.gui.constants import APP_LOGO, pause_for_user logger = logging.getLogger(__name__) @@ -22,6 +22,10 @@ logger = logging.getLogger(__name__) class RegisterUserValidationPage(ValidationPage): def __init__(self, parent=None): + # XXX TODO: + # We should check if we come from signup + # or login, and change title / first step + # accordingly. super(RegisterUserValidationPage, self).__init__(parent) self.setTitle("User Creation") @@ -39,25 +43,40 @@ class RegisterUserValidationPage(ValidationPage): we initialize the srp protocol register and try to register user. """ - print 'register user checks' - wizard = self.wizard() - domain = self.field('provider_domain') - username = self.field('userName') - password = self.field('userPassword') + full_domain = self.field('provider_domain') + domain, port = get_https_domain_and_port(full_domain) - # XXX use pause_for_user from providerinfo - update_signal.emit("head_sentinel", 0) - update_signal.emit("registering with provider", 40) - time.sleep(0.5) - update_signal.emit("registering 2", 60) - time.sleep(1) - update_signal.emit("end_sentinel", 100) - time.sleep(0.5) - return + # FIXME #BUG 638 FIXME FIXME FIXME + verify = False # !!!!!!!!!!!!!!!! + # FIXME #BUG 638 FIXME FIXME FIXME + + ########################################### + # Set Credentials. + # username and password are in different fields + # if they were stored in log_in or sign_up pages. + + from_login = self.wizard().from_login + unamek_base = 'userName' + passwk_base = 'userPassword' + unamek = 'login_%s' % unamek_base if from_login else unamek_base + passwk = 'login_%s' % passwk_base if from_login else passwk_base + + username = self.field(unamek) + password = self.field(passwk) + credentials = username, password + + eipconfigchecker = wizard.eipconfigchecker() + pCertChecker = wizard.providercertchecker( + domain=domain) + + ########################################### + # XXX this only should be setup + # if not from_login. if wizard and wizard.debug_server: # We're debugging + # XXX remove this branch? dbgsrv = wizard.debug_server schema = dbgsrv.scheme netloc = dbgsrv.netloc @@ -71,71 +90,140 @@ class RegisterUserValidationPage(ValidationPage): signup = auth.LeapSRPRegister( scheme=schema, provider=provider, - port=port) + port=port, + verify=verify) else: # this is the real thing signup = auth.LeapSRPRegister( - # XXX FIXME FIXME FIXME FIXME - # XXX FIXME 0 Force HTTPS !!! - # XXX FIXME FIXME FIXME FIXME - #schema="https", - schema="http", - provider=domain) + schema="https", + port=port, + provider=domain, + verify=verify) + + update_signal.emit("head_sentinel", 0) + + ################################################## + # 1) register user + ################################################## + # XXX this only should be DONE + # if NOT from_login. + + step = "register" + update_signal.emit("registering with provider", 40) + logger.debug('registering user') + try: - ok, req = signup.register_user(username, password) + ok, req = signup.register_user( + username, password) + except socket.timeout: - self.set_validation_status( + self.set_error( + step, "Error connecting to provider (timeout)") + pause_for_user() return False except requests.exceptions.ConnectionError as exc: - logger.error(exc) - self.set_validation_status( + logger.error(exc.message) + self.set_error( + step, "Error connecting to provider " "(connection error)") + # XXX we should signal a BAD step + pause_for_user() + update_signal.emit("connection error!", 50) + pause_for_user() return False - if ok: - return True - - # something went wrong. - # not registered, let's catch what. - # get timeout - # ... - if req.status_code == 500: - self.set_validation_status( - "Error during registration (500)") - return False + # XXX check for != OK instead??? - if req.status_code == 404: - self.set_validation_status( - "Error during registration (404)") + if req.status_code in (404, 500): + self.set_error( + step, + "Error during registration (%s)" % req.status_code) + pause_for_user() return False validation_msgs = json.loads(req.content) - logger.debug('validation errors: %s' % validation_msgs) errors = validation_msgs.get('errors', None) + logger.debug('validation errors: %s' % validation_msgs) + if errors and errors.get('login', None): # XXX this sometimes catch the blank username # but we're not allowing that (soon) - self.set_validation_status( + self.set_error( + step, 'Username not available.') - else: - self.set_validation_status( - "Error during sign up") - return False + pause_for_user() + return False + + pause_for_user() + + ################################################## + # 2) fetching eip service config + ################################################## + + step = "fetch_eipconf" + fetching_eipconf_msg = "Fetching eip service configuration" + update_signal.emit(fetching_eipconf_msg, 60) + try: + eipconfigchecker.fetch_eip_service_config( + domain=full_domain) + + # XXX get specific exception + except: + self.set_error( + step, + 'Could not download eip config.') + pause_for_user() + return False + pause_for_user() + + ################################################## + # 3) getting client certificate + ################################################## + + step = "fetch_eipcert" + fetching_clientcert_msg = "Fetching eip certificate" + update_signal.emit(fetching_clientcert_msg, 80) + + try: + pCertChecker.download_new_client_cert( + credentials=credentials, + verify=verify) + + except auth.SRPAuthenticationError as exc: + self.set_error( + step, + "Authentication error: %s" % exc.message) + return False + + pause_for_user() + + ################ + # end ! + ################ + + update_signal.emit("end_sentinel", 100) + pause_for_user() def _do_validation(self): """ called after _do_checks has finished (connected to checker thread finished signal) """ + is_signup = self.field("is_signup") + prevpage = "signup" if is_signup else "login" + wizard = self.wizard() if self.errors: print 'going back with errors' + logger.error(self.errors) + name, first_error = self.pop_first_error() wizard.set_validation_error( - 'signup', 'that name is taken') + prevpage, + first_error) self.go_back() else: print 'going next' @@ -145,4 +233,4 @@ class RegisterUserValidationPage(ValidationPage): wizard = self.wizard() if not wizard: return - return wizard.get_page_index('connecting') + return wizard.get_page_index('lastpage') -- cgit v1.2.3 From 8fd77ba036cb78c81939bbfce312b12cdc90d881 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 9 Nov 2012 18:13:32 +0900 Subject: working version of the fake provider. wizard can now be completely tested against this. --- .../firstrun/tests/integration/fake_provider.py | 175 ++++++++++++++++++++- 1 file changed, 167 insertions(+), 8 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/tests/integration/fake_provider.py b/src/leap/gui/firstrun/tests/integration/fake_provider.py index 27886d3b..09c6c468 100755 --- a/src/leap/gui/firstrun/tests/integration/fake_provider.py +++ b/src/leap/gui/firstrun/tests/integration/fake_provider.py @@ -1,8 +1,8 @@ -#/usr/bin/env python +#!/usr/bin/env python """A server faking some of the provider resources and apis, -used for testing Leap Client requests. +used for testing Leap Client requests -Right needs that you create a subfolder named 'certs', +It needs that you create a subfolder named 'certs', and that you place the following files: [ ] certs/leaptestscert.pem @@ -14,10 +14,14 @@ and that you place the following files: [ ] eip-service.json """ +import binascii import json import os import sys +# python SRP LIB (! important MUST be >=1.0.1 !) +import srp + # GnuTLS Example -- is not working as expected from gnutls import crypto from gnutls.constants import COMP_LZO, COMP_DEFLATE, COMP_NULL @@ -27,6 +31,8 @@ from gnutls.interfaces.twisted import X509Credentials # But we DO NOT want to introduce this dependency. from OpenSSL import SSL +from zope.interface import Interface, Attribute, implements + from twisted.web.server import Site from twisted.web.static import File from twisted.web.resource import Resource @@ -36,21 +42,173 @@ from twisted.internet import reactor # http://twistedmatrix.com/documents/current/web/howto/web-in-60/index.htmln # for more examples +""" +Testing the FAKE_API: +##################### + + 1) register an user + >> curl -d "user[login]=me" -d "user[password_salt]=foo" -d "user[password_verifier]=beef" http://localhost:8000/1/users.json + << {"errors": null} + + 2) check that if you try to register again, it will fail: + >> curl -d "user[login]=me" -d "user[password_salt]=foo" -d "user[password_verifier]=beef" http://localhost:8000/1/users.json + << {"errors": {"login": "already taken!"}} + +""" + +# Globals to mock user/sessiondb + +USERDB = {} +SESSIONDB = {} + + +safe_unhexlify = lambda x: binascii.unhexlify(x) \ + if (len(x) % 2 == 0) else binascii.unhexlify('0' + x) + + +class IUser(Interface): + login = Attribute("User login.") + salt = Attribute("Password salt.") + verifier = Attribute("Password verifier.") + session = Attribute("Session.") + svr = Attribute("Server verifier.") + + +class User(object): + implements(IUser) + + def __init__(self, login, salt, verifier): + self.login = login + self.salt = salt + self.verifier = verifier + self.session = None + + def set_server_verifier(self, svr): + self.svr = svr + + def set_session(self, session): + SESSIONDB[session] = self + self.session = session + + +class FakeUsers(Resource): + def __init__(self, name): + self.name = name + + def render_POST(self, request): + args = request.args + + login = args['user[login]'][0] + salt = args['user[password_salt]'][0] + verifier = args['user[password_verifier]'][0] + + if login in USERDB: + return "%s\n" % json.dumps( + {'errors': {'login': 'already taken!'}}) + + print login, verifier, salt + user = User(login, salt, verifier) + USERDB[login] = user + return json.dumps({'errors': None}) + + +def get_user(request): + login = request.args.get('login') + if login: + user = USERDB.get(login[0], None) + if user: + return user + + session = request.getSession() + user = SESSIONDB.get(session, None) + return user + class FakeSession(Resource): def __init__(self, name): self.name = name def render_GET(self, request): - return json.dumps({'errors': None}) + return "%s\n" % json.dumps({'errors': None}) def render_POST(self, request): - return json.dumps( - {'salt': 'deadbeef', 'B': 'deadbeef', 'errors': None}) + + user = get_user(request) + + if not user: + # XXX get real error from demo provider + return json.dumps({'errors': 'no such user'}) + + A = request.args['A'][0] + + _A = safe_unhexlify(A) + _salt = safe_unhexlify(user.salt) + _verifier = safe_unhexlify(user.verifier) + + svr = srp.Verifier( + user.login, + _salt, + _verifier, + _A, + hash_alg=srp.SHA256, + ng_type=srp.NG_1024) + + s, B = svr.get_challenge() + + _B = binascii.hexlify(B) + + print 'login = %s' % user.login + print 'salt = %s' % user.salt + print 'len(_salt) = %s' % len(_salt) + print 'vkey = %s' % user.verifier + print 'len(vkey) = %s' % len(_verifier) + print 's = %s' % binascii.hexlify(s) + print 'B = %s' % _B + print 'len(B) = %s' % len(_B) + + session = request.getSession() + user.set_session(session) + user.set_server_verifier(svr) + + # yep, this is tricky. + # some things are *already* unhexlified. + data = { + 'salt': user.salt, + 'B': _B, + 'errors': None} + + return json.dumps(data) def render_PUT(self, request): + + # XXX check session??? + user = get_user(request) + + if not user: + print 'NO USER' + return json.dumps({'errors': 'no such user'}) + + data = request.content.read() + auth = data.split("client_auth=") + M = auth[1] if len(auth) > 1 else None + # if not H, return + if not M: + return json.dumps({'errors': 'no M proof passed by client'}) + + svr = user.svr + HAMK = svr.verify_session(binascii.unhexlify(M)) + if HAMK is None: + print 'verification failed!!!' + raise Exception("Authentication failed!") + #import ipdb;ipdb.set_trace() + + assert svr.authenticated() + print "***" + print 'server authenticated user SRP!' + print "***" + return json.dumps( - {'M2': 'deadbeef', 'errors': None}) + {'M2': binascii.hexlify(HAMK), 'errors': None}) class API_Sessions(Resource): @@ -113,6 +271,7 @@ if __name__ == "__main__": apiv1 = Resource() apiv1.putChild("config", config) apiv1.putChild("sessions.json", API_Sessions()) + apiv1.putChild("users.json", FakeUsers(None)) apiv1.putChild("cert", File(get_certs_path() + '/openvpn.pem')) root.putChild("1", apiv1) @@ -120,7 +279,7 @@ if __name__ == "__main__": factory = Site(root) - # regular http + # regular http (for debugging with curl) reactor.listenTCP(8000, factory) # TLS with gnutls --- seems broken :( -- cgit v1.2.3 From fc857d25025ea07d46a8c8f5ffd4fb2902a76c13 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 10:04:09 +0900 Subject: fix cert fetching over https --- src/leap/gui/firstrun/regvalidation.py | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index f6d69a60..7f149ae7 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -68,38 +68,16 @@ class RegisterUserValidationPage(ValidationPage): eipconfigchecker = wizard.eipconfigchecker() pCertChecker = wizard.providercertchecker( - domain=domain) + domain=full_domain) ########################################### # XXX this only should be setup # if not from_login. - if wizard and wizard.debug_server: - # We're debugging - # XXX remove this branch? - dbgsrv = wizard.debug_server - schema = dbgsrv.scheme - netloc = dbgsrv.netloc - port = None - netloc_split = netloc.split(':') - if len(netloc_split) > 1: - provider, port = netloc_split - else: - provider = netloc - - signup = auth.LeapSRPRegister( - scheme=schema, - provider=provider, - port=port, - verify=verify) - - else: - # this is the real thing - signup = auth.LeapSRPRegister( - schema="https", - port=port, - provider=domain, - verify=verify) + signup = auth.LeapSRPRegister( + schema="https", + provider=full_domain, + verify=verify) update_signal.emit("head_sentinel", 0) -- cgit v1.2.3 From 017ed666038adba315155ad7508f8f3b48368b9f Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 10:16:32 +0900 Subject: add signal to start eip conductor after last checks --- src/leap/gui/firstrun/connect.py | 5 ++++ src/leap/gui/firstrun/regvalidation.py | 49 +++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/connect.py b/src/leap/gui/firstrun/connect.py index 283e81b2..a0fe021c 100644 --- a/src/leap/gui/firstrun/connect.py +++ b/src/leap/gui/firstrun/connect.py @@ -1,6 +1,11 @@ """ Connecting Page, used in First Run Wizard """ +# XXX FIXME +# DEPRECATED. All functionality moved to regvalidation +# This file should be removed after checking that one is ok. +# XXX + import logging from PyQt4 import QtGui diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index 7f149ae7..e2d6d425 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -1,7 +1,13 @@ """ Provider Setup Validation Page, -used if First Run Wizard +used in First Run Wizard """ +# XXX This page is called regvalidation +# but it's implementing functionality in the former +# connect page. +# We should remame it to connect again, when we integrate +# the login branch of the wizard. + import logging import json import socket @@ -186,6 +192,41 @@ class RegisterUserValidationPage(ValidationPage): update_signal.emit("end_sentinel", 100) pause_for_user() + # here we go! :) + self.run_eip_checks_for_provider_and_connect(domain) + + def run_eip_checks_for_provider_and_connect(self, domain): + wizard = self.wizard() + conductor = wizard.conductor + start_eip_signal = getattr( + wizard, + 'start_eipconnection_signal', None) + + if conductor: + conductor.set_provider_domain(domain) + conductor.run_checks() + self.conductor = conductor + errors = self.eip_error_check() + if not errors and start_eip_signal: + start_eip_signal.emit() + + else: + logger.warning( + "No conductor found. This means that " + "probably the wizard has been launched " + "in an stand-alone way.") + + def eip_error_check(self): + """ + a version of the main app error checker, + but integrated within the connecting page of the wizard. + consumes the conductor error queue. + pops errors, and add those to the wizard page + """ + logger.debug('eip error check from connecting page') + errq = self.conductor.error_queue + # XXX missing! + def _do_validation(self): """ called after _do_checks has finished @@ -196,7 +237,7 @@ class RegisterUserValidationPage(ValidationPage): wizard = self.wizard() if self.errors: - print 'going back with errors' + logger.debug('going back with errors') logger.error(self.errors) name, first_error = self.pop_first_error() wizard.set_validation_error( @@ -204,7 +245,9 @@ class RegisterUserValidationPage(ValidationPage): first_error) self.go_back() else: - print 'going next' + logger.debug('going next') + # check if this "next" interferes + # with the eip signal. self.go_next() def nextId(self): -- cgit v1.2.3 From 23c3aa67e3f109eb7d75a89caa34b90144f99efa Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 11:48:39 +0900 Subject: fix layout on providerinfo page --- src/leap/gui/firstrun/providerinfo.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index 4df477a7..0c557d66 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -9,7 +9,7 @@ from PyQt4 import QtGui import requests from leap.base import exceptions as baseexceptions -from leap.crypto import certs +#from leap.crypto import certs from leap.eip import exceptions as eipexceptions from leap.gui.progress import ValidationPage @@ -32,26 +32,36 @@ class ProviderInfoPage(ValidationPage): QtGui.QPixmap(APP_LOGO)) def create_info_panel(self): + # Use stacked widget instead + # of reparenting the layout. + + self.infoWidget = QtGui.QStackedWidget() + + info = QtGui.QWidget() + layout = QtGui.QVBoxLayout() + displayName = QtGui.QLabel("") description = QtGui.QLabel("") enrollment_policy = QtGui.QLabel("") # XXX set stylesheet... # prettify a little bit. # bigger fonts and so on... - self.displayName = displayName - self.description = description - self.enrollment_policy = enrollment_policy - # this trick allows us to reparent - QtCore.QObjectCleanupHandler().add(self.layout) - layout = QtGui.QGridLayout() + layout.addWidget(displayName) + layout.addWidget(description) + layout.addWidget(enrollment_policy) + layout.addStretch(1) + + info.setLayout(layout) + self.infoWidget.addWidget(info) - layout.addWidget(displayName, 0, 1) - layout.addWidget(description, 1, 1) - layout.addWidget(enrollment_policy, 2, 1) + self.layout.addWidget(self.infoWidget) - self.setLayout(layout) - self.update() + # add refs to self to allow for + # updates. + self.displayName = displayName + self.description = description + self.enrollment_policy = enrollment_policy def show_provider_info(self): -- cgit v1.2.3 From fe16e62fa83c24d2ef379a3f9e102d336e527656 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 12:25:11 +0900 Subject: cleanup errors on page init and on field change --- src/leap/gui/firstrun/providerinfo.py | 3 +++ src/leap/gui/firstrun/providerselect.py | 18 ++++++++++++++++-- src/leap/gui/firstrun/register.py | 18 +++++++++++++++--- 3 files changed, 34 insertions(+), 5 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index 0c557d66..ef3b32c1 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -31,6 +31,9 @@ class ProviderInfoPage(ValidationPage): QtGui.QWizard.LogoPixmap, QtGui.QPixmap(APP_LOGO)) + self.prev_page = "providerselection" + #self.current_page = "providerinfo" + def create_info_panel(self): # Use stacked widget instead # of reparenting the layout. diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index a20f40b3..3fb76557 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -153,13 +153,27 @@ class SelectProviderPage(QtGui.QWizardPage): def populateErrors(self): # XXX could move this to ValidationMixin + # with some defaults for the validating fields + # (now it only allows one field, manually specified) #logger.debug('getting errors') errors = self.wizard().get_validation_error( self.current_page) if errors: - #logger.debug('errors! -> %s', errors) - self.validationMsg.setText(errors) + bad_str = getattr(self, 'bad_string', None) + cur_str = self.providerNameEdit.text() + showerr = self.validationMsg.setText + if bad_str is None: + # first time we fall here. + # save the current bad_string value + self.bad_string = cur_str + showerr(errors) + else: + # not the first time + if cur_str == bad_str: + showerr(errors) + else: + showerr('') def paintEvent(self, event): """ diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index 0a7ba34b..6d15c156 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -87,13 +87,25 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): def populateErrors(self): # XXX could move this to ValidationMixin + # used in providerselect too - #logger.debug('getting errors') errors = self.wizard().get_validation_error( self.current_page) if errors: - #logger.debug('errors! -> %s', errors) - self.validationMsg.setText(errors) + bad_str = getattr(self, 'bad_string', None) + cur_str = self.userNameLineEdit.text() + showerr = self.validationMsg.setText + if bad_str is None: + # first time we fall here. + # save the current bad_string value + self.bad_string = cur_str + showerr(errors) + else: + # not the first time + if cur_str == bad_str: + showerr(errors) + else: + showerr('') def paintEvent(self, event): """ -- cgit v1.2.3 From 42ba228eecb5726506848bd08758bd1f925905cc Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 12:45:37 +0900 Subject: fix prevalidation error showing in register page. --- src/leap/gui/firstrun/providerselect.py | 14 ++++++++++++++ src/leap/gui/firstrun/register.py | 27 +++++++++++++++++++++++---- src/leap/gui/firstrun/regvalidation.py | 1 + 3 files changed, 38 insertions(+), 4 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 3fb76557..8d1aa869 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -175,6 +175,13 @@ class SelectProviderPage(QtGui.QWizardPage): else: showerr('') + def cleanup_errormsg(self): + """ + we reset bad_string to None + should be called before leaving the page + """ + self.bad_string = None + def paintEvent(self, event): """ we hook our populate errors @@ -190,6 +197,13 @@ class SelectProviderPage(QtGui.QWizardPage): self.validationMsg.setText('') self.certinfoGroup.hide() + def validatePage(self): + # some cleanup before we leave the page + self.cleanup_errormsg() + + # go + return True + def nextId(self): wizard = self.wizard() if not wizard: diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index 6d15c156..b46dd4cd 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -95,18 +95,30 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): bad_str = getattr(self, 'bad_string', None) cur_str = self.userNameLineEdit.text() showerr = self.validationMsg.setText + prev_er = getattr(self, 'prevalidation_error', None) + if bad_str is None: # first time we fall here. # save the current bad_string value self.bad_string = cur_str showerr(errors) else: + if prev_er: + showerr(prev_er) + return # not the first time if cur_str == bad_str: showerr(errors) else: showerr('') + def cleanup_errormsg(self): + """ + we reset bad_string to None + should be called before leaving the page + """ + self.bad_string = None + def paintEvent(self, event): """ we hook our populate errors @@ -118,6 +130,9 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): super(RegisterUserPage, self).paintEvent(event) self.populateErrors() + def set_prevalidation_error(self, error): + self.prevalidation_error = error + def validatePage(self): """ we only pre-validate here password weakness @@ -137,18 +152,22 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): # to assess strenght and avoid silly stuff. if password != password2: - self.set_validation_status('Password does not match.') + self.set_prevalidation_error('Password does not match.') return False if len(password) < 6: - self.set_validation_status('Password too short.') + self.set_prevalidation_error('Password too short.') return False if password == "123456": - # joking - self.set_validation_status('Password too obvious.') + # joking, but not too much. + self.set_prevalidation_error('Password too obvious.') return False + # some cleanup before we leave the page + self.cleanup_errormsg() + + # go return True def initializePage(self): diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index e2d6d425..b1308051 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -94,6 +94,7 @@ class RegisterUserValidationPage(ValidationPage): # if NOT from_login. step = "register" + update_signal.emit("checking availability", 20) update_signal.emit("registering with provider", 40) logger.debug('registering user') -- cgit v1.2.3 From 6dac344ee1826f9413a40c7783517bc640f826ca Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 22:09:05 +0900 Subject: uncomment download_ca_cert call --- src/leap/gui/firstrun/providersetup.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index 2609629a..a1710a3a 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -7,7 +7,7 @@ from PyQt4 import QtGui from leap.gui.progress import ValidationPage -from leap.gui.constants import APP_LOGO +from leap.gui.constants import APP_LOGO, pause_for_user class ProviderSetupValidationPage(ValidationPage): @@ -25,7 +25,6 @@ class ProviderSetupValidationPage(ValidationPage): """ executes actual checks in a separate thread """ - import time domain = self.field('provider_domain') wizard = self.wizard() pconfig = wizard.providerconfig @@ -33,7 +32,9 @@ class ProviderSetupValidationPage(ValidationPage): pCertChecker = wizard.providercertchecker certchecker = pCertChecker(domain=domain) + update_signal.emit('head_sentinel', 0) update_signal.emit('Fetching CA certificate', 30) + pause_for_user() if pconfig: ca_cert_uri = pconfig.get('ca_cert_uri').geturl() @@ -47,11 +48,10 @@ class ProviderSetupValidationPage(ValidationPage): # (Check with the trusted fingerprints dict # or something smart) - #certchecker.download_ca_cert( - #uri=ca_cert_uri, - #verify=False) - - time.sleep(2) + certchecker.download_ca_cert( + uri=ca_cert_uri, + verify=False) + pause_for_user() update_signal.emit('Checking CA fingerprint', 66) #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None) @@ -61,7 +61,6 @@ class ProviderSetupValidationPage(ValidationPage): #validate_fpr = certchecker.check_ca_cert_fingerprint( #fingerprint=sha256_fpr) - time.sleep(0.5) #if not validate_fpr: # XXX update validationMsg # should catch exception @@ -85,11 +84,11 @@ class ProviderSetupValidationPage(ValidationPage): # XXX update validationMsg # should catch exception #return False - time.sleep(0.5) + pause_for_user() #ca_cert_path = checker.ca_cert_path update_signal.emit('end_sentinel', 100) - time.sleep(1) + pause_for_user() def _do_validation(self): """ -- cgit v1.2.3 From 72f3ef94f0d7deffa9adfba6bde57ae3d9c8d165 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 23:03:12 +0900 Subject: connect wizard cancel button with shutdown --- src/leap/gui/firstrun/wizard.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py index 7a3d6973..bbb48149 100755 --- a/src/leap/gui/firstrun/wizard.py +++ b/src/leap/gui/firstrun/wizard.py @@ -61,7 +61,8 @@ class FirstRunWizard(QtGui.QWizard): eipconfigchecker=eipchecks.EIPConfigChecker, start_eipconnection_signal=None, eip_statuschange_signal=None, - debug_server=None): + debug_server=None, + quitcallback=None): super(FirstRunWizard, self).__init__( parent, QtCore.Qt.WindowStaysOnTopHint) @@ -97,6 +98,11 @@ class FirstRunWizard(QtGui.QWizard): self.start_eipconnection_signal = start_eipconnection_signal self.eip_statuschange_signal = eip_statuschange_signal + if quitcallback is not None: + self.button( + QtGui.QWizard.CancelButton).clicked.connect( + quitcallback) + self.providerconfig = None # previously registered # if True, jumps to LogIn page. -- cgit v1.2.3 From 971c5e2217d17f4dab68336c9d8f562ca63fc84b Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 13 Nov 2012 00:14:53 +0900 Subject: check credentials on login branch plus fix repaint on populateErrors on login page, should refactor to ToBeValidated class or something similar, with the other paintEvent functions that implement the painting of errors coming from the "next" validating page. --- src/leap/gui/firstrun/login.py | 118 +++++++++++++++++++------- src/leap/gui/firstrun/providersetup.py | 75 ++++++++++++++--- src/leap/gui/firstrun/regvalidation.py | 150 +++++++++++++++++---------------- 3 files changed, 230 insertions(+), 113 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py index ae4b23c6..4271c774 100644 --- a/src/leap/gui/firstrun/login.py +++ b/src/leap/gui/firstrun/login.py @@ -4,7 +4,7 @@ LogIn Page, used inf First Run Wizard from PyQt4 import QtCore from PyQt4 import QtGui -import requests +#import requests from leap.gui.firstrun.mixins import UserFormMixIn @@ -18,6 +18,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): self.setTitle("Log In") self.setSubTitle("Log in with your credentials.") + self.current_page = "login" self.setPixmap( QtGui.QWizard.LogoPixmap, @@ -68,30 +69,89 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # pagewizard methods + #### begin possible refactor + + def populateErrors(self): + # XXX could move this to ValidationMixin + # used in providerselect and register too + + errors = self.wizard().get_validation_error( + self.current_page) + prev_er = getattr(self, 'prevalidation_error', None) + showerr = self.validationMsg.setText + + if not errors and prev_er: + showerr(prev_er) + return + + if errors: + bad_str = getattr(self, 'bad_string', None) + cur_str = self.userNameLineEdit.text() + + if bad_str is None: + # first time we fall here. + # save the current bad_string value + self.bad_string = cur_str + showerr(errors) + else: + if prev_er: + showerr(prev_er) + return + # not the first time + if cur_str == bad_str: + showerr(errors) + else: + showerr('') + + def cleanup_errormsg(self): + """ + we reset bad_string to None + should be called before leaving the page + """ + self.bad_string = None + + def paintEvent(self, event): + """ + we hook our populate errors + on paintEvent because we need it to catch + when user enters the page coming from next, + and initializePage does not cover that case. + Maybe there's a better event to hook upon. + """ + super(LogInPage, self).paintEvent(event) + self.populateErrors() + + def set_prevalidation_error(self, error): + self.prevalidation_error = error + + #### end possible refactor + def nextId(self): wizard = self.wizard() if not wizard: return - if wizard.is_provider_setup is True: - next_ = 'connecting' if wizard.is_provider_setup is False: - next_ = 'providersetup' + next_ = 'providersetupvalidation' + if wizard.is_provider_setup is True: + # XXX bad name, ok, gonna change that + next_ = 'signupvalidation' return wizard.get_page_index(next_) def initializePage(self): + super(LogInPage, self).initializePage() self.userNameLineEdit.setText('username@provider.example.org') self.userNameLineEdit.cursorPositionChanged.connect( self.onUserNameEdit) self.initial_username_sample = True def validatePage(self): - wizard = self.wizard() - eipconfigchecker = wizard.eipconfigchecker() + #wizard = self.wizard() + #eipconfigchecker = wizard.eipconfigchecker() full_username = self.userNameLineEdit.text() password = self.userPasswordLineEdit.text() if full_username.count('@') != 1: - self.set_validation_status( + self.set_prevalidation_error( "Username must be in the username@provider form.") return False @@ -100,33 +160,33 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): self.setField('login_userName', username) self.setField('login_userPassword', password) + #################################################### + # Validation logic: + # move to provider setup page + #################################################### # Able to contact domain? # can get definition? # two-by-one - try: - eipconfigchecker.fetch_definition(domain=domain) - + #try: + #eipconfigchecker.fetch_definition(domain=domain) +# # we're using requests here for all # the possible error cases that it catches. - except requests.exceptions.ConnectionError as exc: - self.set_validation_status(exc.message[1]) - return False - except requests.exceptions.HTTPError as exc: - self.set_validation_status(exc.message) - return False - wizard.set_providerconfig( - eipconfigchecker.defaultprovider.config) - - # XXX validate user? or we leave that for later? - # I think the best thing to do for that is - # continue to provider setup page, and if - # we catch authentication error there, redirect - # again to this page (by clicking "next" to - # come here). - # Rationale is that we need to verify server certs - # and so on. - - # mark that we came from login page. + #except requests.exceptions.ConnectionError as exc: + #self.set_validation_status(exc.message[1]) + #return False + #except requests.exceptions.HTTPError as exc: + #self.set_validation_status(exc.message) + #return False + #wizard.set_providerconfig( + #eipconfigchecker.defaultprovider.config) + #################################################### + + # XXX I think this is not needed + # since we're also checking for the is_signup field. self.wizard().from_login = True + # some cleanup before we leave the page + self.cleanup_errormsg() + return True diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index a1710a3a..3fb9a19b 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -2,17 +2,24 @@ Provider Setup Validation Page, used if First Run Wizard """ +import logging from PyQt4 import QtGui +from leap.base import auth from leap.gui.progress import ValidationPage from leap.gui.constants import APP_LOGO, pause_for_user +logger = logging.getLogger(__name__) + class ProviderSetupValidationPage(ValidationPage): def __init__(self, parent=None): super(ProviderSetupValidationPage, self).__init__(parent) + is_signup = self.field("is_signup") + self.is_signup = is_signup + self.setTitle("Setting up provider") #self.setSubTitle( #"auto configuring provider...") @@ -25,14 +32,56 @@ class ProviderSetupValidationPage(ValidationPage): """ executes actual checks in a separate thread """ - domain = self.field('provider_domain') + full_domain = self.field('provider_domain') wizard = self.wizard() pconfig = wizard.providerconfig - pCertChecker = wizard.providercertchecker - certchecker = pCertChecker(domain=domain) + #pCertChecker = wizard.providercertchecker + #certchecker = pCertChecker(domain=full_domain) + pCertChecker = wizard.providercertchecker( + domain=full_domain) update_signal.emit('head_sentinel', 0) + + ###################################### + if not self.is_signup: + # We come from login page. + # We try a call to an authenticated + # page here as a mean to catch + # srp authentication errors while + # we are still at one page's reach + # of the login credentials input page. + # (so we're able to go back an correct) + + step = "fetch_eipcert" + update_signal.emit('validating credentials', 20) + + unamek = 'login_userName' + passwk = 'login_userPassword' + + username = self.field(unamek) + password = self.field(passwk) + credentials = username, password + + ################# + # FIXME #BUG #638 + verify = False + + try: + pCertChecker.download_new_client_cert( + credentials=credentials, + verify=verify) + + except auth.SRPAuthenticationError as exc: + self.set_error( + step, + "Authentication error: %s" % exc.message) + return False + + pause_for_user() + + ####################################### + update_signal.emit('Fetching CA certificate', 30) pause_for_user() @@ -48,7 +97,7 @@ class ProviderSetupValidationPage(ValidationPage): # (Check with the trusted fingerprints dict # or something smart) - certchecker.download_ca_cert( + pCertChecker.download_ca_cert( uri=ca_cert_uri, verify=False) pause_for_user() @@ -59,7 +108,7 @@ class ProviderSetupValidationPage(ValidationPage): # XXX get fingerprint dict (types) #sha256_fpr = ca_cert_fingerprint.split('=')[1] - #validate_fpr = certchecker.check_ca_cert_fingerprint( + #validate_fpr = pCertChecker.check_ca_cert_fingerprint( #fingerprint=sha256_fpr) #if not validate_fpr: # XXX update validationMsg @@ -70,7 +119,7 @@ class ProviderSetupValidationPage(ValidationPage): #api_uri = pconfig.get('api_uri', None) #try: - #api_cert_verified = certchecker.verify_api_https(api_uri) + #api_cert_verified = pCertChecker.verify_api_https(api_uri) #except requests.exceptions.SSLError as exc: #logger.error('BUG #638. %s' % exc.message) # XXX RAISE! See #638 @@ -95,15 +144,18 @@ class ProviderSetupValidationPage(ValidationPage): called after _do_checks has finished (connected to checker thread finished signal) """ + prevpage = "providerselection" if self.is_signup else "login" wizard = self.wizard() + if self.errors: - print 'going back with errors' + logger.debug('going back with errors') + name, first_error = self.pop_first_error() wizard.set_validation_error( - 'providerselection', - 'error on provider setup') + prevpage, + first_error) self.go_back() else: - print 'going next' + logger.debug('going next') self.go_next() def nextId(self): @@ -114,5 +166,6 @@ class ProviderSetupValidationPage(ValidationPage): if is_signup is True: next_ = 'signup' if is_signup is False: - next_ = 'connecting' + # XXX bad name. change to connect again. + next_ = 'signupvalidation' return wizard.get_page_index(next_) diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index b1308051..e85c2ac6 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -28,15 +28,20 @@ logger = logging.getLogger(__name__) class RegisterUserValidationPage(ValidationPage): def __init__(self, parent=None): - # XXX TODO: - # We should check if we come from signup - # or login, and change title / first step - # accordingly. - super(RegisterUserValidationPage, self).__init__(parent) - self.setTitle("User Creation") - self.setSubTitle( - "Registering account with provider.") + is_signup = self.field("is_signup") + self.is_signup = is_signup + + if is_signup: + title = "User Creation" + subtitle = "Registering account with provider." + else: + title = "Connecting..." + # XXX uh... really? + subtitle = "Checking connection with provider." + + self.setTitle(title) + self.setSubTitle(subtitle) self.setPixmap( QtGui.QWizard.LogoPixmap, @@ -61,12 +66,12 @@ class RegisterUserValidationPage(ValidationPage): # Set Credentials. # username and password are in different fields # if they were stored in log_in or sign_up pages. + is_signup = self.is_signup - from_login = self.wizard().from_login unamek_base = 'userName' passwk_base = 'userPassword' - unamek = 'login_%s' % unamek_base if from_login else unamek_base - passwk = 'login_%s' % passwk_base if from_login else passwk_base + unamek = 'login_%s' % unamek_base if not is_signup else unamek_base + passwk = 'login_%s' % passwk_base if not is_signup else passwk_base username = self.field(unamek) password = self.field(passwk) @@ -77,73 +82,73 @@ class RegisterUserValidationPage(ValidationPage): domain=full_domain) ########################################### - # XXX this only should be setup - # if not from_login. - - signup = auth.LeapSRPRegister( - schema="https", - provider=full_domain, - verify=verify) + # only if from signup + if is_signup: + signup = auth.LeapSRPRegister( + schema="https", + provider=full_domain, + verify=verify) update_signal.emit("head_sentinel", 0) ################################################## # 1) register user ################################################## - # XXX this only should be DONE - # if NOT from_login. - - step = "register" - update_signal.emit("checking availability", 20) - update_signal.emit("registering with provider", 40) - logger.debug('registering user') - - try: - ok, req = signup.register_user( - username, password) + # only if from signup. + + if is_signup: + + step = "register" + update_signal.emit("checking availability", 20) + update_signal.emit("registering with provider", 40) + logger.debug('registering user') + + try: + ok, req = signup.register_user( + username, password) + + except socket.timeout: + self.set_error( + step, + "Error connecting to provider (timeout)") + pause_for_user() + return False + + except requests.exceptions.ConnectionError as exc: + logger.error(exc.message) + self.set_error( + step, + "Error connecting to provider " + "(connection error)") + # XXX we should signal a BAD step + pause_for_user() + update_signal.emit("connection error!", 50) + pause_for_user() + return False + + # XXX check for != OK instead??? + + if req.status_code in (404, 500): + self.set_error( + step, + "Error during registration (%s)" % req.status_code) + pause_for_user() + return False + + validation_msgs = json.loads(req.content) + errors = validation_msgs.get('errors', None) + logger.debug('validation errors: %s' % validation_msgs) + + if errors and errors.get('login', None): + # XXX this sometimes catch the blank username + # but we're not allowing that (soon) + self.set_error( + step, + 'Username not available.') + pause_for_user() + return False - except socket.timeout: - self.set_error( - step, - "Error connecting to provider (timeout)") pause_for_user() - return False - - except requests.exceptions.ConnectionError as exc: - logger.error(exc.message) - self.set_error( - step, - "Error connecting to provider " - "(connection error)") - # XXX we should signal a BAD step - pause_for_user() - update_signal.emit("connection error!", 50) - pause_for_user() - return False - - # XXX check for != OK instead??? - - if req.status_code in (404, 500): - self.set_error( - step, - "Error during registration (%s)" % req.status_code) - pause_for_user() - return False - - validation_msgs = json.loads(req.content) - errors = validation_msgs.get('errors', None) - logger.debug('validation errors: %s' % validation_msgs) - - if errors and errors.get('login', None): - # XXX this sometimes catch the blank username - # but we're not allowing that (soon) - self.set_error( - step, - 'Username not available.') - pause_for_user() - return False - - pause_for_user() ################################################## # 2) fetching eip service config @@ -168,7 +173,7 @@ class RegisterUserValidationPage(ValidationPage): ################################################## # 3) getting client certificate ################################################## - + # XXX maybe only do this if we come from signup step = "fetch_eipcert" fetching_clientcert_msg = "Fetching eip certificate" update_signal.emit(fetching_clientcert_msg, 80) @@ -233,8 +238,7 @@ class RegisterUserValidationPage(ValidationPage): called after _do_checks has finished (connected to checker thread finished signal) """ - is_signup = self.field("is_signup") - prevpage = "signup" if is_signup else "login" + prevpage = "signup" if self.is_signup else "login" wizard = self.wizard() if self.errors: -- cgit v1.2.3 From d2dcf5a1060d60c451570349a6a06ad102d6924c Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 13 Nov 2012 21:54:04 +0900 Subject: fix missing provider parameter in leapconfig objects chain --- src/leap/gui/firstrun/last.py | 1 + src/leap/gui/firstrun/providerinfo.py | 9 ++++++--- src/leap/gui/firstrun/regvalidation.py | 9 +++++++-- 3 files changed, 14 insertions(+), 5 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/last.py b/src/leap/gui/firstrun/last.py index 6f9abbb5..13b2f548 100644 --- a/src/leap/gui/firstrun/last.py +++ b/src/leap/gui/firstrun/last.py @@ -28,6 +28,7 @@ class LastPage(QtGui.QWizardPage): self.label = QtGui.QLabel() self.label.setWordWrap(True) + # XXX REFACTOR to a Validating Page... self.status_line_1 = QtGui.QLabel() self.status_line_2 = QtGui.QLabel() self.status_line_3 = QtGui.QLabel() diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index ef3b32c1..e642fcd0 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -99,9 +99,6 @@ class ProviderInfoPage(ValidationPage): wizard = self.wizard() prevpage = "providerselection" - netchecker = wizard.netchecker() - providercertchecker = wizard.providercertchecker() - eipconfigchecker = wizard.eipconfigchecker() full_domain = self.field('provider_domain') @@ -109,6 +106,10 @@ class ProviderInfoPage(ValidationPage): domain, port = get_https_domain_and_port(full_domain) _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain) + netchecker = wizard.netchecker() + providercertchecker = wizard.providercertchecker() + eipconfigchecker = wizard.eipconfigchecker(domain=_domain) + update_signal.emit("head_sentinel", 0) pause_for_user() @@ -178,6 +179,8 @@ class ProviderInfoPage(ValidationPage): update_signal.emit("Downloading provider info", 70) try: + # XXX we already set _domain in the initialization + # so it should not be needed here. eipconfigchecker.fetch_definition(domain=_domain) wizard.set_providerconfig( eipconfigchecker.defaultprovider.config) diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index e85c2ac6..6681b953 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -57,6 +57,7 @@ class RegisterUserValidationPage(ValidationPage): wizard = self.wizard() full_domain = self.field('provider_domain') domain, port = get_https_domain_and_port(full_domain) + _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain) # FIXME #BUG 638 FIXME FIXME FIXME verify = False # !!!!!!!!!!!!!!!! @@ -77,7 +78,8 @@ class RegisterUserValidationPage(ValidationPage): password = self.field(passwk) credentials = username, password - eipconfigchecker = wizard.eipconfigchecker() + eipconfigchecker = wizard.eipconfigchecker(domain=_domain) + #XXX change for _domain (sanitized) pCertChecker = wizard.providercertchecker( domain=full_domain) @@ -174,6 +176,7 @@ class RegisterUserValidationPage(ValidationPage): # 3) getting client certificate ################################################## # XXX maybe only do this if we come from signup + step = "fetch_eipcert" fetching_clientcert_msg = "Fetching eip certificate" update_signal.emit(fetching_clientcert_msg, 80) @@ -199,7 +202,7 @@ class RegisterUserValidationPage(ValidationPage): pause_for_user() # here we go! :) - self.run_eip_checks_for_provider_and_connect(domain) + self.run_eip_checks_for_provider_and_connect(_domain) def run_eip_checks_for_provider_and_connect(self, domain): wizard = self.wizard() @@ -208,6 +211,8 @@ class RegisterUserValidationPage(ValidationPage): wizard, 'start_eipconnection_signal', None) + import pdb4qt; pdb4qt.set_trace() + if conductor: conductor.set_provider_domain(domain) conductor.run_checks() -- cgit v1.2.3 From d24c7328fa845737dbb83d512e4b3f287634c4cc Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 14 Nov 2012 00:33:05 +0900 Subject: make tests pass + pep8 They were breaking mainly because I did not bother to have a pass over them to change the PROVIDER settings from the branding case. All good now, although much testing is yet needed and some refactor could be used. long live green tests! --- src/leap/gui/firstrun/__init__.py | 9 ++++++--- src/leap/gui/firstrun/regvalidation.py | 2 -- src/leap/gui/firstrun/tests/integration/fake_provider.py | 10 +++++++--- 3 files changed, 13 insertions(+), 8 deletions(-) (limited to 'src/leap/gui/firstrun') diff --git a/src/leap/gui/firstrun/__init__.py b/src/leap/gui/firstrun/__init__.py index 477e7269..8a70d90e 100644 --- a/src/leap/gui/firstrun/__init__.py +++ b/src/leap/gui/firstrun/__init__.py @@ -1,6 +1,9 @@ -import sip -sip.setapi('QString', 2) -sip.setapi('QVariant', 2) +try: + import sip + sip.setapi('QString', 2) + sip.setapi('QVariant', 2) +except ValueError: + pass import connect import intro diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index 6681b953..dbe30d3c 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -211,8 +211,6 @@ class RegisterUserValidationPage(ValidationPage): wizard, 'start_eipconnection_signal', None) - import pdb4qt; pdb4qt.set_trace() - if conductor: conductor.set_provider_domain(domain) conductor.run_checks() diff --git a/src/leap/gui/firstrun/tests/integration/fake_provider.py b/src/leap/gui/firstrun/tests/integration/fake_provider.py index 09c6c468..33ee0ee6 100755 --- a/src/leap/gui/firstrun/tests/integration/fake_provider.py +++ b/src/leap/gui/firstrun/tests/integration/fake_provider.py @@ -12,8 +12,10 @@ and that you place the following files: [ ] provider.json [ ] eip-service.json - """ +# XXX NOTE: intended for manual debug. +# I intend to include this as a regular test after 0.2.0 release +# (so we can add twisted as a dep there) import binascii import json import os @@ -47,11 +49,13 @@ Testing the FAKE_API: ##################### 1) register an user - >> curl -d "user[login]=me" -d "user[password_salt]=foo" -d "user[password_verifier]=beef" http://localhost:8000/1/users.json + >> curl -d "user[login]=me" -d "user[password_salt]=foo" \ + -d "user[password_verifier]=beef" http://localhost:8000/1/users.json << {"errors": null} 2) check that if you try to register again, it will fail: - >> curl -d "user[login]=me" -d "user[password_salt]=foo" -d "user[password_verifier]=beef" http://localhost:8000/1/users.json + >> curl -d "user[login]=me" -d "user[password_salt]=foo" \ + -d "user[password_verifier]=beef" http://localhost:8000/1/users.json << {"errors": {"login": "already taken!"}} """ -- cgit v1.2.3