""" Select Provider Page, used in First Run Wizard """ from functools import partial import logging import requests from PyQt4 import QtCore from PyQt4 import QtGui from leap.base import exceptions as baseexceptions #from leap.crypto import certs from leap.eip import exceptions as eipexceptions from leap.gui.constants import APP_LOGO from leap.gui.progress import InlineValidationPage from leap.gui import styles from leap.util.web import get_https_domain_and_port logger = logging.getLogger(__name__) # XXX check newer version in progress... def delay(obj, method_str): """ 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.setTitle(self.tr("Enter Provider")) self.setSubTitle(self.tr( "Please enter the domain of the provider you want " "to use for your connection.") ) self.setPixmap( QtGui.QWizard.LogoPixmap, QtGui.QPixmap(APP_LOGO)) self.did_cert_check = False self.current_page = 'providerselection' self.is_done = False self.setupSteps() self.setupUI() #self.disableCheckButton.connect( #self.onDisableCheckButton) self.launchChecks.connect( self.launch_checks) 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 providerNameEdit = QtGui.QLineEdit() providerNameEdit.cursorPositionChanged.connect( self.reset_validation_status) providerNameLabel.setBuddy(providerNameEdit) # add regex validator providerDomainRe = QtCore.QRegExp(r"^[a-z\d_-.]+$") providerNameEdit.setValidator( QtGui.QRegExpValidator(providerDomainRe, self)) self.providerNameEdit = providerNameEdit # Eventually we will seed a list of # well known providers here. #providercombo = QtGui.QComboBox() #if providers: #for provider in providers: #providercombo.addItem(provider) #providerNameSelect = providercombo self.registerField("provider_domain*", self.providerNameEdit) #self.registerField('provider_name_index', providerNameSelect) validationMsg = QtGui.QLabel("") validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet) self.validationMsg = validationMsg providerCheckButton = QtGui.QPushButton(self.tr("chec&k")) self.providerCheckButton = providerCheckButton # cert info # this is used in the callback # for the checkbox changes. # tricky, since the first time came # from the exception message. # should get string from exception too! self.bad_cert_status = self.tr( "Server certificate could not be verified.") self.certInfo = QtGui.QLabel("") self.certInfo.setWordWrap(True) self.certWarning = QtGui.QLabel("") self.trustProviderCertCheckBox = QtGui.QCheckBox( "&Trust this provider certificate.") self.trustProviderCertCheckBox.stateChanged.connect( self.onTrustCheckChanged) self.providerNameEdit.textChanged.connect( self.onProviderChanged) 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() 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 # progress frame def setupValidationFrame(self): qframe = QtGui.QFrame valFrame = qframe() valFrame.setFrameStyle(qframe.NoFrame) # Box | qframe.Plain) # NoFrame, StyledPanel) | qframe.Sunken) #valFrame.setContentsMargins(0, 0, 0, 0) valframeLayout = QtGui.QVBoxLayout() zeros = (0, 0, 0, 0) valframeLayout.setContentsMargins(*zeros) #dummylabel = QtGui.QLabel('test foo') #valframeLayout.addWidget(dummylabel) 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): # trying to delay this... #timer = QtCore.QTimer() #timer.singleShot(0, self.do_checks) self.do_checks() def onCheckButtonClicked(self): #self.disableCheckButton.emit() # XXX trying to get responsiveness. # UI here is blocking, although I'm using # threads and signals :( QtCore.QMetaObject.invokeMethod( self, "onDisableCheckButton") QtCore.QMetaObject.invokeMethod( self, "showStepsFrame") delay(self, "launch_checks") print 'ON CHECK BUTTON --- DONE!' print 'timer.....' def _do_checks(self): """ executes actual checks in a separate thread """ wizard = self.wizard() curpage = "providerselection" 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) def fail(): self.is_done = False return False yield(("head_sentinel", 0), lambda: None) ######################## # 1) try name resolution ######################## logger.debug('checking name resolution') def namecheck(): try: netchecker.check_name_resolution( domain) except baseexceptions.LeapException as exc: logger.error(exc.message) wizard.set_validation_error( curpage, exc.usermessage) return fail() except Exception as exc: wizard.set_validation_error( curpage, exc.message) return fail() else: return True # XXX catch more exceptions yield(("check name", 20), namecheck) ######################### # 2) try https connection ######################### logger.debug('checking https connection') def httpscheck(): 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( curpage, 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 return fail() except baseexceptions.LeapException as exc: wizard.set_validation_error( curpage, exc.usermessage) return fail() except Exception as exc: wizard.set_validation_error( curpage, exc.message) return fail() else: return True yield(("https check", 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. wizard.set_validation_error( curpage, self.tr( "Could not get info from provider.")) return fail() except requests.exceptions.ConnectionError: wizard.set_validation_error( curpage, self.tr( "Could not download provider info " "(refused conn.).")) return fail() except Exception as exc: wizard.set_validation_error( curpage, exc.message) return fail() else: return True yield(("fetch info", 80), fetchinfo) # done! self.is_done = True yield(("end_sentinel", 100), lambda: None) def _inline_validation_ready(self): """ called after _do_checks has finished. """ print 'VALIDATION READY ---------------' self.domain_checked = True if self.is_done: self.wizard().clean_validation_error(self.current_page) self.completeChanged.emit() # cert trust verification # (disabled for now) def is_insecure_cert_trusted(self): return self.trustProviderCertCheckBox.isChecked() def onTrustCheckChanged(self, state): checked = False if state == 2: checked = True if checked: self.reset_validation_status() else: self.set_validation_status(self.bad_cert_status) # trigger signal to redraw next button self.completeChanged.emit() def add_cert_info(self, certinfo): self.certWarning.setText( "Do you want to trust this provider certificate?") self.certInfo.setText( 'SHA-256 fingerprint: %s
' % certinfo) self.certInfo.setWordWrap(True) self.certinfoGroup.show() 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('') 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: if self.is_done: # XXX sure? return True return False def populateErrors(self): # XXX could move this to ValidationMixin # with some defaults for the validating fields # (now it only allows one field, manually specified) #logger.debug('getting errors') errors = self.wizard().get_validation_error( self.current_page) if errors: 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: if not getattr(self, 'domain_checked', None): showerr('') umarkrd() else: self.bad_string = cur_str showerr(errors) def cleanup_errormsg(self): """ we reset bad_string to None should be called before leaving the page """ self.bad_string = None self.domain_checked = False def paintEvent(self, event): """ we hook our populate errors on paintEvent because we need it to catch when user enters the page coming from next, and initializePage does not cover that case. Maybe there's a better event to hook upon. """ super(SelectProviderPage, self).paintEvent(event) self.populateErrors() def initializePage(self): self.validationMsg.setText('') 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 self.cleanup_errormsg() # go return True def nextId(self): wizard = self.wizard() if not wizard: return return wizard.get_page_index('providerinfo')