Skip to content

Commit 32c46cc

Browse files
authored
Code cleanup (#103)
1 parent 4e7ea78 commit 32c46cc

File tree

4 files changed

+163
-134
lines changed

4 files changed

+163
-134
lines changed

tests/DccQrCode.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# ---license-start
2+
# eu-digital-green-certificates / dgc-testdata
3+
# ---
4+
# Copyright (C) 2021 T-Systems International GmbH and all other contributors
5+
# ---
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
# ---license-end
18+
19+
import os
20+
import re
21+
import cbor2
22+
import base64
23+
24+
from zlib import decompress
25+
from base45 import b45decode
26+
from cose.messages import Sign1Message
27+
from cose.headers import Algorithm, KID
28+
from datetime import date, datetime, timezone
29+
from PIL.Image import NONE, open as image_open
30+
from pyzbar.pyzbar import decode as qrcode_decode
31+
32+
33+
# Constants
34+
TIMESTAMP_ISO8601_EXTENDED = "%Y-%m-%dT%H:%M:%S.%fZ"
35+
36+
class DccQrCode():
37+
"Represents a DCC QR code based on a file"
38+
39+
def __init__(self, path):
40+
def datetime_to_string(decoder, value):
41+
'replace datetime objects with a string representation when loading the CBOR'
42+
return {k: v.astimezone(timezone.utc).strftime(TIMESTAMP_ISO8601_EXTENDED) \
43+
if isinstance(v, (date, datetime)) else v for k, v in value.items()}
44+
45+
self.file_path = path
46+
image = image_open( path )
47+
self.qr_code_data = qrcode_decode(image)[0].data.decode()
48+
if not self.qr_code_data.startswith('HC1:'):
49+
raise ValueError('Encoded data does not begin with magic number "HC1:"')
50+
self.decompressed = decompress(b45decode(self.qr_code_data[4:]))
51+
self.sign1Message = Sign1Message.decode(self.decompressed)
52+
self.payload = cbor2.loads(self.sign1Message.payload, object_hook=datetime_to_string)
53+
self._path_country = None
54+
55+
def get_key_id_base64(self):
56+
"returns the key ID of the COSE message"
57+
if KID in self.sign1Message.phdr:
58+
kid = self.sign1Message.phdr[KID]
59+
else:
60+
kid = self.sign1Message.uhdr[KID]
61+
62+
return base64.b64encode(kid).decode("ascii")
63+
64+
def get_path_schema_version(self):
65+
"""Returns the schema version that is encoded in the path (exactly 3 digits separated by dots)
66+
or None if no match is found."""
67+
_previous = None
68+
for subdir_name in self.file_path.split(os.sep):
69+
if re.match("^\\d\\.\\d\\.\\d$", subdir_name):
70+
self._path_country = _previous
71+
return subdir_name
72+
_previous = subdir_name
73+
return None # --> No path schema version
74+
75+
def get_path_country(self):
76+
"""Returns the country code that is encoded in the path right before the schema version"""
77+
if self._path_country is None:
78+
self.get_path_schema_version()
79+
return self._path_country
80+
81+
def get_file_name(self):
82+
return self.file_path.split(os.sep)[-1]

tests/conftest.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,36 @@
1+
import os
12
import pytest
3+
from glob import glob
4+
from pathlib import Path
5+
from DccQrCode import DccQrCode
26

37
def pytest_addoption(parser):
48
parser.addoption("-C", "--country-code", action="store", default="*", help="Country code of tests to run.")
59
parser.addoption("--no-signature-check", action="store_true", default=False, help="Do not verify the signature")
610
parser.addoption("--include-special", action="store_true", default=False, help="Include special cases")
711
parser.addoption("--allow-multi-dcc", action="store_true", default=False, help="Allow multiple DCC in one QR code")
812
parser.addoption("--warn-timedelta", action="store_true", default=False, help="Warn about time deltas")
13+
14+
def pytest_generate_tests(metafunc):
15+
def glob_files(country_code='*', include_special=False):
16+
"Find matching files"
17+
test_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18+
test_files = glob(
19+
str(Path(test_dir, country_code, "*", "*.png")), recursive=False)
20+
if include_special:
21+
test_files.extend( glob(
22+
str(Path(test_dir, country_code, "*", "specialcases", "*.png")), recursive=False) )
23+
return test_files
24+
25+
qr_code_files = glob_files( metafunc.config.getoption("country_code"), metafunc.config.getoption("include_special") )
26+
27+
if "dccQrCode" in metafunc.fixturenames:
28+
metafunc.parametrize("dccQrCode", qr_code_files, indirect=True)
29+
30+
@pytest.fixture
31+
def dccQrCode(request):
32+
"Create a DccQrCode object from the QR Code PNG file (and cache it)"
33+
if not request.param in dccQrCode.cache.keys():
34+
dccQrCode.cache[request.param] = DccQrCode(request.param)
35+
return dccQrCode.cache[request.param]
36+
dccQrCode.cache = {}

tests/constants.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# DCC related
2+
PAYLOAD_ISSUER, PAYLOAD_ISSUE_DATE, PAYLOAD_EXPIRY_DATE, PAYLOAD_HCERT = 1, 6, 4, -260
3+
DCC_TYPES = {'v': "VAC", 't': "TEST", 'r': "REC"}
4+
EXTENDED_KEY_USAGE_OIDs = {'t':'1.3.6.1.4.1.0.1847.2021.1.1','v':'1.3.6.1.4.1.0.1847.2021.1.2','r':'1.3.6.1.4.1.0.1847.2021.1.3',
5+
'T':'1.3.6.1.4.1.1847.2021.1.1', 'V':'1.3.6.1.4.1.1847.2021.1.2', 'R':'1.3.6.1.4.1.1847.2021.1.3'}
6+
7+
# URLs
8+
ACC_KID_LIST = 'https://dgca-verifier-service-eu-acc.cfapps.eu10.hana.ondemand.com/signercertificateStatus'
9+
ACC_CERT_LIST = 'https://dgca-verifier-service-eu-acc.cfapps.eu10.hana.ondemand.com/signercertificateUpdate'
10+
VALUESET_LIST = 'https://dgca-businessrule-service-eu-acc.cfapps.eu10.hana.ondemand.com/valuesets'
11+
SCHEMA_BASE_URI = 'https://raw.githubusercontent.com/ehn-dcc-development/ehn-dcc-schema/release/'
12+
13+
# Headers
14+
X_RESUME_TOKEN = 'x-resume-token'
15+
X_KID = 'X-KID'

0 commit comments

Comments
 (0)