summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/leap/config/pluggableconfig.py2
-rw-r--r--src/leap/config/providerconfig.py18
-rw-r--r--src/leap/crypto/srpauth.py31
-rw-r--r--src/leap/crypto/srpregister.py27
-rw-r--r--src/leap/crypto/tests/__init__.py16
-rwxr-xr-xsrc/leap/crypto/tests/fake_provider.py358
-rw-r--r--src/leap/crypto/tests/test_provider.json15
-rw-r--r--src/leap/crypto/tests/test_srpregister.py207
-rw-r--r--src/leap/gui/mainwindow.py10
-rw-r--r--src/leap/gui/ui/wizard.ui269
-rw-r--r--src/leap/gui/wizard.py56
-rw-r--r--src/leap/platform_init/initializers.py5
-rw-r--r--src/leap/services/eip/vpnlaunchers.py2
-rw-r--r--src/leap/util/privilege_policies.py82
14 files changed, 998 insertions, 100 deletions
diff --git a/src/leap/config/pluggableconfig.py b/src/leap/config/pluggableconfig.py
index 4a742da4..8535fa6b 100644
--- a/src/leap/config/pluggableconfig.py
+++ b/src/leap/config/pluggableconfig.py
@@ -160,7 +160,7 @@ class TranslatableType(object):
def to_python(self, data):
# TODO: add translatable
- return data#LEAPTranslatable(data)
+ return data # LEAPTranslatable(data)
# needed? we already have an extended dict...
#def get_prep_value(self, data):
diff --git a/src/leap/config/providerconfig.py b/src/leap/config/providerconfig.py
index 71b2856f..7651863b 100644
--- a/src/leap/config/providerconfig.py
+++ b/src/leap/config/providerconfig.py
@@ -65,6 +65,11 @@ class ProviderConfig(BaseConfig):
return self._safe_get_value("domain")
def get_enrollment_policy(self):
+ """
+ Returns the enrollment policy
+
+ @rtype: string
+ """
return self._safe_get_value("enrollment_policy")
def get_languages(self):
@@ -75,8 +80,21 @@ class ProviderConfig(BaseConfig):
return self._safe_get_value("name")
def get_services(self):
+ """
+ Returns a list with the services supported by the
+ current provider
+
+ @rtype: list
+ """
return self._safe_get_value("services")
+ def get_services_string(self):
+ """
+ Returns a string with the services supported by the current provider,
+ ready to be shown to the user
+ """
+ return ", ".join(self.get_services())
+
def get_ca_cert_path(self, about_to_download=False):
"""
Returns the path to the certificate for the current provider
diff --git a/src/leap/crypto/srpauth.py b/src/leap/crypto/srpauth.py
index 152d77b5..8028a6dc 100644
--- a/src/leap/crypto/srpauth.py
+++ b/src/leap/crypto/srpauth.py
@@ -50,6 +50,7 @@ class SRPAuth(QtCore.QObject):
LOGIN_KEY = "login"
A_KEY = "A"
CLIENT_AUTH_KEY = "client_auth"
+ SESSION_ID_KEY = "_session_id"
def __init__(self, provider_config):
"""
@@ -272,7 +273,13 @@ class SRPAuth(QtCore.QObject):
"failed"))
logger.debug("Session verified.")
- self.set_session_id(self._session.cookies["_session_id"])
+ session_id = self._session.cookies.get(self.SESSION_ID_KEY, None)
+ if not session_id:
+ logger.error("Bad cookie from server (missing _session_id)")
+ raise SRPAuthenticationError(self.tr("Session cookie "
+ "verification "
+ "failed"))
+ self.set_session_id(session_id)
def authenticate(self, username, password):
"""
@@ -409,11 +416,18 @@ class SRPAuth(QtCore.QObject):
if __name__ == "__main__":
+ import signal
import sys
+
from functools import partial
app = QtGui.QApplication(sys.argv)
- import signal
+ if not len(sys.argv) == 3:
+ print 'Usage: srpauth.py <user> <pass>'
+ sys.exit(0)
+
+ _user = sys.argv[1]
+ _pass = sys.argv[2]
def sigint_handler(*args, **kwargs):
logger.debug('SIGINT catched. shutting down...')
@@ -452,20 +466,9 @@ if __name__ == "__main__":
provider = ProviderConfig()
if provider.load("leap/providers/bitmask.net/provider.json"):
- # url = "%s/tickets" % (provider.get_api_uri(),)
- # print url
- # res = requests.session().get(url, verify=provider.get_ca_cert_path())
- # print res.content
- # res.raise_for_status()
auth = SRPAuth(provider)
- auth_instantiated = partial(auth.authenticate, "test2", "sarasaaaa")
+ auth_instantiated = partial(auth.authenticate, _user, _pass)
checker.add_checks([auth_instantiated, auth.logout])
- #auth.authenticate("test2", "sarasaaaa")
- #res = requests.session().get("%s/cert" % (provider.get_api_uri(),),
- #verify=provider.get_ca_cert_path())
- #print res.content
- #auth.logout()
-
sys.exit(app.exec_())
diff --git a/src/leap/crypto/srpregister.py b/src/leap/crypto/srpregister.py
index 9a9cac76..59aaf257 100644
--- a/src/leap/crypto/srpregister.py
+++ b/src/leap/crypto/srpregister.py
@@ -55,7 +55,7 @@ class SRPRegister(QtCore.QObject):
@type register_path; str
"""
QtCore.QObject.__init__(self)
- leap_assert(provider_config, "Please provider a provider")
+ leap_assert(provider_config, "Please provide a provider")
leap_assert_type(provider_config, ProviderConfig)
self._provider_config = provider_config
@@ -125,15 +125,22 @@ class SRPRegister(QtCore.QObject):
logger.debug("Will try to register user = %s" % (username,))
logger.debug("user_data => %r" % (user_data,))
- req = self._session.post(uri,
- data=user_data,
- timeout=SIGNUP_TIMEOUT,
- verify=self._provider_config.
- get_ca_cert_path())
-
- self.registration_finished.emit(req.ok, req)
-
- return req.ok
+ ok = None
+ try:
+ req = self._session.post(uri,
+ data=user_data,
+ timeout=SIGNUP_TIMEOUT,
+ verify=self._provider_config.
+ get_ca_cert_path())
+
+ except requests.exceptions.SSLError as exc:
+ logger.error("SSLError: %s" % exc.message)
+ req = None
+ ok = False
+ else:
+ ok = req.ok
+ self.registration_finished.emit(ok, req)
+ return ok
if __name__ == "__main__":
diff --git a/src/leap/crypto/tests/__init__.py b/src/leap/crypto/tests/__init__.py
new file mode 100644
index 00000000..7f118735
--- /dev/null
+++ b/src/leap/crypto/tests/__init__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# __init__.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/leap/crypto/tests/fake_provider.py b/src/leap/crypto/tests/fake_provider.py
new file mode 100755
index 00000000..d533b82b
--- /dev/null
+++ b/src/leap/crypto/tests/fake_provider.py
@@ -0,0 +1,358 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# fake_provider.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""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:
+
+XXX check if in use
+
+[ ] test-openvpn.pem
+[ ] test-provider.json
+[ ] test-eip-service.json
+"""
+import binascii
+import json
+import os
+import sys
+
+import srp
+
+from OpenSSL import SSL
+
+from zope.interface import Interface, Attribute, implements
+
+from twisted.web.server import Site, Request
+from twisted.web.static import File
+from twisted.web.resource import Resource
+from twisted.internet import reactor
+
+from leap.common.testing.https_server import where
+
+# See
+# http://twistedmatrix.com/documents/current/web/howto/web-in-60/index.html
+# 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
+ << {"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
+ << {"errors": {"login": "already taken!"}}
+
+"""
+
+# Globals to mock user/sessiondb
+
+_USERDB = {}
+_SESSIONDB = {}
+
+_here = os.path.split(__file__)[0]
+
+
+safe_unhexlify = lambda x: binascii.unhexlify(x) \
+ if (len(x) % 2 == 0) else binascii.unhexlify('0' + x)
+
+
+class IUser(Interface):
+ """
+ Defines the User Interface
+ """
+ login = Attribute("User login.")
+ salt = Attribute("Password salt.")
+ verifier = Attribute("Password verifier.")
+ session = Attribute("Session.")
+ svr = Attribute("Server verifier.")
+
+
+class User(object):
+ """
+ User object.
+ We store it in our simple session mocks
+ """
+
+ implements(IUser)
+
+ def __init__(self, login, salt, verifier):
+ self.login = login
+ self.salt = salt
+ self.verifier = verifier
+ self.session = None
+ self.svr = None
+
+ def set_server_verifier(self, svr):
+ """
+ Adds a svr verifier object to this
+ User instance
+ """
+ self.svr = svr
+
+ def set_session(self, session):
+ """
+ Adds this instance of User to the
+ global session dict
+ """
+ _SESSIONDB[session] = self
+ self.session = session
+
+
+class FakeUsers(Resource):
+ """
+ Resource that handles user registration.
+ """
+
+ def __init__(self, name):
+ self.name = name
+
+ def render_POST(self, request):
+ """
+ Handles POST to the users api resource
+ Simulates a login.
+ """
+ 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 '[server]', login, verifier, salt
+ user = User(login, salt, verifier)
+ _USERDB[login] = user
+ return json.dumps({'errors': None})
+
+
+def getSession(self, sessionInterface=None):
+ """
+ we overwrite twisted.web.server.Request.getSession method to
+ put the right cookie name in place
+ """
+ if not self.session:
+ #cookiename = b"_".join([b'TWISTED_SESSION'] + self.sitepath)
+ cookiename = b"_".join([b'_session_id'] + self.sitepath)
+ sessionCookie = self.getCookie(cookiename)
+ if sessionCookie:
+ try:
+ self.session = self.site.getSession(sessionCookie)
+ except KeyError:
+ pass
+ # if it still hasn't been set, fix it up.
+ if not self.session:
+ self.session = self.site.makeSession()
+ self.addCookie(cookiename, self.session.uid, path=b'/')
+ self.session.touch()
+ if sessionInterface:
+ return self.session.getComponent(sessionInterface)
+ return self.session
+
+
+def get_user(request):
+ """
+ Returns user from the session dict
+ """
+ login = request.args.get('login')
+ if login:
+ user = _USERDB.get(login[0], None)
+ if user:
+ return user
+
+ request.getSession = getSession.__get__(request, Request)
+ session = request.getSession()
+
+ user = _SESSIONDB.get(session, None)
+ return user
+
+
+class FakeSession(Resource):
+ def __init__(self, name):
+ """
+ Initializes session
+ """
+ self.name = name
+
+ def render_GET(self, request):
+ """
+ Handles GET requests.
+ """
+ return "%s\n" % json.dumps({'errors': None})
+
+ def render_POST(self, request):
+ """
+ Handles POST requests.
+ """
+ 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 '[server] login = %s' % user.login
+ print '[server] salt = %s' % user.salt
+ print '[server] len(_salt) = %s' % len(_salt)
+ print '[server] vkey = %s' % user.verifier
+ print '[server] len(vkey) = %s' % len(_verifier)
+ print '[server] s = %s' % binascii.hexlify(s)
+ print '[server] B = %s' % _B
+ print '[server] len(B) = %s' % len(_B)
+
+ # override Request.getSession
+ request.getSession = getSession.__get__(request, Request)
+ 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):
+ """
+ Handles PUT requests.
+ """
+ # XXX check session???
+ user = get_user(request)
+
+ if not user:
+ print '[server] 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 '[server] verification failed!!!'
+ raise Exception("Authentication failed!")
+ #import ipdb;ipdb.set_trace()
+
+ assert svr.authenticated()
+ print "***"
+ print '[server] User successfully authenticated using SRP!'
+ print "***"
+
+ return json.dumps(
+ {'M2': binascii.hexlify(HAMK),
+ 'id': '9c943eb9d96a6ff1b7a7030bdeadbeef',
+ 'errors': None})
+
+
+class API_Sessions(Resource):
+ """
+ Top resource for the API v1
+ """
+ def getChild(self, name, request):
+ return FakeSession(name)
+
+
+class OpenSSLServerContextFactory(object):
+
+ def getContext(self):
+ """
+ Create an SSL context.
+ """
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ #ctx = SSL.Context(SSL.TLSv1_METHOD)
+ ctx.use_certificate_file(where('leaptestscert.pem'))
+ ctx.use_privatekey_file(where('leaptestskey.pem'))
+
+ return ctx
+
+
+def get_provider_factory():
+ """
+ Instantiates a Site that serves the resources
+ that we expect from a valid provider.
+ Listens on:
+ * port 8000 for http connections
+ * port 8443 for https connections
+
+ @rparam: factory for a site
+ @rtype: Site instance
+ """
+ root = Resource()
+ root.putChild("provider.json", File(
+ os.path.join(_here,
+ "test_provider.json")))
+ config = Resource()
+ config.putChild(
+ "eip-service.json",
+ File("./eip-service.json"))
+ apiv1 = Resource()
+ apiv1.putChild("config", config)
+ apiv1.putChild("sessions", API_Sessions())
+ apiv1.putChild("users", FakeUsers(None))
+ apiv1.putChild("cert", File(
+ os.path.join(_here,
+ 'openvpn.pem')))
+ root.putChild("1", apiv1)
+
+ factory = Site(root)
+ return factory
+
+
+if __name__ == "__main__":
+
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+
+ factory = get_provider_factory()
+
+ # regular http (for debugging with curl)
+ reactor.listenTCP(8000, factory)
+ reactor.listenSSL(8443, factory, OpenSSLServerContextFactory())
+ reactor.run()
diff --git a/src/leap/crypto/tests/test_provider.json b/src/leap/crypto/tests/test_provider.json
new file mode 100644
index 00000000..c37bef8f
--- /dev/null
+++ b/src/leap/crypto/tests/test_provider.json
@@ -0,0 +1,15 @@
+{
+ "api_uri": "https://localhost:8443",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: 0f17c033115f6b76ff67871872303ff65034efe7dd1b910062ca323eb4da5c7e",
+ "ca_cert_uri": "https://bitmask.net/ca.crt",
+ "default_language": "en",
+ "domain": "example.com",
+ "enrollment_policy": "open",
+ "name": {
+ "en": "Bitmask"
+ },
+ "services": [
+ "openvpn"
+ ]
+}
diff --git a/src/leap/crypto/tests/test_srpregister.py b/src/leap/crypto/tests/test_srpregister.py
new file mode 100644
index 00000000..5ba7306f
--- /dev/null
+++ b/src/leap/crypto/tests/test_srpregister.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+# test_srpregister.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Tests for:
+ * leap/crypto/srpregister.py
+ * leap/crypto/srpauth.py
+"""
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+import os
+import sys
+
+from mock import MagicMock
+from nose.twistedtools import reactor, threaded_reactor, stop_reactor
+from twisted.python import log
+
+from leap.common.testing.https_server import where
+from leap.config.providerconfig import ProviderConfig
+from leap.crypto import srpregister, srpauth
+from leap.crypto.tests import fake_provider
+
+log.startLogging(sys.stdout)
+
+
+def _get_capath():
+ return where("cacert.pem")
+
+_here = os.path.split(__file__)[0]
+
+
+class ImproperlyConfiguredError(Exception):
+ """
+ Raised if the test provider is missing configuration
+ """
+
+
+class SRPTestCase(unittest.TestCase):
+ """
+ Tests for the SRP Register and Auth classes
+ """
+ __name__ = "SRPRegister and SRPAuth tests"
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Sets up this TestCase with a simple and faked provider instance:
+
+ * runs a threaded reactor
+ * loads a mocked ProviderConfig that points to the certs in the
+ leap.common.testing module.
+ """
+ factory = fake_provider.get_provider_factory()
+ http = reactor.listenTCP(8001, factory)
+ https = reactor.listenSSL(
+ 0, factory,
+ fake_provider.OpenSSLServerContextFactory())
+ get_port = lambda p: p.getHost().port
+ cls.http_port = get_port(http)
+ cls.https_port = get_port(https)
+
+ provider = ProviderConfig()
+ provider.get_ca_cert_path = MagicMock()
+ provider.get_ca_cert_path.return_value = _get_capath()
+
+ provider.get_api_uri = MagicMock()
+ provider.get_api_uri.return_value = cls._get_https_uri()
+
+ loaded = provider.load(path=os.path.join(
+ _here, "test_provider.json"))
+ if not loaded:
+ raise ImproperlyConfiguredError(
+ "Could not load test provider config")
+ cls.register = srpregister.SRPRegister(provider_config=provider)
+
+ cls.auth = srpauth.SRPAuth(provider)
+ cls._auth_instance = cls.auth.__dict__['_SRPAuth__instance']
+ cls.authenticate = cls._auth_instance.authenticate
+ cls.logout = cls._auth_instance.logout
+
+ # run!
+ threaded_reactor()
+
+ @classmethod
+ def tearDownClass(cls):
+ """
+ Stops reactor when tearing down the class
+ """
+ stop_reactor()
+
+ # helper methods
+
+ @classmethod
+ def _get_https_uri(cls):
+ """
+ Returns a https uri with the right https port initialized
+ """
+ return "https://localhost:%s" % (cls.https_port,)
+
+ # Register tests
+
+ def test_register_user(self):
+ """
+ Checks if the registration of an unused name works as expected when
+ it is the first time that we attempt to register that user, as well as
+ when we request a user that is taken.
+ """
+ # pristine registration
+ ok = self.register.register_user("foouser_firsttime", "barpass")
+ self.assertTrue(ok)
+
+ # second registration attempt with the same user should return errors
+ ok = self.register.register_user("foouser_second", "barpass")
+ self.assertTrue(ok)
+
+ # FIXME currently we are catching this in an upper layer,
+ # we could bring the error validation to the SRPRegister class
+ ok = self.register.register_user("foouser_second", "barpass")
+
+ def test_correct_http_uri(self):
+ """
+ Checks that registration autocorrect http uris to https ones.
+ """
+ HTTP_URI = "http://localhost:%s" % (self.https_port, )
+ HTTPS_URI = "https://localhost:%s/1/users" % (self.https_port, )
+ provider = ProviderConfig()
+ provider.get_ca_cert_path = MagicMock()
+ provider.get_ca_cert_path.return_value = _get_capath()
+ provider.get_api_uri = MagicMock()
+
+ # we introduce a http uri in the config file...
+ provider.get_api_uri.return_value = HTTP_URI
+ loaded = provider.load(path=os.path.join(
+ _here, "test_provider.json"))
+ if not loaded:
+ raise ImproperlyConfiguredError(
+ "Could not load test provider config")
+
+ self.register = srpregister.SRPRegister(provider_config=provider)
+
+ # ... and we check that we're correctly taking the HTTPS protocol
+ # instead
+ self.assertEquals(self.register._get_registration_uri(),
+ HTTPS_URI)
+ ok = self.register.register_user("test_failhttp", "barpass")
+ self.assertTrue(ok)
+
+ # XXX need to assert that _get_registration_uri was called too
+
+ # Auth tests
+
+ def test_auth(self):
+ """
+ Checks whether a pair of valid credentials is able to be authenticated.
+ """
+ TEST_USER = "register_test_auth"
+ TEST_PASS = "pass"
+
+ # pristine registration, should go well
+ ok = self.register.register_user(TEST_USER, TEST_PASS)
+ self.assertTrue(ok)
+
+ self.authenticate(TEST_USER, TEST_PASS)
+ with self.assertRaises(AssertionError):
+ # AssertionError: already logged in
+ # We probably could take this as its own exception
+ self.authenticate(TEST_USER, TEST_PASS)
+
+ self.logout()
+
+ # cannot log out two times in a row (there's no session)
+ with self.assertRaises(AssertionError):
+ self.logout()
+
+ def test_auth_with_bad_credentials(self):
+ """
+ Checks that auth does not succeed with bad credentials.
+ """
+ TEST_USER = "register_test_auth"
+ TEST_PASS = "pass"
+
+ # non-existent credentials, should fail
+ with self.assertRaises(srpauth.SRPAuthenticationError):
+ self.authenticate("baduser_1", "passwrong")
+
+ # good user, bad password, should fail
+ with self.assertRaises(srpauth.SRPAuthenticationError):
+ self.authenticate(TEST_USER, "passwrong")
+
+ # bad user, good password, should fail too :)
+ with self.assertRaises(srpauth.SRPAuthenticationError):
+ self.authenticate("myunclejoe", TEST_PASS)
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
index 3c256bea..6a73fb76 100644
--- a/src/leap/gui/mainwindow.py
+++ b/src/leap/gui/mainwindow.py
@@ -181,11 +181,11 @@ class MainWindow(QtGui.QMainWindow):
self._stop_eip)
self._action_eip_write = QtGui.QAction(
QtGui.QIcon(":/images/Arrow-Up-32.png"),
- "0.0 Kb", self)
+ "%12.2f Kb" % (0.0,), self)
self._action_eip_write.setEnabled(False)
self._action_eip_read = QtGui.QAction(
QtGui.QIcon(":/images/Arrow-Down-32.png"),
- "0.0 Kb", self)
+ "%12.2f Kb" % (0.0,), self)
self._action_eip_read.setEnabled(False)
self._action_visible = QtGui.QAction(self.tr("Hide"), self)
@@ -202,7 +202,7 @@ class MainWindow(QtGui.QMainWindow):
self._wizard_firstrun = False
if self._first_run():
self._wizard_firstrun = True
- self._wizard = Wizard(self._checker_thread)
+ self._wizard = Wizard(self._checker_thread, standalone=standalone)
# Give this window time to finish init and then show the wizard
QtCore.QTimer.singleShot(1, self._launch_wizard)
self._wizard.accepted.connect(self._finish_init)
@@ -764,12 +764,12 @@ class MainWindow(QtGui.QMainWindow):
"""
upload = float(data[self._vpn.TUNTAP_WRITE_KEY])
upload = upload / 1000.0
- upload_str = "%s Kb" % (upload,)
+ upload_str = "%12.2f Kb" % (upload,)
self.ui.lblUpload.setText(upload_str)
self._action_eip_write.setText(upload_str)
download = float(data[self._vpn.TUNTAP_READ_KEY])
download = download / 1000.0
- download_str = "%s Kb" % (download,)
+ download_str = "%12.2f Kb" % (download,)
self.ui.lblDownload.setText(download_str)
self._action_eip_read.setText(download_str)
diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui
index b8fed183..ed7fe37c 100644
--- a/src/leap/gui/ui/wizard.ui
+++ b/src/leap/gui/ui/wizard.ui
@@ -100,7 +100,7 @@
<string>Provider selection</string>
</property>
<property name="subTitle">
- <string>Please enter the domain of the provider you want to user for your connection</string>
+ <string>Please enter the domain of the provider you want to use for your connection</string>
</property>
<attribute name="pageId">
<string notr="true">1</string>
@@ -158,20 +158,20 @@
<item row="4" column="0" colspan="3">
<widget class="QGroupBox" name="grpCheckProvider">
<property name="title">
- <string>Checking provider</string>
+ <string>Checking for a valid provider</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
- <string>Download provider information</string>
+ <string>Getting provider information</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
- <string>HTTPS Connection</string>
+ <string>Can we stablish a secure connection?</string>
</property>
</widget>
</item>
@@ -244,7 +244,7 @@
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
- <string>Name resolution</string>
+ <string>Can we reach this provider?</string>
</property>
</widget>
</item>
@@ -278,47 +278,118 @@
<string>Provider Information</string>
</property>
<property name="subTitle">
- <string>Services offered by this provider</string>
+ <string>Description of services offered by this provider</string>
</property>
<attribute name="pageId">
<string notr="true">2</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
- <item row="0" column="1" colspan="2">
- <spacer name="horizontalSpacer_6">
+ <item row="0" column="0">
+ <spacer name="verticalSpacer_5">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
- <height>0</height>
+ <width>20</width>
+ <height>40</height>
</size>
</property>
</spacer>
</item>
- <item row="5" column="0" colspan="2">
- <widget class="QLabel" name="label_12">
+ <item row="0" column="1">
+ <spacer name="verticalSpacer_13">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="2">
+ <spacer name="verticalSpacer_17">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="3">
+ <spacer name="verticalSpacer_18">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="4">
+ <spacer name="verticalSpacer_19">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="5">
+ <spacer name="verticalSpacer_20">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblProviderName">
<property name="text">
- <string>&lt;b&gt;Enrollment policy:&lt;/b&gt;</string>
+ <string>Name</string>
</property>
</widget>
</item>
- <item row="2" column="1" colspan="2">
+ <item row="1" column="1">
<widget class="QLabel" name="lblProviderURL">
<property name="text">
<string>URL</string>
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="lblProviderName">
- <property name="text">
- <string>Name</string>
+ <item row="1" column="6">
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
</property>
- </widget>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
</item>
- <item row="4" column="2">
+ <item row="2" column="0">
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -326,42 +397,75 @@
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
- <height>0</height>
+ <height>20</height>
</size>
</property>
</spacer>
</item>
- <item row="1" column="1">
- <spacer name="verticalSpacer_13">
+ <item row="2" column="1" colspan="6">
+ <widget class="QLabel" name="lblProviderDesc">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Desc</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="lblServ">
+ <property name="text">
+ <string>&lt;b&gt;Services offered:&lt;/b&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="lblServicesOffered">
+ <property name="text">
+ <string>services</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="6">
+ <spacer name="horizontalSpacer_6">
<property name="orientation">
- <enum>Qt::Vertical</enum>
+ <enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>20</width>
- <height>40</height>
+ <width>40</width>
+ <height>20</height>
</size>
</property>
</spacer>
</item>
- <item row="5" column="2">
- <widget class="QLabel" name="lblProviderPolicy">
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_12">
<property name="text">
- <string>policy</string>
+ <string>&lt;b&gt;Enrollment policy:&lt;/b&gt;</string>
</property>
</widget>
</item>
- <item row="3" column="0" colspan="3">
- <widget class="QLabel" name="lblProviderDesc">
+ <item row="4" column="1">
+ <widget class="QLabel" name="lblProviderPolicy">
<property name="text">
- <string>Desc</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
+ <string>policy</string>
</property>
</widget>
</item>
- <item row="6" column="0">
+ <item row="5" column="0">
<spacer name="verticalSpacer_14">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -374,6 +478,32 @@
</property>
</spacer>
</item>
+ <item row="5" column="1">
+ <spacer name="verticalSpacer_15">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="5" column="6">
+ <spacer name="verticalSpacer_16">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
<widget class="WizardPage" name="setup_provider_page">
@@ -386,11 +516,47 @@
<attribute name="pageId">
<string notr="true">3</string>
</attribute>
- <layout class="QGridLayout" name="gridLayout_5">
- <item row="1" column="0">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>60</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="lblSetupProviderExpl">
+ <property name="text">
+ <string>We are downloading some bits that we need to establish a secure connection with the provider for the first time.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
- <string>Checking provider</string>
+ <string>Setting up provider</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="2" column="1">
@@ -440,21 +606,21 @@
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
- <string>Download CA Certificate</string>
+ <string>Getting info from the Certificate Authority</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
- <string>Check CA Certificate Fingerprint</string>
+ <string>Do we trust this Certificate Authority?</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
- <string>Check API Certificate</string>
+ <string>Establishing a trust relationship with this provider</string>
</property>
</widget>
</item>
@@ -496,20 +662,7 @@
</layout>
</widget>
</item>
- <item row="0" column="0">
- <spacer name="verticalSpacer_3">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>60</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="0">
+ <item>
<spacer name="verticalSpacer_8">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -662,7 +815,7 @@
<string>Congratulations!</string>
</property>
<property name="subTitle">
- <string>You have successfully configured the LEAP client.</string>
+ <string>You have successfully configured the LEAP Client.</string>
</property>
<attribute name="pageId">
<string notr="true">6</string>
diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py
index dee3b230..7759b98d 100644
--- a/src/leap/gui/wizard.py
+++ b/src/leap/gui/wizard.py
@@ -27,6 +27,7 @@ from functools import partial
from ui_wizard import Ui_Wizard
from leap.config.providerconfig import ProviderConfig
from leap.crypto.srpregister import SRPRegister
+from leap.util.privilege_policies import is_missing_policy_permissions
from leap.services.eip.providerbootstrapper import ProviderBootstrapper
logger = logging.getLogger(__name__)
@@ -50,9 +51,20 @@ class Wizard(QtGui.QWizard):
BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
- def __init__(self, checker):
+ def __init__(self, checker, standalone=False):
+ """
+ Constructor for the main Wizard.
+
+ @param checker: Checker thread that the wizard should use.
+ @type checker: CheckerThread
+ @param standalone: If True, the application is running as standalone
+ and the wizard should display some messages according to this.
+ @type standalone: bool
+ """
QtGui.QWizard.__init__(self)
+ self.standalone = standalone
+
self.ui = Ui_Wizard()
self.ui.setupUi(self)
@@ -64,14 +76,21 @@ class Wizard(QtGui.QWizard):
self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png")
# Correspondence for services and their name to display
+ EIP_LABEL = self.tr("Encrypted Internet")
+
+ if self._is_need_eip_password_warning():
+ EIP_LABEL += " " + self.tr(
+ "(<b>will need admin password to start</b>)")
+
self.SERVICE_DISPLAY = [
- self.tr("Encrypted Internet")
+ EIP_LABEL
]
self.SERVICE_CONFIG = [
"openvpn"
]
self._selected_services = set()
+ self._shown_services = set()
self._show_register = False
@@ -235,7 +254,7 @@ class Wizard(QtGui.QWizard):
error_msg = req.json().get("errors").get("login")[0]
if not error_msg.istitle():
error_msg = "%s %s" % (old_username, error_msg)
- self._set_register_status(error_msg)
+ self._set_register_status(error_msg, error=True)
except:
logger.error("Unknown error: %r" % (req.content,))
self.ui.btnRegister.setEnabled(True)
@@ -451,13 +470,15 @@ class Wizard(QtGui.QWizard):
for service in self._provider_config.get_services():
try:
- checkbox = QtGui.QCheckBox(self)
- service_index = self.SERVICE_CONFIG.index(service)
- checkbox.setText(self.SERVICE_DISPLAY[service_index])
- self.ui.serviceListLayout.addWidget(checkbox)
- checkbox.stateChanged.connect(
- partial(self._service_selection_changed, service))
- checkbox.setChecked(True)
+ if service not in self._shown_services:
+ checkbox = QtGui.QCheckBox(self)
+ service_index = self.SERVICE_CONFIG.index(service)
+ checkbox.setText(self.SERVICE_DISPLAY[service_index])
+ self.ui.serviceListLayout.addWidget(checkbox)
+ checkbox.stateChanged.connect(
+ partial(self._service_selection_changed, service))
+ checkbox.setChecked(True)
+ self._shown_services.add(service)
except ValueError:
logger.error(
self.tr("Something went wrong while trying to "
@@ -486,7 +507,8 @@ class Wizard(QtGui.QWizard):
self._provider_config)
if pageId == self.PRESENT_PROVIDER_PAGE:
- self.page(pageId).setSubTitle(self.tr("Services offered by %s") %
+ self.page(pageId).setSubTitle(self.tr("Description of services "
+ "offered by %s") %
(self._provider_config
.get_name(),))
@@ -499,6 +521,9 @@ class Wizard(QtGui.QWizard):
self.ui.lblProviderDesc.setText(
"<i>%s</i>" %
(self._provider_config.get_description(lang=lang),))
+
+ self.ui.lblServicesOffered.setText(self._provider_config
+ .get_services_string())
self.ui.lblProviderPolicy.setText(self._provider_config
.get_enrollment_policy())
@@ -511,6 +536,15 @@ class Wizard(QtGui.QWizard):
if pageId == self.SERVICES_PAGE:
self._populate_services()
+ def _is_need_eip_password_warning(self):
+ """
+ Returns True if we need to add a warning about eip needing
+ administrative permissions to start. That can be either
+ because we are running in standalone mode, or because we could
+ not find the needed privilege escalation mechanisms being operative.
+ """
+ return self.standalone or is_missing_policy_permissions()
+
def nextId(self):
"""
Sets the next page id for the wizard based on wether the user
diff --git a/src/leap/platform_init/initializers.py b/src/leap/platform_init/initializers.py
index 049d32a2..cf7e71b8 100644
--- a/src/leap/platform_init/initializers.py
+++ b/src/leap/platform_init/initializers.py
@@ -134,6 +134,11 @@ def DarwinInitializer():
"is not found inside this bundle.")
BADEXEC_MSG = ("Tried to install tuntaposx kext, but the installer "
"failed to be launched.")
+
+ # TODO DRY this with other cases, and
+ # factor out to _should_install() function.
+ # Leave the dialog as a more generic thing.
+
if not _darwin_has_tun_kext():
msg = QtGui.QMessageBox()
msg.setWindowTitle(msg.tr("TUN Driver"))
diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py
index 37c6256e..3d36736d 100644
--- a/src/leap/services/eip/vpnlaunchers.py
+++ b/src/leap/services/eip/vpnlaunchers.py
@@ -410,7 +410,7 @@ class DarwinVPNLauncher(VPNLauncher):
@rtype: dict
"""
- return {"LD_LIBRARY_PATH": os.path.join(
+ return {"DYLD_LIBRARY_PATH": os.path.join(
providerconfig.get_path_prefix(),
"..", "lib")}
diff --git a/src/leap/util/privilege_policies.py b/src/leap/util/privilege_policies.py
new file mode 100644
index 00000000..e74c4d33
--- /dev/null
+++ b/src/leap/util/privilege_policies.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# privilege_policies.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Helpers to determine if the needed policies for privilege escalation
+are operative under this client run.
+"""
+import logging
+import os
+import platform
+
+from abc import ABCMeta, abstractmethod
+
+logger = logging.getLogger(__name__)
+
+
+def is_missing_policy_permissions():
+ """
+ Returns True if we do not have implemented a policy checker for this
+ platform, or if the policy checker exists but it cannot find the
+ appropriate policy mechanisms in place.
+
+ @rtype: bool
+ """
+ _system = platform.system()
+ platform_checker = _system + "PolicyChecker"
+ policy_checker = globals().get(platform_checker, None)
+ if not policy_checker:
+ # it is true that we miss permission to escalate
+ # privileges without asking for password each time.
+ logger.debug("we could not find a policy checker implementation "
+ "for %s" % (_system,))
+ return True
+ return policy_checker().is_missing_policy_permissions()
+
+
+class PolicyChecker:
+ """
+ Abstract PolicyChecker class
+ """
+
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def is_missing_policy_permissions(self):
+ """
+ Returns True if we could not find any policy mechanisms that
+ are defined to be in used for this particular platform.
+
+ @rtype: bool
+ """
+ return True
+
+
+class LinuxPolicyChecker(PolicyChecker):
+ """
+ PolicyChecker for Linux
+ """
+ LINUX_POLKIT_FILE = ("/usr/share/polkit-1/actions/"
+ "net.openvpn.gui.leap.policy")
+
+ def is_missing_policy_permissions(self):
+ """
+ Returns True if we could not find the appropriate policykit file
+ in place
+
+ @rtype: bool
+ """
+ return not os.path.isfile(self.LINUX_POLKIT_FILE)