diff options
Diffstat (limited to 'src/leap')
-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/util/privilege_policies.py | 82 |
12 files changed, 992 insertions, 99 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 863640ef..f359d7c1 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) @@ -758,12 +758,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/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) |