diff --git a/.travis.yml b/.travis.yml index 1d0a871..4194ab7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,13 @@ language: python sudo: false python: - 2.7 + - 3.3 + - 3.4 branches: only: - master - develop install: - "pip install -r requirements.txt" -script: nosetests tests --logging-level=INFO +script: nosetests tests --with-coverage --cover-package=bankid + diff --git a/README.rst b/README.rst index 7d45b63..4bf4b4e 100644 --- a/README.rst +++ b/README.rst @@ -84,14 +84,7 @@ The PyBankID solution can be tested with ``nosetests``: .. code-block:: bash - nosetests tests/ --logging-level=INFO - -The logging level option is needed due to an issue with the `suds -`_ module, where a debug level line -breaks the tests. - -Only one test is run against the actual BankID test server, to see that the client -can connect and that all urls are up to date. + nosetests tests/ Documentation ------------- diff --git a/bankid/__init__.py b/bankid/__init__.py index e957b75..4be9b26 100644 --- a/bankid/__init__.py +++ b/bankid/__init__.py @@ -10,10 +10,10 @@ # release. 'dev' as a _version_extra string means this is a development # version. _version_major = 0 -_version_minor = 1 -_version_patch = 4 -#_version_extra = 'dev4' -#_version_extra = 'b1' +_version_minor = 2 +_version_patch = 0 +# _version_extra = 'dev4' +# _version_extra = 'alpha0' _version_extra = '' # Uncomment this for full releases # Construct full version string from these. @@ -28,8 +28,8 @@ description = "BankID client for Python" -long_description = \ -"""PyBankID is a client for performing BankID signing. +long_description = """ +PyBankID is a client for performing BankID signing. The Swedish BankID solution for digital signing uses a SOAP connection solution, and this module aims at providing a simplifying @@ -43,8 +43,9 @@ license = 'MIT' -authors = {'hbldh': ('Henrik Blidh', 'henrik.blidh@nedomkull.com'), - } +authors = { + 'hbldh': ('Henrik Blidh', 'henrik.blidh@nedomkull.com'), +} author = 'Henrik Blidh' author_email = 'henrik.blidh@nedomkull.com' url = 'https://github.com/hbldh/pybankid/' @@ -54,11 +55,13 @@ keywords = ['BankID', 'SOAP'] classifiers = [ 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX :: Linux', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X', - 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Topic :: Utilities' - ] +] diff --git a/bankid/client.py b/bankid/client.py index 2ca96e8..77f44fb 100644 --- a/bankid/client.py +++ b/bankid/client.py @@ -20,7 +20,7 @@ from __future__ import absolute_import import warnings -import StringIO +import six import base64 import datetime @@ -37,7 +37,7 @@ class BankIDClient(object): - def __init__(self, certificates, test_server=True): + def __init__(self, certificates, test_server=False): self.certs = certificates if test_server: @@ -76,7 +76,7 @@ def authenticate(self, personal_number, **kwargs): out = self.client.service.Authenticate( personalNumber=personal_number, **kwargs) except WebFault as e: - raise get_error_class(e)("Could not complete Authenticate order.") + raise get_error_class(e, "Could not complete Authenticate order.") return self._dictify(out) @@ -102,7 +102,7 @@ def sign(self, user_visible_data, personal_number=None, **kwargs): userVisibleData=base64.b64encode(user_visible_data), personalNumber=personal_number, **kwargs) except WebFault as e: - raise get_error_class(e)("Could not complete Sign order.") + raise get_error_class(e, "Could not complete Sign order.") return self._dictify(out) @@ -121,7 +121,7 @@ def collect(self, order_ref): try: out = self.client.service.Collect(orderRef=order_ref) except WebFault as e: - raise get_error_class(e)("Could not complete Collect call.") + raise get_error_class(e, "Could not complete Collect call.") return self._dictify(out) @@ -153,7 +153,7 @@ def _dictify(self, doc): try: for k in doc: if isinstance(doc[k], Text): - out[k] = unicode(doc[k]) + out[k] = doc[k].decode('utf8') elif isinstance(doc[k], datetime.datetime): out[k] = doc[k] else: @@ -163,6 +163,7 @@ def _dictify(self, doc): return out + class RequestsTransport(HttpAuthenticated): """A Requests-based transport for suds, enabling the use of https and certificates when communicating with the SOAP service. @@ -184,7 +185,7 @@ def open(self, request): resp = self.requests_session.get(request.url, data=request.message, headers=request.headers) - result = StringIO.StringIO(resp.content.decode('utf-8')) + result = six.BytesIO(six.b(resp.content.decode('utf-8'))) return result def send(self, request): diff --git a/bankid/exceptions.py b/bankid/exceptions.py index b59d9c6..16ea57c 100644 --- a/bankid/exceptions.py +++ b/bankid/exceptions.py @@ -6,7 +6,7 @@ .. module:: exceptions :platform: Unix, Windows - :synopsis: + :synopsis: .. moduleauthor:: hbldh @@ -19,15 +19,15 @@ from __future__ import unicode_literals from __future__ import absolute_import -import re +import six -def get_error_class(exc): - s = re.search("Server raised fault: '([\w_]+)'", unicode(exc)) - if s: - return _ERROR_CODE_TO_CLASS[s.groups()[0]] +def get_error_class(exc, exception_text): + error_class = _ERROR_CODE_TO_CLASS.get(six.text_type(exc.fault.faultstring)) + if error_class is None: + return BankIDError("{0}: {1}".format(exc, exception_text)) else: - raise exc + return error_class(exception_text) class BankIDError(Exception): diff --git a/bankid/testcert.py b/bankid/testcert.py index 9408eb0..03ba9fd 100644 --- a/bankid/testcert.py +++ b/bankid/testcert.py @@ -26,7 +26,7 @@ import requests _TEST_CERT_PASSWORD = 'qwerty123' -_TEST_CERT_URL = "https://www.bankid.com/assets/bankid/rp/fptestcert1.P12" +_TEST_CERT_URL = "https://www.bankid.com/assets/bankid/rp/FPTestcert2_20150818_102329.pfx" def create_test_server_cert_and_key(destination_path): @@ -63,10 +63,10 @@ def create_test_server_cert_and_key(destination_path): def split_test_cert_and_key(): """Fetch the P12 certificate from BankID servers, split it into a certificate part and a key part and return the two components as text data. - + :returns: Tuple of certificate and key string data. :rtype: tuple - + """ # Paths to temporary files. cert_tmp_path = os.path.join(tempfile.gettempdir(), os.path.basename(_TEST_CERT_URL)) @@ -114,6 +114,7 @@ def split_test_cert_and_key(): return certificate, key + def main(): paths = create_test_server_cert_and_key(os.path.expanduser('~')) print('Saved certificate as {0}'.format(paths[0])) diff --git a/requirements.txt b/requirements.txt index 9b2af19..535e5ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ requests>=2.7.0 -suds==0.4 +suds-jurko>=0.6 +six>=1.9.0 diff --git a/tests/test_client.py b/tests/test_client.py index e4b4ebe..df9b8b1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,7 +6,7 @@ .. module:: test_client :platform: Unix, Windows - :synopsis: + :synopsis: .. moduleauthor:: hbldh @@ -24,8 +24,11 @@ import tempfile import uuid +from nose.tools import raises + import bankid.client import bankid.testcert +import bankid.exceptions def get_random_personal_number(): @@ -37,7 +40,7 @@ def _luhn_digit(id_): Code adapted from [Faker] (https://github.com/joke2k/faker/blob/master/faker/providers/ssn/sv_SE/__init__.py) - :param id_: The partial number to calcualte checksum of. + :param id_: The partial number to calculate checksum of. :type id_: str :return: Integer digit in [0, 9]. :rtype: int @@ -62,21 +65,22 @@ def digits_of(n): pn = "{0:04d}{1:02d}{2:02d}{3:03d}".format(year, month, day, suffix) return pn + str(_luhn_digit(pn[2:])) + class TestClientOnTestServer(object): """Test Suite testing once against the actual BankID test server to test that connection can be made with BankIDClient. """ def __init__(self): - self.cert_file = None + self.certificate_file = None self.key_file = None - def setUp(self): + def setup(self): certificate, key = bankid.testcert.create_test_server_cert_and_key(tempfile.gettempdir()) self.certificate_file = certificate self.key_file = key - def tearDown(self): + def teardown(self): try: os.remove(self.certificate_file) os.remove(self.key_file) @@ -85,11 +89,23 @@ def tearDown(self): def test_authentication_and_collect(self): """Authenticate call and then collect with the returned orderRef UUID.""" - + c = bankid.client.BankIDClient(certificates=(self.certificate_file, self.key_file), test_server=True) out = c.authenticate(get_random_personal_number()) assert isinstance(out, dict) # UUID.__init__ performs the UUID compliance assertion. order_ref = uuid.UUID(out.get('orderRef'), version=4) - collect_status = c.collect(order_ref) - assert collect_status.get('progressStatus') in ('OUTSTANDING_TRANSACTION', 'NO_CLIENT') \ No newline at end of file + collect_status = c.collect(out.get('orderRef')) + assert collect_status.get('progressStatus') in ('OUTSTANDING_TRANSACTION', 'NO_CLIENT') + + @raises(bankid.exceptions.InvalidParametersError) + def test_invalid_orderref_raises_error(self): + c = bankid.client.BankIDClient(certificates=(self.certificate_file, self.key_file), test_server=True) + collect_status = c.collect('invalid-uuid') + + @raises(bankid.exceptions.AlreadyInProgressError) + def test_already_in_progress_raises_error(self): + c = bankid.client.BankIDClient(certificates=(self.certificate_file, self.key_file), test_server=True) + pn = get_random_personal_number() + out = c.authenticate(pn) + out2 = c.authenticate(pn) \ No newline at end of file