"""
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.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__)
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(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.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
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)
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()
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')