initial commit
authorKali Kaneko <kali@leap.se>
Thu, 14 Mar 2013 00:44:43 +0000 (09:44 +0900)
committerKali Kaneko <kali@leap.se>
Thu, 14 Mar 2013 00:44:43 +0000 (09:44 +0900)
17 files changed:
setup.py [new file with mode: 0644]
src/leap/__init__.py [new file with mode: 0644]
src/leap/common/__init__.py [new file with mode: 0644]
src/leap/common/__init__.pyc [new file with mode: 0644]
src/leap/common/certs.py [new file with mode: 0644]
src/leap/common/certs.pyc [new file with mode: 0644]
src/leap/common/check.py [new file with mode: 0644]
src/leap/common/check.pyc [new file with mode: 0644]
src/leap/common/files.py [new file with mode: 0644]
src/leap/common/files.pyc [new file with mode: 0644]
src/leap/common/testing/__init__.py [new file with mode: 0644]
src/leap/common/testing/basetest.py [new file with mode: 0644]
src/leap/common/testing/cacert.pem [new file with mode: 0644]
src/leap/common/testing/https_server.py [new file with mode: 0644]
src/leap/common/testing/leaptestscert.pem [new file with mode: 0644]
src/leap/common/testing/leaptestskey.pem [new file with mode: 0644]
src/leap/common/testing/test_basetest.py [new file with mode: 0644]

diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..21c34f1
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# setup.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/>.
+"""
+setup file for leap.common
+"""
+from setuptools import setup, find_packages
+
+requirements = [
+]
+
+# XXX add classifiers, docs
+
+setup(
+    name='leap.common',
+    version='0.2.0-dev',
+    url='https://leap.se/',
+    license='GPLv3+',
+    author='The LEAP Encryption Access Project',
+    author_email='info@leap.se',
+    description='Common files used by the LEAP Client project.',
+    long_description=(
+        "Common files used by the LEAP Client project."
+    ),
+    namespace_packages=["leap"],
+    package_dir = {'': 'src'},
+    packages=find_packages('src'),
+    #test_suite='leap.common.tests',
+    #install_requires=requirements,
+)
diff --git a/src/leap/__init__.py b/src/leap/__init__.py
new file mode 100644 (file)
index 0000000..3ad9513
--- /dev/null
@@ -0,0 +1,2 @@
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)
diff --git a/src/leap/common/__init__.py b/src/leap/common/__init__.py
new file mode 100644 (file)
index 0000000..a70a9a8
--- /dev/null
@@ -0,0 +1,9 @@
+import logging
+logger = logging.getLogger(__name__)
+
+try:
+    import pygeoip
+    HAS_GEOIP = True
+except ImportError:
+    logger.debug('PyGeoIP not found. Disabled Geo support.')
+    HAS_GEOIP = False
diff --git a/src/leap/common/__init__.pyc b/src/leap/common/__init__.pyc
new file mode 100644 (file)
index 0000000..fb08022
Binary files /dev/null and b/src/leap/common/__init__.pyc differ
diff --git a/src/leap/common/certs.py b/src/leap/common/certs.py
new file mode 100644 (file)
index 0000000..4cb70dd
--- /dev/null
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+# certs.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/>.
+
+"""
+Implements cert checks and helpers
+"""
+
+import os
+import time
+import logging
+
+from OpenSSL import crypto
+from dateutil.parser import parse as dateparse
+
+from leap.common.check import leap_assert
+
+logger = logging.getLogger(__name__)
+
+
+def get_cert_from_string(string):
+    """
+    Returns the x509 from the contents of this string
+
+    @param string: certificate contents as downloaded
+    @type string: str
+
+    @return: x509 or None
+    """
+    leap_assert(string, "We need something to load")
+
+    x509 = None
+    try:
+        x509 = crypto.load_certificate(crypto.FILETYPE_PEM, string)
+    except Exception as e:
+        logger.error("Something went wrong while loading the certificate: %r"
+                     % (e,))
+    return x509
+
+
+def get_privatekey_from_string(string):
+    """
+    Returns the private key from the contents of this string
+
+    @param string: private key contents as downloaded
+    @type string: str
+
+    @return: private key or None
+    """
+    leap_assert(string, "We need something to load")
+
+    pkey = None
+    try:
+        pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, string)
+    except Exception as e:
+        logger.error("Something went wrong while loading the certificate: %r"
+                     % (e,))
+    return pkey
+
+
+def get_digest(cert_data, method):
+    """
+    Returns the digest for the cert_data using the method specified
+
+    @param cert_data: certificate data in string form
+    @type cert_data: str
+    @param method: method to be used for digest
+    @type method: str
+
+    @rtype: str
+    """
+    x509 = get_cert_from_string(cert_data)
+    digest = x509.digest(method).replace(":", "").lower()
+
+    return digest
+
+
+def can_load_cert_and_pkey(string):
+    """
+    Loads certificate and private key from a buffer, returns True if
+    everything went well, False otherwise
+
+    @param string: buffer containing the cert and private key
+    @type string: str or any kind of buffer
+
+    @rtype: bool
+    """
+    can_load = True
+
+    try:
+        cert = get_cert_from_string(string)
+        key = get_privatekey_from_string(string)
+
+        leap_assert(cert, 'The certificate could not be loaded')
+        leap_assert(key, 'The private key could not be loaded')
+    except Exception as e:
+        can_load = False
+        logger.error("Something went wrong while trying to load "
+                     "the certificate: %r" % (e,))
+
+    return can_load
+
+
+def is_valid_pemfile(cert):
+    """
+    Checks that the passed string is a valid pem certificate
+
+    @param cert: String containing pem content
+    @type cert: str
+
+    @rtype: bool
+    """
+    leap_assert(cert, "We need a cert to load")
+
+    return can_load_cert_and_pkey(cert)
+
+
+def get_cert_time_boundaries(certfile):
+    """
+    Returns the time boundaries for the certificate saved in certfile
+
+    @param certfile: path to certificate
+    @type certfile: str
+
+    @rtype: tuple (from, to)
+    """
+    cert = get_cert_from_string(certfile)
+    leap_assert(cert, 'There was a problem loading the certificate')
+
+    fromts, tots = (cert.get_notBefore(), cert.get_notAfter())
+    from_, to_ = map(
+        lambda ts: time.gmtime(time.mktime(dateparse(ts).timetuple())),
+        (fromts, tots))
+    return from_, to_
+
+
+def should_redownload(certfile, now=time.gmtime):
+    """
+    Returns True if any of the checks don't pass, False otherwise
+
+    @param certfile: path to certificate
+    @type certfile: str
+    @param now: current date function, ONLY USED FOR TESTING
+
+    @rtype: bool
+    """
+    exists = os.path.isfile(certfile)
+
+    if not exists:
+        return True
+
+    certdata = None
+    try:
+        with open(certfile, "r") as f:
+            certdata = f.read()
+            if not is_valid_pemfile(certdata):
+                return True
+    except:
+        return True
+
+    valid_from, valid_to = get_cert_time_boundaries(certdata)
+
+    if not (valid_from < now() < valid_to):
+        return True
+
+    return False
diff --git a/src/leap/common/certs.pyc b/src/leap/common/certs.pyc
new file mode 100644 (file)
index 0000000..eecff8b
Binary files /dev/null and b/src/leap/common/certs.pyc differ
diff --git a/src/leap/common/check.py b/src/leap/common/check.py
new file mode 100644 (file)
index 0000000..9787341
--- /dev/null
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# check.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/>.
+
+"""
+Set of functions to help checking situations
+"""
+import logging
+import inspect
+import traceback
+
+
+logger = logging.getLogger(__name__)
+
+
+def leap_assert(condition, message=""):
+    """
+    Asserts the condition and displays the message if that's not
+    met. It also logs the error and its backtrace.
+
+    @param condition: condition to check
+    @type condition: bool
+    @param message: message to display if the condition isn't met
+    @type message: str
+    """
+    if not condition:
+        logger.error("Bug: %s" % (message,))
+        try:
+            frame = inspect.currentframe()
+            stack_trace = traceback.format_stack(frame)
+            logger.error(''.join(stack_trace))
+        except Exception as e:
+            logger.error("Bug in leap_assert: %r" % (e,))
+    assert condition, message
+
+
+def leap_assert_type(var, expectedType):
+    """
+    Helper assert check for a variable's expected type
+
+    @param var: variable to check
+    @type var: any
+    @param expectedType: type to check agains
+    @type expectedType: type
+    """
+    leap_assert(isinstance(var, expectedType),
+                "Expected type %r instead of %r" %
+                (expectedType, type(var)))
diff --git a/src/leap/common/check.pyc b/src/leap/common/check.pyc
new file mode 100644 (file)
index 0000000..7c48489
Binary files /dev/null and b/src/leap/common/check.pyc differ
diff --git a/src/leap/common/files.py b/src/leap/common/files.py
new file mode 100644 (file)
index 0000000..7c878e1
--- /dev/null
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+# files.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/>.
+
+"""
+Implements file helper methods
+"""
+
+import os
+import stat
+import logging
+import time
+import errno
+
+logger = logging.getLogger(__name__)
+
+
+def check_and_fix_urw_only(cert):
+    """
+    Test for 600 mode and try to set it if anything different found
+
+    Might raise OSError
+
+    @param cert: Certificate path
+    @type cert: str
+    """
+    mode = stat.S_IMODE(os.stat(cert).st_mode)
+
+    if mode != int('600', 8):
+        try:
+            logger.warning('Bad permission on %s attempting to set 600' %
+                           (cert,))
+            os.chmod(cert, stat.S_IRUSR | stat.S_IWUSR)
+        except OSError:
+            logger.error('Error while trying to chmod 600 %s' %
+                         cert)
+            raise
+
+
+def get_mtime(filename):
+    """
+    Returns the modified time or None if the file doesn't exist
+
+    @param filename: path to check
+    @type filename: str
+
+    @rtype: str
+    """
+    try:
+        mtime = time.ctime(os.path.getmtime(filename)) + " GMT"
+        return mtime
+    except OSError:
+        return None
+
+
+def mkdir_p(path):
+    """
+    Creates the path and all the intermediate directories that don't
+    exist
+
+    Might raise OSError
+
+    @param path: path to create
+    @type path: str
+    """
+    try:
+        os.makedirs(path)
+    except OSError as exc:
+        if exc.errno == errno.EEXIST and os.path.isdir(path):
+            pass
+        else:
+            raise
diff --git a/src/leap/common/files.pyc b/src/leap/common/files.pyc
new file mode 100644 (file)
index 0000000..5f0273c
Binary files /dev/null and b/src/leap/common/files.pyc differ
diff --git a/src/leap/common/testing/__init__.py b/src/leap/common/testing/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/leap/common/testing/basetest.py b/src/leap/common/testing/basetest.py
new file mode 100644 (file)
index 0000000..2359754
--- /dev/null
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+# leap.common.testing.basetest.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/>.
+"""
+Common testing facilities
+"""
+import os
+import platform
+import shutil
+import tempfile
+
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
+
+from leap.common.files import mkdir_p, check_and_fix_urw_only
+
+_system = platform.system()
+
+
+class BaseLeapTest(unittest.TestCase):
+    """
+    Base Leap TestCase
+    """
+
+    __name__ = "leap_test"
+
+    @classmethod
+    def setUpClass(cls):
+        cls.old_path = os.environ['PATH']
+        cls.old_home = os.environ['HOME']
+        cls.tempdir = tempfile.mkdtemp(prefix="leap_tests-")
+        cls.home = cls.tempdir
+        bin_tdir = os.path.join(
+            cls.tempdir,
+            'bin')
+        os.environ["PATH"] = bin_tdir
+        os.environ["HOME"] = cls.tempdir
+
+    @classmethod
+    def tearDownClass(cls):
+        os.environ["PATH"] = cls.old_path
+        os.environ["HOME"] = cls.old_home
+        # safety check
+        assert cls.tempdir.startswith('/tmp/leap_tests-')
+        shutil.rmtree(cls.tempdir)
+
+    # you have to override these methods
+    # this way we ensure we did not put anything
+    # here that you can forget to call.
+
+    def setUp(self):
+        raise NotImplementedError("abstract base class")
+
+    def tearDown(self):
+        raise NotImplementedError("abstract base class")
+
+    #
+    # helper methods
+    #
+
+    def get_tempfile(self, filename):
+        return os.path.join(self.tempdir, filename)
+
+    def _missing_test_for_plat(self, do_raise=False):
+        if do_raise:
+            raise NotImplementedError(
+                "This test is not implemented "
+                "for the running platform: %s" %
+                _system)
+
+    def touch(self, filepath):
+        folder, filename = os.path.split(filepath)
+        if not os.path.isdir(folder):
+            mkdir_p(folder)
+        # XXX should move to test_basetest
+        self.assertTrue(os.path.isdir(folder))
+
+        with open(filepath, 'w') as fp:
+            fp.write(' ')
+
+        # XXX should move to test_basetest
+        self.assertTrue(os.path.isfile(filepath))
+
+    def chmod600(self, filepath):
+        check_and_fix_urw_only(filepath)
diff --git a/src/leap/common/testing/cacert.pem b/src/leap/common/testing/cacert.pem
new file mode 100644 (file)
index 0000000..6989c48
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID1TCCAr2gAwIBAgIJAOv0BS09D8byMA0GCSqGSIb3DQEBBQUAMIGAMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKY3liZXJzcGFjZTEnMCUGA1UECgweTEVBUCBFbmNy
+eXB0aW9uIEFjY2VzcyBQcm9qZWN0MRYwFAYDVQQDDA10ZXN0cy1sZWFwLnNlMRsw
+GQYJKoZIhvcNAQkBFgxpbmZvQGxlYXAuc2UwHhcNMTIwODMxMTYyNjMwWhcNMTUw
+ODMxMTYyNjMwWjCBgDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCmN5YmVyc3BhY2Ux
+JzAlBgNVBAoMHkxFQVAgRW5jcnlwdGlvbiBBY2Nlc3MgUHJvamVjdDEWMBQGA1UE
+AwwNdGVzdHMtbGVhcC5zZTEbMBkGCSqGSIb3DQEJARYMaW5mb0BsZWFwLnNlMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1pU7OU+abrUXFZwp6X0LlF0f
+xQvC1Nmr5sFH7N9RTu3bdwY2t57ECP2TPkH6+x7oOvCTgAMxIE1scWEEkfgKViqW
+FH/Om1UW1PMaiDYGtFuqEuxM95FvaYxp2K6rzA37WNsedA28sCYzhRD+/5HqbCNT
+3rRS2cPaVO8kXI/5bgd8bUk3009pWTg4SvTtOW/9MWJbBH5f5JWmMn7Ayt6hIdT/
+E6npofEK/UCqAlEscARYFXSB/F8nK1whjo9mGFjMUd7d/25UbFHqOk4K7ishD4DH
+F7LaS84rS+Sjwn3YtDdDQblGghJfz8X1AfPSGivGnvLVdkmMF9Y2hJlSQ7+C5wID
+AQABo1AwTjAdBgNVHQ4EFgQUnpJEv4FnlqKbfm7mprudKdrnOAowHwYDVR0jBBgw
+FoAUnpJEv4FnlqKbfm7mprudKdrnOAowDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
+AQUFAAOCAQEAGW66qwdK/ATRVZkTpI2sgi+2dWD5tY4VyZuJIrRwfXsGPeVvmdsa
+zDmwW5dMkth1Of5yO6o7ijvUvfnw/UCLNLNICKZhH5G0DHstfBeFc0jnP2MqOZCp
+puRGPBlO2nxUCvoGcPRUKGQK9XSYmxcmaSFyzKVDMLnmH+Lakj5vaY9a8ZAcZTz7
+T5qePxKAxg+RIlH8Ftc485QP3fhqPYPrRsL3g6peiqCvIRshoP1MSoh19boI+1uX
+wHQ/NyDkL5ErKC5JCSpaeF8VG1ek570kKWQLuQAbnlXZw+Sqfu35CIdizHaYGEcx
+xA8oXH4L2JaT2x9GKDSpCmB2xXy/NVamUg==
+-----END CERTIFICATE-----
diff --git a/src/leap/common/testing/https_server.py b/src/leap/common/testing/https_server.py
new file mode 100644 (file)
index 0000000..08d5089
--- /dev/null
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+# leap.common.testing.https_server.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 simple HTTPS server to be used in tests
+"""
+from BaseHTTPServer import HTTPServer
+import os
+import ssl
+import SocketServer
+import threading
+import unittest
+
+_where = os.path.split(__file__)[0]
+
+
+def where(filename):
+    return os.path.join(_where, filename)
+
+
+class HTTPSServer(HTTPServer):
+    def server_bind(self):
+        SocketServer.TCPServer.server_bind(self)
+        self.socket = ssl.wrap_socket(
+            self.socket, server_side=True,
+            certfile=where("leaptestscert.pem"),
+            keyfile=where("leaptestskey.pem"),
+            ca_certs=where("cacert.pem"),
+            ssl_version=ssl.PROTOCOL_SSLv23)
+
+
+class TestServerThread(threading.Thread):
+    def __init__(self, test_object, request_handler):
+        threading.Thread.__init__(self)
+        self.request_handler = request_handler
+        self.test_object = test_object
+
+    def run(self):
+        self.server = HTTPSServer(('localhost', 0), self.request_handler)
+        host, port = self.server.socket.getsockname()
+        self.test_object.HOST, self.test_object.PORT = host, port
+        self.test_object.server_started.set()
+        self.test_object = None
+        try:
+            self.server.serve_forever(0.05)
+        finally:
+            self.server.server_close()
+
+    def stop(self):
+        self.server.shutdown()
+
+
+class BaseHTTPSServerTestCase(unittest.TestCase):
+    """
+    derived classes need to implement a request_handler
+    """
+    def setUp(self):
+        self.server_started = threading.Event()
+        self.thread = TestServerThread(self, self.request_handler)
+        self.thread.start()
+        self.server_started.wait()
+
+    def tearDown(self):
+        self.thread.stop()
+
+    def get_server(self):
+        host, port = self.HOST, self.PORT
+        if host == "127.0.0.1":
+            host = "localhost"
+        return "%s:%s" % (host, port)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/src/leap/common/testing/leaptestscert.pem b/src/leap/common/testing/leaptestscert.pem
new file mode 100644 (file)
index 0000000..65596b1
--- /dev/null
@@ -0,0 +1,84 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            eb:f4:05:2d:3d:0f:c6:f3
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=cyberspace, O=LEAP Encryption Access Project, CN=tests-leap.se/emailAddress=info@leap.se
+        Validity
+            Not Before: Aug 31 16:30:17 2012 GMT
+            Not After : Aug 31 16:30:17 2013 GMT
+        Subject: C=US, ST=cyberspace, L=net, O=LEAP Encryption Access Project, CN=localhost/emailAddress=info@leap.se
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:bc:f1:c4:05:ce:4b:d5:9b:9a:fa:c1:a5:0c:89:
+                    15:7e:05:69:b6:a4:62:38:3a:d6:14:4a:36:aa:3c:
+                    31:70:54:2e:bf:7d:05:19:ad:7b:0c:a9:a6:7d:46:
+                    be:83:62:cb:ea:b9:48:6c:7d:78:a0:10:0b:ad:8a:
+                    74:7a:b8:ff:32:85:64:36:90:dc:38:dd:90:6e:07:
+                    82:70:ae:5f:4e:1f:f4:46:98:f3:98:b4:fa:08:65:
+                    bf:d6:ec:a9:ba:7e:a8:f0:40:a2:d0:1a:cb:e6:fc:
+                    95:c5:54:63:92:5b:b8:0a:36:cc:26:d3:2b:ad:16:
+                    ff:49:53:f4:65:7c:64:27:9a:f5:12:75:11:a5:0c:
+                    5a:ea:1e:e4:31:f3:a6:2b:db:0e:4a:5d:aa:47:3a:
+                    f0:5e:2a:d5:6f:74:b6:f8:bc:9a:73:d0:fa:8a:be:
+                    a8:69:47:9b:07:45:d9:b5:cd:1c:9b:c5:41:9a:65:
+                    cc:99:a0:bd:bf:b5:e8:9f:66:5f:69:c9:6d:c8:68:
+                    50:68:74:ae:8e:12:7e:9c:24:4f:dc:05:61:b7:8a:
+                    6d:2a:95:43:d9:3f:fe:d8:c9:a7:ae:63:cd:30:d5:
+                    95:84:18:2d:12:b5:2d:a6:fe:37:dd:74:b8:f8:a5:
+                    59:18:8f:ca:f7:ae:63:0d:9d:66:51:7d:9c:40:48:
+                    9b:a1
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                B2:50:B4:C6:38:8F:BA:C4:3B:69:4C:6B:45:7C:CF:08:48:36:02:E0
+            X509v3 Authority Key Identifier: 
+                keyid:9E:92:44:BF:81:67:96:A2:9B:7E:6E:E6:A6:BB:9D:29:DA:E7:38:0A
+
+    Signature Algorithm: sha1WithRSAEncryption
+         aa:ab:d4:27:e3:cb:42:05:55:fd:24:b3:e5:55:7d:fb:ce:6c:
+         ff:c7:96:f0:7d:30:a1:53:4a:04:eb:a4:24:5e:96:ee:65:ef:
+         e5:aa:08:47:9d:aa:95:2a:bb:6a:28:9f:51:62:63:d9:7d:1a:
+         81:a0:72:f7:9f:33:6b:3b:f4:dc:85:cd:2a:ee:83:a9:93:3d:
+         75:53:91:fa:0b:1b:10:83:11:2c:03:4e:ac:bf:c3:e6:25:74:
+         9f:14:13:4a:43:66:c2:d7:1c:6c:94:3e:a6:f3:a5:bd:01:2c:
+         9f:20:29:2e:62:82:12:d8:8b:70:1b:88:2b:18:68:5a:45:80:
+         46:2a:6a:d5:df:1f:d3:e8:57:39:0a:be:1a:d8:b0:3e:e5:b6:
+         c3:69:b7:5e:c0:7b:b3:a8:a6:78:ee:0a:3d:a0:74:40:fb:42:
+         9f:f4:98:7f:47:cc:15:28:eb:b1:95:77:82:a8:65:9b:46:c3:
+         4f:f9:f4:72:be:bd:24:28:5c:0d:b3:89:e4:13:71:c8:a7:54:
+         1b:26:15:f3:c1:b2:a9:13:77:54:c2:b9:b0:c7:24:39:00:4c:
+         1a:a7:9b:e7:ad:4a:3a:32:c2:81:0d:13:2d:27:ea:98:00:a9:
+         0e:9e:38:3b:8f:80:34:17:17:3d:49:7e:f4:a5:19:05:28:08:
+         7d:de:d3:1f
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAOv0BS09D8bzMA0GCSqGSIb3DQEBBQUAMIGAMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKY3liZXJzcGFjZTEnMCUGA1UECgweTEVBUCBFbmNy
+eXB0aW9uIEFjY2VzcyBQcm9qZWN0MRYwFAYDVQQDDA10ZXN0cy1sZWFwLnNlMRsw
+GQYJKoZIhvcNAQkBFgxpbmZvQGxlYXAuc2UwHhcNMTIwODMxMTYzMDE3WhcNMTMw
+ODMxMTYzMDE3WjCBijELMAkGA1UEBhMCVVMxEzARBgNVBAgMCmN5YmVyc3BhY2Ux
+DDAKBgNVBAcMA25ldDEnMCUGA1UECgweTEVBUCBFbmNyeXB0aW9uIEFjY2VzcyBQ
+cm9qZWN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxGzAZBgkqhkiG9w0BCQEWDGluZm9A
+bGVhcC5zZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALzxxAXOS9Wb
+mvrBpQyJFX4FabakYjg61hRKNqo8MXBULr99BRmtewyppn1GvoNiy+q5SGx9eKAQ
+C62KdHq4/zKFZDaQ3DjdkG4HgnCuX04f9EaY85i0+ghlv9bsqbp+qPBAotAay+b8
+lcVUY5JbuAo2zCbTK60W/0lT9GV8ZCea9RJ1EaUMWuoe5DHzpivbDkpdqkc68F4q
+1W90tvi8mnPQ+oq+qGlHmwdF2bXNHJvFQZplzJmgvb+16J9mX2nJbchoUGh0ro4S
+fpwkT9wFYbeKbSqVQ9k//tjJp65jzTDVlYQYLRK1Lab+N910uPilWRiPyveuYw2d
+ZlF9nEBIm6ECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl
+blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFLJQtMY4j7rEO2lM
+a0V8zwhINgLgMB8GA1UdIwQYMBaAFJ6SRL+BZ5aim35u5qa7nSna5zgKMA0GCSqG
+SIb3DQEBBQUAA4IBAQCqq9Qn48tCBVX9JLPlVX37zmz/x5bwfTChU0oE66QkXpbu
+Ze/lqghHnaqVKrtqKJ9RYmPZfRqBoHL3nzNrO/Tchc0q7oOpkz11U5H6CxsQgxEs
+A06sv8PmJXSfFBNKQ2bC1xxslD6m86W9ASyfICkuYoIS2ItwG4grGGhaRYBGKmrV
+3x/T6Fc5Cr4a2LA+5bbDabdewHuzqKZ47go9oHRA+0Kf9Jh/R8wVKOuxlXeCqGWb
+RsNP+fRyvr0kKFwNs4nkE3HIp1QbJhXzwbKpE3dUwrmwxyQ5AEwap5vnrUo6MsKB
+DRMtJ+qYAKkOnjg7j4A0Fxc9SX70pRkFKAh93tMf
+-----END CERTIFICATE-----
diff --git a/src/leap/common/testing/leaptestskey.pem b/src/leap/common/testing/leaptestskey.pem
new file mode 100644 (file)
index 0000000..fe6291a
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAvPHEBc5L1Zua+sGlDIkVfgVptqRiODrWFEo2qjwxcFQuv30F
+Ga17DKmmfUa+g2LL6rlIbH14oBALrYp0erj/MoVkNpDcON2QbgeCcK5fTh/0Rpjz
+mLT6CGW/1uypun6o8ECi0BrL5vyVxVRjklu4CjbMJtMrrRb/SVP0ZXxkJ5r1EnUR
+pQxa6h7kMfOmK9sOSl2qRzrwXirVb3S2+Lyac9D6ir6oaUebB0XZtc0cm8VBmmXM
+maC9v7Xon2ZfacltyGhQaHSujhJ+nCRP3AVht4ptKpVD2T/+2MmnrmPNMNWVhBgt
+ErUtpv433XS4+KVZGI/K965jDZ1mUX2cQEiboQIDAQABAoIBAQCh/+yhSbrtoCgm
+PegEsnix/3QfPBxWt+Obq/HozglZlWQrnMbFuF+bgM4V9ZUdU5UhYNF+66mEG53X
+orGyE3IDYCmHO3cGbroKDPhDIs7mTjGEYlniIbGLh6oPXgU8uKKis9ik84TGPOUx
+NuTUtT07zLYHx+FX3DLwLUKLzTaWWSRgA7nxNwCY8aPqDxCkXEyZHvSlm9KYZnhe
+nVevycoHR+chxL6X/ebbBt2FKR7tl4328mlDXvMXr0vahPH94CuXEvfTj+f6ZxZF
+OctdikyRfd8O3ebrUw0XjafPYyTsDMH0/rQovEBVlecEHqh6Z9dBFlogRq5DSun9
+jem4bBXRAoGBAPGPi4g21pTQPqTFxpqea8TsPqIfo3csfMDPdzT246MxzALHqCfG
+yZi4g2JYJrReSWHulZDORO5skSKNEb5VTA/3xFhKLt8CULZOakKBDLkzRXlnDFXg
+Jsu9vtjDWjQcJsdsRx1tc5V6s+hmel70aaUu/maUlEYZnyIXaTe+1SB1AoGBAMg9
+EMEO5YN52pOI5qPH8j7uyVKtZWKRiR6jb5KA5TxWqZalSdPV6YwDqV/e+HjWrZNw
+kSEFONY0seKpIHwXchx91aym7rDHUgOoBQfCWufRMYvRXLhfOTBu4X+U52++i8wt
+FvKgh6eSmc7VayAaDfHp7yfrIfS03IiN0T35mGj9AoGAPCoXg7a83VW8tId5/trE
+VsjMlM6yhSU0cUV7GFsBuYzWlj6qODX/0iTqvFzeTwBI4LZu1CE78/Jgd62RJMnT
+5wo8Ag1//RVziuSe/K9tvtbxT9qFrQHmR8qbtRt65Q257uOeFstDBZEJLDIR+oJ/
+qZ+5x0zsXUVWaERSdYr3RF0CgYEApKDgN3oB5Ti4Jnh1984aMver+heptYKmU9RX
+lQH4dsVhpQO8UTgcTgtso+/0JZWLHB9+ksFyW1rzrcETfjLglOA4XzzYHeuiWHM5
+v4lhqBpsO+Ij80oHAPUI3RYVud/VnEauCUlGftWfM1hwPPJu6KhHAnDleAWDE5pV
+oDinwBkCgYEAnn/OceaqA2fNYp1IRegbFzpewjUlHLq3bXiCIVhO7W/HqsdfUxjE
+VVdjEno/pAG7ZCO5j8u+rLkG2ZIVY3qsUENUiXz52Q08qEltgM8nfirK7vIQkfd9
+YISRE3QHYJd+ArY4v+7rNeF1O5eIEyzPAbvG5raeZFcZ6POxy66uWKo=
+-----END RSA PRIVATE KEY-----
diff --git a/src/leap/common/testing/test_basetest.py b/src/leap/common/testing/test_basetest.py
new file mode 100644 (file)
index 0000000..3674185
--- /dev/null
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+# leap.common.testing.test_basetest
+# 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/>.
+"""
+Unittests for base test
+...becase it's oh so meta"""
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
+
+import os
+import StringIO
+
+from leap.testing.basetest import BaseLeapTest
+
+# global for tempdir checking
+_tempdir = None
+
+
+class _TestCaseRunner(object):
+    def run_testcase(self, testcase=None):
+        if not testcase:
+            return None
+        loader = unittest.TestLoader()
+        suite = loader.loadTestsFromTestCase(testcase)
+
+        # Create runner, and run testcase
+        io = StringIO.StringIO()
+        runner = unittest.TextTestRunner(stream=io)
+        results = runner.run(suite)
+        return results
+
+
+class TestAbstractBaseLeapTest(unittest.TestCase, _TestCaseRunner):
+
+    def test_abstract_base_class(self):
+        class _BaseTest(BaseLeapTest):
+            def test_dummy_method(self):
+                pass
+
+            def test_tautology(self):
+                assert True
+
+        results = self.run_testcase(_BaseTest)
+
+        # should be 2 errors: NotImplemented
+        # raised for setUp/tearDown
+        self.assertEquals(results.testsRun, 2)
+        self.assertEquals(len(results.failures), 0)
+        self.assertEquals(len(results.errors), 2)
+
+
+class TestInitBaseLeapTest(BaseLeapTest):
+
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def test_path_is_changed(self):
+        os_path = os.environ['PATH']
+        self.assertTrue(os_path.startswith(self.tempdir))
+
+    def test_old_path_is_saved(self):
+        self.assertTrue(len(self.old_path) > 1)
+
+
+class TestCleanedBaseLeapTest(unittest.TestCase, _TestCaseRunner):
+
+    def test_tempdir_is_cleaned_after_tests(self):
+        class _BaseTest(BaseLeapTest):
+            def setUp(self):
+                global _tempdir
+                _tempdir = self.tempdir
+
+            def tearDown(self):
+                pass
+
+            def test_tempdir_created(self):
+                self.assertTrue(os.path.isdir(self.tempdir))
+
+            def test_tempdir_created_on_setupclass(self):
+                self.assertEqual(_tempdir, self.tempdir)
+
+        results = self.run_testcase(_BaseTest)
+        self.assertEquals(results.testsRun, 2)
+        self.assertEquals(len(results.failures), 0)
+        self.assertEquals(len(results.errors), 0)
+
+        # did we cleaned the tempdir?
+        self.assertFalse(os.path.isdir(_tempdir))
+
+if __name__ == "__main__":
+    unittest.main()