Skip to content

Commit 5e37f41

Browse files
authored
Merge pull request #10 from Mastercard/enc_openapi_5.2.1
Changes to make compatible with OpenAPI Generator 5.2.1
2 parents 3ef12f1 + ce5effb commit 5e37f41

File tree

6 files changed

+111
-57
lines changed

6 files changed

+111
-57
lines changed

client_encryption/api_encryption.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import inspect
23
from functools import wraps
34
from warnings import warn
45
from client_encryption.field_level_encryption_config import FieldLevelEncryptionConfig
@@ -17,19 +18,41 @@ def __init__(self, encryption_conf_file):
1718
with open(encryption_conf_file, encoding='utf-8') as json_file:
1819
self._encryption_conf = FieldLevelEncryptionConfig(json_file.read())
1920

21+
def field_encryption_call_api(self, func):
22+
"""Decorator for API call_api. func is APIClient.call_api"""
23+
24+
@wraps(func)
25+
def call_api_function(*args, **kwargs):
26+
check_type = inspect.signature(func.__self__.call_api).parameters.get("_check_type") is None
27+
if check_type:
28+
kwargs["_preload_content"] = False # version 4.3.1
29+
return func(*args, **kwargs)
30+
31+
call_api_function.__fle__ = True
32+
33+
return call_api_function
34+
2035
def field_encryption(self, func):
2136
"""Decorator for API call_api. func is APIClient.call_api"""
2237

2338
@wraps(func)
2439
def call_api_function(*args, **kwargs):
2540
"""Wrap call_api and add field encryption layer to it."""
2641

42+
check_type = inspect.signature(func.__self__.call_api).parameters.get("_check_type") is not None
43+
2744
in_body = kwargs.get("body", None)
28-
kwargs["body"] = self._encrypt_payload(args[4], in_body) if in_body else in_body
29-
kwargs["_preload_content"] = False
45+
46+
in_headers = kwargs.get("headers", None)
47+
48+
kwargs["body"] = self._encrypt_payload(in_headers, in_body) if in_body else in_body
3049

3150
response = func(*args, **kwargs)
32-
response._body = self._decrypt_payload(response.getheaders(), response.data)
51+
52+
if check_type:
53+
response.data = self._decrypt_payload(response.getheaders(), response.data)
54+
else:
55+
response._body = self._decrypt_payload(response.getheaders(), response.data)
3356

3457
return response
3558

@@ -98,14 +121,15 @@ def add_encryption_layer(api_client, encryption_conf_file):
98121
"""Decorate APIClient.call_api with field level encryption"""
99122

100123
api_encryption = ApiEncryption(encryption_conf_file)
101-
api_client.call_api = api_encryption.field_encryption(api_client.call_api)
124+
api_client.request = api_encryption.field_encryption(api_client.request)
125+
api_client.call_api = api_encryption.field_encryption_call_api(api_client.call_api)
102126

103127
__check_oauth(api_client) # warn the user if authentication layer is missing/not set
104128

105129

106130
def __check_oauth(api_client):
107131
try:
108-
api_client.request.__wrapped__
132+
api_client.rest_client.request.__wrapped__
109133
except AttributeError:
110134
__oauth_warn()
111135

client_encryption/json_path_utils.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def get_node(tree, path, create=False):
2121
if __not_root(path):
2222
current = __get_node(tree, path.split(_SEPARATOR), create)
2323

24-
return current
24+
return current # is a dict
2525

2626

2727
def update_node(tree, path, node_str):
@@ -37,11 +37,15 @@ def update_node(tree, path, node_str):
3737
else:
3838
current_node = tree
3939

40-
if to_set in current_node and type(current_node[to_set]) is dict and type(json.loads(node_str)) is dict:
41-
current_node[to_set].update(json.loads(node_str))
42-
else:
43-
current_node[to_set] = json.loads(node_str)
40+
try:
41+
node_json = json.loads(node_str)
42+
except json.JSONDecodeError:
43+
node_json = node_str
4444

45+
if to_set in current_node and type(current_node[to_set]) is dict and type(node_json) is dict:
46+
current_node[to_set].update(node_json)
47+
else:
48+
current_node[to_set] = node_json
4549
else:
4650
tree.clear()
4751
tree.update(json.loads(node_str))
@@ -62,7 +66,11 @@ def pop_node(tree, path):
6266
else:
6367
node = tree
6468

65-
return json.dumps(node.pop(to_delete))
69+
deleted_elem = node.pop(to_delete)
70+
if isinstance(deleted_elem, str):
71+
return deleted_elem
72+
else:
73+
return json.dumps(deleted_elem)
6674

6775
else:
6876
node = json.dumps(tree)

tests/test_api_encryption.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import unittest
22
from unittest.mock import patch, Mock
33
import json
4-
from tests.utils.api_encryption_test_utils import MockApiClient, MockService
4+
from tests.utils.api_encryption_test_utils import MockApiClient, MockService, MockRestApiClient
55
from tests import get_config_for_test, TEST_CONFIG
66
import client_encryption.api_encryption as to_test
77

@@ -170,9 +170,9 @@ def test_add_encryption_layer_post(self):
170170
}
171171
}, headers={"Content-Type": "application/json"})
172172

173-
self.assertIn("data", response.data)
174-
self.assertIn("secret", response.data["data"])
175-
self.assertEqual(secret2-secret1, response.data["data"]["secret"])
173+
self.assertIn("data", json.loads(response.data))
174+
self.assertIn("secret", json.loads(response.data)["data"])
175+
self.assertEqual(secret2-secret1, json.loads(response.data)["data"]["secret"])
176176
self.assertDictEqual({"Content-Type": "application/json"}, response.getheaders())
177177

178178
def test_add_encryption_layer_delete(self):
@@ -187,17 +187,18 @@ def test_add_encryption_layer_delete(self):
187187
}
188188
}, headers={"Content-Type": "application/json"})
189189

190-
self.assertEqual("OK", response.data)
190+
self.assertEqual("OK", json.loads(response.data))
191191
self.assertDictEqual({"Content-Type": "application/json"}, response.getheaders())
192192

193193
def test_add_encryption_layer_get(self):
194194
test_client = MockApiClient()
195195
to_test.add_encryption_layer(test_client, self._json_config)
196196
response = MockService(test_client).do_something_get(headers={"Content-Type": "application/json"})
197+
json_res = json.loads(response.data)
197198

198-
self.assertIn("data", response.data)
199-
self.assertIn("secret", response.data["data"])
200-
self.assertEqual([53, 84, 75], response.data["data"]["secret"])
199+
self.assertIn("data", json_res)
200+
self.assertIn("secret", json_res['data'])
201+
self.assertEqual([53, 84, 75], json_res["data"]["secret"])
201202
self.assertDictEqual({"Content-Type": "application/json"}, response.getheaders())
202203

203204
def test_add_header_encryption_layer_post_no_oaep_algo(self):
@@ -216,9 +217,9 @@ def test_add_header_encryption_layer_post_no_oaep_algo(self):
216217
"encryptedData": {}
217218
}, headers={"Content-Type": "application/json"})
218219

219-
self.assertIn("data", response.data)
220-
self.assertIn("secret", response.data["data"])
221-
self.assertEqual(secret2-secret1, response.data["data"]["secret"])
220+
self.assertIn("data", json.loads(response.data))
221+
self.assertIn("secret", json.loads(response.data)["data"])
222+
self.assertEqual(secret2-secret1, json.loads(response.data)["data"]["secret"])
222223
self.assertDictEqual({"Content-Type": "application/json", "x-oaep-digest": "SHA256"}, response.getheaders())
223224

224225
def test_add_header_encryption_layer_post_no_cert_fingerprint(self):
@@ -237,9 +238,9 @@ def test_add_header_encryption_layer_post_no_cert_fingerprint(self):
237238
"encryptedData": {}
238239
}, headers={"Content-Type": "application/json"})
239240

240-
self.assertIn("data", response.data)
241-
self.assertIn("secret", response.data["data"])
242-
self.assertEqual(secret2-secret1, response.data["data"]["secret"])
241+
self.assertIn("data", json.loads(response.data))
242+
self.assertIn("secret", json.loads(response.data)["data"])
243+
self.assertEqual(secret2-secret1, json.loads(response.data)["data"]["secret"])
243244
self.assertDictEqual({"Content-Type": "application/json"}, response.getheaders())
244245

245246
def test_add_header_encryption_layer_post_no_pubkey_fingerprint(self):
@@ -258,9 +259,9 @@ def test_add_header_encryption_layer_post_no_pubkey_fingerprint(self):
258259
"encryptedData": {}
259260
}, headers={"Content-Type": "application/json"})
260261

261-
self.assertIn("data", response.data)
262-
self.assertIn("secret", response.data["data"])
263-
self.assertEqual(secret2-secret1, response.data["data"]["secret"])
262+
self.assertIn("data", json.loads(response.data))
263+
self.assertIn("secret", json.loads(response.data)["data"])
264+
self.assertEqual(secret2-secret1, json.loads(response.data)["data"]["secret"])
264265
self.assertDictEqual({"Content-Type": "application/json"}, response.getheaders())
265266

266267
def test_add_header_encryption_layer_no_iv(self):
@@ -294,9 +295,9 @@ def test_add_header_encryption_layer_post(self):
294295
"encryptedData": {}
295296
}, headers={"Content-Type": "application/json"})
296297

297-
self.assertIn("data", response.data)
298-
self.assertIn("secret", response.data["data"])
299-
self.assertEqual(secret2-secret1, response.data["data"]["secret"])
298+
self.assertIn("data", json.loads(response.data))
299+
self.assertIn("secret", json.loads(response.data)["data"])
300+
self.assertEqual(secret2-secret1, json.loads(response.data)["data"]["secret"])
300301
self.assertDictEqual({"Content-Type": "application/json"}, response.getheaders())
301302

302303
def test_add_header_encryption_layer_delete(self):
@@ -324,15 +325,16 @@ def test_add_header_encryption_layer_get(self):
324325
to_test.add_encryption_layer(test_client, self._json_config)
325326
response = MockService(test_client).do_something_get_use_headers(headers={"Content-Type": "application/json"})
326327

327-
self.assertIn("data", response.data)
328-
self.assertIn("secret", response.data["data"])
329-
self.assertEqual([53, 84, 75], response.data["data"]["secret"])
328+
self.assertIn("data", json.loads(response.data))
329+
self.assertIn("secret", json.loads(response.data)["data"])
330+
self.assertEqual([53, 84, 75], json.loads(response.data)["data"]["secret"])
330331
self.assertDictEqual({"Content-Type": "application/json"}, response.getheaders())
331332

332333
@patch('client_encryption.api_encryption.__oauth_warn')
333334
def test_add_encryption_layer_oauth_set(self, __oauth_warn):
334335
test_client = MockApiClient()
335-
to_test.add_encryption_layer(test_client, self._json_config)
336+
test_rest_client = MockRestApiClient(test_client)
337+
to_test.add_encryption_layer(test_rest_client, self._json_config)
336338

337339
assert not __oauth_warn.called
338340

tests/test_field_level_encryption.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ def test_encrypt_payload_with_type_integer(self):
128128
encrypted_payload = to_test.encrypt_payload(payload, self._config)
129129
self.__assert_payload_encrypted(payload, encrypted_payload, self._config)
130130

131+
def test_encrypt_payload_with_type_float(self):
132+
payload = {
133+
"data": 123.34,
134+
"encryptedData": {}
135+
}
136+
137+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
138+
self.__assert_payload_encrypted(payload, encrypted_payload, self._config)
139+
131140
def test_encrypt_payload_with_type_boolean(self):
132141
payload = {
133142
"data": False,

tests/test_json_path_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,9 @@ def test_update_node_empty_path(self):
147147

148148
def test_update_node_not_json(self):
149149
sample_json = self.__get_sample_json()
150+
node = to_test.update_node(sample_json, "node1.node2", "not a json string")
150151

151-
self.assertRaises(json.JSONDecodeError, to_test.update_node, sample_json, "node1.node2", "not a json string")
152+
self.assertIsInstance(node["node1"]["node2"], str, "not a json string")
152153

153154
def test_update_node_primitive_type(self):
154155
sample_json = self.__get_sample_json()
@@ -204,7 +205,7 @@ def test_pop_node(self):
204205
sample_json = self.__get_sample_json()
205206
node = to_test.pop_node(sample_json, "node1.node2.colour")
206207
self.assertIsInstance(node, str, "Not a string")
207-
self.assertEqual("red", json.loads(node))
208+
self.assertEqual("red", node)
208209
self.assertDictEqual({"node1": {
209210
"node2": {
210211
"shape": "circle",

tests/utils/api_encryption_test_utils.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,32 @@ def __init__(self, api_client=None):
2525
self.api_client = api_client
2626

2727
def do_something_get(self, **kwargs):
28-
return self.api_client.call_api("testservice", "GET", None, None, kwargs["headers"])
28+
return self.api_client.request("GET", "testservice", None, kwargs["headers"])
2929

3030
def do_something_post(self, **kwargs):
31-
return self.api_client.call_api("testservice", "POST", None, None, kwargs["headers"], body=kwargs["body"])
31+
return self.api_client.request("POST", "testservice", None, kwargs["headers"], post_params=None, body=kwargs["body"])
3232

3333
def do_something_delete(self, **kwargs):
34-
return self.api_client.call_api("testservice", "DELETE", None, None, kwargs["headers"], body=kwargs["body"])
34+
return self.api_client.request("DELETE", "testservice", None, kwargs["headers"], post_params=None, body=kwargs["body"])
3535

3636
def do_something_get_use_headers(self, **kwargs):
37-
return self.api_client.call_api("testservice/headers", "GET", None, None, kwargs["headers"])
37+
return self.api_client.request("GET", "testservice/headers", None, kwargs["headers"])
3838

3939
def do_something_post_use_headers(self, **kwargs):
40-
return self.api_client.call_api("testservice/headers", "POST", None, None, kwargs["headers"], body=kwargs["body"])
40+
return self.api_client.request("POST", "testservice/headers", None, headers=kwargs["headers"], post_params=None, body=kwargs["body"])
4141

4242
def do_something_delete_use_headers(self, **kwargs):
43-
return self.api_client.call_api("testservice/headers", "DELETE", None, None, kwargs["headers"], body=kwargs["body"])
43+
return self.api_client.request("DELETE", "testservice/headers", None, headers=kwargs["headers"], post_params=None, body=kwargs["body"])
44+
45+
46+
class MockRestApiClient(object):
47+
48+
def __init__(self, request):
49+
self.request = request
50+
self.rest_client = request
51+
52+
def call_api(self):
53+
pass
4454

4555

4656
class MockApiClient(object):
@@ -56,33 +66,25 @@ def __init__(self, configuration=None, header_name=None, header_value=None,
5666
def request(self, method, url, query_params=None, headers=None,
5767
post_params=None, body=None, _preload_content=True,
5868
_request_timeout=None):
59-
pass
60-
61-
def call_api(self, resource_path, method,
62-
path_params=None, query_params=None, header_params=None,
63-
body=None, post_params=None, files=None,
64-
response_type=None, auth_settings=None, async_req=None,
65-
_return_http_data_only=None, collection_formats=None,
66-
_preload_content=True, _request_timeout=None):
6769
check = -1
6870

6971
if body:
70-
if resource_path == "testservice/headers":
71-
iv = header_params["x-iv"]
72-
encrypted_key = header_params["x-key"]
73-
oaep_digest_algo = header_params["x-oaep-digest"] if "x-oaep-digest" in header_params else None
72+
if url == "testservice/headers":
73+
iv = headers["x-iv"]
74+
encrypted_key = headers["x-key"]
75+
oaep_digest_algo = headers["x-oaep-digest"] if "x-oaep-digest" in headers else None
7476

7577
params = SessionKeyParams(self._config, encrypted_key, iv, oaep_digest_algo)
7678
else:
7779
params = None
7880

7981
plain = encryption.decrypt_payload(body, self._config, params)
80-
check = plain["data"]["secret2"]-plain["data"]["secret1"]
82+
check = plain["data"]["secret2"] - plain["data"]["secret1"]
8183
res = {"data": {"secret": check}}
8284
else:
8385
res = {"data": {"secret": [53, 84, 75]}}
8486

85-
if resource_path == "testservice/headers" and method in ["GET", "POST", "PUT"]:
87+
if url == "testservice/headers" and method in ["GET", "POST", "PUT"]:
8688
params = SessionKeyParams.generate(self._config)
8789
json_resp = encryption.encrypt_payload(res, self._config, params)
8890

@@ -106,3 +108,11 @@ def call_api(self, resource_path, method,
106108
response.data = "OK" if check == 0 else "KO"
107109

108110
return response
111+
112+
def call_api(self, resource_path, method,
113+
path_params=None, query_params=None, header_params=None,
114+
body=None, post_params=None, files=None,
115+
response_type=None, auth_settings=None, async_req=None,
116+
_return_http_data_only=None, collection_formats=None,
117+
_preload_content=True, _request_timeout=None, _check_type=None):
118+
pass

0 commit comments

Comments
 (0)