Skip to content

Commit d26faac

Browse files
author
e107109
committed
Fix array payload issue
1 parent ff95dcf commit d26faac

File tree

3 files changed

+198
-9
lines changed

3 files changed

+198
-9
lines changed

client_encryption/field_level_encryption.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ def encrypt_payload(payload, config, _params=None):
1212
"""Encrypt some fields of a JSON payload using the given configuration."""
1313

1414
try:
15-
json_payload = copy.deepcopy(payload) if type(payload) is dict else json.loads(payload)
15+
if type(payload) is dict or type(payload) is list:
16+
json_payload = copy.deepcopy(payload)
17+
else:
18+
json_payload = json.loads(payload)
1619

1720
for elem, target in config.paths["$"].to_encrypt.items():
1821
if not _params:
@@ -47,7 +50,10 @@ def decrypt_payload(payload, config, _params=None):
4750
"""Decrypt some fields of a JSON payload using the given configuration."""
4851

4952
try:
50-
json_payload = payload if type(payload) is dict else json.loads(payload)
53+
if type(payload) is dict or type(payload) is list:
54+
json_payload = payload
55+
else:
56+
json_payload = json.loads(payload)
5157

5258
for elem, target in config.paths["$"].to_decrypt.items():
5359
try:

client_encryption/json_path_utils.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ def update_node(tree, path, node_str):
4242
except json.JSONDecodeError:
4343
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:
45+
if type(current_node) is list:
46+
if to_set in current_node[0] and type(current_node[0][to_set]) is dict and type(node_json) is dict:
47+
current_node[0][to_set].update(node_json)
48+
else:
49+
current_node[0][to_set] = node_json
50+
elif to_set in current_node and type(current_node[to_set]) is dict and type(node_json) is dict:
4651
current_node[to_set].update(node_json)
4752
else:
4853
current_node[to_set] = node_json
@@ -66,7 +71,10 @@ def pop_node(tree, path):
6671
else:
6772
node = tree
6873

69-
deleted_elem = node.pop(to_delete)
74+
if type(node) is list:
75+
deleted_elem = node[0].pop(to_delete)
76+
else:
77+
deleted_elem = node.pop(to_delete)
7078
if isinstance(deleted_elem, str):
7179
return deleted_elem
7280
else:
@@ -91,8 +99,9 @@ def cleanup_node(tree, path, target):
9199
node = __get_node(tree, parent, False)
92100
else:
93101
node = tree
94-
95-
if not node[to_delete]:
102+
if type(node) is list and not node[0][to_delete]:
103+
del node[0][to_delete]
104+
elif not node[to_delete]:
96105
del node[to_delete]
97106

98107
else:
@@ -107,12 +116,23 @@ def __get_node(tree, node_list, create):
107116
last_node = node_list.pop()
108117

109118
for node in node_list:
110-
current = current[node]
119+
if type(current) is list:
120+
current = current[0][node]
121+
else:
122+
current = current[node]
111123

112-
if type(current) is not dict:
124+
if type(current) is not dict and type(current) is not list:
113125
raise ValueError("'" + current + "' is not of dict type")
114126

115-
if last_node not in current and create:
127+
if type(current) is list:
128+
if not current and create:
129+
d = dict()
130+
d[last_node] = {}
131+
current.append(d)
132+
elif last_node not in current[0] and create:
133+
current[0][last_node] = {}
134+
return current[0][last_node]
135+
elif last_node not in current and create:
116136
current[last_node] = {}
117137

118138
return current[last_node]

tests/test_field_level_encryption.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ def __assert_payload_encrypted(self, payload, encrypted, config):
7878
del payload["encryptedData"]
7979
self.assertEqual(payload, to_test.decrypt_payload(encrypted, config))
8080

81+
def __assert_array_payload_encrypted(self, payload, encrypted, config):
82+
self.assertNotIn("data", encrypted[0])
83+
self.assertIn("encryptedData", encrypted[0])
84+
enc_data = encrypted[0]["encryptedData"]
85+
self.assertEqual(6, len(enc_data.keys()))
86+
self.assertIsNotNone(enc_data["iv"])
87+
self.assertIsNotNone(enc_data["encryptedKey"])
88+
self.assertIsNotNone(enc_data["encryptedValue"])
89+
self.assertEqual("SHA256", enc_data["oaepHashingAlgo"])
90+
self.assertEqual("761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79", enc_data["keyFingerprint"])
91+
self.assertEqual("80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279", enc_data["certFingerprint"])
92+
del payload[0]["encryptedData"]
93+
self.assertEqual(payload, to_test.decrypt_payload(encrypted, config))
94+
8195
def test_encrypt_payload_base64_field_encoding(self):
8296
payload = {
8397
"data": {
@@ -155,6 +169,155 @@ def test_encrypt_payload_with_type_list(self):
155169
encrypted_payload = to_test.encrypt_payload(payload, self._config)
156170
self.__assert_payload_encrypted(payload, encrypted_payload, self._config)
157171

172+
def test_encrypt_array_payload_with_type_string(self):
173+
payload = [{
174+
"data": "item1",
175+
"encryptedData": {}
176+
}]
177+
178+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
179+
self.__assert_array_payload_encrypted(payload, encrypted_payload, self._config)
180+
181+
def test_encrypt_array_payload_with_type_list(self):
182+
payload = [{
183+
"data": ["item1", "item2", "item3"],
184+
"encryptedData": {}
185+
}]
186+
187+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
188+
self.__assert_array_payload_encrypted(payload, encrypted_payload, self._config)
189+
190+
def test_encrypt_array_payload_with_type_object(self):
191+
192+
payload = [{
193+
"data": {
194+
"field1": "value1",
195+
"field2": "value2"
196+
},
197+
"encryptedData": {}
198+
}]
199+
200+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
201+
self.__assert_array_payload_encrypted(payload, encrypted_payload, self._config)
202+
203+
def test_encrypt_array_payload_with_type_multiple_object(self):
204+
205+
payload = [{
206+
"data": {
207+
"field1": "value1",
208+
"field2": "value2"
209+
},
210+
"encryptedData": {}
211+
},
212+
{
213+
"data": {
214+
"field1": "value1",
215+
"field2": "value2"
216+
},
217+
"encryptedData": {}
218+
}
219+
]
220+
221+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
222+
self.__assert_array_payload_encrypted(payload, encrypted_payload, self._config)
223+
224+
def test_encrypt_array_payload_skip_when_in_path_does_not_exist(self):
225+
payload = [{
226+
"dataNotToEncrypt": {
227+
"field1": "value1",
228+
"field2": "value2"
229+
},
230+
"encryptedData": {}
231+
}]
232+
233+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
234+
235+
self.assertEqual(payload, encrypted_payload)
236+
237+
def test_encrypt_array_payload_create_node_when_out_path_parent_exists(self):
238+
self._config._paths["$"]._to_encrypt = {"data": "encryptedDataParent.encryptedData"}
239+
240+
payload = [{
241+
"data": {
242+
"field1": "value1",
243+
"field2": "value2"
244+
},
245+
"encryptedDataParent": {}
246+
}]
247+
248+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
249+
250+
self.assertNotIn("data", encrypted_payload[0])
251+
self.assertIn("encryptedDataParent", encrypted_payload[0])
252+
self.assertIn("encryptedData", encrypted_payload[0]["encryptedDataParent"])
253+
254+
def test_encrypt_array_payload_with_multiple_encryption_paths(self):
255+
self._config._paths["$"]._to_encrypt = {"data1": "encryptedData1", "data2": "encryptedData2"}
256+
257+
payload = [{
258+
"data1": {
259+
"field1": "value1",
260+
"field2": "value2"
261+
},
262+
"data2": {
263+
"field3": "value3",
264+
"field4": "value4"
265+
},
266+
"encryptedData1": {},
267+
"encryptedData2": {}
268+
}]
269+
270+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
271+
272+
self.assertNotIn("data1", encrypted_payload[0])
273+
self.assertNotIn("data2", encrypted_payload[0])
274+
enc_data1 = encrypted_payload[0]["encryptedData1"]
275+
enc_data2 = encrypted_payload[0]["encryptedData2"]
276+
self.assertIsNotNone(enc_data1["iv"])
277+
self.assertIsNotNone(enc_data1["encryptedKey"])
278+
self.assertIsNotNone(enc_data1["encryptedValue"])
279+
self.assertIsNotNone(enc_data2["iv"])
280+
self.assertIsNotNone(enc_data2["encryptedKey"])
281+
self.assertIsNotNone(enc_data2["encryptedValue"])
282+
self.assertNotEqual(enc_data1["iv"], enc_data2["iv"], "using same set of params")
283+
284+
def test_encrypt_array_payload_when_root_as_in_path(self):
285+
self._config._paths["$"]._to_encrypt = {"$": "encryptedData"}
286+
287+
payload = [{
288+
"field1": "value1",
289+
"field2": "value2"
290+
}]
291+
292+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
293+
294+
self.assertNotIn("field1", encrypted_payload[0])
295+
self.assertNotIn("field2", encrypted_payload[0])
296+
self.assertIn("encryptedData", encrypted_payload[0])
297+
self.assertEqual(6, len(encrypted_payload[0]["encryptedData"].keys()))
298+
299+
def test_encrypt_array_payload_when_out_path_same_as_in_path(self):
300+
self._config._paths["$"]._to_encrypt = {"data": "data"}
301+
302+
payload = [{
303+
"data": {
304+
"field1": "value1",
305+
"field2": "value2"
306+
}
307+
}]
308+
309+
encrypted_payload = to_test.encrypt_payload(payload, self._config)
310+
311+
self.assertIn("data", encrypted_payload[0])
312+
self.assertNotIn("field1", encrypted_payload[0]["data"])
313+
self.assertNotIn("field2", encrypted_payload[0]["data"])
314+
self.assertIn("iv", encrypted_payload[0]["data"])
315+
self.assertIn("encryptedKey", encrypted_payload[0]["data"])
316+
self.assertIn("encryptedValue", encrypted_payload[0]["data"])
317+
self.assertIn("certFingerprint", encrypted_payload[0]["data"])
318+
self.assertIn("keyFingerprint", encrypted_payload[0]["data"])
319+
self.assertIn("oaepHashingAlgo", encrypted_payload[0]["data"])
320+
158321
def test_encrypt_payload_skip_when_in_path_does_not_exist(self):
159322
payload = {
160323
"dataNotToEncrypt": {

0 commit comments

Comments
 (0)