summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/leap/app.py9
-rw-r--r--src/leap/base/config.py1
-rw-r--r--src/leap/baseapp/leap_app.py54
-rw-r--r--src/leap/baseapp/mainwindow.py32
-rw-r--r--src/leap/crypto/__init__.py0
-rw-r--r--src/leap/crypto/leapkeyring.py64
-rw-r--r--src/leap/eip/checks.py3
-rwxr-xr-xsrc/leap/gui/firstrunwizard.py489
-rw-r--r--src/leap/gui/tests/integration/fake_user_signup.py80
9 files changed, 727 insertions, 5 deletions
diff --git a/src/leap/app.py b/src/leap/app.py
index 3170de4a..341f6a6e 100644
--- a/src/leap/app.py
+++ b/src/leap/app.py
@@ -3,6 +3,7 @@ import logging
# This is only needed for Python v2 but is harmless for Python v3.
import sip
sip.setapi('QVariant', 2)
+sip.setapi('QString', 2)
from PyQt4.QtGui import (QApplication, QSystemTrayIcon, QMessageBox)
from leap import __version__ as VERSION
@@ -50,6 +51,14 @@ def main():
logger.info('Starting app')
app = QApplication(sys.argv)
+ # needed for initializing qsettings
+ # it will write .config/leap/leap.conf
+ # top level app settings
+ # in a platform independent way
+ app.setOrganizationName("leap")
+ app.setApplicationName("leap")
+ app.setOrganizationDomain("leap.se")
+
if not QSystemTrayIcon.isSystemTrayAvailable():
QMessageBox.critical(None, "Systray",
"I couldn't detect"
diff --git a/src/leap/base/config.py b/src/leap/base/config.py
index dc047f80..57f9f1b7 100644
--- a/src/leap/base/config.py
+++ b/src/leap/base/config.py
@@ -149,6 +149,7 @@ class JSONLeapConfig(BaseLeapConfig):
if not fetcher:
fetcher = self.fetcher
logger.debug('verify: %s', verify)
+ logger.debug('uri: %s', uri)
request = fetcher.get(uri, verify=verify)
# XXX should send a if-modified-since header
diff --git a/src/leap/baseapp/leap_app.py b/src/leap/baseapp/leap_app.py
index 98ca292e..460d1269 100644
--- a/src/leap/baseapp/leap_app.py
+++ b/src/leap/baseapp/leap_app.py
@@ -1,5 +1,9 @@
import logging
+import sip
+sip.setapi('QVariant', 2)
+
+from PyQt4 import QtCore
from PyQt4 import QtGui
from leap.gui import mainwindow_rc
@@ -23,9 +27,9 @@ class MainWindowMixin(object):
widget = QtGui.QWidget()
self.setCentralWidget(widget)
+ mainLayout = QtGui.QVBoxLayout()
# add widgets to layout
#self.createWindowHeader()
- mainLayout = QtGui.QVBoxLayout()
#mainLayout.addWidget(self.headerBox)
mainLayout.addWidget(self.statusIconBox)
if self.debugmode:
@@ -33,11 +37,51 @@ class MainWindowMixin(object):
mainLayout.addWidget(self.loggerBox)
widget.setLayout(mainLayout)
+ self.createMainActions()
+ self.createMainMenus()
+
self.setWindowTitle("LEAP Client")
self.set_app_icon()
- self.resize(400, 300)
self.set_statusbarMessage('ready')
- logger.debug('set ready.........')
+
+ def createMainActions(self):
+ #self.openAct = QtGui.QAction("&Open...", self, shortcut="Ctrl+O",
+ #triggered=self.open)
+
+ self.firstRunWizardAct = QtGui.QAction(
+ "&First run wizard...", self,
+ triggered=self.launch_first_run_wizard)
+ self.aboutAct = QtGui.QAction("&About", self, triggered=self.about)
+
+ #self.aboutQtAct = QtGui.QAction("About &Qt", self,
+ #triggered=QtGui.qApp.aboutQt)
+
+ def createMainMenus(self):
+ self.connMenu = QtGui.QMenu("&Connections", self)
+ #self.viewMenu.addSeparator()
+ self.connMenu.addAction(self.quitAction)
+
+ self.settingsMenu = QtGui.QMenu("&Settings", self)
+ self.settingsMenu.addAction(self.firstRunWizardAct)
+
+ self.helpMenu = QtGui.QMenu("&Help", self)
+ self.helpMenu.addAction(self.aboutAct)
+ #self.helpMenu.addAction(self.aboutQtAct)
+
+ self.menuBar().addMenu(self.connMenu)
+ self.menuBar().addMenu(self.settingsMenu)
+ self.menuBar().addMenu(self.helpMenu)
+
+ def launch_first_run_wizard(self):
+ settings = QtCore.QSettings()
+ settings.setValue('FirstRunWizardDone', False)
+ logger.debug('should run first run wizard again...')
+
+ from leap.gui.firstrunwizard import FirstRunWizard
+ wizard = FirstRunWizard(
+ parent=self,
+ success_cb=self.initReady.emit)
+ wizard.show()
def set_app_icon(self):
icon = QtGui.QIcon(APP_LOGO)
@@ -88,6 +132,10 @@ class MainWindowMixin(object):
"""
cleans state before shutting down app.
"""
+ # save geometry for restoring
+ settings = QtCore.QSettings()
+ settings.setValue("Geometry", self.saveGeometry())
+
# TODO:make sure to shutdown all child process / threads
# in conductor
# XXX send signal instead?
diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py
index 55be55f7..1accac30 100644
--- a/src/leap/baseapp/mainwindow.py
+++ b/src/leap/baseapp/mainwindow.py
@@ -26,18 +26,26 @@ class LeapWindow(QtGui.QMainWindow,
newLogLine = QtCore.pyqtSignal([str])
statusChange = QtCore.pyqtSignal([object])
+ mainappReady = QtCore.pyqtSignal([])
+ initReady = QtCore.pyqtSignal([])
def __init__(self, opts):
logger.debug('init leap window')
self.debugmode = getattr(opts, 'debug', False)
-
super(LeapWindow, self).__init__()
if self.debugmode:
self.createLogBrowser()
+
EIPConductorAppMixin.__init__(self, opts=opts)
StatusAwareTrayIconMixin.__init__(self)
MainWindowMixin.__init__(self)
+ settings = QtCore.QSettings()
+ geom = settings.value("Geometry")
+ if geom:
+ self.restoreGeometry(geom)
+ self.wizard_done = settings.value("FirstRunWizardDone")
+
self.initchecks = InitChecksThread(self.run_eip_checks)
# bind signals
@@ -51,8 +59,28 @@ class LeapWindow(QtGui.QMainWindow,
self.timer.timeout.connect(
lambda: self.onTimerTick())
- # ... all ready. go!
+ # do frwizard and init signals
+ self.mainappReady.connect(self.do_first_run_wizard_check)
+ self.initReady.connect(self.runchecks_and_eipconnect)
+ # ... all ready. go!
+ # calls do_first_run_wizard_check
+ self.mainappReady.emit()
+
+ def do_first_run_wizard_check(self):
+ logger.debug('first run wizard check...')
+ if self.wizard_done:
+ self.initReady.emit()
+ else:
+ # need to run first-run-wizard
+ logger.debug('running first run wizard')
+ from leap.gui.firstrunwizard import FirstRunWizard
+ wizard = FirstRunWizard(
+ parent=self,
+ success_cb=self.initReady.emit)
+ wizard.show()
+
+ def runchecks_and_eipconnect(self):
self.initchecks.begin()
diff --git a/src/leap/crypto/__init__.py b/src/leap/crypto/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/leap/crypto/__init__.py
diff --git a/src/leap/crypto/leapkeyring.py b/src/leap/crypto/leapkeyring.py
new file mode 100644
index 00000000..bb0ca147
--- /dev/null
+++ b/src/leap/crypto/leapkeyring.py
@@ -0,0 +1,64 @@
+import os
+
+import keyring
+
+#############
+# Disclaimer
+#############
+# This currently is not a keyring, it's more like a joke.
+# No, seriously.
+# We're affected by this **bug**
+
+# https://bitbucket.org/kang/python-keyring-lib/
+# issue/65/dbusexception-method-opensession-with
+
+# so using the gnome keyring does not seem feasible right now.
+# I thought this was the next best option to store secrets in plain sight.
+
+# in the future we should move to use the gnome/kde/macosx/win keyrings.
+
+
+class LeapCryptedFileKeyring(keyring.backend.CryptedFileKeyring):
+
+ filename = os.path.expanduser("~/.config/leap/.secrets")
+
+ def __init__(self, seed=None):
+ self.seed = seed
+
+ def _get_new_password(self):
+ # XXX every time this method is called,
+ # $deity kills a kitten.
+ return "secret%s" % self.seed
+
+ def _init_file(self):
+ self.keyring_key = self._get_new_password()
+ self.set_password('keyring_setting', 'pass_ref', 'pass_ref_value')
+
+ def _unlock(self):
+ self.keyring_key = self._get_new_password()
+ print 'keyring key ', self.keyring_key
+ try:
+ ref_pw = self.get_password(
+ 'keyring_setting',
+ 'pass_ref')
+ print 'ref pw ', ref_pw
+ assert ref_pw == "pass_ref_value"
+ except AssertionError:
+ self._lock()
+ raise ValueError('Incorrect password')
+
+
+def leap_set_password(key, value, seed="xxx"):
+ keyring.set_keyring(LeapCryptedFileKeyring(seed=seed))
+ keyring.set_password('leap', key, value)
+
+
+def leap_get_password(key, seed="xxx"):
+ keyring.set_keyring(LeapCryptedFileKeyring(seed=seed))
+ return keyring.get_password('leap', key)
+
+
+if __name__ == "__main__":
+ leap_set_password('test', 'bar')
+ passwd = leap_get_password('test')
+ assert passwd == 'bar'
diff --git a/src/leap/eip/checks.py b/src/leap/eip/checks.py
index f79d47f5..413a3467 100644
--- a/src/leap/eip/checks.py
+++ b/src/leap/eip/checks.py
@@ -232,6 +232,9 @@ class ProviderCertChecker(object):
# verify=verify
# Workaround for #638. return to verification
# when That's done!!!
+
+ # XXX HOOK SRP here...
+ # will have to be more generic in the future.
req = self.fetcher.get(uri, verify=False)
req.raise_for_status()
except requests.exceptions.SSLError:
diff --git a/src/leap/gui/firstrunwizard.py b/src/leap/gui/firstrunwizard.py
new file mode 100755
index 00000000..abdff7cf
--- /dev/null
+++ b/src/leap/gui/firstrunwizard.py
@@ -0,0 +1,489 @@
+#!/usr/bin/env python
+import logging
+import json
+import socket
+
+import sip
+sip.setapi('QString', 2)
+sip.setapi('QVariant', 2)
+
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+from leap.crypto import leapkeyring
+from leap.gui import mainwindow_rc
+
+logger = logging.getLogger(__name__)
+
+APP_LOGO = ':/images/leap-color-small.png'
+
+# registration ######################
+# move to base/
+import binascii
+
+import requests
+import srp
+
+from leap.base import constants as baseconstants
+
+SIGNUP_TIMEOUT = getattr(baseconstants, 'SIGNUP_TIMEOUT', 5)
+
+
+class LeapSRPRegister(object):
+
+ def __init__(self,
+ schema="https",
+ provider=None,
+ port=None,
+ register_path="1/users.json",
+ method="POST",
+ fetcher=requests,
+ srp=srp,
+ hashfun=srp.SHA256,
+ ng_constant=srp.NG_1024):
+
+ self.schema = schema
+ self.provider = provider
+ self.port = port
+ self.register_path = register_path
+ self.method = method
+ self.fetcher = fetcher
+ self.srp = srp
+ self.HASHFUN = hashfun
+ self.NG = ng_constant
+
+ self.init_session()
+
+ def init_session(self):
+ self.session = self.fetcher.session()
+
+ def get_registration_uri(self):
+ # XXX assert is https!
+ # use urlparse
+ if self.port:
+ uri = "%s://%s:%s/%s" % (
+ self.schema,
+ self.provider,
+ self.port,
+ self.register_path)
+ else:
+ uri = "%s://%s/%s" % (
+ self.schema,
+ self.provider,
+ self.register_path)
+
+ return uri
+
+ def register_user(self, username, password, keep=False):
+ """
+ @rtype: tuple
+ @rvalue: (ok, request)
+ """
+ salt, vkey = self.srp.create_salted_verification_key(
+ username,
+ password,
+ self.HASHFUN,
+ self.NG)
+
+ user_data = {
+ 'user[login]': username,
+ 'user[password_verifier]': binascii.hexlify(vkey),
+ 'user[password_salt]': binascii.hexlify(salt)}
+
+ uri = self.get_registration_uri()
+ logger.debug('post to uri: %s' % uri)
+
+ # XXX get self.method
+ req = self.session.post(
+ uri, data=user_data,
+ timeout=SIGNUP_TIMEOUT)
+ logger.debug(req)
+ logger.debug('user_data: %s', user_data)
+ #logger.debug('response: %s', req.text)
+ # we catch it in the form
+ #req.raise_for_status()
+ return (req.ok, req)
+
+######################################
+
+ErrorLabelStyleSheet = """
+QLabel { color: red;
+ font-weight: bold}
+"""
+
+
+class FirstRunWizard(QtGui.QWizard):
+
+ def __init__(
+ self, parent=None, providers=None,
+ success_cb=None):
+ super(FirstRunWizard, self).__init__(
+ parent,
+ QtCore.Qt.WindowStaysOnTopHint)
+
+ # XXX hardcoded for tests
+ if not providers:
+ providers = ('springbok',)
+ self.providers = providers
+
+ # success callback
+ self.success_cb = success_cb
+
+ self.addPage(IntroPage())
+ self.addPage(SelectProviderPage(providers=providers))
+
+ self.addPage(RegisterUserPage(wizard=self))
+ #self.addPage(GlobalEIPSettings())
+ self.addPage(LastPage())
+
+ self.setPixmap(
+ QtGui.QWizard.BannerPixmap,
+ QtGui.QPixmap(':/images/banner.png'))
+ self.setPixmap(
+ QtGui.QWizard.BackgroundPixmap,
+ QtGui.QPixmap(':/images/background.png'))
+
+ self.setWindowTitle("First Run Wizard")
+
+ # TODO: set style for MAC / windows ...
+ #self.setWizardStyle()
+
+ def setWindowFlags(self, flags):
+ logger.debug('setting window flags')
+ QtGui.QWizard.setWindowFlags(self, flags)
+
+ def focusOutEvent(self, event):
+ # needed ?
+ self.setFocus(True)
+ self.activateWindow()
+ self.raise_()
+ self.show()
+
+ def accept(self):
+ """
+ final step in the wizard.
+ gather the info, update settings
+ and call the success callback.
+ """
+ provider = self.get_provider()
+ username = self.field('userName')
+ password = self.field('userPassword')
+ remember_pass = self.field('rememberPassword')
+
+ logger.debug('chosen provider: %s', provider)
+ logger.debug('username: %s', username)
+ logger.debug('remember password: %s', remember_pass)
+ super(FirstRunWizard, self).accept()
+
+ settings = QtCore.QSettings()
+ settings.setValue("FirstRunWizardDone", True)
+ settings.setValue(
+ "eip_%s_username" % provider,
+ username)
+ settings.setValue("%s_remember_pass" % provider, remember_pass)
+
+ seed = self.get_random_str(10)
+ settings.setValue("%s_seed" % provider, seed)
+
+ leapkeyring.leap_set_password(username, password, seed=seed)
+
+ logger.debug('First Run Wizard Done.')
+ cb = self.success_cb
+ if cb and callable(cb):
+ self.success_cb()
+
+ def get_provider(self):
+ provider = self.field('provider_index')
+ return self.providers[provider]
+
+ def get_random_str(self, n):
+ 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))
+
+
+class IntroPage(QtGui.QWizardPage):
+ def __init__(self, parent=None):
+ super(IntroPage, self).__init__(parent)
+
+ self.setTitle("First run wizard.")
+
+ #self.setPixmap(
+ #QtGui.QWizard.WatermarkPixmap,
+ #QtGui.QPixmap(':/images/watermark1.png'))
+
+ label = QtGui.QLabel(
+ "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 of the Leap App.")
+
+ label.setWordWrap(True)
+
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(label)
+ self.setLayout(layout)
+
+
+class SelectProviderPage(QtGui.QWizardPage):
+ def __init__(self, parent=None, providers=None):
+ super(SelectProviderPage, self).__init__(parent)
+
+ self.setTitle("Select Provider")
+ self.setSubTitle(
+ "Please select which provider do you want "
+ "to use for your connection."
+ )
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ providerNameLabel = QtGui.QLabel("&Provider:")
+
+ providercombo = QtGui.QComboBox()
+ if providers:
+ for provider in providers:
+ providercombo.addItem(provider)
+ providerNameSelect = providercombo
+
+ providerNameLabel.setBuddy(providerNameSelect)
+
+ self.registerField('provider_index', providerNameSelect)
+
+ layout = QtGui.QGridLayout()
+ layout.addWidget(providerNameLabel, 0, 0)
+ layout.addWidget(providerNameSelect, 0, 1)
+ self.setLayout(layout)
+
+
+class RegisterUserPage(QtGui.QWizardPage):
+ setSigningUpStatus = QtCore.pyqtSignal([])
+
+ def __init__(self, parent=None, wizard=None):
+ super(RegisterUserPage, self).__init__(parent)
+
+ # bind wizard page signals
+ self.setSigningUpStatus.connect(
+ self.set_status_validating)
+
+ # XXX check for no wizard pased
+ # getting provider from previous step
+ provider = wizard.get_provider()
+
+ self.setTitle("User registration")
+ self.setSubTitle(
+ "Register a new user with provider %s." %
+ provider)
+ self.setPixmap(
+ QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(APP_LOGO))
+
+ rememberPasswordCheckBox = QtGui.QCheckBox(
+ "&Remember password.")
+ rememberPasswordCheckBox.setChecked(True)
+
+ userNameLabel = QtGui.QLabel("User &name:")
+ userNameLineEdit = QtGui.QLineEdit()
+ userNameLineEdit.cursorPositionChanged.connect(
+ self.reset_validation_status)
+ userNameLabel.setBuddy(userNameLineEdit)
+
+ # add regex validator
+ usernameRe = QtCore.QRegExp(r"^[A-Za-z\d_]+$")
+ userNameLineEdit.setValidator(
+ QtGui.QRegExpValidator(usernameRe, self))
+ self.userNameLineEdit = userNameLineEdit
+
+ userPasswordLabel = QtGui.QLabel("&Password:")
+ self.userPasswordLineEdit = QtGui.QLineEdit()
+ self.userPasswordLineEdit.setEchoMode(
+ QtGui.QLineEdit.Password)
+
+ userPasswordLabel.setBuddy(self.userPasswordLineEdit)
+
+ self.registerField('userName', self.userNameLineEdit)
+ self.registerField('userPassword', self.userPasswordLineEdit)
+ 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(self.userPasswordLineEdit, 2, 3)
+
+ layout.addWidget(rememberPasswordCheckBox, 3, 3, 3, 4)
+ self.setLayout(layout)
+
+ def reset_validation_status(self):
+ """
+ empty the validation msg
+ """
+ self.validationMsg.setText('')
+
+ def set_status_validating(self):
+ """
+ set validation msg to 'registering...'
+ """
+ # XXX this is NOT WORKING.
+ # My guess is that, even if we are using
+ # signals to trigger this, it does
+ # not show until the validate function
+ # returns.
+ # I guess it is because there is no delay...
+ logger.debug('registering........')
+ self.validationMsg.setText('registering...')
+ # need to call update somehow???
+
+ def set_status_invalid_username(self):
+ """
+ set validation msg to
+ not available user
+ """
+ self.validationMsg.setText('Username not available.')
+
+ def set_status_server_500(self):
+ """
+ set validation msg to
+ internal server error
+ """
+ self.validationMsg.setText("Error during registration (500)")
+
+ def set_status_timeout(self):
+ """
+ set validation msg to
+ timeout
+ """
+ self.validationMsg.setText("Error connecting to provider (timeout)")
+
+ def set_status_unknown_error(self):
+ """
+ set validation msg to
+ unknown error
+ """
+ self.validationMsg.setText("Error during signup")
+
+ # overwritten methods
+
+ def initializePage(self):
+ """
+ inits wizard page
+ """
+ self.validationMsg.setText('')
+
+ def validatePage(self):
+ """
+ validation
+ we initialize the srp protocol register
+ and try to register user. if error
+ returned we write validation error msg
+ above the form.
+ """
+ # the slot for this signal is not doing
+ # what's expected. Investigate why,
+ # right now we're not giving any feedback
+ # to the user re. what's going on. The only
+ # thing I can see as a workaround is setting
+ # a low timeout.
+ self.setSigningUpStatus.emit()
+
+ username = self.userNameLineEdit.text()
+ password = self.userPasswordLineEdit.text()
+
+ # XXX TODO -- remove debug info
+ # XXX get from provider info
+ # XXX enforce https
+ # and pass a verify value
+
+ signup = LeapSRPRegister(
+ schema="http",
+ provider="springbok",
+
+ #provider="localhost",
+ #register_path="timeout",
+ #port=8000
+ )
+ try:
+ ok, req = signup.register_user(username, password)
+ except socket.timeout:
+ self.set_status_timeout()
+ return False
+
+ if ok:
+ return True
+
+ # something went wrong.
+ # not registered, let's catch what.
+ # get timeout
+ # ...
+ if req.status_code == 500:
+ self.set_status_server_500()
+ return False
+
+ validation_msgs = json.loads(req.content)
+ logger.debug('validation errors: %s' % validation_msgs)
+ errors = validation_msgs.get('errors', None)
+ if errors and errors.get('login', None):
+ self.set_status_invalid_username()
+ else:
+ self.set_status_unknown_error()
+ return False
+
+
+class GlobalEIPSettings(QtGui.QWizardPage):
+ def __init__(self, parent=None):
+ super(GlobalEIPSettings, self).__init__(parent)
+
+
+class LastPage(QtGui.QWizardPage):
+ def __init__(self, parent=None):
+ super(LastPage, self).__init__(parent)
+
+ self.setTitle("Ready to go!")
+
+ #self.setPixmap(
+ #QtGui.QWizard.WatermarkPixmap,
+ #QtGui.QPixmap(':/images/watermark2.png'))
+
+ self.label = QtGui.QLabel()
+ self.label.setWordWrap(True)
+
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(self.label)
+ self.setLayout(layout)
+
+ def initializePage(self):
+ finishText = self.wizard().buttonText(
+ QtGui.QWizard.FinishButton)
+ finishText = finishText.replace('&', '')
+ self.label.setText(
+ "Click '<i>%s</i>' to end the wizard and start "
+ "encrypting your connection." % finishText)
+
+
+if __name__ == '__main__':
+ # standalone test
+ import sys
+ import logging
+ logging.basicConfig()
+ logger = logging.getLogger()
+ logger.setLevel(logging.DEBUG)
+
+ app = QtGui.QApplication(sys.argv)
+ wizard = FirstRunWizard()
+ wizard.show()
+ sys.exit(app.exec_())
diff --git a/src/leap/gui/tests/integration/fake_user_signup.py b/src/leap/gui/tests/integration/fake_user_signup.py
new file mode 100644
index 00000000..12f18966
--- /dev/null
+++ b/src/leap/gui/tests/integration/fake_user_signup.py
@@ -0,0 +1,80 @@
+"""
+simple server to test registration and
+authentication
+
+To test:
+
+curl -d login=python_test_user -d password_salt=54321\
+ -d password_verifier=12341234 \
+ http://localhost:8000/users.json
+
+"""
+from BaseHTTPServer import HTTPServer
+from BaseHTTPServer import BaseHTTPRequestHandler
+import cgi
+import urlparse
+
+HOST = "localhost"
+PORT = 8000
+
+LOGIN_ERROR = """{"errors":{"login":["has already been taken"]}}"""
+
+
+class request_handler(BaseHTTPRequestHandler):
+ responses = {
+ '/': ['ok\n'],
+ '/users.json': ['ok\n'],
+ '/timeout': ['ok\n']
+ }
+
+ def do_GET(self):
+ path = urlparse.urlparse(self.path)
+ message = '\n'.join(
+ self.responses.get(
+ path.path, None))
+ self.send_response(200)
+ self.end_headers()
+ self.wfile.write(message)
+
+ def do_POST(self):
+ form = cgi.FieldStorage(
+ fp=self.rfile,
+ headers=self.headers,
+ environ={'REQUEST_METHOD': 'POST',
+ 'CONTENT_TYPE': self.headers['Content-Type'],
+ })
+ data = dict(
+ (key, form[key].value) for key in form.keys())
+ path = urlparse.urlparse(self.path)
+ message = '\n'.join(
+ self.responses.get(
+ path.path, ''))
+
+ login = data.get('login', None)
+ #password_salt = data.get('password_salt', None)
+ #password_verifier = data.get('password_verifier', None)
+
+ if path.geturl() == "/timeout":
+ print 'timeout'
+ self.send_response(200)
+ self.end_headers()
+ self.wfile.write(message)
+ import time
+ time.sleep(10)
+ return
+
+ ok = True if (login == "python_test_user") else False
+ if ok:
+ self.send_response(200)
+ self.end_headers()
+ self.wfile.write(message)
+
+ else:
+ self.send_response(500)
+ self.end_headers()
+ self.wfile.write(LOGIN_ERROR)
+
+
+if __name__ == "__main__":
+ server = HTTPServer((HOST, PORT), request_handler)
+ server.serve_forever()