diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/config/pluggableconfig.py | 2 | ||||
| -rw-r--r-- | src/leap/config/providerconfig.py | 18 | ||||
| -rw-r--r-- | src/leap/crypto/srpauth.py | 31 | ||||
| -rw-r--r-- | src/leap/crypto/srpregister.py | 27 | ||||
| -rw-r--r-- | src/leap/crypto/tests/__init__.py | 16 | ||||
| -rwxr-xr-x | src/leap/crypto/tests/fake_provider.py | 358 | ||||
| -rw-r--r-- | src/leap/crypto/tests/test_provider.json | 15 | ||||
| -rw-r--r-- | src/leap/crypto/tests/test_srpregister.py | 207 | ||||
| -rw-r--r-- | src/leap/gui/mainwindow.py | 10 | ||||
| -rw-r--r-- | src/leap/gui/ui/wizard.ui | 269 | ||||
| -rw-r--r-- | src/leap/gui/wizard.py | 56 | ||||
| -rw-r--r-- | src/leap/platform_init/initializers.py | 5 | ||||
| -rw-r--r-- | src/leap/services/eip/vpnlaunchers.py | 2 | ||||
| -rw-r--r-- | src/leap/util/privilege_policies.py | 82 | 
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><b>Enrollment policy:</b></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><b>Services offered:</b></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><b>Enrollment policy:</b></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) | 
