diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/gui/firstrun/last.py | 2 | ||||
| -rw-r--r-- | src/leap/gui/firstrun/login.py | 244 | ||||
| -rw-r--r-- | src/leap/gui/firstrun/providerinfo.py | 178 | ||||
| -rw-r--r-- | src/leap/gui/firstrun/providerselect.py | 334 | ||||
| -rw-r--r-- | src/leap/gui/firstrun/providersetup.py | 177 | ||||
| -rw-r--r-- | src/leap/gui/firstrun/register.py | 260 | ||||
| -rw-r--r-- | src/leap/gui/firstrun/regvalidation.py | 179 | ||||
| -rwxr-xr-x | src/leap/gui/firstrun/wizard.py | 11 | ||||
| -rw-r--r-- | src/leap/gui/mainwindow_rc.py | 367 | ||||
| -rw-r--r-- | src/leap/gui/progress.py | 299 | ||||
| -rw-r--r-- | src/leap/gui/styles.py | 18 | ||||
| -rw-r--r-- | src/leap/gui/test_mainwindow_rc.py | 2 | ||||
| -rw-r--r-- | src/leap/gui/threads.py | 8 | 
13 files changed, 1434 insertions, 645 deletions
| diff --git a/src/leap/gui/firstrun/last.py b/src/leap/gui/firstrun/last.py index 13b2f548..d33d2e77 100644 --- a/src/leap/gui/firstrun/last.py +++ b/src/leap/gui/firstrun/last.py @@ -78,6 +78,8 @@ class LastPage(QtGui.QWizardPage):                      break          except GeneratorExit:              pass +        except StopIteration: +            pass      def initializePage(self):          wizard = self.wizard() diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py index 4271c774..02bace86 100644 --- a/src/leap/gui/firstrun/login.py +++ b/src/leap/gui/firstrun/login.py @@ -4,17 +4,22 @@ LogIn Page, used inf First Run Wizard  from PyQt4 import QtCore  from PyQt4 import QtGui -#import requests +import requests +from leap.base import auth  from leap.gui.firstrun.mixins import UserFormMixIn +from leap.gui.progress import InlineValidationPage +from leap.gui import styles  from leap.gui.constants import APP_LOGO, FULL_USERNAME_REGEX -from leap.gui.styles import ErrorLabelStyleSheet -class LogInPage(QtGui.QWizardPage, UserFormMixIn): +class LogInPage(InlineValidationPage, UserFormMixIn):  # InlineValidationPage +      def __init__(self, parent=None): +          super(LogInPage, self).__init__(parent) +        self.current_page = "login"          self.setTitle("Log In")          self.setSubTitle("Log in with your credentials.") @@ -24,6 +29,12 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):              QtGui.QWizard.LogoPixmap,              QtGui.QPixmap(APP_LOGO)) +        self.setupSteps() +        self.setupUI() + +        self.do_confirm_next = False + +    def setupUI(self):          userNameLabel = QtGui.QLabel("User &name:")          userNameLineEdit = QtGui.QLineEdit()          userNameLineEdit.cursorPositionChanged.connect( @@ -34,6 +45,9 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):          usernameRe = QtCore.QRegExp(FULL_USERNAME_REGEX)          userNameLineEdit.setValidator(              QtGui.QRegExpValidator(usernameRe, self)) + +        #userNameLineEdit.setPlaceholderText( +            #'username@provider.example.org')          self.userNameLineEdit = userNameLineEdit          userPasswordLabel = QtGui.QLabel("&Password:") @@ -49,7 +63,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):          layout.setColumnMinimumWidth(0, 20)          validationMsg = QtGui.QLabel("") -        validationMsg.setStyleSheet(ErrorLabelStyleSheet) +        validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet)          self.validationMsg = validationMsg          layout.addWidget(validationMsg, 0, 3) @@ -58,18 +72,38 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):          layout.addWidget(userPasswordLabel, 2, 0)          layout.addWidget(self.userPasswordLineEdit, 2, 3) +        # add validation frame +        self.setupValidationFrame() +        layout.addWidget(self.valFrame, 4, 2, 4, 2) +        self.valFrame.hide() + +        self.nextText("Log in")          self.setLayout(layout)          #self.registerField('is_login_wizard') +    def nextText(self, text): +        self.setButtonText( +            QtGui.QWizard.NextButton, text) + +    def nextFocus(self): +        self.wizard().button( +            QtGui.QWizard.NextButton).setFocus() + +    def disableNextButton(self): +        self.wizard().button( +            QtGui.QWizard.NextButton).setDisabled(True) +      def onUserNameEdit(self, *args):          if self.initial_username_sample:              self.userNameLineEdit.setText('') +            # XXX set regular color              self.initial_username_sample = None -    # pagewizard methods - -    #### begin possible refactor +    def disableFields(self): +        for field in (self.userNameLineEdit, +                      self.userPasswordLineEdit): +            field.setDisabled(True)      def populateErrors(self):          # XXX could move this to ValidationMixin @@ -77,13 +111,13 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):          errors = self.wizard().get_validation_error(              self.current_page) -        prev_er = getattr(self, 'prevalidation_error', None) +        #prev_er = getattr(self, 'prevalidation_error', None)          showerr = self.validationMsg.setText -        if not errors and prev_er: -            showerr(prev_er) -            return - +        #if not errors and prev_er: +            #showerr(prev_er) +            #return +#          if errors:              bad_str = getattr(self, 'bad_string', None)              cur_str = self.userNameLineEdit.text() @@ -94,13 +128,14 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):                  self.bad_string = cur_str                  showerr(errors)              else: -                if prev_er: -                    showerr(prev_er) -                    return +                #if prev_er: +                    #showerr(prev_er) +                    #return                  # not the first time                  if cur_str == bad_str:                      showerr(errors)                  else: +                    self.focused_field = False                      showerr('')      def cleanup_errormsg(self): @@ -124,7 +159,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):      def set_prevalidation_error(self, error):          self.prevalidation_error = error -    #### end possible refactor +    # pagewizard methods      def nextId(self):          wizard = self.wizard() @@ -139,54 +174,157 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):      def initializePage(self):          super(LogInPage, self).initializePage() -        self.userNameLineEdit.setText('username@provider.example.org') -        self.userNameLineEdit.cursorPositionChanged.connect( +        username = self.userNameLineEdit +        username.setText('username@provider.example.org') +        username.cursorPositionChanged.connect(              self.onUserNameEdit)          self.initial_username_sample = True +        self.validationMsg.setText('') +        self.valFrame.hide() + +    def reset_validation_status(self): +        """ +        empty the validation msg +        and clean the inline validation widget. +        """ +        self.validationMsg.setText('') +        self.steps.removeAllSteps() +        self.clearTable()      def validatePage(self): -        #wizard = self.wizard() -        #eipconfigchecker = wizard.eipconfigchecker() +        """ +        if not register done, do checks. +        if done, wait for click. +        """ +        self.disableNextButton() +        self.cleanup_errormsg() +        self.clean_wizard_errors(self.current_page) + +        if self.do_confirm_next: +            full_username = self.userNameLineEdit.text() +            password = self.userPasswordLineEdit.text() +            username, domain = full_username.split('@') +            self.setField('provider_domain', domain) +            self.setField('login_userName', username) +            self.setField('login_userPassword', password) + +            return True + +        if not self.is_done(): +            self.reset_validation_status() +            self.do_checks() + +        return self.is_done() + +    def _do_checks(self): +        # XXX convert this to inline          full_username = self.userNameLineEdit.text() -        password = self.userPasswordLineEdit.text() -        if full_username.count('@') != 1: -            self.set_prevalidation_error( -                "Username must be in the username@provider form.") -            return False +        ########################### +        # 0) check user@domain form +        ########################### + +        def checkusername(): +            if full_username.count('@') != 1: +                return self.fail( +                    self.tr( +                        "Username must be in the username@provider form.")) +            else: +                return True + +        yield(("head_sentinel", 0), checkusername) + +        # XXX I think this is not needed +        # since we're also checking for the is_signup field. +        #self.wizard().from_login = True          username, domain = full_username.split('@') -        self.setField('provider_domain', domain) -        self.setField('login_userName', username) -        self.setField('login_userPassword', password) - -        #################################################### -        # Validation logic: -        # move to provider setup page -        #################################################### +        password = self.userPasswordLineEdit.text() + +        # We try a call to an authenticated +        # page here as a mean to catch +        # srp authentication errors while +        wizard = self.wizard() +        eipconfigchecker = wizard.eipconfigchecker() + +        ######################## +        # 1) try name resolution +        ######################## +        # show the frame before going on... +        QtCore.QMetaObject.invokeMethod( +            self, "showStepsFrame") +          # 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) -        #################################################### +        def resolvedomain(): +            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: +                return self.fail(exc.message[1]) +            except requests.exceptions.HTTPError as exc: +                return self.fail(exc.message) +            except Exception as exc: +                # XXX get catchall error msg +                return self.fail( +                    exc.message) + +        yield((self.tr("resolving domain name"), 20), resolvedomain) + +        wizard.set_providerconfig( +            eipconfigchecker.defaultprovider.config) + +        ######################## +        # 2) do authentication +        ######################## +        credentials = username, password +        pCertChecker = wizard.providercertchecker( +            domain=domain) + +        def validate_credentials(): +            ################# +            # FIXME #BUG #638 +            verify = False + +            try: +                pCertChecker.download_new_client_cert( +                    credentials=credentials, +                    verify=verify) + +            except auth.SRPAuthenticationError as exc: +                return self.fail( +                    self.tr("Authentication error: %s" % exc.message)) + +            except Exception as exc: +                return self.fail(exc.message) -        # XXX I think this is not needed -        # since we're also checking for the is_signup field. -        self.wizard().from_login = True +            else: +                return True -        # some cleanup before we leave the page -        self.cleanup_errormsg() +        yield(('Validating credentials', 20), validate_credentials) + +        self.set_done() +        yield(("end_sentinel", 0), lambda: None) + +    def green_validation_status(self): +        val = self.validationMsg +        val.setText(self.tr('Credentials validated.')) +        val.setStyleSheet(styles.GreenLineEdit) -        return True +    def on_checks_validation_ready(self): +        """ +        after checks +        """ +        if self.is_done(): +            self.disableFields() +            self.cleanup_errormsg() +            self.clean_wizard_errors(self.current_page) +            # make the user confirm the transition +            # to next page. +            self.nextText('&Next') +            self.nextFocus() +            self.green_validation_status() +            self.do_confirm_next = True diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index e642fcd0..c5b2984c 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -3,42 +3,33 @@ Provider Info Page, used in First run Wizard  """  import logging -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.util.web import get_https_domain_and_port - -from leap.gui.constants import APP_LOGO, pause_for_user +from leap.gui.constants import APP_LOGO  logger = logging.getLogger(__name__) -class ProviderInfoPage(ValidationPage): +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.setTitle(self.tr("Provider Info")) +        self.setSubTitle(self.tr( +            "This is what provider says."))          self.setPixmap(              QtGui.QWizard.LogoPixmap,              QtGui.QPixmap(APP_LOGO)) -        self.prev_page = "providerselection" -        #self.current_page = "providerinfo" +        self.create_info_panel()      def create_info_panel(self):          # Use stacked widget instead          # of reparenting the layout. -        self.infoWidget = QtGui.QStackedWidget() +        infoWidget = QtGui.QStackedWidget()          info = QtGui.QWidget()          layout = QtGui.QVBoxLayout() @@ -46,22 +37,29 @@ class ProviderInfoPage(ValidationPage):          displayName = QtGui.QLabel("")          description = QtGui.QLabel("")          enrollment_policy = QtGui.QLabel("") +          # XXX set stylesheet...          # prettify a little bit.          # bigger fonts and so on... +        # We could use a QFrame here +          layout.addWidget(displayName)          layout.addWidget(description)          layout.addWidget(enrollment_policy)          layout.addStretch(1)          info.setLayout(layout) -        self.infoWidget.addWidget(info) +        infoWidget.addWidget(info) -        self.layout.addWidget(self.infoWidget) +        pageLayout = QtGui.QVBoxLayout() +        pageLayout.addWidget(infoWidget) +        self.setLayout(pageLayout)          # add refs to self to allow for          # updates. +        # Watch out! Have to get rid of these references! +        # this should be better handled with signals !!          self.displayName = displayName          self.description = description          self.enrollment_policy = enrollment_policy @@ -76,8 +74,10 @@ class ProviderInfoPage(ValidationPage):          dn = pconfig.get('display_name')          display_name = dn[lang] if dn else '' +        domain_name = self.field('provider_domain') +          self.displayName.setText( -            "<b>%s</b>" % display_name) +            "<b>%s</b> https://%s" % (display_name, domain_name))          desc = pconfig.get('description')          description_text = desc[lang] if desc else '' @@ -89,142 +89,10 @@ class ProviderInfoPage(ValidationPage):              self.enrollment_policy.setText(                  'enrollment policy: %s' % enroll) -    def _do_checks(self, update_signal=None): -        """ -        executes actual checks in a separate thread -        """ -        def pause_and_finish(): -            update_signal.emit("end_sentinel", 100) -            pause_for_user() - -        wizard = self.wizard() -        prevpage = "providerselection" - -        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) - -        netchecker = wizard.netchecker() -        providercertchecker = wizard.providercertchecker() -        eipconfigchecker = wizard.eipconfigchecker(domain=_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: -            netchecker.check_name_resolution( -                domain) - -        except baseexceptions.LeapException as exc: -            logger.error(exc.message) -            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 - -        ################################## -        # 3) try download provider info... -        ################################## - -        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) -        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 -        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! -        pause_and_finish() - -    def _do_validation(self): -        """ -        called after _do_checks has finished -        (connected to checker thread finished signal) -        """ -        print 'validation...' -        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()          next_ = "providersetupvalidation"          return wizard.get_page_index(next_) + +    def initializePage(self): +        self.show_provider_info() diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 8d1aa869..3ffc6ff6 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -3,35 +3,69 @@ Select Provider Page, used in First Run Wizard  """  import logging +import requests +  from PyQt4 import QtCore  from PyQt4 import QtGui -#from leap.base import exceptions as baseexceptions +from leap.base import exceptions as baseexceptions  #from leap.crypto import certs -#from leap.eip import exceptions as eipexceptions +from leap.eip import exceptions as eipexceptions +from leap.gui.progress import InlineValidationPage +from leap.gui import styles +from leap.util.web import get_https_domain_and_port  from leap.gui.constants import APP_LOGO -from leap.gui.styles import ErrorLabelStyleSheet  logger = logging.getLogger(__name__) -class SelectProviderPage(QtGui.QWizardPage): +def delay(obj, method_str): +    # XXX check newer version in progress.py... +    """ +    this is a hack to get responsiveness in the ui +    """ +    QtCore.QTimer().singleShot( +        10, +        lambda: QtCore.QMetaObject.invokeMethod( +            obj, method_str)) + + +class SelectProviderPage(InlineValidationPage): + +    #disableCheckButton = QtCore.pyqtSignal() +    launchChecks = QtCore.pyqtSignal() +      def __init__(self, parent=None, providers=None):          super(SelectProviderPage, self).__init__(parent) +        self.current_page = 'providerselection' -        self.setTitle("Enter Provider") -        self.setSubTitle( +        self.setTitle(self.tr("Enter Provider")) +        self.setSubTitle(self.tr(              "Please enter the domain of the provider you want " -            "to use for your connection." +            "to use for your connection.")          )          self.setPixmap(              QtGui.QWizard.LogoPixmap,              QtGui.QPixmap(APP_LOGO))          self.did_cert_check = False -        self.current_page = 'providerselection' +        self.is_done = False + +        self.setupSteps() +        self.setupUI() + +        self.launchChecks.connect( +            self.launch_checks) + +        self.providerNameEdit.editingFinished.connect( +            lambda: self.providerCheckButton.setFocus(True)) + +    def setupUI(self): +        """ +        initializes the UI +        """          providerNameLabel = QtGui.QLabel("h&ttps://")          # note that we expect the bare domain name          # we will add the scheme later @@ -59,8 +93,10 @@ class SelectProviderPage(QtGui.QWizardPage):          #self.registerField('provider_name_index', providerNameSelect)          validationMsg = QtGui.QLabel("") -        validationMsg.setStyleSheet(ErrorLabelStyleSheet) +        validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet)          self.validationMsg = validationMsg +        providerCheckButton = QtGui.QPushButton(self.tr("chec&k!")) +        self.providerCheckButton = providerCheckButton          # cert info @@ -69,7 +105,8 @@ class SelectProviderPage(QtGui.QWizardPage):          # 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.bad_cert_status = self.tr( +            "Server certificate could not be verified.")          self.certInfo = QtGui.QLabel("")          self.certInfo.setWordWrap(True) @@ -81,25 +118,226 @@ class SelectProviderPage(QtGui.QWizardPage):              self.onTrustCheckChanged)          self.providerNameEdit.textChanged.connect(              self.onProviderChanged) +        self.providerCheckButton.clicked.connect( +            self.onCheckButtonClicked)          layout = QtGui.QGridLayout()          layout.addWidget(validationMsg, 0, 2)          layout.addWidget(providerNameLabel, 1, 1)          layout.addWidget(providerNameEdit, 1, 2) +        layout.addWidget(providerCheckButton, 1, 3) + +        # add certinfo group +        # XXX not shown now. should move to validation box. +        #layout.addWidget(certinfoGroup, 4, 1, 4, 2) +        #self.certinfoGroup = certinfoGroup +        #self.certinfoGroup.hide() + +        # add validation frame +        self.setupValidationFrame() +        layout.addWidget(self.valFrame, 4, 2, 4, 2) +        self.valFrame.hide() -        # XXX get a groupbox or something.... -        certinfoGroup = QtGui.QGroupBox("Certificate validation") +        self.setLayout(layout) + +    # certinfo + +    def setupCertInfoGroup(self): +        # XXX not used now. +        certinfoGroup = QtGui.QGroupBox( +            self.tr("Certificate validation"))          certinfoLayout = QtGui.QVBoxLayout()          certinfoLayout.addWidget(self.certInfo)          certinfoLayout.addWidget(self.certWarning)          certinfoLayout.addWidget(self.trustProviderCertCheckBox)          certinfoGroup.setLayout(certinfoLayout) +        self.certinfoGroup = self.certinfoGroup -        layout.addWidget(certinfoGroup, 4, 1, 4, 2) -        self.certinfoGroup = certinfoGroup -        self.certinfoGroup.hide() +    # progress frame -        self.setLayout(layout) +    def setupValidationFrame(self): +        qframe = QtGui.QFrame +        valFrame = qframe() +        valFrame.setFrameStyle(qframe.NoFrame) +        valframeLayout = QtGui.QVBoxLayout() +        zeros = (0, 0, 0, 0) +        valframeLayout.setContentsMargins(*zeros) + +        valframeLayout.addWidget(self.stepsTableWidget) +        valFrame.setLayout(valframeLayout) +        self.valFrame = valFrame + +    @QtCore.pyqtSlot() +    def onDisableCheckButton(self): +        #print 'CHECK BUTTON DISABLED!!!' +        self.providerCheckButton.setDisabled(True) + +    @QtCore.pyqtSlot() +    def launch_checks(self): +        self.do_checks() + +    def onCheckButtonClicked(self): +        QtCore.QMetaObject.invokeMethod( +            self, "onDisableCheckButton") + +        QtCore.QMetaObject.invokeMethod( +            self, "showStepsFrame") + +        # is this still needed? +        # XXX can I doo delay(self, "do_checks") ? +        delay(self, "launch_checks") + +    def _do_checks(self): +        """ +        generator that yields actual checks +        that are executed in a separate thread +        """ + +        wizard = self.wizard() +        full_domain = self.providerNameEdit.text() + +        # 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) + +        netchecker = wizard.netchecker() + +        providercertchecker = wizard.providercertchecker() +        eipconfigchecker = wizard.eipconfigchecker(domain=_domain) + +        yield(("head_sentinel", 0), lambda: None) + +        ######################## +        # 1) try name resolution +        ######################## + +        def namecheck(): +            """ +            in which we check if +            we are able to name resolve +            this domain +            """ +            try: +                netchecker.check_name_resolution( +                    domain) + +            except baseexceptions.LeapException as exc: +                logger.error(exc.message) +                return self.fail(exc.usermessage) + +            except Exception as exc: +                return self.fail(exc.message) + +            else: +                return True + +        logger.debug('checking name resolution') +        yield((self.tr("checking domain name"), 20), namecheck) + +        ######################### +        # 2) try https connection +        ######################### + +        def httpscheck(): +            """ +            in which we check +            if the provider +            is offering service over +            https +            """ +            try: +                providercertchecker.is_https_working( +                    "https://%s" % _domain, +                    verify=True) + +            except eipexceptions.HttpsBadCertError as exc: +                logger.debug('exception') +                return self.fail(exc.usermessage) +                # XXX skipping for now... +                ############################################## +                # We had this validation logic +                # in the provider selection page before +                ############################################## +                #if self.trustProviderCertCheckBox.isChecked(): +                    #pass +                #else: +                #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 + +            except baseexceptions.LeapException as exc: +                return self.fail(exc.usermessage) + +            except Exception as exc: +                return self.fail(exc.message) + +            else: +                return True + +        logger.debug('checking https connection') +        yield((self.tr("checking https connection"), 40), httpscheck) + +        ################################## +        # 3) try download provider info... +        ################################## + +        def fetchinfo(): +            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) +            except requests.exceptions.SSLError: +                # XXX we should have catched this before. +                # but cert checking is broken. +                return self.fail(self.tr( +                    "Could not get info from provider.")) +            except requests.exceptions.ConnectionError: +                return self.fail(self.tr( +                    "Could not download provider info " +                    "(refused conn.).")) + +            except Exception as exc: +                return self.fail( +                    self.tr(exc.message)) +            else: +                return True + +        yield((self.tr("fetching provider info"), 80), fetchinfo) + +        # done! + +        self.is_done = True +        yield(("end_sentinel", 100), lambda: None) + +    def on_checks_validation_ready(self): +        """ +        called after _do_checks has finished. +        """ +        self.domain_checked = True +        self.completeChanged.emit() +        # let's set focus... +        if self.is_done: +            self.wizard().clean_validation_error(self.current_page) +            nextbutton = self.wizard().button(QtGui.QWizard.NextButton) +            nextbutton.setFocus() +        else: +            self.providerNameEdit.setFocus() + +    # cert trust verification +    # (disabled for now)      def is_insecure_cert_trusted(self):          return self.trustProviderCertCheckBox.isChecked() @@ -117,38 +355,50 @@ class SelectProviderPage(QtGui.QWizardPage):          # trigger signal to redraw next button          self.completeChanged.emit() +    def add_cert_info(self, certinfo): +        self.certWarning.setText( +            "Do you want to <b>trust this provider certificate?</b>") +        self.certInfo.setText( +            'SHA-256 fingerprint: <i>%s</i><br>' % certinfo) +        self.certInfo.setWordWrap(True) +        self.certinfoGroup.show() +      def onProviderChanged(self, text): +        self.is_done = False +        provider = self.providerNameEdit.text() +        if provider: +            self.providerCheckButton.setDisabled(False) +        else: +            self.providerCheckButton.setDisabled(True)          self.completeChanged.emit()      def reset_validation_status(self):          """          empty the validation msg +        and clean the inline validation widget.          """          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 <b>trust this provider certificate?</b>") -        self.certInfo.setText( -            'SHA-256 fingerprint: <i>%s</i><br>' % certinfo) -        self.certInfo.setWordWrap(True) -        self.certinfoGroup.show() +        self.steps.removeAllSteps() +        self.clearTable() +        self.domain_checked = False      # pagewizard methods      def isComplete(self):          provider = self.providerNameEdit.text() +        if not self.is_done: +            return False +          if not provider:              return False          else:              if self.is_insecure_cert_trusted():                  return True              if not self.did_cert_check: -                return True +                if self.is_done: +                    # XXX sure? +                    return True              return False      def populateErrors(self): @@ -163,17 +413,33 @@ class SelectProviderPage(QtGui.QWizardPage):              bad_str = getattr(self, 'bad_string', None)              cur_str = self.providerNameEdit.text()              showerr = self.validationMsg.setText +            markred = lambda: self.providerNameEdit.setStyleSheet( +                styles.ErrorLineEdit) +            umarkrd = lambda: self.providerNameEdit.setStyleSheet( +                styles.RegularLineEdit)              if bad_str is None:                  # first time we fall here.                  # save the current bad_string value                  self.bad_string = cur_str                  showerr(errors) +                markred()              else:                  # not the first time +                # XXX hey, this is getting convoluted. +                # roll out this. +                # but be careful about all the possibilities +                # with going back and forth once you +                # enter a domain.                  if cur_str == bad_str:                      showerr(errors) +                    markred()                  else: -                    showerr('') +                    if not getattr(self, 'domain_checked', None): +                        showerr('') +                        umarkrd() +                    else: +                        self.bad_string = cur_str +                        showerr(errors)      def cleanup_errormsg(self):          """ @@ -181,6 +447,7 @@ class SelectProviderPage(QtGui.QWizardPage):          should be called before leaving the page          """          self.bad_string = None +        self.domain_checked = False      def paintEvent(self, event):          """ @@ -195,7 +462,14 @@ class SelectProviderPage(QtGui.QWizardPage):      def initializePage(self):          self.validationMsg.setText('') -        self.certinfoGroup.hide() +        if hasattr(self, 'certinfoGroup'): +            # XXX remove ? +            self.certinfoGroup.hide() +        self.is_done = False +        self.providerCheckButton.setDisabled(True) +        self.valFrame.hide() +        self.steps.removeAllSteps() +        self.clearTable()      def validatePage(self):          # some cleanup before we leave the page diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index 3fb9a19b..1a362794 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -6,10 +6,10 @@ import logging  from PyQt4 import QtGui -from leap.base import auth +from leap.base import exceptions as baseexceptions  from leap.gui.progress import ValidationPage -from leap.gui.constants import APP_LOGO, pause_for_user +from leap.gui.constants import APP_LOGO  logger = logging.getLogger(__name__) @@ -17,21 +17,26 @@ logger = logging.getLogger(__name__)  class ProviderSetupValidationPage(ValidationPage):      def __init__(self, parent=None):          super(ProviderSetupValidationPage, self).__init__(parent) +        self.current_page = "providersetupvalidation" + +        # XXX needed anymore?          is_signup = self.field("is_signup")          self.is_signup = is_signup -        self.setTitle("Setting up provider") -        #self.setSubTitle( -            #"auto configuring provider...") +        self.setTitle(self.tr("Provider setup")) +        self.setSubTitle( +            self.tr("Doing autoconfig."))          self.setPixmap(              QtGui.QWizard.LogoPixmap,              QtGui.QPixmap(APP_LOGO)) -    def _do_checks(self, update_signal=None): +    def _do_checks(self):          """ -        executes actual checks in a separate thread +        generator that yields actual checks +        that are executed in a separate thread          """ +          full_domain = self.field('provider_domain')          wizard = self.wizard()          pconfig = wizard.providerconfig @@ -41,68 +46,50 @@ class ProviderSetupValidationPage(ValidationPage):          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) +        yield(("head_sentinel", 0), lambda: None) -            unamek = 'login_userName' -            passwk = 'login_userPassword' +        ######################## +        # 1) fetch ca cert +        ######################## -            username = self.field(unamek) -            password = self.field(passwk) -            credentials = username, password - -            ################# -            # FIXME #BUG #638 -            verify = False +        def fetchcacert(): +            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)              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() +                pCertChecker.download_ca_cert( +                    uri=ca_cert_uri, +                    verify=False) -        ####################################### +            except baseexceptions.LeapException as exc: +                logger.error(exc.message) +                # XXX this should be _ method +                return self.fail(self.tr(exc.usermessage)) -        update_signal.emit('Fetching CA certificate', 30) -        pause_for_user() +            except Exception as exc: +                return self.fail(exc.message) -        if pconfig: -            ca_cert_uri = pconfig.get('ca_cert_uri').geturl() -        else: -            ca_cert_uri = None +            else: +                return True -        # 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) +        yield((self.tr('Fetching CA certificate'), 30), +              fetchcacert) -        pCertChecker.download_ca_cert( -            uri=ca_cert_uri, -            verify=False) -        pause_for_user() +        ######################### +        # 2) check CA fingerprint +        ######################### -        update_signal.emit('Checking CA fingerprint', 66) +        def checkcafingerprint(): +            # XXX get the real thing!!! +            pass          #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None)          # XXX get fingerprint dict (types) @@ -115,31 +102,41 @@ class ProviderSetupValidationPage(ValidationPage):              # should catch exception              #return False -        update_signal.emit('Validating api certificate', 90) - -        #api_uri = pconfig.get('api_uri', None) -        #try: -            #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 -            # 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 -        pause_for_user() -        #ca_cert_path = checker.ca_cert_path - -        update_signal.emit('end_sentinel', 100) -        pause_for_user() - -    def _do_validation(self): +        yield((self.tr("Checking CA fingerprint"), 60), +              checkcafingerprint) + +        ######################### +        # 2) check CA fingerprint +        ######################### + +        def validatecacert(): +            pass +            #api_uri = pconfig.get('api_uri', None) +            #try: +                #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 +                # 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 + +            #??? +            #ca_cert_path = checker.ca_cert_path + +        yield((self.tr('Validating api certificate'), 90), validatecacert) + +        self.set_done() +        yield(('end_sentinel', 100), lambda: None) + +    def on_checks_validation_ready(self):          """          called after _do_checks has finished          (connected to checker thread finished signal) @@ -153,10 +150,11 @@ class ProviderSetupValidationPage(ValidationPage):              wizard.set_validation_error(                  prevpage,                  first_error) -            self.go_back() +            # XXX don't go back, signal error +            #self.go_back()          else: -            logger.debug('going next') -            self.go_next() +            logger.debug('should be going next, wait on user') +            #self.go_next()      def nextId(self):          wizard = self.wizard() @@ -169,3 +167,8 @@ class ProviderSetupValidationPage(ValidationPage):              # XXX bad name. change to connect again.              next_ = 'signupvalidation'          return wizard.get_page_index(next_) + +    def initializePage(self): +        super(ProviderSetupValidationPage, self).initializePage() +        self.set_undone() +        self.completeChanged.emit() diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index b46dd4cd..e85723cb 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -1,8 +1,11 @@  """  Register User Page, used in First Run Wizard  """ +import json  import logging +import socket +import requests  from PyQt4 import QtCore  from PyQt4 import QtGui @@ -11,24 +14,37 @@ from leap.gui.firstrun.mixins import UserFormMixIn  logger = logging.getLogger(__name__) +from leap.base import auth +from leap.gui import styles  from leap.gui.constants import APP_LOGO, BARE_USERNAME_REGEX +from leap.gui.progress import InlineValidationPage  from leap.gui.styles import ErrorLabelStyleSheet -class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): +class RegisterUserPage(InlineValidationPage, UserFormMixIn):      def __init__(self, parent=None):          super(RegisterUserPage, self).__init__(parent) +        self.current_page = "signup" -        self.setTitle("Sign Up") +        self.setTitle(self.tr("Sign Up")) +        # subtitle is set in the initializePage          self.setPixmap(              QtGui.QWizard.LogoPixmap,              QtGui.QPixmap(APP_LOGO)) -        self.current_page = "signup" +        # commit page means there's no way back after this... +        # XXX should change the text on the "commit" button... +        self.setCommitPage(True) + +        self.setupSteps() +        self.setupUI() +        self.do_confirm_next = False +        self.focused_field = False +    def setupUI(self):          userNameLabel = QtGui.QLabel("User &name:")          userNameLineEdit = QtGui.QLineEdit()          userNameLineEdit.cursorPositionChanged.connect( @@ -59,6 +75,7 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):          self.registerField('userName*', self.userNameLineEdit)          self.registerField('userPassword*', self.userPasswordLineEdit) +        self.registerField('userPassword2*', self.userPassword2LineEdit)          # XXX missing password confirmation          # XXX validator! @@ -81,21 +98,72 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):          layout.addWidget(self.userPasswordLineEdit, 2, 3)          layout.addWidget(self.userPassword2LineEdit, 3, 3)          layout.addWidget(rememberPasswordCheckBox, 4, 3, 4, 4) + +        # add validation frame +        self.setupValidationFrame() +        layout.addWidget(self.valFrame, 5, 2, 5, 2) +        self.valFrame.hide() +          self.setLayout(layout) +        self.commitText("Sign up!") -    # pagewizard methods +    # commit button + +    def commitText(self, text): +        # change "commit" button text +        self.setButtonText( +            QtGui.QWizard.CommitButton, text) + +    @property +    def commitButton(self): +        return self.wizard().button(QtGui.QWizard.CommitButton) + +    def commitFocus(self): +        self.commitButton.setFocus() + +    def disableCommitButton(self): +        self.commitButton.setDisabled(True) + +    def disableFields(self): +        for field in (self.userNameLineEdit, +                      self.userPasswordLineEdit, +                      self.userPassword2LineEdit): +            field.setDisabled(True) + +    # error painting + +    def markRedAndGetFocus(self, field): +        field.setStyleSheet(styles.ErrorLineEdit) +        if not self.focused_field: +            self.focused_field = True +            field.setFocus(QtCore.Qt.OtherFocusReason) + +    def markRegular(self, field): +        field.setStyleSheet(styles.RegularLineEdit)      def populateErrors(self): -        # XXX could move this to ValidationMixin -        # used in providerselect too +        def showerr(text): +            self.validationMsg.setText(text) +            err_lower = text.lower() +            if "username" in err_lower: +                self.markRedAndGetFocus( +                    self.userNameLineEdit) +            if "password" in err_lower: +                self.markRedAndGetFocus( +                    self.userPasswordLineEdit) + +        def unmarkred(): +            for field in (self.userNameLineEdit, +                          self.userPasswordLineEdit, +                          self.userPassword2LineEdit): +                self.markRegular(field)          errors = self.wizard().get_validation_error(              self.current_page)          if errors:              bad_str = getattr(self, 'bad_string', None)              cur_str = self.userNameLineEdit.text() -            showerr = self.validationMsg.setText -            prev_er = getattr(self, 'prevalidation_error', None) +            #prev_er = getattr(self, 'prevalidation_error', None)              if bad_str is None:                  # first time we fall here. @@ -103,14 +171,20 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):                  self.bad_string = cur_str                  showerr(errors)              else: -                if prev_er: -                    showerr(prev_er) -                    return +                #if prev_er: +                    #showerr(prev_er) +                    #return                  # not the first time                  if cur_str == bad_str:                      showerr(errors)                  else: +                    self.focused_field = False                      showerr('') +                    unmarkred() +        else: +            # no errors +            self.focused_field = False +            unmarkred()      def cleanup_errormsg(self):          """ @@ -130,45 +204,149 @@ 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): +    def _do_checks(self):          """ -        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. +        generator that yields actual checks +        that are executed in a separate thread          """ - -        #username = self.userNameLineEdit.text() +        provider = self.field('provider_domain') +        username = self.userNameLineEdit.text()          password = self.userPasswordLineEdit.text()          password2 = self.userPassword2LineEdit.text() -        # we better have here -        # some call to a password checker... -        # to assess strenght and avoid silly stuff. +        def checkpass(): +            # we better have here +            # some call to a password checker... +            # to assess strenght and avoid silly stuff. + +            if password != password2: +                return self.fail(self.tr('Password does not match..')) + +            if len(password) < 6: +                #self.set_prevalidation_error('Password too short.') +                return self.fail(self.tr('Password too short.')) + +            if password == "123456": +                # joking, but not too much. +                #self.set_prevalidation_error('Password too obvious.') +                return self.fail(self.tr('Password too obvious.')) + +            # go +            return True + +        yield(("head_sentinel", 0), checkpass) -        if password != password2: -            self.set_prevalidation_error('Password does not match.') -            return False +        # XXX should emit signal for .show the frame! +        # XXX HERE! -        if len(password) < 6: -            self.set_prevalidation_error('Password too short.') -            return False +        ################################################## +        # 1) register user +        ################################################## -        if password == "123456": -            # joking, but not too much. -            self.set_prevalidation_error('Password too obvious.') -            return False +        # show the frame before going on... +        QtCore.QMetaObject.invokeMethod( +            self, "showStepsFrame") -        # some cleanup before we leave the page +        def register(): +            # XXX FIXME! +            verify = False + +            signup = auth.LeapSRPRegister( +                schema="https", +                provider=provider, +                verify=verify) +            try: +                ok, req = signup.register_user( +                    username, password) + +            except socket.timeout: +                return self.fail( +                    self.tr("Error connecting to provider (timeout)")) + +            except requests.exceptions.ConnectionError as exc: +                logger.error(exc.message) +                return self.fail( +                    self.tr('Error Connecting to provider (connerr).')) +            except Exception as exc: +                return self.fail(exc.message) + +            # XXX check for != OK instead??? + +            if req.status_code in (404, 500): +                return self.fail( +                    self.tr( +                        "Error during registration (%s)") % req.status_code) + +            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) +                return self.fail( +                    self.tr('Username not available.')) + +        logger.debug('registering user') +        yield(("registering with provider", 40), register) + +        self.set_done() +        yield(("end_sentinel", 0), lambda: None) + +    def on_checks_validation_ready(self): +        """ +        after checks +        """ +        if self.is_done(): +            self.disableFields() +            self.cleanup_errormsg() +            self.clean_wizard_errors(self.current_page) +            # make the user confirm the transition +            # to next page. +            self.commitText('Connect!') +            self.commitFocus() +            self.green_validation_status() +            self.do_confirm_next = True + +    def green_validation_status(self): +        val = self.validationMsg +        val.setText(self.tr('Registration succeeded!')) +        val.setStyleSheet(styles.GreenLineEdit) + +    def reset_validation_status(self): +        """ +        empty the validation msg +        and clean the inline validation widget. +        """ +        self.validationMsg.setText('') +        self.steps.removeAllSteps() +        self.clearTable() + +    # pagewizard methods + +    def validatePage(self): +        """ +        if not register done, do checks. +        if done, wait for click. +        """ +        self.disableCommitButton()          self.cleanup_errormsg() +        self.clean_wizard_errors(self.current_page) + +        # After a successful validation +        # (ie, success register with server) +        # we change the commit button text +        # and set this flag to True. +        if self.do_confirm_next: +            return True + +        if not self.is_done(): +            # calls checks, which after successful +            # execution will call on_checks_validation_ready +            self.reset_validation_status() +            self.do_checks() -        # go -        return True +        return self.is_done()      def initializePage(self):          """ @@ -176,13 +354,15 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):          """          provider = self.field('provider_domain')          self.setSubTitle( -            "Register a new user with provider %s." % +            self.tr("Register a new user with provider %s.") %              provider)          self.validationMsg.setText('')          self.userPassword2LineEdit.setText('') +        self.valFrame.hide()      def nextId(self):          wizard = self.wizard()          if not wizard:              return +        # XXX this should be called connect          return wizard.get_page_index('signupvalidation') diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index dbe30d3c..0e67834b 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -9,18 +9,18 @@ used in First Run Wizard  # the login branch of the wizard.  import logging -import json -import socket +#import json +#import socket  from PyQt4 import QtGui -import requests +#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, pause_for_user +from leap.gui.constants import APP_LOGO  logger = logging.getLogger(__name__) @@ -29,16 +29,10 @@ class RegisterUserValidationPage(ValidationPage):      def __init__(self, parent=None):          super(RegisterUserValidationPage, self).__init__(parent) -        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." +        title = "Connecting..." +        # XXX uh... really? +        subtitle = "Checking connection with provider."          self.setTitle(title)          self.setSubTitle(subtitle) @@ -67,7 +61,7 @@ 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 +        is_signup = self.field("is_signup")          unamek_base = 'userName'          passwk_base = 'userPassword' @@ -83,125 +77,58 @@ class RegisterUserValidationPage(ValidationPage):          pCertChecker = wizard.providercertchecker(              domain=full_domain) -        ########################################### -        # only if from signup -        if is_signup: -            signup = auth.LeapSRPRegister( -                schema="https", -                provider=full_domain, -                verify=verify) - -        update_signal.emit("head_sentinel", 0) +        yield(("head_sentinel", 0), lambda: None)          ################################################## -        # 1) register user +        # 1) fetching eip service config          ################################################## -        # 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') - +        def fetcheipconf():              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 - -            pause_for_user() +                eipconfigchecker.fetch_eip_service_config( +                    domain=full_domain) -        ################################################## -        # 2) fetching eip service config -        ################################################## +            # XXX get specific exception +            except Exception as exc: +                return self.fail(exc.message) -        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() +        yield((self.tr("Fetching provider config..."), 40), +              fetcheipconf)          ################################################## -        # 3) getting client certificate +        # 2) 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) -        try: -            pCertChecker.download_new_client_cert( -                credentials=credentials, -                verify=verify) +        def fetcheipcert(): +            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 +            except auth.SRPAuthenticationError as exc: +                return self.fail(self.tr( +                    "Authentication error: %s" % exc.message)) +            else: +                return True -        pause_for_user() +        yield((self.tr("Fetching eip certificate"), 80), +              fetcheipcert)          ################          # end !          ################ +        self.set_done() +        yield(("end_sentinel", 100), lambda: None) -        update_signal.emit("end_sentinel", 100) -        pause_for_user() - +    def on_checks_validation_ready(self): +        """ +        called after _do_checks has finished +        (connected to checker thread finished signal) +        """ +        # this should be called CONNECT PAGE AGAIN.          # here we go! :) +        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)          self.run_eip_checks_for_provider_and_connect(_domain)      def run_eip_checks_for_provider_and_connect(self, domain): @@ -225,6 +152,14 @@ class RegisterUserValidationPage(ValidationPage):                  "probably the wizard has been launched "                  "in an stand-alone way.") +        # XXX look for a better place to signal +        # we are done. +        # We could probably have a fake validatePage +        # that checks if the domain transfer has been +        # done to conductor object, triggers the start_signal +        # and does the go_next() +        self.set_done() +      def eip_error_check(self):          """          a version of the main app error checker, @@ -241,7 +176,8 @@ class RegisterUserValidationPage(ValidationPage):          called after _do_checks has finished          (connected to checker thread finished signal)          """ -        prevpage = "signup" if self.is_signup else "login" +        is_signup = self.field("is_signup") +        prevpage = "signup" if is_signup else "login"          wizard = self.wizard()          if self.errors: @@ -253,13 +189,16 @@ class RegisterUserValidationPage(ValidationPage):                  first_error)              self.go_back()          else: -            logger.debug('going next') -            # check if this "next" interferes -            # with the eip signal. -            self.go_next() +            logger.debug('should go next, wait for user to click next') +            #self.go_next()      def nextId(self):          wizard = self.wizard()          if not wizard:              return          return wizard.get_page_index('lastpage') + +    def initializePage(self): +        super(RegisterUserValidationPage, self).initializePage() +        self.set_undone() +        self.completeChanged.emit() diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py index bbb48149..9b77b877 100755 --- a/src/leap/gui/firstrun/wizard.py +++ b/src/leap/gui/firstrun/wizard.py @@ -39,7 +39,7 @@ TODO-ish:  [ ] Document signals used / expected.  [ ] Separate style from widgets.  [ ] Fix TOFU Widget for provider cert. -[ ] Refactor widgets out. +[X] Refactor widgets out.  [ ] Follow more MVC style.  [ ] Maybe separate "first run wizard" into different wizards      that share some of the pages? @@ -137,6 +137,10 @@ class FirstRunWizard(QtGui.QWizard):              QtGui.QWizard.BackgroundPixmap,              QtGui.QPixmap(':/images/background.png')) +        # set options +        self.setOption(QtGui.QWizard.IndependentPages, on=False) +        self.setOption(QtGui.QWizard.NoBackButtonOnStartPage, on=True) +          self.setWindowTitle("First Run Wizard")          # TODO: set style for MAC / windows ... @@ -167,6 +171,11 @@ class FirstRunWizard(QtGui.QWizard):      def set_validation_error(self, pagename, error):          self.validation_errors[pagename] = error +    def clean_validation_error(self, pagename): +        vald = self.validation_errors +        if pagename in vald: +            del vald[pagename] +      def get_validation_error(self, pagename):          return self.validation_errors.get(pagename, None) diff --git a/src/leap/gui/mainwindow_rc.py b/src/leap/gui/mainwindow_rc.py index 63e9f6be..5bee35c7 100644 --- a/src/leap/gui/mainwindow_rc.py +++ b/src/leap/gui/mainwindow_rc.py @@ -2,7 +2,7 @@  # Resource object code  # -# Created: Tue Nov 6 01:22:11 2012 +# Created: Wed Nov 21 04:25:36 2012  #      by: The Resource Compiler for PyQt (Qt v4.8.2)  #  # WARNING! All changes made in this file will be lost! @@ -236,6 +236,87 @@ qt_resource_data = "\  \x71\xa4\x40\xda\x14\x7a\xd1\x73\x1f\xf4\x7f\xb7\xf9\x1f\xc2\x26\  \x56\xd5\x70\x45\xfc\x8a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\  \x60\x82\ +\x00\x00\x04\xec\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\ +\x01\x1e\x75\x38\x35\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x13\x74\x45\ +\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x52\x6f\x64\x6e\x65\x79\x20\ +\x44\x61\x77\x65\x73\x0e\xd8\x7e\x1d\x00\x00\x04\x4a\x49\x44\x41\ +\x54\x48\x89\x8d\x96\x5d\x6c\x53\x65\x18\xc7\x7f\xef\x39\x6b\xbb\ +\x7e\x9c\x75\x65\xad\x2b\x9b\xfb\xd0\x31\xdd\x14\xb6\x8c\x19\x44\ +\x90\x44\x63\x82\x42\x88\x5e\x90\x98\xcc\x19\x15\x13\xd4\x18\x76\ +\x61\xd4\x18\xe3\x85\x57\xca\x05\xe1\xc2\x0c\xa3\xa8\x51\xd0\x4c\ +\x12\xe3\x85\x31\x80\x26\x6a\xe2\x85\x23\xb0\x38\xb6\xc1\x1c\xce\ +\xb1\x40\x59\xf6\xe5\xca\xda\xae\xed\xfa\x75\x7a\x5e\x2f\x4e\xd7\ +\x59\xd6\x32\xfe\xc9\x7b\xf3\x9e\xe7\xf9\xff\x9f\xe7\xff\x9e\xf3\ +\x9c\x57\x48\x29\x59\x0f\xbd\x7b\x85\x0d\x17\xed\x1e\xbb\xb2\x07\ +\x20\x94\x30\x7e\x22\xc6\x48\xcf\x59\x99\x5a\x2f\x57\x94\x12\xf8\ +\xec\x55\x61\x71\x65\x6d\x47\xfc\xbe\xda\x47\x9d\x5a\xa5\xbf\xda\ +\x69\xaf\xda\xe0\x28\x2f\x07\x58\x5c\x4e\x26\xe7\xe3\x89\x9b\xf1\ +\x68\x78\x6e\x6e\x61\xfa\x8f\x98\x9a\x7a\xfb\x95\xe3\x32\x73\xc7\ +\x02\x9f\x76\x89\x8e\xba\xda\xda\x2f\xb7\x37\xdf\xdf\xe6\x2a\x13\ +\x8a\x94\x06\x82\xc2\x38\x89\x40\x08\x85\x98\x2e\x8d\xf3\x13\xe3\ +\x97\xa6\xa6\xa7\x5f\x7e\xed\x94\x1c\x5a\x57\xa0\xef\xa0\xfd\x70\ +\x5b\xf3\x96\x03\xcd\xde\x8a\x6a\x61\x64\xd7\x73\xc0\x14\x53\x54\ +\x26\x82\x4b\xf3\x97\x26\x2e\x7f\xd5\xfd\x79\xe2\xdd\x92\x02\x27\ +\x5f\x2a\x7b\xe1\x89\xce\x1d\xc7\xbc\x76\x55\x13\xc5\x98\xac\x4e\ +\x10\x0a\xa4\xa2\x6b\x45\x80\x60\x22\x1b\xfd\x6d\xf0\xdc\xa1\x17\ +\x4f\xe8\x5f\xaf\x11\x38\xfa\x9c\xf0\x6e\xdb\xf4\xc0\xf9\x6d\xf5\ +\xfe\x26\x30\xf2\x89\xca\xc6\x76\xd4\x07\xf7\xa3\xd4\x74\x80\xd5\ +\x65\x6e\xa6\xe3\x64\x03\xfd\x64\x2f\x9e\x40\x46\x67\xff\x27\xa3\ +\x30\x70\x63\x6e\x72\xe0\xea\xd8\xf6\x37\xbf\x95\x41\x73\x27\x87\ +\x06\x8f\xa7\x6f\x6b\x7d\x4d\x01\x39\x80\x52\xff\x08\x4a\xe3\xae\ +\x55\xf2\x5c\x27\x6a\xf3\x6e\x2c\x7b\x8f\x9a\x5d\xe5\x61\xb0\xb5\ +\xbe\xa6\xa9\xc1\xe3\xe9\x5b\x95\x04\x7a\xbb\x44\x47\x5b\x53\xcb\ +\x4e\x15\xbd\x98\x31\xc8\x70\x00\xfd\xfc\xc7\x64\xce\xbc\x81\x7e\ +\xe1\x13\xc8\x75\x2d\xb4\x8d\x28\xb5\x0f\x15\xc4\xaa\xe8\xb4\x35\ +\xb5\xec\xec\xed\x12\x1d\x00\x65\x00\xee\x72\x65\x9f\x5f\x73\x38\ +\x05\x6b\x0f\x35\x3b\xf6\x03\xfa\xc0\xf1\x3c\x29\xb3\xc3\xa8\xf7\ +\x3e\x8e\xf0\xb5\x98\x22\xf6\x0d\x05\xf1\x02\xf0\x6b\x0e\xa7\xbb\ +\x5c\xd9\x07\x0c\x29\x00\x9a\xc3\xd5\x69\x55\xd5\xe2\xd5\x47\xe7\ +\x56\xc9\x01\xe1\xbe\x1b\xe1\xb9\x67\xf5\x79\x70\x7c\x4d\x8e\x55\ +\x55\xd1\x1c\xae\xce\xbc\x45\x15\x6e\x5f\x9d\x90\xc5\xed\x29\xa8\ +\xae\xa2\x06\xcb\x53\x47\xa0\xcc\x66\x76\x37\xfa\x3d\xd9\xa9\x81\ +\xb5\x71\x52\xa7\xc2\xed\xab\x83\x9c\x45\x76\xbb\x56\x25\xa5\xa4\ +\xe8\xab\xb9\x02\x9b\x86\x65\xf7\x87\x08\xcd\x6f\x92\x8f\x9f\x21\ +\xf5\xdd\xf3\xa0\xa7\x10\xe5\x6e\x44\x45\x2d\x38\x7d\x08\x21\x90\ +\xd2\xe4\xcc\x0b\x24\x12\xd1\x9b\x42\xbd\xab\x81\x6c\xba\x28\xb7\ +\x94\x06\x65\xcd\x4f\x22\x2a\x1b\x00\x30\xa6\xff\x24\xd5\xb7\x1f\ +\x74\x73\x14\xc9\x64\x04\x99\x8c\x80\xc5\x8e\xe2\xae\x03\xab\x93\ +\x44\x22\x7a\x33\x6f\xd1\x52\x64\x61\x0a\xb5\xbc\x28\xb1\xb1\x34\ +\x83\x91\xb3\xc1\x98\x1d\xc1\x98\x1d\x41\x3f\xd7\x9b\x27\x2f\x40\ +\x26\x81\x11\xfc\x07\x99\x8a\x99\x9c\x2b\x1d\x44\x97\x63\x83\xc9\ +\xe8\xfc\x33\x36\x23\x05\xaa\x05\xd2\xcb\xc8\x74\xcc\xfc\x88\x72\ +\x5d\xa5\x7f\x3c\x74\x3b\x03\x0b\x90\x52\xed\x44\x97\x63\x83\x79\ +\x81\x48\xd2\x38\x3d\x1b\xcf\xbc\x53\x1f\xb9\xe4\x44\x1a\x45\x93\ +\xac\xcf\x7e\x83\xda\xb8\xcb\x2c\xf4\xd7\xf7\xd1\x2f\x9e\x2c\xce\ +\x2e\x14\xe6\xd2\x65\xf1\x48\xd2\x38\x0d\x39\x8b\x7a\x4e\xc9\xa1\ +\xd1\xc0\xb5\xfe\xac\xb7\xb5\x64\x55\xc2\xe5\x47\x54\x36\x98\xe7\ +\x60\xd3\x4a\xc6\x65\xbd\xad\x8c\x06\xae\xf5\xf7\xe4\x26\x6b\x7e\ +\x54\x04\x42\xa1\xee\xe1\x90\x31\x29\x1c\xde\xd2\xbd\xaf\x03\xe1\ +\xf0\x32\x1c\x32\x26\x03\xa1\x50\x77\x7e\xef\xd6\x69\xfa\x58\x7b\ +\xe7\x31\x5f\x78\x54\x23\xb3\x5c\x90\xac\xf8\xdb\x10\x0e\xf3\xab\ +\x35\x82\x13\xc8\xa5\xe9\x42\x76\x8b\x83\x85\xca\xcd\xd1\xdf\x47\ +\x06\x8b\x4f\xd3\x15\xf4\x1d\xb4\x1f\xde\xd2\xd4\x7a\x60\x93\x1a\ +\xaa\x26\x74\xfd\xce\x4a\xf7\x34\x72\x35\xeb\x99\xbf\x3c\x79\xe5\ +\xf6\xff\x83\x15\x7c\xf0\xb4\xd8\xbe\xb9\xa9\xe6\x8b\x1d\x0d\xd5\ +\xad\xae\xd8\x94\x22\x13\x21\x90\xb7\xcc\x29\xa1\x22\xec\x1e\x62\ +\xae\x3a\xa3\xff\xfa\xfc\xdf\xe7\xc6\x66\x5e\x3f\xf2\x0b\xfd\x52\ +\x16\x8e\x84\x02\x01\x21\x84\x0a\x54\x01\x95\x9a\x1d\xdf\x7b\x7b\ +\xac\x6f\xdd\x57\xb7\xb1\x6d\x83\xbb\xd2\x53\xe3\x10\x2e\x9f\xcd\ +\xb0\x00\xfc\x9b\x54\xf4\x99\x84\x8c\x2d\x86\xc3\xe1\x2b\x81\xd9\ +\xbf\x0e\xff\x9c\xfe\x28\x9e\x22\x08\x84\x80\xb0\x94\x32\x5c\xb2\ +\x03\x21\x84\x13\xf0\x00\xee\xdc\xd2\x5c\x56\x3c\x5b\xeb\x69\x79\ +\xb8\x51\x74\x18\x12\xe5\xc2\x75\x39\x3c\x74\x83\xc9\x78\x86\x10\ +\x10\x03\x96\x80\x48\x6e\x2d\x4a\xb9\x7a\x01\x28\x79\xab\xc8\x89\ +\x59\x00\x2b\x60\xcb\x2d\x0b\xa0\x02\x3a\x90\x02\xd2\x40\x12\xc8\ +\x48\x79\xab\x87\x26\xfe\x03\x26\x93\xd5\x41\x51\x76\x98\xdb\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\  \x00\x00\x0b\xd7\  \x89\  \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -1491,94 +1572,180 @@ qt_resource_data = "\  \xc3\x25\x0d\x25\x35\x01\xd7\x0f\x5b\xb5\x7e\x8e\x93\x83\xff\x0f\  \x92\x04\x28\x92\xfd\x58\xc9\xac\x00\x00\x00\x00\x49\x45\x4e\x44\  \xae\x42\x60\x82\ -\x00\x00\x05\x5f\ +\x00\x00\x05\x24\  \x89\  \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\  \x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\  \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x00\x8e\x00\x00\x00\x8e\ -\x01\x6b\xdf\xd6\xc9\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\ +\x01\x1e\x75\x38\x35\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\  \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x04\xdc\x49\x44\ -\x41\x54\x48\x89\x8d\x95\x79\x6c\x54\x55\x14\xc6\x7f\xf7\xce\x4c\ -\x3b\xa5\xed\x14\x6b\x3b\x25\x42\x17\x18\x28\x65\xba\x90\x6e\x18\ -\xa3\xc5\x05\xa4\x86\x20\x21\xa0\x0d\x88\x24\x26\x98\x10\x28\x68\ -\x42\x40\x8c\x20\x1a\xa3\x16\x43\x48\x8c\x9a\x50\x08\xc6\x14\xad\ -\x9a\x22\x8d\xa2\xa6\x01\x42\x13\x20\x44\x49\x3b\x2c\x5d\x87\xd2\ -\x05\x64\xb1\xad\x85\x76\xa6\xed\x74\x9b\xf7\xae\x7f\xcc\x62\x9b\ -\x69\x2b\xe7\x9f\x97\x73\x5e\xce\xf7\x9d\x7b\xce\x77\xcf\x15\x4a\ -\x29\xa6\xb3\x95\x55\x0b\x2c\xe1\x26\x73\x8e\x80\x3c\x14\xb9\x20\ -\x24\x4a\xd4\x02\x0e\xa3\x34\x39\x2a\x96\xd7\xba\xa6\xcb\x17\x53\ -\x11\xac\x3b\x97\x91\xaf\x94\x2c\x45\x91\x0d\x88\x29\xf2\x15\xd0\ -\xa0\xeb\xa2\xf8\xe7\xc2\xeb\x17\x1f\x89\xa0\xe8\x44\x46\x98\x77\ -\xa6\xf8\x00\xe4\x1e\x83\x30\x18\x72\xe3\x0a\x48\x8d\x49\xc7\x66\ -\x49\xe7\x71\x73\x02\x12\xc9\x83\x91\x2e\x5a\x5d\x8d\xdc\x74\x37\ -\x52\xdb\x73\x01\xaf\x3e\xa6\x03\x9f\xc7\x98\xfa\xf7\x7e\xf3\x5c\ -\xc7\xf0\x94\x04\xaf\x9c\xc9\x4a\xd3\x05\x15\x40\x66\x72\x54\x2a\ -\xc5\xf6\xfd\xb4\xba\xae\xd2\xe6\xba\xc6\xbd\x81\x16\x06\x46\x7b\ -\x30\x00\xb1\x66\x2b\x89\x51\x69\x24\x5b\x32\x48\xb6\x64\x73\xd8\ -\xf9\x09\x6d\xee\x66\x80\x66\x29\xd5\xfa\x9f\x96\xd5\xd7\x85\x10\ -\xf8\x2a\x97\xb5\x52\xc8\xcc\x75\x29\x6f\x92\x1f\xbf\x94\x32\xe7\ -\xc7\x74\x79\xda\x31\x08\x81\x44\x60\x10\x02\x03\x4c\xf0\xe3\xcc\ -\x4f\xb0\x7a\xfe\x1e\xea\xfa\x1c\xfc\xd8\x76\x04\x4d\xd7\xda\xbc\ -\xa3\x61\x59\xa7\x56\xd5\x7a\x00\x64\x80\xc9\xd7\x16\x32\xd7\xa6\ -\x6c\x26\xde\x6c\xa5\xe4\xca\x66\xee\x0f\xb6\x4d\xd1\xfa\xff\xac\ -\x77\xf8\x6f\xca\x1b\x77\x32\x2b\x2c\x9a\x8d\xf3\x8b\x41\x60\x33\ -\x86\x8f\x95\x04\xfe\x4b\xf0\x0d\x14\xe4\x9e\xe4\xa8\x54\xf2\xe3\ -\x9f\xa5\xbc\xe5\x20\x9a\xd2\xfe\x17\x1c\x40\x20\x88\x09\x4f\xe0\ -\xcf\xfb\x15\x64\xc7\x2e\x21\x35\x26\x13\x50\x3b\xd6\x9c\x5e\x5c\ -\x00\x60\x04\x50\x4a\x96\x1a\x84\xc1\xb0\xd5\xbe\x9f\x63\xcd\x1f\ -\xa1\x29\x0d\x39\x95\x6e\xc6\xd9\x9c\xa8\x34\x5e\xb6\xed\x24\xc9\ -\x92\xc1\xe0\xd8\x43\xca\xea\xb7\xb3\xdd\xfe\x21\xbb\x2e\x6f\x14\ -\xa3\x8c\x94\x02\xe9\x72\x65\xd5\x02\x0b\x8a\xec\xdc\xb8\x02\x9c\ -\xbd\x0e\xee\x3e\x42\x5b\x00\x56\xcd\xdb\xc1\xb6\xec\x63\x24\x59\ -\x32\x00\xb8\xe3\x6e\xe0\xe1\xd0\x1d\x3a\x7a\x2f\xf1\x94\xf5\x05\ -\x00\x7b\x51\x55\x4e\xbc\x0c\x37\x99\x73\x00\x31\xdf\x92\x4e\x73\ -\xdf\x95\x10\xa0\xe4\xe8\x45\x21\xb1\xe5\x49\x6f\xb0\x74\xce\x6b\ -\x08\xff\x08\x3b\x07\x5b\x39\xd5\x7a\xc0\x4f\x74\x8d\x45\x33\xb3\ -\x00\xf0\x86\x69\xb9\x52\x40\x1e\xc0\x3c\x8b\x9d\x5b\xfd\xce\x09\ -\x40\x85\x49\x9b\x78\x2f\xef\x38\x6b\x6d\x6f\x05\x63\xb9\xd6\x97\ -\x28\x4c\xd9\x12\xf4\xbb\x06\xdb\x29\x6b\x78\x9b\x21\xaf\xdb\x47\ -\x36\xd0\xc2\xbc\x68\x3b\x00\x4a\x53\x79\x46\xdf\xf5\x87\x38\x73\ -\x02\xbd\x23\xff\xf8\x24\x28\x24\x9b\x52\xdf\x61\xd9\x9c\x22\x00\ -\x5e\x4c\xdc\x84\xa3\xeb\x0c\x46\x69\x60\x43\xda\x3e\x84\xff\x62\ -\x0f\x8e\xf5\xf1\x6d\xe3\x6e\x3c\x63\xae\xe0\xcc\x46\xb4\x41\x66\ -\x18\x23\x91\x42\xa2\x2b\x3d\xcf\x08\x42\x02\x08\x11\x54\x2c\x11\ -\xc6\x68\x22\x4d\xd1\x13\x4e\xf3\x42\xe2\x06\x52\x2c\x19\x18\x84\ -\x09\x00\x5d\x69\x7c\xdf\xbc\x8f\xbe\x91\x4e\x0c\x62\xa2\x22\x02\ -\x58\x42\x28\x19\x58\x5c\xf4\x0c\x77\x12\x1b\x6e\xf5\x57\xe6\xa2\ -\xb4\x71\x2f\xe5\x2d\x9f\xa1\xfb\xe5\xba\x24\x61\x25\xd6\x88\xa4\ -\x20\x48\x55\xc7\x61\xda\x5d\xa1\x33\x33\x1b\xa3\xf0\x78\x07\xd0\ -\x95\x0e\x88\x1a\x09\x38\x00\xda\xdd\x4d\xcc\xb5\x4c\x1c\x68\xf5\ -\xdd\x0a\xbe\xbb\x51\x12\x02\x72\x7f\xa0\x85\x8b\xf7\x7e\x08\x89\ -\x03\xcc\x8a\x5c\x48\x9b\xbb\xc9\x77\x02\x70\x48\xa3\x34\x39\x00\ -\xd5\xea\x6e\x62\x81\x25\x33\x24\xe1\x52\xe7\x29\x3a\x3d\x1d\x41\ -\x5f\xa1\xa8\x6c\x3d\xe8\xaf\x30\xd4\x66\x47\x67\xd0\xec\xaa\x07\ -\x40\x57\xa2\x56\xfa\xf7\x79\x83\xa3\xe7\x02\xf6\xd8\x7c\xe2\x23\ -\x66\x4f\x48\x50\x4a\xe7\xcc\x5f\xc7\x83\x7e\x4d\xe7\xef\xdc\x72\ -\xd7\x4f\x0a\x6e\x09\x8b\x67\xee\x63\x4f\x73\xb9\xbb\x1a\x50\x37\ -\x2b\x57\x5c\xef\x96\x00\xba\x2e\x8a\xbd\xfa\x98\x7e\xd4\xf9\x29\ -\x9b\xd3\xde\x0f\xaa\x24\x60\x8d\x0f\xff\xe0\xfc\xbd\x0a\xaa\xef\ -\x94\xf3\x6b\xfb\x57\x93\x82\x03\x14\xda\x76\x73\xc4\x59\xc2\xb0\ -\x36\x04\x8a\x62\x18\xb7\x4d\xd7\x9e\xcd\x3a\x04\xec\x5c\x6f\xdb\ -\x46\xa4\x21\x82\x93\xed\x5f\x22\x94\xf2\x6d\xd0\x69\xb6\xa9\xc1\ -\xff\x2d\x48\x7c\x9d\x11\x22\xf8\xba\xe5\x10\x42\x70\xf4\xe4\xf2\ -\xba\x2d\x30\x6e\x9b\xc6\x98\xfa\xf7\x02\xcd\x27\xda\x8f\x30\xac\ -\x8f\xf2\x6e\xf6\x31\x12\x66\x24\x4f\x59\x6d\xb0\x2d\xe1\x56\x36\ -\xd8\x0f\x32\x4c\x04\x65\xad\x5f\x00\xe2\xb6\xd7\x33\xba\x2b\xf0\ -\x7f\xe2\x83\x73\x2e\x33\x4b\xd7\x44\x25\x02\x5b\x6a\x4c\x26\x5b\ -\x17\xed\xa7\xee\xc1\x79\x5a\xfb\xae\x72\x77\xc0\xc9\x98\xe6\xc1\ -\x00\x44\x99\x2c\x24\x46\xa5\x31\x37\x26\x8b\x85\xb1\xcf\x70\xd4\ -\x79\x80\xa6\xbe\xab\x80\xb8\x2d\xa4\xf6\xea\xc9\x65\x0d\x35\x93\ -\x12\x00\xac\xfe\x2d\x6f\x86\x6f\x9f\xab\x1d\x61\x32\x5c\x3c\x69\ -\x7d\x9e\x85\x31\x59\xd8\x2c\x76\x22\x8c\x91\x48\x04\x43\x5e\x0f\ -\x6d\xfd\x8d\xdc\x70\x35\x70\xb9\xbb\x9a\x61\x6d\x08\x21\x38\xea\ -\xf5\x8c\xee\xfa\x65\xb5\xb3\x7f\x3c\xde\x94\x8f\xfe\x9a\xd3\x8b\ -\x0b\xa4\x54\xa5\x80\x3d\x10\x93\xfe\x1b\x3a\x51\xa2\xea\x26\x8a\ -\xe2\xca\x15\xf5\x67\x27\xc3\x99\x92\x20\x60\x45\x55\x39\xf1\xde\ -\x30\x2d\x57\x69\x2a\x4f\x40\x9e\x10\x4a\x82\xa8\x11\xe0\xd0\x95\ -\xa8\xad\x5c\x71\xbd\x7b\xba\xfc\x7f\x01\xe3\xf6\xed\xcb\x2c\x97\ -\xd8\xbf\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x13\x74\x45\ +\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x52\x6f\x64\x6e\x65\x79\x20\ +\x44\x61\x77\x65\x73\x0e\xd8\x7e\x1d\x00\x00\x04\x82\x49\x44\x41\ +\x54\x48\x89\x8d\x96\x6d\x88\x54\x55\x18\xc7\x7f\xe7\xbe\xcd\xdc\ +\xb9\xb3\xb3\x33\xfb\x1a\xeb\xbe\xe9\x6e\xad\x2f\x69\xea\x4a\x68\ +\x8a\x22\x24\x94\x58\x7e\xaa\x48\x23\x30\x41\xa1\xec\x5b\x51\x41\ +\xf8\x31\xa3\x3e\x44\x94\x44\x92\x09\xa5\x46\x54\x84\x21\x59\x44\ +\x18\x96\x19\xe8\xb2\x5a\x9b\xba\xea\xa4\xfb\x66\xee\xce\xce\xec\ +\xec\xcc\xdc\xbd\xf3\x76\xef\xe9\x43\xb3\x63\xe6\xbe\x3d\xf0\x7c\ +\xfb\x9f\xff\xef\x3c\xcf\x3d\xf7\x39\x47\x48\x29\x99\x2d\x36\xbf\ +\x27\x7c\x6a\x40\x7f\xc0\x6f\xaa\x8f\x02\x64\x1d\xf7\x84\x3b\x51\ +\xb8\xf0\xed\x8b\x32\x37\xdb\x5a\x31\x1d\x60\xf7\x01\xa1\x67\x2a\ +\x42\x6f\x37\xd7\x76\xac\xaf\x0a\xd6\xd5\x07\x2c\x7f\xb5\x69\x05\ +\x7c\x52\xba\x38\xb6\x93\xb3\xed\x6c\x62\xdc\x8e\xdd\xea\x8b\x5d\ +\x39\x15\x4c\xa7\x5e\xfe\x70\x97\x2c\xcc\x19\xb0\xf5\xa0\xb1\xa2\ +\xb9\x7e\xfe\xa1\x65\x1d\xab\x96\x7a\x5a\x56\xc9\xbb\x0e\x92\x3b\ +\x75\x02\x81\xa1\x9a\x50\x50\xbd\x9e\x2b\xe7\xff\xe8\x1f\xbe\xb1\ +\xe3\xd8\xce\x7c\xf7\xac\x80\x6d\x9f\x87\xf7\x2d\x69\x59\xf9\x5c\ +\x5d\x7d\x4d\x9d\xe3\xa6\x67\xeb\x00\x00\x01\x35\x44\x7c\x78\x74\ +\xe4\x42\x5f\xd7\xc7\x47\x9f\x4a\xbe\x36\x2d\xe0\x89\xc3\xe6\xb3\ +\x6b\x96\xae\xdf\x6f\x04\xf5\xa0\x27\xdd\x39\x99\x87\xb4\x7a\xaa\ +\xb4\x05\xa4\x8a\x43\x8c\x24\xaf\x65\xce\xf4\xfc\xfa\xc2\x17\xcf\ +\x38\x9f\xdc\x05\x78\xfc\x80\xa8\x59\xdc\xb6\xfc\xb7\xc6\xd6\x79\ +\x6d\x73\x33\x17\x6c\xa8\xdc\x43\x83\xb1\x0c\x9f\x08\x72\xc9\xf9\ +\x9e\x73\x99\x4f\x19\xb8\xd1\x17\xbd\x18\xed\x59\xfd\xcd\x2e\x39\ +\x0a\xa0\x4c\xca\xab\xab\xea\x8e\x34\x35\x37\xcc\xd1\x1c\x56\x5a\ +\x4f\xd2\xea\x5b\x8d\x5f\x54\x10\x2b\x46\x39\x97\x39\x8c\x2b\x5d\ +\x5a\x9a\x5b\xdb\xaa\xab\x6a\x8f\x4c\xea\x14\xf8\xf7\xa3\xde\xd7\ +\xd2\xb1\xd6\x15\x73\x33\xaf\xd1\xdb\xb9\xd7\xdc\x88\x82\x4a\xda\ +\x1d\xe1\x97\xd4\x07\xb8\xb2\x08\x40\x51\xb8\xb4\x37\xcf\x5f\xbb\ +\xf5\xa0\xb1\xa2\x0c\xf0\xfb\xd4\x2d\x56\xd0\xb4\x40\x20\xc4\x54\ +\xa9\x94\x53\x53\xfc\xac\x0b\xed\x22\xa0\x86\xc9\x4b\x87\x4b\xce\ +\x09\xd2\xee\x2d\x14\xa1\xa2\x08\x0d\x21\x54\x02\x96\xdf\xf2\xfb\ +\xd4\x2d\x00\x1a\x80\x65\x55\x76\xaa\x86\x86\xbc\xdd\xb1\x52\x97\ +\xc5\x5d\xbb\x5f\x57\xb1\x9b\x88\xd6\x02\xc0\xad\x42\x0f\x97\xb3\ +\x3f\xa2\x08\xed\x0e\x8d\x66\xe8\x58\x66\xa8\xb3\x0c\x08\x9b\x91\ +\x26\x4f\x4a\x54\x45\x9d\xd1\x7c\x81\xff\x21\x1a\x7d\xcb\xf1\x3c\ +\x8f\x91\x42\x2f\xc7\xe3\x7b\x31\xd5\x10\x86\x12\xb8\x43\x57\x14\ +\x0a\xe1\x40\xa8\xa9\x0c\x30\x0d\xb3\x5a\x11\x3a\x0a\x0a\x4c\x61\ +\x0c\x10\x50\x23\x2c\x31\x1f\x43\xb8\x3a\x49\xf7\x26\xc7\xe3\x7b\ +\x89\xe5\xa3\x00\x58\x6a\x15\x11\xa3\x11\x53\x0d\x23\x10\xe4\xbd\ +\x1c\xa6\xe1\xab\x2e\x03\x9c\xfc\x44\x5c\x57\x8c\x96\xff\x9a\x77\ +\x98\x9b\x08\x28\x61\xba\xed\x2f\xf1\x64\x91\x4e\x73\x1b\x16\xb5\ +\x14\x65\x8e\x0b\xf6\x57\x0c\xe5\xcf\x97\xb5\xb6\x9b\xc0\x76\x12\ +\xf8\x14\x8b\xb0\xde\x48\xde\x4b\xe1\xe4\xb3\xf1\x32\x20\x69\x8f\ +\x0d\x08\xe9\xad\x14\xc2\x00\x60\x51\xe0\x11\x96\x04\x36\x23\x50\ +\x18\x2b\x0c\x52\x70\x73\xdc\xa3\x2d\x06\xa0\x3f\x77\x96\x53\xa9\ +\xfd\x53\x56\x99\xf3\x6c\x86\x73\xbd\x54\xa9\xb5\x24\x27\xc6\x07\ +\xca\x00\xdb\x49\x75\x25\x9d\xa1\xad\xe8\x3a\xa6\x5a\x89\x81\x85\ +\xf0\x34\x3c\xcf\xa3\xcd\xd8\x80\x2e\x4c\x14\xa1\x13\x2b\x5c\xe5\ +\x58\xe2\x55\x60\xe6\x09\xac\xba\x3a\xb6\x93\xe9\x2a\x03\xb2\x39\ +\xf7\x78\x2e\x53\x7c\x65\x3c\xd0\x67\x49\x3c\xbc\xa2\xa4\xb5\x76\ +\x0d\x9a\xf0\x53\x55\x3a\x31\xb6\x1b\xe7\x64\xea\x1d\x26\xbc\xc4\ +\x8c\xe6\x02\x85\x82\x2d\xed\x6c\xce\x3d\x0e\xa5\xff\xe0\xd8\xce\ +\x7c\x77\x74\xa0\xff\x74\x8d\xd2\x00\xc0\x60\xbe\x9b\x44\xb1\xbf\ +\xbc\xc8\x93\x2e\x97\x9c\xef\xb8\xea\xfc\x34\xa3\x39\x40\x8d\x52\ +\x47\x74\x70\xf0\xf4\xe4\x64\x2d\x1f\xfc\x78\x22\xb6\x3d\x31\xe4\ +\x44\x4d\xc5\xc2\xc3\xe5\x77\xfb\x6b\x3c\x59\xa4\x20\x1d\x06\xf2\ +\xe7\xf8\x21\xf9\xe6\xac\xe6\xa6\x12\x20\x31\xe4\x44\xe3\x89\xf8\ +\xf6\x72\x45\xff\x9f\xa6\x9d\x0b\xef\x7f\x3f\xe9\xff\xbb\xc2\x95\ +\x1e\x0b\xcd\x87\xc9\xca\x34\xd7\xb3\x67\x98\xad\xef\x9a\xd0\x08\ +\x3b\xb5\xe9\xae\xde\xde\x3d\x53\x4e\xd3\xc9\xd8\xf6\x59\x78\x5f\ +\xc7\xbc\x96\x1d\x6a\x4d\xa1\x3e\xe9\xc6\x67\xdd\x35\x40\x58\x8d\ +\xe0\x8e\x6a\xc3\xbd\x43\xfd\x87\x8e\x3e\x9d\x9a\xfe\x3e\x98\x8c\ +\x4d\x6f\x19\xab\xdb\xda\xeb\x3f\x5a\xd0\x5e\xb7\x28\xad\x24\x94\ +\xac\x9c\xfa\x46\xf3\x0b\x3f\x15\x6e\xd8\xfb\xeb\xda\xc8\xe5\x8b\ +\x5d\xb1\xe7\x7f\xde\x57\x3c\x2d\x65\x69\xea\x4d\x05\x10\x42\xa8\ +\x40\x35\x10\x36\x2b\xa8\xdd\xf8\x7a\xf0\xa5\x96\xe6\xea\x65\xa1\ +\x50\x20\x12\x08\x2b\x41\xa3\x42\xd1\x01\x72\x29\xaf\x38\x91\x2c\ +\x66\xd2\xa9\x6c\xf2\xfa\xf5\xd8\x9f\x27\xdf\x98\x78\x37\x67\x33\ +\x0a\x8c\x01\x49\x29\x65\x72\xda\x0a\x84\x10\x16\x10\x01\x2a\x4b\ +\x59\x61\x04\x89\x34\x3c\xa8\x2c\x6c\x5a\xa5\xad\x90\x12\x65\xe0\ +\x6c\xf1\xfc\xcd\xb3\x5e\xb4\x60\x33\x06\x64\x80\x14\x30\x5e\xca\ +\x84\x94\xb7\x1f\x00\xd3\xbe\x2a\x4a\x30\x1d\x30\x00\x5f\x29\x75\ +\x40\x05\x8a\x40\x0e\xc8\x03\x59\xa0\x20\xe5\xd4\x37\xd5\x3f\x13\ +\x05\x02\x8c\xec\xcf\x7e\xae\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +\x00\x00\x05\x64\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ +\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x04\xe1\x49\x44\ +\x41\x54\x48\x89\xb5\x95\x4b\x6c\x54\x55\x18\xc7\x7f\xe7\x9c\x3b\ +\xd3\x99\x32\xb5\xf4\x41\x1f\x38\x02\xa5\x8d\x4c\x54\xb0\x25\x48\ +\xa2\x12\x62\x14\x17\x46\x12\x64\x81\x26\xb0\x31\x18\x12\x36\x04\ +\xd2\x45\xe9\xca\x84\x5d\x29\x1a\x43\x80\x6e\x0c\x2b\xdc\x88\x09\ +\xa0\x21\x04\x35\x18\xa2\x26\x3e\xa3\x62\xa8\x4f\x7c\x61\x95\x81\ +\xb6\x4c\xcb\xbc\x67\xee\x39\x9f\x8b\xdb\xde\x4e\x2b\x10\x37\xde\ +\xe4\x4b\xee\xf3\xf7\xff\xfe\xdf\xf9\xee\x77\x94\x88\xf0\x7f\x1e\ +\xde\xdd\x1e\x9e\x53\xaa\x23\x6a\xcc\x33\xf1\x58\xec\xc9\xc6\xae\ +\x15\x2b\xeb\x97\x76\x2e\xb5\xc5\x62\x29\xfb\xd7\x5f\x63\xb9\x6b\ +\xd7\x7f\x2c\x55\x2a\xe7\x2b\xd6\x7e\xb0\x59\xa4\x70\x27\x86\xba\ +\xad\x03\xa5\xd4\x87\xb1\xd8\xcb\xc9\xc7\x1e\xdd\xdd\xd9\xde\xd6\ +\x11\xab\x8b\xa3\x4a\x45\xc8\xe5\xc1\x18\x48\x2c\xc2\x79\x86\x5c\ +\x21\xef\x7e\x1f\xbd\x7c\x65\xe2\xa7\x5f\xf7\x3c\x59\xad\xbe\xf7\ +\x9f\x04\xce\x2b\xb5\xa2\x75\x69\xe7\x1b\xa9\x0d\x8f\xaf\x4f\x54\ +\x6d\x84\x52\x29\x7c\x26\xc0\xec\xfb\x32\x13\x34\x24\xb8\x91\x9f\ +\x9e\xfa\xe5\xe3\x4f\xdf\xb6\xb7\xb2\xbb\x9f\x10\x29\xd5\xf2\xe6\ +\x09\xbc\xa7\x54\x6a\xc5\xda\xde\xf7\x7b\xba\xbb\x93\x3a\x57\x98\ +\x83\x2c\x80\x2e\x14\x11\xcf\xc3\xc6\x23\xf2\xdd\xa7\x9f\x7d\x91\ +\x1f\xbb\xf6\xf8\x13\x22\xfe\x2c\x53\xcf\x9e\x1c\x50\x4a\xb7\x2e\ +\x5f\x76\xa2\x67\xf9\xb2\x24\xd9\x3c\x4e\x24\x0c\x0b\xd8\x9a\x6b\ +\x07\xc1\x3d\xc0\x01\xce\xf7\x21\x5b\x54\x3d\x0f\xaf\x5e\xa7\xeb\ +\xeb\x5f\xab\x75\x10\x0a\x6c\x8a\xc7\x87\xee\x7f\xe8\x81\x3e\x29\ +\x56\x02\x80\x08\x95\xed\xdb\xa9\x6e\xd9\x32\x27\x52\x03\xb5\x9e\ +\x87\x1d\x18\xc0\xef\xed\x0d\xc5\x8d\x55\x3a\xd9\xf7\xe0\x0b\xef\ +\x46\xa3\xeb\x67\xb9\x1e\xc0\x05\xa5\xee\x5f\xf9\xd8\xfa\x17\xeb\ +\x7c\x67\x1c\x0a\x01\xec\x8e\x1d\xf8\xcf\x3d\x17\x64\xa1\x14\xfa\ +\xf4\xe9\xb0\x3c\x2e\x12\x81\xfd\xfb\xa1\xb7\x17\xfa\xfa\x70\x43\ +\x43\xf0\xf5\xd7\x88\x15\x9a\x1b\x9b\x96\x34\x25\xdb\x5f\x47\xa9\ +\x5e\x44\x44\x03\x78\x9e\xf7\x52\xc7\xe2\xa6\x25\x0e\x15\x64\xe9\ +\x79\xd8\x9e\x9e\xd0\xa6\xdb\xbe\x1d\x7f\xeb\xd6\x20\xd3\x68\x14\ +\x06\x07\x51\x7d\x7d\x28\xa5\x50\x91\x08\x74\x77\x63\x95\xc2\x29\ +\x85\xad\x38\x5a\xdb\x5a\x97\x9d\x85\x9e\xd0\xc1\xa2\xd6\x96\x07\ +\x11\x70\x5a\x07\x59\x3a\x07\x43\x43\xa8\xc1\x41\x58\xbd\x3a\x50\ +\xd9\xb1\x03\x17\x89\xa0\x52\x29\xd4\x9a\x35\xa1\xb8\x3d\x79\x12\ +\xff\xcc\x19\xc4\x18\x10\x41\xb4\xa6\x2e\x16\x5b\x1c\xd1\xfa\x29\ +\xe0\x67\x0d\x10\x6b\x6d\xba\xcf\x39\xc1\x69\x3d\x17\xd6\xe2\x86\ +\x87\x91\xcb\x97\xe7\x16\xec\xf9\xe7\x43\xb8\x88\xe0\xbf\xf9\x26\ +\xd5\x53\xa7\x82\xc4\xb4\xc6\x79\x1e\x62\x0c\x26\x12\xa5\x2e\x11\ +\xdf\x00\xa0\xdf\x52\x2a\x51\xb7\x28\xde\xee\xbc\xc8\x7c\x01\xad\ +\x71\xce\xe1\xbf\xf2\x0a\x32\x3a\x3a\x57\x2e\xe7\xf0\x7d\x9f\xe2\ +\xa1\x43\x64\x07\x06\x28\x5f\xb9\x82\x2d\x95\x70\xc6\x04\xdf\x18\ +\x83\xad\x5a\xe2\x4b\x9a\x97\x87\x25\xc2\x98\x60\xe1\x6a\x7b\x7b\ +\x06\x28\xc6\x20\x4a\x81\x13\x9c\xb8\xf0\x1f\x70\x80\xb5\x16\x3f\ +\x9d\x46\xd2\x69\x74\x4b\x0b\x5e\x32\x89\x6e\x6e\xc6\x96\xcb\x88\ +\xb8\xa0\x8b\xb6\x89\xe4\xbe\x79\x68\xd5\x75\xb9\xd7\xb4\x8b\x95\ +\x00\xae\x54\x00\x8f\x44\x88\xec\xdb\x07\xa9\x14\xd6\xd9\xda\xf6\ +\x26\xd6\xdf\x8f\x03\x0a\x87\x0f\x07\x5d\x37\x31\x41\x65\x7c\x1c\ +\xe2\x71\xbc\xce\x26\xf2\x13\x53\x7f\x84\xff\x41\x71\x32\xf3\xa7\ +\x3f\x39\x81\xd3\x26\xb4\xea\x97\xca\xe8\x5d\xbb\x90\x55\xab\x70\ +\x2e\xc8\xa6\xf0\xea\xab\x54\x3f\xfa\x28\x14\xa9\xef\xef\x27\xb6\ +\x77\x2f\x56\x24\x08\xc0\x2f\x14\xa8\x7a\x9a\x72\xae\xf8\x71\x58\ +\xa2\xc2\x44\x66\xb4\x9c\xcd\x3c\xeb\x46\x7f\x40\x25\x12\x38\xdf\ +\x27\x3e\x32\x82\x5e\xbb\x36\x84\xe5\x87\x87\x29\x1c\x3b\x86\x44\ +\xa3\x34\x1e\x3f\x4e\xdd\xc6\x8d\x00\x24\xfa\xfb\x71\xbe\x4f\xf6\ +\xc8\x91\xb0\xb4\x3e\x4c\x39\xe7\x2e\x84\x0e\xf0\xfd\xe3\x19\xe7\ +\xc6\x2d\x8e\xca\xf8\x38\xd5\x4c\x06\xff\xd2\xa5\x10\x9e\x3b\x78\ +\x90\xdc\xd1\xa3\x41\x96\xe5\x32\x93\x3b\x77\x52\xba\x78\x31\x28\ +\x63\xb9\x4c\xe9\xdb\x6f\xf1\x67\x5c\xe8\xf6\x26\xa6\x27\x32\x57\ +\x37\xc3\x15\xa8\x19\x76\x17\xea\xeb\x87\xbb\xfa\x52\xfd\xf6\xf2\ +\x2f\x46\x24\x58\x8b\xc4\xe0\x20\xf6\xd6\x2d\x72\x23\x23\x41\x8f\ +\xd7\x36\x41\x34\x4a\xeb\xc8\x08\xd9\x13\x27\x28\x5c\xbc\x18\x0c\ +\xc4\xa8\x87\xac\xec\x18\xff\xfb\xab\x9f\x36\x6f\x16\xf9\x7c\x9e\ +\xc0\x01\xa5\xf4\xa6\x64\xfb\x67\x1d\x0d\x0d\xeb\x2a\x63\xd7\x91\ +\x05\xc0\xd9\xde\x9f\x27\x52\x73\x0d\x50\x97\xba\xcf\xa5\xbf\xff\ +\x7d\xe4\xe9\x42\x69\xcf\xac\xfb\x79\xe3\xfa\xac\x52\xa9\xb6\x54\ +\xd7\xfb\x8d\xc6\x24\x2b\x7f\xa4\xff\x05\x58\x08\x0d\x47\xb7\x67\ +\x88\x76\x77\xca\x74\xfa\xe6\x17\x92\x9e\x9c\x37\xae\x6f\xbb\xe1\ +\xc4\xda\x9a\xdf\x58\xb2\x62\xe9\x23\xfe\x6f\xd7\xa2\x36\x5f\x9c\ +\x2f\xb0\x40\xc4\x6b\x6b\x44\x9a\x13\x99\xc9\x1f\xc7\xde\x89\x15\ +\x4a\x77\xdf\x70\x00\x94\x52\x0d\x31\x68\x39\x12\x31\x03\xa9\x55\ +\xcb\xb7\x35\x18\xd3\xaa\x7c\x8b\x9d\xce\x53\x9d\xce\x83\xd1\x98\ +\xc5\x09\x74\x43\x0c\xab\x95\x4c\x66\x72\x63\x17\xae\x5e\x3f\x78\ +\x08\xde\x05\xa6\x80\x29\xb9\x93\x03\xa5\x54\x04\x68\x01\x16\x03\ +\x4d\xbd\xd0\xb5\x49\xeb\x8d\x5d\x9e\xe9\x6b\x69\x69\x68\x6b\x6c\ +\x5c\xd4\x58\xf5\xad\xbd\x99\xc9\x4d\xdf\x98\xce\x5f\x1b\xf5\xed\ +\x97\xe7\xe0\x93\x71\x48\xcf\xc2\x81\x8c\x88\x64\xef\xe8\x60\x46\ +\xa8\x1e\xb8\x67\x26\xea\x81\x38\x50\x07\x18\x40\x11\xec\x3b\x15\ +\xa0\x0c\xe4\x81\x1c\x70\x0b\xc8\xca\xec\x8c\x98\x39\xfe\x01\x76\ +\x95\xba\xf1\x06\x3a\xff\x81\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\  "  qt_resource_name = "\ @@ -1590,6 +1757,11 @@ qt_resource_name = "\  \x05\xcd\xf4\xe7\  \x00\x63\  \x00\x6f\x00\x6e\x00\x6e\x00\x5f\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x13\ +\x09\xd2\x6c\x67\ +\x00\x45\ +\x00\x6d\x00\x62\x00\x6c\x00\x65\x00\x6d\x00\x2d\x00\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x2e\x00\x70\ +\x00\x6e\x00\x67\  \x00\x12\  \x04\xe4\x91\x47\  \x00\x63\ @@ -1609,21 +1781,28 @@ qt_resource_name = "\  \x00\x6c\  \x00\x65\x00\x61\x00\x70\x00\x2d\x00\x63\x00\x6f\x00\x6c\x00\x6f\x00\x72\x00\x2d\x00\x73\x00\x6d\x00\x61\x00\x6c\x00\x6c\x00\x2e\  \x00\x70\x00\x6e\x00\x67\ -\x00\x0b\ -\x01\x64\x80\x07\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x06\x1a\x44\xa7\ +\x00\x44\ +\x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x61\x00\x63\x00\x63\x00\x65\x00\x70\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x10\ +\x0f\xc3\x90\x67\ +\x00\x44\ +\x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\  "  qt_resource_struct = "\  \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x02\ -\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x2d\x4e\ -\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x5b\xd7\ -\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xf7\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x02\ +\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x32\x3e\ +\x00\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x12\xe7\  \x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x19\xd2\ -\x00\x00\x00\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x20\xbd\ +\x00\x00\x01\x02\x00\x00\x00\x00\x00\x01\x00\x00\x60\xc7\ +\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x1e\xc2\ +\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xf7\ +\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x25\xad\ +\x00\x00\x01\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x65\xef\  "  def qInitResources(): diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 6e8abc1f..6f13a1ac 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -17,11 +17,31 @@ from leap.gui.threads import FunThread  from leap.gui import mainwindow_rc -CHECKMARK_IMG = ":/images/checked.png" +ICON_CHECKMARK = ":/images/Dialog-accept.png" +ICON_FAILED = ":/images/Dialog-error.png" +ICON_WAITING = ":/images/Emblem-question.png"  logger = logging.getLogger(__name__) +# XXX import this from threads +def delay(obj, method_str=None, call_args=None): +    """ +    this is a hack to get responsiveness in the ui +    """ +    if callable(obj) and not method_str: +        QtCore.QTimer().singleShot( +            50, +            lambda: obj()) +        return + +    if method_str: +        QtCore.QTimer().singleShot( +            50, +            lambda: QtCore.QMetaObject.invokeMethod( +                obj, method_str)) + +  class ImgWidget(QtGui.QWidget):      # XXX move to widgets @@ -125,6 +145,8 @@ class StepsTableWidget(QtGui.QTableWidget):          # this disables the table grid.          # we should add alignment to the ImgWidget (it's top-left now)          self.setShowGrid(False) +        self.setFocusPolicy(QtCore.Qt.NoFocus) +        #self.setStyleSheet("QTableView{outline: 0;}")          # XXX change image for done to rc @@ -144,46 +166,94 @@ class StepsTableWidget(QtGui.QTableWidget):          # some failing tests if they are not critical. -class ValidationPage(QtGui.QWizardPage): -    """ -    class to be used as an intermediate -    between two pages in a wizard. -    shows feedback to the user and goes back if errors, -    goes forward if ok. -    initializePage triggers a one shot timer -    that calls do_checks. -    Derived classes should implement -    _do_checks and -    _do_validation -    """ +class WithStepsMixIn(object): -    # signals +    # worker threads for checks -    stepChanged = QtCore.pyqtSignal([str, int]) +    def setupStepsProcessingQueue(self): +        self.steps_queue = Queue.Queue() +        self.stepscheck_timer = QtCore.QTimer() +        self.stepscheck_timer.timeout.connect(self.processStepsQueue) +        self.stepscheck_timer.start(100) +        # we need to keep a reference to child threads +        self.threads = [] -    def __init__(self, parent=None): -        super(ValidationPage, self).__init__(parent) +    def do_checks(self): -        self.steps = ProgressStepContainer() -        self.progress = QtGui.QProgressBar(self) +        # yo dawg, I heard you like checks +        # so I put a __do_checks in your do_checks +        # for calling others' _do_checks -        # steps table widget -        self.stepsTableWidget = StepsTableWidget(self) +        def __do_checks(fun=None, queue=None): -        layout = QtGui.QVBoxLayout() -        layout.addWidget(self.progress) -        layout.addWidget(self.stepsTableWidget) +            for checkcase in fun(): +                checkmsg, checkfun = checkcase -        self.setLayout(layout) -        self.layout = layout +                queue.put(checkmsg) +                if checkfun() is False: +                    queue.put("failed") +                    break -        self.timer = QtCore.QTimer() +        t = FunThread(fun=partial( +            __do_checks, +            fun=self._do_checks, +            queue=self.steps_queue)) +        t.finished.connect(self.on_checks_validation_ready) +        t.begin() +        self.threads.append(t) -        # connect the new step status -        # signal to status handler -        self.stepChanged.connect( -            self.onStepStatusChanged) +    def fail(self, err=None): +        """ +        return failed state +        and send error notification as +        a nice side effect +        """ +        wizard = self.wizard() +        senderr = lambda err: wizard.set_validation_error( +            self.current_page, err) +        self.set_undone() +        if err: +            senderr(err) +        return False + +    @QtCore.pyqtSlot() +    def launch_checks(self): +        self.do_checks() + +    # slot +    #@QtCore.pyqtSlot(str, int) +    def onStepStatusChanged(self, status, progress=None): +        if status not in ("head_sentinel", "end_sentinel"): +            self.add_status_line(status) +        if status in ("end_sentinel"): +            self.checks_finished = True +            self.set_checked_icon() +        if progress and hasattr(self, 'progress'): +            self.progress.setValue(progress) +            self.progress.update() +    def processStepsQueue(self): +        """ +        consume steps queue +        and pass messages +        to the ui updater functions +        """ +        while self.steps_queue.qsize(): +            try: +                status = self.steps_queue.get(0) +                if status == "failed": +                    self.set_failed_icon() +                else: +                    self.onStepStatusChanged(*status) +            except Queue.Empty: +                pass + +    def setupSteps(self): +        self.steps = ProgressStepContainer() +        # steps table widget +        self.stepsTableWidget = StepsTableWidget(self) +        zeros = (0, 0, 0, 0) +        self.stepsTableWidget.setContentsMargins(*zeros)          self.errors = OrderedDict()      def set_error(self, name, error): @@ -230,34 +300,63 @@ class ValidationPage(QtGui.QWizardPage):      def resizeTable(self):          # resize first column to ~80%          table = self.stepsTableWidget -        FIRST_COLUMN_PERCENT = 0.75 +        FIRST_COLUMN_PERCENT = 0.70          width = table.width()          logger.debug('populate table. width=%s' % width)          table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) -    def onStepStatusChanged(self, status, progress=None): -        if status not in ("head_sentinel", "end_sentinel"): -            self.add_status_line(status) -        if progress: -            self.progress.setValue(progress) -            self.progress.update() +    def set_item_icon(self, img=ICON_CHECKMARK, current=True): +        """ +        mark the last item +        as done +        """ +        # setting cell widget. +        # see note on StepsTableWidget about plans to +        # change this for a better solution. +        index = len(self.steps) +        table = self.stepsTableWidget +        _index = index - 1 if current else index - 2 +        table.setCellWidget( +            _index, +            ProgressStep.DONE, +            ImgWidget(img=img)) +        table.update() + +    def set_failed_icon(self): +        self.set_item_icon(img=ICON_FAILED, current=True) + +    def set_checking_icon(self): +        self.set_item_icon(img=ICON_WAITING, current=True) + +    def set_checked_icon(self, current=True): +        self.set_item_icon(current=current)      def add_status_line(self, message): +        """ +        adds a new status line +        and mark the next-to-last item +        as done +        """          index = len(self.steps)          step = ProgressStep(message, False, index=index)          self.steps.addStep(step)          self.populateStepsTable() -        table = self.stepsTableWidget +        self.set_checking_icon() +        self.set_checked_icon(current=False) -        # setting cell widget. -        # see note on StepsTableWidget about plans to -        # change this for a better solution. +    # Sets/unsets done flag +    # for isComplete checks -        table.setCellWidget( -            index - 1, -            ProgressStep.DONE, -            ImgWidget(img=CHECKMARK_IMG)) -        table.update() +    def set_done(self): +        self.done = True +        self.completeChanged.emit() + +    def set_undone(self): +        self.done = False +        self.completeChanged.emit() + +    def is_done(self): +        return self.done      def go_back(self):          self.wizard().back() @@ -265,6 +364,99 @@ class ValidationPage(QtGui.QWizardPage):      def go_next(self):          self.wizard().next() + +""" +We will use one base class for the intermediate pages +and another one for the in-page validations, both sharing the creation +of the tablewidgets. +The logic of this split comes from where I was trying to solve +the ui update using signals, but now that it's working well with +queues I could join them again. +""" + +import Queue +from functools import partial + + +class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): + +    def __init__(self, parent=None): +        super(InlineValidationPage, self).__init__(parent) +        self.setupStepsProcessingQueue() +        self.done = False + +    # slot + +    @QtCore.pyqtSlot() +    def showStepsFrame(self): +        self.valFrame.show() +        self.update() + +    # progress frame + +    def setupValidationFrame(self): +        qframe = QtGui.QFrame +        valFrame = qframe() +        valFrame.setFrameStyle(qframe.NoFrame) +        valframeLayout = QtGui.QVBoxLayout() +        zeros = (0, 0, 0, 0) +        valframeLayout.setContentsMargins(*zeros) + +        valframeLayout.addWidget(self.stepsTableWidget) +        valFrame.setLayout(valframeLayout) +        self.valFrame = valFrame + + +class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): +    """ +    class to be used as an intermediate +    between two pages in a wizard. +    shows feedback to the user and goes back if errors, +    goes forward if ok. +    initializePage triggers a one shot timer +    that calls do_checks. +    Derived classes should implement +    _do_checks and +    _do_validation +    """ + +    # signals +    stepChanged = QtCore.pyqtSignal([str, int]) + +    def __init__(self, parent=None): +        super(ValidationPage, self).__init__(parent) +        self.setupSteps() +        #self.connect_step_status() + +        layout = QtGui.QVBoxLayout() +        self.progress = QtGui.QProgressBar(self) +        layout.addWidget(self.progress) +        layout.addWidget(self.stepsTableWidget) + +        self.setLayout(layout) +        self.layout = layout + +        self.timer = QtCore.QTimer() +        self.done = False + +        self.setupStepsProcessingQueue() + +    def isComplete(self): +        return self.is_done() + +    ######################## + +    def show_progress(self): +        self.progress.show() +        self.stepsTableWidget.show() + +    def hide_progress(self): +        self.progress.hide() +        self.stepsTableWidget.hide() + +    # pagewizard methods. +    # if overriden, child classes should call super. +      def initializePage(self):          self.clean_errors()          self.clean_wizard_errors() @@ -272,16 +464,3 @@ class ValidationPage(QtGui.QWizardPage):          self.clearTable()          self.resizeTable()          self.timer.singleShot(0, self.do_checks) - -    def do_checks(self): -        """ -        launches a thread to do the checks -        """ -        signal = self.stepChanged -        self.checks = FunThread( -            self._do_checks(update_signal=signal)) -        self.checks.finished.connect(self._do_validation) -        self.checks.begin() -        #logger.debug('check thread started!') -        #logger.debug('waiting for it to terminate...') -        self.checks.wait() diff --git a/src/leap/gui/styles.py b/src/leap/gui/styles.py index 759817ce..b482922e 100644 --- a/src/leap/gui/styles.py +++ b/src/leap/gui/styles.py @@ -1,4 +1,16 @@ -ErrorLabelStyleSheet = """ -QLabel { color: red; -         font-weight: bold} +GreenLineEdit = "QLabel {color: green; font-weight: bold}" +ErrorLabelStyleSheet = """QLabel { color: red; font-weight: bold }""" +ErrorLineEdit = """QLineEdit { border: 1px solid red; }""" + + +# XXX this is bad. +# and you should feel bad for it. +# The original style has a sort of box color +# white/beige left-top/right-bottom or something like +# that. + +RegularLineEdit = """ +QLineEdit { +    border: 1px solid black; +}  """ diff --git a/src/leap/gui/test_mainwindow_rc.py b/src/leap/gui/test_mainwindow_rc.py index c2fb3f78..c5abb4aa 100644 --- a/src/leap/gui/test_mainwindow_rc.py +++ b/src/leap/gui/test_mainwindow_rc.py @@ -26,4 +26,4 @@ class MainWindowResourcesTest(unittest.TestCase):      def test_mainwindow_resources_hash(self):          self.assertEqual(              hashlib.md5(mainwindow_rc.qt_resource_data).hexdigest(), -            'cc7f55e551df55e39c7dbedc1f7de4c2') +            '53e196f29061d8f08f112e5a2e64eb53') diff --git a/src/leap/gui/threads.py b/src/leap/gui/threads.py index 176c19b1..8aad8866 100644 --- a/src/leap/gui/threads.py +++ b/src/leap/gui/threads.py @@ -3,10 +3,16 @@ from PyQt4 import QtCore  class FunThread(QtCore.QThread): -    def __init__(self, fun, parent=None): +    def __init__(self, fun=None, parent=None): +          QtCore.QThread.__init__(self, parent) +        self.exiting = False          self.fun = fun +    def __del__(self): +        self.exiting = True +        self.wait() +      def run(self):          if self.fun:              self.fun() | 
