Skip to content

Commit

Permalink
Async and Sync clients, implementing v6 API (#59)
Browse files Browse the repository at this point in the history
Version 1.0.0

Breaking changes: Deprecating v5 of BankID API clients in favour of only v6 clients.

Sync and Async clients
Implementing the v6 API
Removing all v5 and v5.1 API implementations
Updated documentation
Corrected the example app to work with v1.0.0 of PyBankID
Contains and fixes #53, #54, #56, #57, #58

Big thanks to @tiwilliam and @mxamin for implementing the async client and v6 clients respectively.
  • Loading branch information
hbldh authored Mar 28, 2024
1 parent e7eb228 commit 16039a3
Show file tree
Hide file tree
Showing 39 changed files with 1,776 additions and 853 deletions.
18 changes: 9 additions & 9 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,21 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: [3.7, 3.8, 3.9, '3.10', '3.11']
os: [ubuntu-latest]
python-version: [3.9, '3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Upgrade pip. setuptools and wheel
run: python -m pip install --upgrade pip setuptools wheel

- name: Install dependencies
run: pip install -r requirements.txt

- name: Install development dependencies
run: pip install pytest pytest-cov mock flake8
run: pip install -r requirements-dev.txt

- name: Lint with flake8
run: |
Expand All @@ -47,8 +44,11 @@ jobs:
run: |
pytest tests --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml --cov=bankid --cov-report=xml --cov-report=html
- name: Upgrade pip. setuptools and wheel
run: python -m pip install --upgrade pip setuptools wheel

- name: Upload pytest test results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }}
path: junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pypi-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ jobs:

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.x'

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Created by .ignore support plugin (hsz.mobi)
.vscode/

### Python template
# Byte-compiled / optimized / DLL files
Expand Down Expand Up @@ -26,6 +27,8 @@ var/
*.egg-info/
.installed.cfg
*.egg
.venv
.python-version

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down
113 changes: 70 additions & 43 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ providing authentication and signing functionality to end users. This package
provides a simplifying interface for initiating authentication
and signing orders and then collecting the results from the BankID servers.

The only supported BankID API version supported by PyBankID from version 1.0.0
is v6.0, which means that the Secure Start solution is the only supported way
of providing BankID services. PyBankID versions prior to 1.0.0 will not
work after 1st of May 2024.

If you intend to use PyBankID in your project, you are advised to read
the `BankID Relying Party Guidelines
<https://www.bankid.com/utvecklare/rp-info>`_ before
the `BankID Integration Guide
<https://www.bankid.com/en/utvecklare/guider/teknisk-integrationsguide>`_ before
doing anything else. There, one can find information
about how the BankID methods are defined and how to use them.

Expand All @@ -36,17 +41,21 @@ PyBankID can be installed though pip:
Usage
-----

``BankIDJSONClient`` is the client to be used to
communicate with the BankID service. It uses the JSON 5.1 API released in April 2020.
PyBankID provides both a synchronous and an asynchronous client for
communication with BankID services. Example below will use the asynchronous
client, but the synchronous client is used in the same way by merely omitting
the ``await`` keyword.

JSON client
~~~~~~~~~~~
Synchronous client
~~~~~~~~~~~~~~~~~~

.. code-block:: python
>>> from bankid import BankIDJSONClient
>>> client = BankIDJSONClient(certificates=('path/to/certificate.pem',
'path/to/key.pem'))
>>> from bankid import BankIDClient
>>> client = BankIDClient(certificates=(
'path/to/certificate.pem',
'path/to/key.pem',
))
Connection to production server is the default in the client. If test
server is desired, send in the ``test_server=True`` keyword in the init
Expand All @@ -58,8 +67,7 @@ is initiated as such:

.. code-block:: python
>>> client.authenticate(end_user_ip='194.168.2.25',
personal_number="YYYYMMDDXXXX")
>>> client.authenticate(end_user_ip='194.168.2.25')
{
'orderRef': 'ee3421ea-2096-4000-8130-82648efe0927',
'autoStartToken': 'e8df5c3c-c67b-4a01-bfe5-fefeab760beb',
Expand All @@ -71,19 +79,36 @@ and a sign order is initiated in a similar fashion:

.. code-block:: python
>>> client.sign(end_user_ip='194.168.2.25',
user_visible_data="The information to sign.",
personal_number="YYYYMMDDXXXX")
>>> client.sign(
end_user_ip='194.168.2.25',
user_visible_data="The information to sign."
)
{
'orderRef': 'ee3421ea-2096-4000-8130-82648efe0927',
'autoStartToken': 'e8df5c3c-c67b-4a01-bfe5-fefeab760beb',
'qrStartToken': '01f94e28-857f-4d8a-bf8e-6c5a24466658',
'qrStartSecret': 'b4214886-3b5b-46ab-bc08-6862fddc0e06'
}
If you want to ascertain that only one individual can authenticate or sign, you can
specify this using the ``requirement`` keyword:

.. code-block:: python
>>> client.sign(
end_user_ip='194.168.2.25',
user_visible_data="The information to sign."
requirement={"personalNumber": "YYYYMMDDXXXX"}
)
{
'orderRef': 'ee3421ea-2096-4000-8130-82648efe0927',
'autoStartToken': 'e8df5c3c-c67b-4a01-bfe5-fefeab760beb',
'qrStartToken': '01f94e28-857f-4d8a-bf8e-6c5a24466658',
'qrStartSecret': 'b4214886-3b5b-46ab-bc08-6862fddc0e06'
}
Since the ``BankIDJSONClient`` is using the BankID ``v5`` JSON API, the ``personal_number`` can now be omitted when calling
``authenticate`` and ``sign``. See BankID Relying Party Guidelines
for more information about this.
If someone else than the one you specified tries to authenticate or sign, the
BankID app will state that the request is not intended for the user.

The status of an order can then be studied by polling
with the ``collect`` method using the received ``orderRef``:
Expand Down Expand Up @@ -126,38 +151,40 @@ with the ``collect`` method using the received ``orderRef``:
}
Please note that the ``collect`` method should be used sparingly: in the
BankID Relying Party Guidelines
it states that *"collect should be called every two seconds and must not be
`BankID Integration Guide <https://www.bankid.com/en/utvecklare/guider/teknisk-integrationsguide>`_
it is specified that *"collect should be called every two seconds and must not be
called more frequent than once per second"*.

PyBankID and QR code
--------------------
Asynchronous client
~~~~~~~~~~~~~~~~~~~

PyBankID cannot generate QR codes for you, but there is an example application in the
`examples folder of the repo <https://github.com/hbldh/pybankid/tree/master/examples>`_ where a
Flask application called ``qrdemo`` shows one way to do authentication with animated QR codes.
The asynchronous client is used in the same way as the asynchronous client, but the
methods are blocking.

The content for the QR code is generated by this method:
The synchronous guide above can be used as a reference for the asynchronous client
as well, by simply adding the ``await`` keyword:

.. code-block:: python
import hashlib
import hmac
from math import floor
import time
>>> from bankid import BankIDAsyncClient
>>> client = BankIDAsyncClient(certificates=(
'path/to/certificate.pem',
'path/to/key.pem',
))
>>> await client.authenticate(end_user_ip='194.168.2.25')
{
'orderRef': 'ee3421ea-2096-4000-8130-82648efe0927',
'autoStartToken': 'e8df5c3c-c67b-4a01-bfe5-fefeab760beb',
'qrStartToken': '01f94e28-857f-4d8a-bf8e-6c5a24466658',
'qrStartSecret': 'b4214886-3b5b-46ab-bc08-6862fddc0e06'
}
def generate_qr_code_content(qr_start_token: str, start_t: float, qr_start_secret: str):
"""Given QR start token, time.time() when initiated authentication call was made and the
QR start secret, calculate the current QR code content to display.
"""
elapsed_seconds_since_call = int(floor(time.time() - start_t))
qr_auth_code = hmac.new(
qr_start_secret.encode(),
msg=str(elapsed_seconds_since_call).encode(),
digestmod=hashlib.sha256,
).hexdigest()
return f"bankid.{qr_start_token}.{elapsed_seconds_since_call}.{qr_auth_code}"
PyBankID and QR codes
~~~~~~~~~~~~~~~~~~~~~

PyBankID can generate QR codes for you, and there is an example application in the
`examples folder of the repo <https://github.com/hbldh/pybankid/tree/master/examples>`_ where a
Flask application called ``qrdemo`` shows one way to do authentication with animated QR codes.

Certificates
------------
Expand All @@ -167,7 +194,7 @@ Production certificates

If you want to use BankID in a production environment, then you will have to
purchase this service from one of the
`selling banks <https://www.bankid.com/kontakt/foeretag/saeljare>`_.
`selling banks <https://www.bankid.com/foretag/anslut-foeretag>`_.
They will then provide you with a certificate that can be used to authenticate
your company/application with the BankID servers.

Expand All @@ -189,7 +216,7 @@ be obtained through PyBankID:
dir_to_save_cert_and_key_in)
>>> print(cert_and_key)
['/home/hbldh/certificate.pem', '/home/hbldh/key.pem']
>>> client = bankid.BankIDJSONClient(
>>> client = bankid.BankIDClient(
certificates=cert_and_key, test_server=True)
Testing
Expand All @@ -199,4 +226,4 @@ The PyBankID solution can be tested with `pytest <https://pytest.org/>`_:

.. code-block:: bash
py.test
py.test tests/
17 changes: 9 additions & 8 deletions bankid/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:mod:`bankid`
Expand All @@ -12,20 +11,22 @@
and signing orders and then collecting the results from the BankID servers.
If you intend to use PyBankID in your project, you are advised to read
the `BankID Relying Party Guidelines
<https://www.bankid.com/utvecklare/rp-info>`_ before
the `BankID Relying Party Integration Guide
<https://www.bankid.com/en/utvecklare/guider/teknisk-integrationsguide>`_ before
doing anything else. There, one can find information
about how the BankID methods are defined and how to use them.
"""

from .jsonclient import BankIDJSONClient
from .certutils import create_bankid_test_server_cert_and_key
from .__version__ import __version__, version
import bankid.exceptions
from bankid import exceptions
from bankid.__version__ import __version__, version
from bankid.certutils import create_bankid_test_server_cert_and_key
from bankid.syncclient import BankIDClient
from bankid.asyncclient import BankIDAsyncClient

__all__ = [
"BankIDJSONClient",
"BankIDClient",
"BankIDAsyncClient",
"exceptions",
"create_bankid_test_server_cert_and_key",
"__version__",
Expand Down
3 changes: 1 addition & 2 deletions bankid/__version__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Version info
"""

__version__ = "0.14.1"
__version__ = "1.0.0"
version = __version__ # backwards compatibility name
Loading

0 comments on commit 16039a3

Please sign in to comment.