summaryrefslogtreecommitdiff
path: root/src/leap/gui/firstrun
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/gui/firstrun')
-rw-r--r--src/leap/gui/firstrun/__init__.py28
-rw-r--r--src/leap/gui/firstrun/connect.py218
-rw-r--r--src/leap/gui/firstrun/constants.py0
-rw-r--r--src/leap/gui/firstrun/intro.py68
-rw-r--r--src/leap/gui/firstrun/last.py119
-rw-r--r--src/leap/gui/firstrun/login.py336
-rw-r--r--src/leap/gui/firstrun/mixins.py18
-rw-r--r--src/leap/gui/firstrun/providerinfo.py110
-rw-r--r--src/leap/gui/firstrun/providerselect.py475
-rw-r--r--src/leap/gui/firstrun/providersetup.py161
-rw-r--r--src/leap/gui/firstrun/register.py391
-rwxr-xr-xsrc/leap/gui/firstrun/tests/integration/fake_provider.py302
-rwxr-xr-xsrc/leap/gui/firstrun/wizard.py309
13 files changed, 2535 insertions, 0 deletions
diff --git a/src/leap/gui/firstrun/__init__.py b/src/leap/gui/firstrun/__init__.py
new file mode 100644
index 00000000..d802fa1f
--- /dev/null
+++ b/src/leap/gui/firstrun/__init__.py
@@ -0,0 +1,28 @@
+try:
+ import sip
+ sip.setapi('QString', 2)
+ sip.setapi('QVariant', 2)
+except ValueError:
+ pass
+
+import intro
+import connect
+import last
+import login
+import mixins
+import providerinfo
+import providerselect
+import providersetup
+import register
+
+__all__ = [
+ 'intro',
+ 'connect',
+ 'last',
+ 'login',
+ 'mixins',
+ 'providerinfo',
+ 'providerselect',
+ 'providersetup',
+ 'register',
+] # ,'wizard']
diff --git a/src/leap/gui/firstrun/connect.py b/src/leap/gui/firstrun/connect.py
new file mode 100644
index 00000000..209174a1
--- /dev/null
+++ b/src/leap/gui/firstrun/connect.py
@@ -0,0 +1,218 @@
+"""
+Provider Setup Validation Page,
+used in First Run Wizard
+"""
+import logging
+
+from PyQt4 import QtGui
+
+#import requests
+
+from leap.gui.progress import ValidationPage
+from leap.util.web import get_https_domain_and_port
+
+from leap.base import auth
+from leap.gui.constants import APP_LOGO, APP_WATERMARK
+
+logger = logging.getLogger(__name__)
+
+
+class ConnectionPage(ValidationPage):
+
+ def __init__(self, parent=None):
+ super(ConnectionPage, self).__init__(parent)
+ self.current_page = "connect"
+
+ title = self.tr("Connecting...")
+ subtitle = self.tr("Setting up a encrypted "
+ "connection with the provider")
+
+ self.setTitle(title)
+ self.setSubTitle(subtitle)
+
+ self.setPixmap(
+ QtGui.QWizard.WatermarkPixmap,
+ QtGui.QPixmap(APP_WATERMARK))
+
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ def _do_checks(self, update_signal=None):
+ """
+ executes actual checks in a separate thread
+
+ we initialize the srp protocol register
+ and try to register user.
+ """
+ wizard = self.wizard()
+ full_domain = self.field('provider_domain')
+ domain, port = get_https_domain_and_port(full_domain)
+
+ pconfig = wizard.eipconfigchecker(domain=domain)
+ # this should be persisted...
+ pconfig.defaultprovider.load()
+ pconfig.set_api_domain()
+
+ pCertChecker = wizard.providercertchecker(
+ domain=domain)
+ pCertChecker.set_api_domain(pconfig.apidomain)
+
+ ###########################################
+ # Set Credentials.
+ # username and password are in different fields
+ # if they were stored in log_in or sign_up pages.
+ from_login = wizard.from_login
+
+ unamek_base = 'userName'
+ passwk_base = 'userPassword'
+ unamek = 'login_%s' % unamek_base if from_login else unamek_base
+ passwk = 'login_%s' % passwk_base if from_login else passwk_base
+
+ username = self.field(unamek)
+ password = self.field(passwk)
+ credentials = username, password
+
+ yield(("head_sentinel", 0), lambda: None)
+
+ ##################################################
+ # 1) fetching eip service config
+ ##################################################
+ def fetcheipconf():
+ try:
+ pconfig.fetch_eip_service_config()
+
+ # XXX get specific exception
+ except Exception as exc:
+ return self.fail(exc.message)
+
+ yield((self.tr("Getting EIP configuration files"), 40),
+ fetcheipconf)
+
+ ##################################################
+ # 2) getting client certificate
+ ##################################################
+
+ def fetcheipcert():
+ try:
+ downloaded = pCertChecker.download_new_client_cert(
+ credentials=credentials)
+ if not downloaded:
+ logger.error('Could not download client cert')
+ return False
+
+ except auth.SRPAuthenticationError as exc:
+ return self.fail(self.tr(
+ "Authentication error: %s" % exc.message))
+
+ except Exception as exc:
+ return self.fail(exc.message)
+ else:
+ return True
+
+ yield((self.tr("Getting EIP certificate"), 80),
+ fetcheipcert)
+
+ ################
+ # end !
+ ################
+ 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)
+ """
+ # here we go! :)
+ if self.is_done():
+ nextbutton = self.wizard().button(QtGui.QWizard.NextButton)
+ nextbutton.setFocus()
+
+ 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):
+ wizard = self.wizard()
+ conductor = wizard.conductor
+ start_eip_signal = getattr(
+ wizard,
+ 'start_eipconnection_signal', None)
+
+ if conductor:
+ conductor.set_provider_domain(domain)
+ # we could run some of the checks to be
+ # sure everything is in order, but
+ # I see no point in doing it, we assume
+ # we've gone thru all checks during the wizard.
+ #conductor.run_checks()
+ #self.conductor = conductor
+ #errors = self.eip_error_check()
+ #if not errors and start_eip_signal:
+ if start_eip_signal:
+ start_eip_signal.emit()
+
+ else:
+ logger.warning(
+ "No conductor found. This means that "
+ "probably the wizard has been launched "
+ "in an stand-alone way.")
+
+ self.set_done()
+
+ #def eip_error_check(self):
+ #"""
+ #a version of the main app error checker,
+ #but integrated within the connecting page of the wizard.
+ #consumes the conductor error queue.
+ #pops errors, and add those to the wizard page
+ #"""
+ # TODO handle errors.
+ # We should redirect them to the log viewer
+ # with a brief message.
+ # XXX move to LAST PAGE instead.
+ #logger.debug('eip error check from connecting page')
+ #errq = self.conductor.error_queue
+
+ #def _do_validation(self):
+ #"""
+ #called after _do_checks has finished
+ #(connected to checker thread finished signal)
+ #"""
+ #from_login = self.wizard().from_login
+ #prevpage = "login" if from_login else "signup"
+
+ #wizard = self.wizard()
+ #if self.errors:
+ #logger.debug('going back with errors')
+ #logger.error(self.errors)
+ #name, first_error = self.pop_first_error()
+ #wizard.set_validation_error(
+ #prevpage,
+ #first_error)
+ #self.go_back()
+
+ def nextId(self):
+ wizard = self.wizard()
+ return wizard.get_page_index('lastpage')
+
+ def initializePage(self):
+ super(ConnectionPage, self).initializePage()
+ self.set_undone()
+ cancelbutton = self.wizard().button(QtGui.QWizard.CancelButton)
+ cancelbutton.hide()
+ self.completeChanged.emit()
+
+ wizard = self.wizard()
+ eip_statuschange_signal = wizard.eip_statuschange_signal
+ if eip_statuschange_signal:
+ eip_statuschange_signal.connect(
+ lambda status: self.send_status(
+ status))
+
+ def send_status(self, status):
+ wizard = self.wizard()
+ wizard.openvpn_status.append(status)
diff --git a/src/leap/gui/firstrun/constants.py b/src/leap/gui/firstrun/constants.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/leap/gui/firstrun/constants.py
diff --git a/src/leap/gui/firstrun/intro.py b/src/leap/gui/firstrun/intro.py
new file mode 100644
index 00000000..8e5014e6
--- /dev/null
+++ b/src/leap/gui/firstrun/intro.py
@@ -0,0 +1,68 @@
+"""
+Intro page used in first run wizard
+"""
+
+from PyQt4 import QtGui
+
+from leap.gui.constants import APP_LOGO, APP_WATERMARK
+
+
+class IntroPage(QtGui.QWizardPage):
+ def __init__(self, parent=None):
+ super(IntroPage, self).__init__(parent)
+
+ self.setTitle(self.tr("First run wizard"))
+
+ self.setPixmap(
+ QtGui.QWizard.WatermarkPixmap,
+ QtGui.QPixmap(APP_WATERMARK))
+
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ label = QtGui.QLabel(self.tr(
+ "Now we will guide you through "
+ "some configuration that is needed before you "
+ "can connect for the first time.<br><br>"
+ "If you ever need to modify these options again, "
+ "you can find the wizard in the '<i>Settings</i>' menu from the "
+ "main window.<br><br>"
+ "Do you want to <b>sign up</b> for a new account, or <b>log "
+ "in</b> with an already existing username?<br>"))
+ label.setWordWrap(True)
+
+ radiobuttonGroup = QtGui.QGroupBox()
+
+ self.sign_up = QtGui.QRadioButton(
+ self.tr("Sign up for a new account"))
+ self.sign_up.setChecked(True)
+ self.log_in = QtGui.QRadioButton(
+ self.tr("Log In with my credentials"))
+
+ radiobLayout = QtGui.QVBoxLayout()
+ radiobLayout.addWidget(self.sign_up)
+ radiobLayout.addWidget(self.log_in)
+ radiobuttonGroup.setLayout(radiobLayout)
+
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(label)
+ layout.addWidget(radiobuttonGroup)
+ self.setLayout(layout)
+
+ #self.registerField('is_signup', self.sign_up)
+
+ def validatePage(self):
+ return True
+
+ def nextId(self):
+ """
+ returns next id
+ in a non-linear wizard
+ """
+ if self.sign_up.isChecked():
+ next_ = 'providerselection'
+ if self.log_in.isChecked():
+ next_ = 'login'
+ wizard = self.wizard()
+ return wizard.get_page_index(next_)
diff --git a/src/leap/gui/firstrun/last.py b/src/leap/gui/firstrun/last.py
new file mode 100644
index 00000000..6a01ba34
--- /dev/null
+++ b/src/leap/gui/firstrun/last.py
@@ -0,0 +1,119 @@
+"""
+Last Page, used in First Run Wizard
+"""
+import logging
+
+from PyQt4 import QtGui
+
+from leap.util.coroutines import coroutine
+from leap.gui.constants import APP_LOGO, APP_WATERMARK
+
+logger = logging.getLogger(__name__)
+
+
+class LastPage(QtGui.QWizardPage):
+ def __init__(self, parent=None):
+ super(LastPage, self).__init__(parent)
+
+ self.setTitle(self.tr(
+ "Connecting to Encrypted Internet Proxy service..."))
+
+ self.setPixmap(
+ QtGui.QWizard.WatermarkPixmap,
+ QtGui.QPixmap(APP_WATERMARK))
+
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ self.label = QtGui.QLabel()
+ self.label.setWordWrap(True)
+
+ self.wizard_done = False
+
+ # XXX REFACTOR to a Validating Page...
+ self.status_line_1 = QtGui.QLabel()
+ self.status_line_2 = QtGui.QLabel()
+ self.status_line_3 = QtGui.QLabel()
+ self.status_line_4 = QtGui.QLabel()
+ self.status_line_5 = QtGui.QLabel()
+
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(self.label)
+
+ # make loop
+ layout.addWidget(self.status_line_1)
+ layout.addWidget(self.status_line_2)
+ layout.addWidget(self.status_line_3)
+ layout.addWidget(self.status_line_4)
+ layout.addWidget(self.status_line_5)
+
+ self.setLayout(layout)
+
+ def isComplete(self):
+ return self.wizard_done
+
+ def set_status_line(self, line, status):
+ statusline = getattr(self, 'status_line_%s' % line)
+ if statusline:
+ statusline.setText(status)
+
+ def set_finished_status(self):
+ self.setTitle(self.tr('You are now using an encrypted connection!'))
+ finishText = self.wizard().buttonText(
+ QtGui.QWizard.FinishButton)
+ finishText = finishText.replace('&', '')
+ self.label.setText(self.tr(
+ "Click '<i>%s</i>' to end the wizard and "
+ "save your settings." % finishText))
+ self.wizard_done = True
+ self.completeChanged.emit()
+
+ @coroutine
+ def eip_status_handler(self):
+ # XXX this can be changed to use
+ # signals. See progress.py
+ logger.debug('logging status in last page')
+ self.validation_done = False
+ status_count = 1
+ try:
+ while True:
+ status = (yield)
+ status_count += 1
+ # XXX add to line...
+ logger.debug('status --> %s', status)
+ self.set_status_line(status_count, status)
+ if status == "connected":
+ self.set_finished_status()
+ self.completeChanged.emit()
+ break
+ self.completeChanged.emit()
+ except GeneratorExit:
+ pass
+ except StopIteration:
+ pass
+
+ def initializePage(self):
+ super(LastPage, self).initializePage()
+ wizard = self.wizard()
+ wizard.button(QtGui.QWizard.FinishButton).setDisabled(True)
+
+ handler = self.eip_status_handler()
+
+ # get statuses done in prev page
+ for st in wizard.openvpn_status:
+ self.send_status(handler.send, st)
+
+ # bind signal for events yet to come
+ eip_statuschange_signal = wizard.eip_statuschange_signal
+ if eip_statuschange_signal:
+ eip_statuschange_signal.connect(
+ lambda status: self.send_status(
+ handler.send, status))
+ self.completeChanged.emit()
+
+ def send_status(self, cb, status):
+ try:
+ cb(status)
+ except StopIteration:
+ pass
diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py
new file mode 100644
index 00000000..1efceaa9
--- /dev/null
+++ b/src/leap/gui/firstrun/login.py
@@ -0,0 +1,336 @@
+"""
+LogIn Page, used inf First Run Wizard
+"""
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+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, APP_WATERMARK, FULL_USERNAME_REGEX
+
+
+class LogInPage(InlineValidationPage, UserFormMixIn): # InlineValidationPage
+
+ def __init__(self, parent=None):
+
+ super(LogInPage, self).__init__(parent)
+ self.current_page = "login"
+
+ self.setTitle(self.tr("Log In"))
+ self.setSubTitle(self.tr("Log in with your credentials"))
+ self.current_page = "login"
+
+ self.setPixmap(
+ QtGui.QWizard.WatermarkPixmap,
+ QtGui.QPixmap(APP_WATERMARK))
+
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ self.setupSteps()
+ self.setupUI()
+
+ self.do_confirm_next = False
+
+ def setupUI(self):
+ userNameLabel = QtGui.QLabel(self.tr("User &name:"))
+ userNameLineEdit = QtGui.QLineEdit()
+ userNameLineEdit.cursorPositionChanged.connect(
+ self.reset_validation_status)
+ userNameLabel.setBuddy(userNameLineEdit)
+
+ # let's add regex validator
+ usernameRe = QtCore.QRegExp(FULL_USERNAME_REGEX)
+ userNameLineEdit.setValidator(
+ QtGui.QRegExpValidator(usernameRe, self))
+
+ #userNameLineEdit.setPlaceholderText(
+ #'username@provider.example.org')
+ self.userNameLineEdit = userNameLineEdit
+
+ userPasswordLabel = QtGui.QLabel(self.tr("&Password:"))
+ self.userPasswordLineEdit = QtGui.QLineEdit()
+ self.userPasswordLineEdit.setEchoMode(
+ QtGui.QLineEdit.Password)
+ userPasswordLabel.setBuddy(self.userPasswordLineEdit)
+
+ self.registerField('login_userName*', self.userNameLineEdit)
+ self.registerField('login_userPassword*', self.userPasswordLineEdit)
+
+ layout = QtGui.QGridLayout()
+ layout.setColumnMinimumWidth(0, 20)
+
+ validationMsg = QtGui.QLabel("")
+ validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet)
+ self.validationMsg = validationMsg
+
+ layout.addWidget(validationMsg, 0, 3)
+ layout.addWidget(userNameLabel, 1, 0)
+ layout.addWidget(self.userNameLineEdit, 1, 3)
+ layout.addWidget(userPasswordLabel, 2, 0)
+ layout.addWidget(self.userPasswordLineEdit, 2, 3)
+
+ # add validation frame
+ self.setupValidationFrame()
+ layout.addWidget(self.valFrame, 4, 2, 4, 2)
+ self.valFrame.hide()
+
+ self.nextText(self.tr("Log in"))
+ self.setLayout(layout)
+
+ #self.registerField('is_login_wizard')
+
+ # actual checks
+
+ def _do_checks(self):
+
+ full_username = self.userNameLineEdit.text()
+ ###########################
+ # 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)
+
+ username, domain = full_username.split('@')
+ 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(domain=domain)
+
+ ########################
+ # 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
+ 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)
+ else:
+ return True
+
+ 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)
+
+ else:
+ return True
+
+ yield(('Validating credentials', 60), validate_credentials)
+
+ self.set_done()
+ yield(("end_sentinel", 100), lambda: None)
+
+ def green_validation_status(self):
+ val = self.validationMsg
+ val.setText(self.tr('Credentials validated.'))
+ val.setStyleSheet(styles.GreenLineEdit)
+
+ 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
+
+ # ui update
+
+ 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 onUserNamePositionChanged(self, *args):
+ if self.initial_username_sample:
+ self.userNameLineEdit.setText('')
+ # XXX set regular color
+ self.initial_username_sample = None
+
+ def onUserNameTextChanged(self, *args):
+ if self.initial_username_sample:
+ k = args[0][-1]
+ self.initial_username_sample = None
+ self.userNameLineEdit.setText(k)
+
+ def disableFields(self):
+ for field in (self.userNameLineEdit,
+ self.userPasswordLineEdit):
+ field.setDisabled(True)
+
+ def populateErrors(self):
+ # XXX could move this to ValidationMixin
+ # used in providerselect and register too
+
+ errors = self.wizard().get_validation_error(
+ self.current_page)
+ showerr = self.validationMsg.setText
+
+ if errors:
+ bad_str = getattr(self, 'bad_string', None)
+ cur_str = self.userNameLineEdit.text()
+
+ if bad_str is None:
+ # first time we fall here.
+ # save the current bad_string value
+ self.bad_string = cur_str
+ showerr(errors)
+ else:
+ # not the first time
+ if cur_str == bad_str:
+ showerr(errors)
+ else:
+ self.focused_field = False
+ showerr('')
+
+ def cleanup_errormsg(self):
+ """
+ we reset bad_string to None
+ should be called before leaving the page
+ """
+ self.bad_string = None
+
+ def paintEvent(self, event):
+ """
+ we hook our populate errors
+ on paintEvent because we need it to catch
+ when user enters the page coming from next,
+ and initializePage does not cover that case.
+ Maybe there's a better event to hook upon.
+ """
+ super(LogInPage, self).paintEvent(event)
+ self.populateErrors()
+
+ def set_prevalidation_error(self, error):
+ self.prevalidation_error = error
+
+ # pagewizard methods
+
+ def nextId(self):
+ wizard = self.wizard()
+ if not wizard:
+ return
+ if wizard.is_provider_setup is False:
+ next_ = 'providersetupvalidation'
+ if wizard.is_provider_setup is True:
+ # XXX bad name, ok, gonna change that
+ next_ = 'signupvalidation'
+ return wizard.get_page_index(next_)
+
+ def initializePage(self):
+ super(LogInPage, self).initializePage()
+ username = self.userNameLineEdit
+ username.setText('username@provider.example.org')
+ username.cursorPositionChanged.connect(
+ self.onUserNamePositionChanged)
+ username.textChanged.connect(
+ self.onUserNameTextChanged)
+ 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):
+ """
+ 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)
+ self.wizard().from_login = True
+
+ return True
+
+ if not self.is_done():
+ self.reset_validation_status()
+ self.do_checks()
+
+ return self.is_done()
diff --git a/src/leap/gui/firstrun/mixins.py b/src/leap/gui/firstrun/mixins.py
new file mode 100644
index 00000000..c4731893
--- /dev/null
+++ b/src/leap/gui/firstrun/mixins.py
@@ -0,0 +1,18 @@
+"""
+mixins used in First Run Wizard
+"""
+
+
+class UserFormMixIn(object):
+
+ def reset_validation_status(self):
+ """
+ empty the validation msg
+ """
+ self.validationMsg.setText('')
+
+ def set_validation_status(self, msg):
+ """
+ set generic validation status
+ """
+ self.validationMsg.setText(msg)
diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py
new file mode 100644
index 00000000..3385e9e7
--- /dev/null
+++ b/src/leap/gui/firstrun/providerinfo.py
@@ -0,0 +1,110 @@
+"""
+Provider Info Page, used in First run Wizard
+"""
+import logging
+
+from PyQt4 import QtGui
+
+from leap.gui.constants import APP_LOGO, APP_WATERMARK
+from leap.util.translations import translate
+
+logger = logging.getLogger(__name__)
+
+
+class ProviderInfoPage(QtGui.QWizardPage):
+
+ def __init__(self, parent=None):
+ super(ProviderInfoPage, self).__init__(parent)
+
+ self.setTitle(self.tr("Provider Information"))
+ self.setSubTitle(self.tr(
+ "Services offered by this provider"))
+
+ self.setPixmap(
+ QtGui.QWizard.WatermarkPixmap,
+ QtGui.QPixmap(APP_WATERMARK))
+
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ self.create_info_panel()
+
+ def create_info_panel(self):
+ # Use stacked widget instead
+ # of reparenting the layout.
+
+ infoWidget = QtGui.QStackedWidget()
+
+ info = QtGui.QWidget()
+ layout = QtGui.QVBoxLayout()
+
+ displayName = QtGui.QLabel("")
+ description = QtGui.QLabel("")
+ enrollment_policy = QtGui.QLabel("")
+
+ # XXX set stylesheet...
+ # prettify a little bit.
+ # bigger fonts and so on...
+
+ # We could use a QFrame here
+
+ layout.addWidget(displayName)
+ layout.addWidget(description)
+ layout.addWidget(enrollment_policy)
+ layout.addStretch(1)
+
+ info.setLayout(layout)
+ infoWidget.addWidget(info)
+
+ 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.description.setWordWrap(True)
+ self.enrollment_policy = enrollment_policy
+
+ def show_provider_info(self):
+
+ # XXX get multilingual objects
+ # directly from the config object
+
+ lang = "en"
+ pconfig = self.wizard().providerconfig
+
+ dn = pconfig.get('name')
+ display_name = dn[lang] if dn else ''
+ domain_name = self.field('provider_domain')
+
+ self.displayName.setText(
+ "<b>%s</b> https://%s" % (display_name, domain_name))
+
+ desc = pconfig.get('description')
+
+ #description_text = desc[lang] if desc else ''
+ description_text = translate(desc) if desc else ''
+
+ self.description.setText(
+ "<i>%s</i>" % description_text)
+
+ # XXX should translate this...
+ enroll = pconfig.get('enrollment_policy')
+ if enroll:
+ self.enrollment_policy.setText(
+ '<b>%s</b>: <em>%s</em>' % (
+ self.tr('enrollment policy'),
+ enroll))
+
+ 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
new file mode 100644
index 00000000..36bb4510
--- /dev/null
+++ b/src/leap/gui/firstrun/providerselect.py
@@ -0,0 +1,475 @@
+"""
+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 <b>trust this provider certificate?</b>"))
+ # XXX Check if this needs to abstracted to remove certinfo
+ self.certInfo.setText(
+ self.tr('SHA-256 fingerprint: <i>%s</i><br>' % 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')
diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py
new file mode 100644
index 00000000..40a14048
--- /dev/null
+++ b/src/leap/gui/firstrun/providersetup.py
@@ -0,0 +1,161 @@
+"""
+Provider Setup Validation Page,
+used if First Run Wizard
+"""
+import logging
+
+import requests
+
+from PyQt4 import QtGui
+
+from leap.base import exceptions as baseexceptions
+from leap.gui.progress import ValidationPage
+
+from leap.gui.constants import APP_LOGO, APP_WATERMARK
+
+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(self.tr("Provider setup"))
+ self.setSubTitle(
+ self.tr("Gathering configuration options for this provider"))
+
+ self.setPixmap(
+ QtGui.QWizard.WatermarkPixmap,
+ QtGui.QPixmap(APP_WATERMARK))
+
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ def _do_checks(self):
+ """
+ generator that yields actual checks
+ that are executed in a separate thread
+ """
+
+ full_domain = self.field('provider_domain')
+ wizard = self.wizard()
+ pconfig = wizard.providerconfig
+
+ #pCertChecker = wizard.providercertchecker
+ #certchecker = pCertChecker(domain=full_domain)
+ pCertChecker = wizard.providercertchecker(
+ domain=full_domain)
+
+ yield(("head_sentinel", 0), lambda: None)
+
+ ########################
+ # 1) fetch ca cert
+ ########################
+
+ 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_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))
+
+ except Exception as exc:
+ return self.fail(exc.message)
+
+ else:
+ return True
+
+ yield((self.tr('Fetching CA certificate'), 30),
+ fetchcacert)
+
+ #########################
+ # 2) check CA fingerprint
+ #########################
+
+ def checkcafingerprint():
+ # XXX get the real thing!!!
+ pass
+ #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None)
+
+ # XXX get fingerprint dict (types)
+ #sha256_fpr = ca_cert_fingerprint.split('=')[1]
+
+ #validate_fpr = pCertChecker.check_ca_cert_fingerprint(
+ #fingerprint=sha256_fpr)
+ #if not validate_fpr:
+ # XXX update validationMsg
+ # should catch exception
+ #return False
+
+ yield((self.tr("Checking CA fingerprint"), 60),
+ checkcafingerprint)
+
+ #########################
+ # 2) check CA fingerprint
+ #########################
+
+ def validatecacert():
+ api_uri = pconfig.get('api_uri', None)
+ try:
+ pCertChecker.verify_api_https(api_uri)
+ except requests.exceptions.SSLError as exc:
+ return self.fail("Validation Error")
+ except Exception as exc:
+ return self.fail(exc.message)
+ else:
+ return True
+
+ 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)
+ """
+ wizard = self.wizard()
+ prevpage = "login" if wizard.from_login else "providerselection"
+
+ if self.errors:
+ logger.debug('going back with errors')
+ name, first_error = self.pop_first_error()
+ wizard.set_validation_error(
+ prevpage,
+ first_error)
+
+ def nextId(self):
+ wizard = self.wizard()
+ from_login = wizard.from_login
+ if from_login:
+ next_ = 'connect'
+ else:
+ next_ = 'signup'
+ 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
new file mode 100644
index 00000000..2ae926d1
--- /dev/null
+++ b/src/leap/gui/firstrun/register.py
@@ -0,0 +1,391 @@
+"""
+Register User Page, used in First Run Wizard
+"""
+import json
+import logging
+import socket
+
+import requests
+
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+from leap.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, APP_WATERMARK, BARE_USERNAME_REGEX
+from leap.gui.progress import InlineValidationPage
+from leap.gui.styles import ErrorLabelStyleSheet
+
+
+class RegisterUserPage(InlineValidationPage, UserFormMixIn):
+
+ def __init__(self, parent=None):
+
+ super(RegisterUserPage, self).__init__(parent)
+ self.current_page = "signup"
+
+ self.setTitle(self.tr("Sign Up"))
+ # subtitle is set in the initializePage
+
+ self.setPixmap(
+ QtGui.QWizard.WatermarkPixmap,
+ QtGui.QPixmap(APP_WATERMARK))
+
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ # 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(self.tr("User &name:"))
+ userNameLineEdit = QtGui.QLineEdit()
+ userNameLineEdit.cursorPositionChanged.connect(
+ self.reset_validation_status)
+ userNameLabel.setBuddy(userNameLineEdit)
+
+ # let's add regex validator
+ usernameRe = QtCore.QRegExp(BARE_USERNAME_REGEX)
+ userNameLineEdit.setValidator(
+ QtGui.QRegExpValidator(usernameRe, self))
+ self.userNameLineEdit = userNameLineEdit
+
+ userPasswordLabel = QtGui.QLabel(self.tr("&Password:"))
+ self.userPasswordLineEdit = QtGui.QLineEdit()
+ self.userPasswordLineEdit.setEchoMode(
+ QtGui.QLineEdit.Password)
+ userPasswordLabel.setBuddy(self.userPasswordLineEdit)
+
+ userPassword2Label = QtGui.QLabel(self.tr("Password (again):"))
+ self.userPassword2LineEdit = QtGui.QLineEdit()
+ self.userPassword2LineEdit.setEchoMode(
+ QtGui.QLineEdit.Password)
+ userPassword2Label.setBuddy(self.userPassword2LineEdit)
+
+ rememberPasswordCheckBox = QtGui.QCheckBox(
+ self.tr("&Remember username and password."))
+ rememberPasswordCheckBox.setChecked(True)
+
+ self.registerField('userName*', self.userNameLineEdit)
+ self.registerField('userPassword*', self.userPasswordLineEdit)
+ self.registerField('userPassword2*', self.userPassword2LineEdit)
+
+ # XXX missing password confirmation
+ # XXX validator!
+
+ self.registerField('rememberPassword', rememberPasswordCheckBox)
+
+ layout = QtGui.QGridLayout()
+ layout.setColumnMinimumWidth(0, 20)
+
+ validationMsg = QtGui.QLabel("")
+ validationMsg.setStyleSheet(ErrorLabelStyleSheet)
+
+ self.validationMsg = validationMsg
+
+ layout.addWidget(validationMsg, 0, 3)
+ layout.addWidget(userNameLabel, 1, 0)
+ layout.addWidget(self.userNameLineEdit, 1, 3)
+ layout.addWidget(userPasswordLabel, 2, 0)
+ layout.addWidget(userPassword2Label, 3, 0)
+ layout.addWidget(self.userPasswordLineEdit, 2, 3)
+ layout.addWidget(self.userPassword2LineEdit, 3, 3)
+ layout.addWidget(rememberPasswordCheckBox, 4, 3, 4, 4)
+
+ # add validation frame
+ self.setupValidationFrame()
+ layout.addWidget(self.valFrame, 5, 2, 5, 2)
+ self.valFrame.hide()
+
+ self.setLayout(layout)
+ self.commitText("Sign up!")
+
+ # 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 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(RegisterUserPage, self).paintEvent(event)
+ self.populateErrors()
+
+ 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):
+ 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()
+ #prev_er = getattr(self, 'prevalidation_error', None)
+
+ if bad_str is None:
+ # first time we fall here.
+ # save the current bad_string value
+ self.bad_string = cur_str
+ showerr(errors)
+ else:
+ #if prev_er:
+ #showerr(prev_er)
+ #return
+ # not the first time
+ if cur_str == bad_str:
+ showerr(errors)
+ else:
+ self.focused_field = False
+ showerr('')
+ unmarkred()
+ else:
+ # no errors
+ self.focused_field = False
+ unmarkred()
+
+ def cleanup_errormsg(self):
+ """
+ we reset bad_string to None
+ should be called before leaving the page
+ """
+ self.bad_string = None
+
+ 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()
+
+ # actual checks
+
+ def _do_checks(self):
+ """
+ generator that yields actual checks
+ that are executed in a separate thread
+ """
+ wizard = self.wizard()
+
+ provider = self.field('provider_domain')
+ username = self.userNameLineEdit.text()
+ password = self.userPasswordLineEdit.text()
+ password2 = self.userPassword2LineEdit.text()
+
+ pconfig = wizard.eipconfigchecker(domain=provider)
+ pconfig.defaultprovider.load()
+ pconfig.set_api_domain()
+
+ 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)
+
+ # XXX should emit signal for .show the frame!
+ # XXX HERE!
+
+ ##################################################
+ # 1) register user
+ ##################################################
+
+ # show the frame before going on...
+ QtCore.QMetaObject.invokeMethod(
+ self, "showStepsFrame")
+
+ def register():
+
+ signup = auth.LeapSRPRegister(
+ schema="https",
+ provider=pconfig.apidomain,
+ verify=pconfig.cacert)
+ 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)
+
+ try:
+ validation_msgs = json.loads(req.content)
+ errors = validation_msgs.get('errors', None)
+ logger.debug('validation errors: %s' % validation_msgs)
+ except ValueError:
+ # probably bad json returned
+ return self.fail(
+ self.tr(
+ "Could not register (bad response)"))
+
+ 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.'))
+
+ return True
+
+ logger.debug('registering user')
+ yield(("Registering username", 40), register)
+
+ self.set_done()
+ yield(("end_sentinel", 100), 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
+
+ # 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()
+
+ return self.is_done()
+
+ def initializePage(self):
+ """
+ inits wizard page
+ """
+ provider = unicode(self.field('provider_domain'))
+ if provider:
+ # here we should have provider
+ # but in tests we might not.
+
+ # XXX this error causes a segfault on free()
+ # that we might want to get fixed ...
+ #self.setSubTitle(
+ #self.tr("Register a new user with provider %s.") %
+ #provider)
+ self.setSubTitle(
+ self.tr("Register a new user with provider <em>%s</em>" %
+ provider))
+ self.validationMsg.setText('')
+ self.userPassword2LineEdit.setText('')
+ self.valFrame.hide()
+
+ def nextId(self):
+ wizard = self.wizard()
+ return wizard.get_page_index('connect')
diff --git a/src/leap/gui/firstrun/tests/integration/fake_provider.py b/src/leap/gui/firstrun/tests/integration/fake_provider.py
new file mode 100755
index 00000000..668db5d1
--- /dev/null
+++ b/src/leap/gui/firstrun/tests/integration/fake_provider.py
@@ -0,0 +1,302 @@
+#!/usr/bin/env python
+"""A server faking some of the provider resources and apis,
+used for testing Leap Client requests
+
+It needs that you create a subfolder named 'certs',
+and that you place the following files:
+
+[ ] certs/leaptestscert.pem
+[ ] certs/leaptestskey.pem
+[ ] certs/cacert.pem
+[ ] certs/openvpn.pem
+
+[ ] provider.json
+[ ] eip-service.json
+"""
+# XXX NOTE: intended for manual debug.
+# I intend to include this as a regular test after 0.2.0 release
+# (so we can add twisted as a dep there)
+import binascii
+import json
+import os
+import sys
+
+# python SRP LIB (! important MUST be >=1.0.1 !)
+import srp
+
+# GnuTLS Example -- is not working as expected
+#from gnutls import crypto
+#from gnutls.constants import COMP_LZO, COMP_DEFLATE, COMP_NULL
+#from gnutls.interfaces.twisted import X509Credentials
+
+# Going with OpenSSL as a workaround instead
+# But we DO NOT want to introduce this dependency.
+from OpenSSL import SSL
+
+from zope.interface import Interface, Attribute, implements
+
+from twisted.web.server import Site
+from twisted.web.static import File
+from twisted.web.resource import Resource
+from twisted.internet import reactor
+
+from leap.testing.https_server import where
+
+# See
+# http://twistedmatrix.com/documents/current/web/howto/web-in-60/index.htmln
+# for more examples
+
+"""
+Testing the FAKE_API:
+#####################
+
+ 1) register an user
+ >> curl -d "user[login]=me" -d "user[password_salt]=foo" \
+ -d "user[password_verifier]=beef" http://localhost:8000/1/users.json
+ << {"errors": null}
+
+ 2) check that if you try to register again, it will fail:
+ >> curl -d "user[login]=me" -d "user[password_salt]=foo" \
+ -d "user[password_verifier]=beef" http://localhost:8000/1/users.json
+ << {"errors": {"login": "already taken!"}}
+
+"""
+
+# Globals to mock user/sessiondb
+
+USERDB = {}
+SESSIONDB = {}
+
+
+safe_unhexlify = lambda x: binascii.unhexlify(x) \
+ if (len(x) % 2 == 0) else binascii.unhexlify('0' + x)
+
+
+class IUser(Interface):
+ login = Attribute("User login.")
+ salt = Attribute("Password salt.")
+ verifier = Attribute("Password verifier.")
+ session = Attribute("Session.")
+ svr = Attribute("Server verifier.")
+
+
+class User(object):
+ implements(IUser)
+
+ def __init__(self, login, salt, verifier):
+ self.login = login
+ self.salt = salt
+ self.verifier = verifier
+ self.session = None
+
+ def set_server_verifier(self, svr):
+ self.svr = svr
+
+ def set_session(self, session):
+ SESSIONDB[session] = self
+ self.session = session
+
+
+class FakeUsers(Resource):
+ def __init__(self, name):
+ self.name = name
+
+ def render_POST(self, request):
+ args = request.args
+
+ login = args['user[login]'][0]
+ salt = args['user[password_salt]'][0]
+ verifier = args['user[password_verifier]'][0]
+
+ if login in USERDB:
+ return "%s\n" % json.dumps(
+ {'errors': {'login': 'already taken!'}})
+
+ print login, verifier, salt
+ user = User(login, salt, verifier)
+ USERDB[login] = user
+ return json.dumps({'errors': None})
+
+
+def get_user(request):
+ login = request.args.get('login')
+ if login:
+ user = USERDB.get(login[0], None)
+ if user:
+ return user
+
+ session = request.getSession()
+ user = SESSIONDB.get(session, None)
+ return user
+
+
+class FakeSession(Resource):
+ def __init__(self, name):
+ self.name = name
+
+ def render_GET(self, request):
+ return "%s\n" % json.dumps({'errors': None})
+
+ def render_POST(self, request):
+
+ user = get_user(request)
+
+ if not user:
+ # XXX get real error from demo provider
+ return json.dumps({'errors': 'no such user'})
+
+ A = request.args['A'][0]
+
+ _A = safe_unhexlify(A)
+ _salt = safe_unhexlify(user.salt)
+ _verifier = safe_unhexlify(user.verifier)
+
+ svr = srp.Verifier(
+ user.login,
+ _salt,
+ _verifier,
+ _A,
+ hash_alg=srp.SHA256,
+ ng_type=srp.NG_1024)
+
+ s, B = svr.get_challenge()
+
+ _B = binascii.hexlify(B)
+
+ print 'login = %s' % user.login
+ print 'salt = %s' % user.salt
+ print 'len(_salt) = %s' % len(_salt)
+ print 'vkey = %s' % user.verifier
+ print 'len(vkey) = %s' % len(_verifier)
+ print 's = %s' % binascii.hexlify(s)
+ print 'B = %s' % _B
+ print 'len(B) = %s' % len(_B)
+
+ session = request.getSession()
+ user.set_session(session)
+ user.set_server_verifier(svr)
+
+ # yep, this is tricky.
+ # some things are *already* unhexlified.
+ data = {
+ 'salt': user.salt,
+ 'B': _B,
+ 'errors': None}
+
+ return json.dumps(data)
+
+ def render_PUT(self, request):
+
+ # XXX check session???
+ user = get_user(request)
+
+ if not user:
+ print 'NO USER'
+ return json.dumps({'errors': 'no such user'})
+
+ data = request.content.read()
+ auth = data.split("client_auth=")
+ M = auth[1] if len(auth) > 1 else None
+ # if not H, return
+ if not M:
+ return json.dumps({'errors': 'no M proof passed by client'})
+
+ svr = user.svr
+ HAMK = svr.verify_session(binascii.unhexlify(M))
+ if HAMK is None:
+ print 'verification failed!!!'
+ raise Exception("Authentication failed!")
+ #import ipdb;ipdb.set_trace()
+
+ assert svr.authenticated()
+ print "***"
+ print 'server authenticated user SRP!'
+ print "***"
+
+ return json.dumps(
+ {'M2': binascii.hexlify(HAMK), 'errors': None})
+
+
+class API_Sessions(Resource):
+ def getChild(self, name, request):
+ return FakeSession(name)
+
+
+def get_certs_path():
+ script_path = os.path.realpath(os.path.dirname(sys.argv[0]))
+ certs_path = os.path.join(script_path, 'certs')
+ return certs_path
+
+
+def get_TLS_credentials():
+ # XXX this is giving errors
+ # XXX REview! We want to use gnutls!
+
+ cert = crypto.X509Certificate(
+ open(where('leaptestscert.pem')).read())
+ key = crypto.X509PrivateKey(
+ open(where('leaptestskey.pem')).read())
+ ca = crypto.X509Certificate(
+ open(where('cacert.pem')).read())
+ #crl = crypto.X509CRL(open(certs_path + '/crl.pem').read())
+ #cred = crypto.X509Credentials(cert, key, [ca], [crl])
+ cred = X509Credentials(cert, key, [ca])
+ cred.verify_peer = True
+ cred.session_params.compressions = (COMP_LZO, COMP_DEFLATE, COMP_NULL)
+ return cred
+
+
+class OpenSSLServerContextFactory:
+ # XXX workaround for broken TLS interface
+ # from gnuTLS.
+
+ def getContext(self):
+ """Create an SSL context.
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'."""
+
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ #certs_path = get_certs_path()
+ #ctx.use_certificate_file(certs_path + '/leaptestscert.pem')
+ #ctx.use_privatekey_file(certs_path + '/leaptestskey.pem')
+ ctx.use_certificate_file(where('leaptestscert.pem'))
+ ctx.use_privatekey_file(where('leaptestskey.pem'))
+ return ctx
+
+
+def serve_fake_provider():
+ root = Resource()
+ root.putChild("provider.json", File("./provider.json"))
+ config = Resource()
+ config.putChild(
+ "eip-service.json",
+ File("./eip-service.json"))
+ apiv1 = Resource()
+ apiv1.putChild("config", config)
+ apiv1.putChild("sessions.json", API_Sessions())
+ apiv1.putChild("users.json", FakeUsers(None))
+ apiv1.putChild("cert", File(get_certs_path() + '/openvpn.pem'))
+ root.putChild("1", apiv1)
+
+ cred = get_TLS_credentials()
+
+ factory = Site(root)
+
+ # regular http (for debugging with curl)
+ reactor.listenTCP(8000, factory)
+
+ # TLS with gnutls --- seems broken :(
+ #reactor.listenTLS(8003, factory, cred)
+
+ # OpenSSL
+ reactor.listenSSL(8443, factory, OpenSSLServerContextFactory())
+
+ reactor.run()
+
+
+if __name__ == "__main__":
+
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+
+ serve_fake_provider()
diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py
new file mode 100755
index 00000000..f198dca0
--- /dev/null
+++ b/src/leap/gui/firstrun/wizard.py
@@ -0,0 +1,309 @@
+#!/usr/bin/env python
+import logging
+
+import sip
+try:
+ sip.setapi('QString', 2)
+ sip.setapi('QVariant', 2)
+except ValueError:
+ pass
+
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+from leap.base import checks as basechecks
+from leap.crypto import leapkeyring
+from leap.eip import checks as eipchecks
+
+from leap.gui import firstrun
+
+from leap.gui import mainwindow_rc
+
+try:
+ from collections import OrderedDict
+except ImportError:
+ # We must be in 2.6
+ from leap.util.dicts import OrderedDict
+
+logger = logging.getLogger(__name__)
+
+"""
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+Work in progress!
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+This wizard still needs to be refactored out.
+
+TODO-ish:
+
+[X] Break file in wizard / pages files (and its own folder).
+[ ] Separate presentation from logic.
+[ ] Have a "manager" class for connections, that can be
+ dep-injected for testing.
+[ ] Document signals used / expected.
+[ ] Separate style from widgets.
+[ ] Fix TOFU Widget for provider cert.
+[X] Refactor widgets out.
+[ ] Follow more MVC style.
+[ ] Maybe separate "first run wizard" into different wizards
+ that share some of the pages?
+"""
+
+
+def get_pages_dict():
+ return OrderedDict((
+ ('intro', firstrun.intro.IntroPage),
+ ('providerselection',
+ firstrun.providerselect.SelectProviderPage),
+ ('login', firstrun.login.LogInPage),
+ ('providerinfo', firstrun.providerinfo.ProviderInfoPage),
+ ('providersetupvalidation',
+ firstrun.providersetup.ProviderSetupValidationPage),
+ ('signup', firstrun.register.RegisterUserPage),
+ ('connect',
+ firstrun.connect.ConnectionPage),
+ ('lastpage', firstrun.last.LastPage)
+ ))
+
+
+class FirstRunWizard(QtGui.QWizard):
+
+ def __init__(
+ self,
+ conductor_instance,
+ parent=None,
+ pages_dict=None,
+ username=None,
+ providers=None,
+ success_cb=None, is_provider_setup=False,
+ trusted_certs=None,
+ netchecker=basechecks.LeapNetworkChecker,
+ providercertchecker=eipchecks.ProviderCertChecker,
+ eipconfigchecker=eipchecks.EIPConfigChecker,
+ start_eipconnection_signal=None,
+ eip_statuschange_signal=None,
+ debug_server=None,
+ quitcallback=None):
+ super(FirstRunWizard, self).__init__(
+ parent,
+ QtCore.Qt.WindowStaysOnTopHint)
+
+ # we keep a reference to the conductor
+ # to be able to launch eip checks and connection
+ # in the connection page, before the wizard has ended.
+ self.conductor = conductor_instance
+
+ self.username = username
+ self.providers = providers
+
+ # success callback
+ self.success_cb = success_cb
+
+ # is provider setup?
+ self.is_provider_setup = is_provider_setup
+
+ # a dict with trusted fingerprints
+ # in the form {'nospacesfingerprint': ['host1', 'host2']}
+ self.trusted_certs = trusted_certs
+
+ # Checkers
+ self.netchecker = netchecker
+ self.providercertchecker = providercertchecker
+ self.eipconfigchecker = eipconfigchecker
+
+ # debug server
+ self.debug_server = debug_server
+
+ # Signals
+ # will be emitted in connecting page
+ self.start_eipconnection_signal = start_eipconnection_signal
+ self.eip_statuschange_signal = eip_statuschange_signal
+
+ if quitcallback is not None:
+ self.button(
+ QtGui.QWizard.CancelButton).clicked.connect(
+ quitcallback)
+
+ self.providerconfig = None
+ # previously registered
+ # if True, jumps to LogIn page.
+ # by setting 1st page??
+ #self.is_previously_registered = is_previously_registered
+ # XXX ??? ^v
+ self.is_previously_registered = bool(self.username)
+ self.from_login = False
+
+ pages_dict = pages_dict or get_pages_dict()
+ self.add_pages_from_dict(pages_dict)
+
+ self.validation_errors = {}
+ self.openvpn_status = []
+
+ self.setPixmap(
+ QtGui.QWizard.BannerPixmap,
+ QtGui.QPixmap(':/images/banner.png'))
+ self.setPixmap(
+ 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 ...
+ #self.setWizardStyle()
+
+ #
+ # setup pages in wizard
+ #
+
+ def add_pages_from_dict(self, pages_dict):
+ """
+ @param pages_dict: the dictionary with pages, where
+ values are a tuple of InstanceofWizardPage, kwargs.
+ @type pages_dict: dict
+ """
+ for name, page in pages_dict.items():
+ # XXX check for is_previously registered
+ # and skip adding the signup branch if so
+ self.addPage(page())
+ self.pages_dict = pages_dict
+
+ def get_page_index(self, page_name):
+ """
+ returns the index of the given page
+ @param page_name: the name of the desired page
+ @type page_name: str
+ @rparam: index of page in wizard
+ @rtype: int
+ """
+ return self.pages_dict.keys().index(page_name)
+
+ #
+ # validation errors
+ #
+
+ 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)
+
+ def accept(self):
+ """
+ final step in the wizard.
+ gather the info, update settings
+ and call the success callback if any has been passed.
+ """
+ super(FirstRunWizard, self).accept()
+
+ # username and password are in different fields
+ # if they were stored in log_in or sign_up pages.
+ from_login = self.from_login
+ unamek_base = 'userName'
+ passwk_base = 'userPassword'
+ unamek = 'login_%s' % unamek_base if from_login else unamek_base
+ passwk = 'login_%s' % passwk_base if from_login else passwk_base
+
+ username = self.field(unamek)
+ password = self.field(passwk)
+ provider = self.field('provider_domain')
+ remember_pass = self.field('rememberPassword')
+
+ logger.debug('chosen provider: %s', provider)
+ logger.debug('username: %s', username)
+ logger.debug('remember password: %s', remember_pass)
+
+ # we are assuming here that we only remember one username
+ # in the form username@provider.domain
+ # We probably could extend this to support some form of
+ # profiles.
+
+ settings = QtCore.QSettings()
+
+ settings.setValue("FirstRunWizardDone", True)
+ settings.setValue("provider_domain", provider)
+ full_username = "%s@%s" % (username, provider)
+
+ settings.setValue("remember_user_and_pass", remember_pass)
+
+ if remember_pass:
+ settings.setValue("username", full_username)
+ seed = self.get_random_str(10)
+ settings.setValue("%s_seed" % provider, seed)
+
+ # XXX #744: comment out for 0.2.0 release
+ # if we need to have a version of python-keyring < 0.9
+ leapkeyring.leap_set_password(
+ full_username, password, seed=seed)
+
+ logger.debug('First Run Wizard Done.')
+ cb = self.success_cb
+ if cb and callable(cb):
+ self.success_cb()
+
+ # misc helpers
+
+ def get_random_str(self, n):
+ """
+ returns a random string
+ :param n: the length of the desired string
+ :rvalue: str
+ """
+ from string import (ascii_uppercase, ascii_lowercase, digits)
+ from random import choice
+ return ''.join(choice(
+ ascii_uppercase +
+ ascii_lowercase +
+ digits) for x in range(n))
+
+ def set_providerconfig(self, providerconfig):
+ """
+ sets a providerconfig attribute
+ used when we fetch and parse a json configuration
+ """
+ self.providerconfig = providerconfig
+
+ def get_provider_by_index(self): # pragma: no cover
+ """
+ returns the value of a provider given its index.
+ this was used in the select provider page,
+ in the case where we were preseeding providers in a combobox
+ """
+ # Leaving it here for the moment when we go back at the
+ # option of preseeding with known provider values.
+ provider = self.field('provider_index')
+ return self.providers[provider]
+
+
+if __name__ == '__main__':
+ # standalone test
+ # it can be (somehow) run against
+ # gui/tests/integration/fake_user_signup.py
+
+ import sys
+ import logging
+ logging.basicConfig()
+ logger = logging.getLogger()
+ logger.setLevel(logging.DEBUG)
+
+ app = QtGui.QApplication(sys.argv)
+ server = sys.argv[1] if len(sys.argv) > 1 else None
+
+ trusted_certs = {
+ "3DF83F316BFA0186"
+ "0A11A5C9C7FC24B9"
+ "18C62B941192CC1A"
+ "49AE62218B2A4B7C": ['springbok']}
+
+ wizard = FirstRunWizard(
+ None, trusted_certs=trusted_certs,
+ debug_server=server)
+ wizard.show()
+ sys.exit(app.exec_())