""" 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.crypto import certs from leap.eip import exceptions as eipexceptions from leap.gui.progress import InlineValidationPage from leap.gui import styles from leap.gui.utils import delay from leap.util.web import get_https_domain_and_port from leap.gui.constants import APP_LOGO, APP_WATERMARK logger = logging.getLogger(__name__) class SelectProviderPage(InlineValidationPage): launchChecks = QtCore.pyqtSignal() def __init__(self, parent=None, providers=None): super(SelectProviderPage, self).__init__(parent) self.current_page = 'providerselection' 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.WatermarkPixmap, QtGui.QPixmap(APP_WATERMARK)) self.setPixmap( QtGui.QWizard.LogoPixmap, QtGui.QPixmap(APP_LOGO)) self.did_cert_check = False self.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 providerNameEdit = QtGui.QLineEdit() providerNameEdit.cursorPositionChanged.connect( self.reset_validation_status) providerNameLabel.setBuddy(providerNameEdit) # add regex validator providerDomainRe = QtCore.QRegExp(r"^[a-z1-9_\-\.]+$") 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( self.tr("&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): # pragma: no cover # 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) 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") 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: #import ipdb;ipdb.set_trace() 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 if it is a valid provider"), 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 for a secure 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: 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("Getting info from the provider"), 80), fetchinfo) # done! self.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() def onTrustCheckChanged(self, state): # pragma: no cover XXX 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): # pragma: no cover XXX self.certWarning.setText( self.tr("Do you want to trust this provider certificate?")) # XXX Check if this needs to abstracted to remove certinfo self.certInfo.setText( self.tr('SHA-256 fingerprint: %s
' % certinfo)) self.certInfo.setWordWrap(True) self.certinfoGroup.show() def onProviderChanged(self, text): self.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.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')