Skip to content

Commit 683cb31

Browse files
authored
Merge branch 'master' into new-apify-storage-clients
2 parents 4e4fa93 + d49ce13 commit 683cb31

File tree

6 files changed

+919
-3069
lines changed

6 files changed

+919
-3069
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ All notable changes to this project will be documented in this file.
88
### 🚀 Features
99

1010
- Expose `logger` argument on `Actor.call` to control log redirection from started Actor run ([#487](https://github.com/apify/apify-sdk-python/pull/487)) ([aa6fa47](https://github.com/apify/apify-sdk-python/commit/aa6fa4750ea1bc7909be1191c0d276a2046930c2)) by [@Pijukatel](https://github.com/Pijukatel)
11+
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
12+
13+
### 🐛 Bug Fixes
14+
15+
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
1116

1217

1318
<!-- git-cliff-unreleased-end -->

src/apify/_consts.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
EVENT_LISTENERS_TIMEOUT = timedelta(seconds=5)
77

88
BASE64_REGEXP = '[-A-Za-z0-9+/]*={0,3}'
9-
ENCRYPTED_INPUT_VALUE_PREFIX = 'ENCRYPTED_VALUE'
10-
ENCRYPTED_INPUT_VALUE_REGEXP = re.compile(f'^{ENCRYPTED_INPUT_VALUE_PREFIX}:({BASE64_REGEXP}):({BASE64_REGEXP})$')
9+
ENCRYPTED_STRING_VALUE_PREFIX = 'ENCRYPTED_VALUE'
10+
ENCRYPTED_JSON_VALUE_PREFIX = 'ENCRYPTED_JSON'
11+
ENCRYPTED_INPUT_VALUE_REGEXP = re.compile(
12+
f'^({ENCRYPTED_STRING_VALUE_PREFIX}|{ENCRYPTED_JSON_VALUE_PREFIX}):(?:({BASE64_REGEXP}):)?({BASE64_REGEXP}):({BASE64_REGEXP})$'
13+
)

src/apify/_crypto.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import base64
44
import hashlib
55
import hmac
6+
import json
67
import string
78
from typing import Any
89

@@ -14,7 +15,7 @@
1415
from apify_shared.utils import ignore_docs
1516
from crawlee._utils.crypto import crypto_random_object_id
1617

17-
from apify._consts import ENCRYPTED_INPUT_VALUE_REGEXP
18+
from apify._consts import ENCRYPTED_INPUT_VALUE_REGEXP, ENCRYPTED_JSON_VALUE_PREFIX, ENCRYPTED_STRING_VALUE_PREFIX
1819

1920
ENCRYPTION_KEY_LENGTH = 32
2021
ENCRYPTION_IV_LENGTH = 16
@@ -147,14 +148,20 @@ def decrypt_input_secrets(private_key: rsa.RSAPrivateKey, input_data: Any) -> An
147148
if isinstance(value, str):
148149
match = ENCRYPTED_INPUT_VALUE_REGEXP.fullmatch(value)
149150
if match:
150-
encrypted_password = match.group(1)
151-
encrypted_value = match.group(2)
152-
input_data[key] = private_decrypt(
151+
prefix = match.group(1)
152+
encrypted_password = match.group(3)
153+
encrypted_value = match.group(4)
154+
decrypted_value = private_decrypt(
153155
encrypted_password,
154156
encrypted_value,
155157
private_key=private_key,
156158
)
157159

160+
if prefix == ENCRYPTED_STRING_VALUE_PREFIX:
161+
input_data[key] = decrypted_value
162+
elif prefix == ENCRYPTED_JSON_VALUE_PREFIX:
163+
input_data[key] = json.loads(decrypted_value)
164+
158165
return input_data
159166

160167

tests/unit/actor/test_actor_key_value_store.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from ..test_crypto import PRIVATE_KEY_PASSWORD, PRIVATE_KEY_PEM_BASE64, PUBLIC_KEY
88
from apify import Actor
9-
from apify._consts import ENCRYPTED_INPUT_VALUE_PREFIX
9+
from apify._consts import ENCRYPTED_JSON_VALUE_PREFIX, ENCRYPTED_STRING_VALUE_PREFIX
1010
from apify._crypto import public_encrypt
1111

1212

@@ -59,15 +59,49 @@ async def test_get_input_with_encrypted_secrets(monkeypatch: pytest.MonkeyPatch)
5959
monkeypatch.setenv(ApifyEnvVars.INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE, PRIVATE_KEY_PASSWORD)
6060

6161
input_key = 'INPUT'
62+
secret_string_legacy = 'secret-string'
6263
secret_string = 'secret-string'
63-
encrypted_secret = public_encrypt(secret_string, public_key=PUBLIC_KEY)
64+
secret_object = {'foo': 'bar', 'baz': 'qux'}
65+
secret_array = ['foo', 'bar', 'baz']
66+
67+
# The legacy encryption format uses ENCRYPTED_STRING_VALUE_PREFIX prefix, value in raw string and does
68+
# not include schemahash. The new format uses ENCRYPTED_JSON_VALUE_PREFIX prefix, value in JSON format
69+
# and includes schemahash. We are testing both formats to ensure backward compatibility.
70+
71+
encrypted_string_legacy = public_encrypt(secret_string_legacy, public_key=PUBLIC_KEY)
72+
encrypted_string = public_encrypt(json_dumps(secret_string), public_key=PUBLIC_KEY)
73+
encrypted_object = public_encrypt(json_dumps(secret_object), public_key=PUBLIC_KEY)
74+
encrypted_array = public_encrypt(json_dumps(secret_array), public_key=PUBLIC_KEY)
75+
6476
input_with_secret = {
6577
'foo': 'bar',
66-
'secret': f'{ENCRYPTED_INPUT_VALUE_PREFIX}:{encrypted_secret["encrypted_password"]}:{encrypted_secret["encrypted_value"]}', # noqa: E501
78+
'secret_string_legacy': (
79+
f'{ENCRYPTED_STRING_VALUE_PREFIX}:'
80+
f'{encrypted_string_legacy["encrypted_password"]}:'
81+
f'{encrypted_string_legacy["encrypted_value"]}'
82+
),
83+
'secret_string': (
84+
f'{ENCRYPTED_JSON_VALUE_PREFIX}:schemahash:'
85+
f'{encrypted_string["encrypted_password"]}:'
86+
f'{encrypted_string["encrypted_value"]}'
87+
),
88+
'secret_object': (
89+
f'{ENCRYPTED_JSON_VALUE_PREFIX}:schemahash:'
90+
f'{encrypted_object["encrypted_password"]}:'
91+
f'{encrypted_object["encrypted_value"]}'
92+
),
93+
'secret_array': (
94+
f'{ENCRYPTED_JSON_VALUE_PREFIX}:schemahash:'
95+
f'{encrypted_array["encrypted_password"]}:'
96+
f'{encrypted_array["encrypted_value"]}'
97+
),
6798
}
6899

69100
async with Actor as actor:
70-
await actor.set_value(key=input_key, value=input_with_secret)
101+
await actor.set_value(key=input_key, value=input_with_secret, content_type='application/json')
71102
actor_input = await actor.get_input()
72103
assert actor_input['foo'] == input_with_secret['foo']
73-
assert actor_input['secret'] == secret_string
104+
assert actor_input['secret_string_legacy'] == secret_string_legacy
105+
assert actor_input['secret_string'] == secret_string
106+
assert actor_input['secret_object'] == secret_object
107+
assert actor_input['secret_array'] == secret_array

0 commit comments

Comments
 (0)