airbyte-cdk 0.62.1__py3-none-any.whl → 0.62.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +15 -3
- airbyte_cdk/utils/airbyte_secrets_utils.py +8 -2
- {airbyte_cdk-0.62.1.dist-info → airbyte_cdk-0.62.2.dist-info}/METADATA +1 -1
- {airbyte_cdk-0.62.1.dist-info → airbyte_cdk-0.62.2.dist-info}/RECORD +9 -9
- unit_tests/sources/declarative/auth/test_oauth.py +27 -0
- unit_tests/utils/test_secret_utils.py +13 -1
- {airbyte_cdk-0.62.1.dist-info → airbyte_cdk-0.62.2.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-0.62.1.dist-info → airbyte_cdk-0.62.2.dist-info}/WHEEL +0 -0
- {airbyte_cdk-0.62.1.dist-info → airbyte_cdk-0.62.2.dist-info}/top_level.txt +0 -0
@@ -14,6 +14,7 @@ from airbyte_cdk.models import FailureType, Level
|
|
14
14
|
from airbyte_cdk.sources.http_logger import format_http_message
|
15
15
|
from airbyte_cdk.sources.message import MessageRepository, NoopMessageRepository
|
16
16
|
from airbyte_cdk.utils import AirbyteTracedException
|
17
|
+
from airbyte_cdk.utils.airbyte_secrets_utils import add_to_secrets
|
17
18
|
from requests.auth import AuthBase
|
18
19
|
|
19
20
|
from ..exceptions import DefaultBackoffException
|
@@ -115,9 +116,20 @@ class AbstractOauth2Authenticator(AuthBase):
|
|
115
116
|
def _get_refresh_access_token_response(self) -> Any:
|
116
117
|
try:
|
117
118
|
response = requests.request(method="POST", url=self.get_token_refresh_endpoint(), data=self.build_refresh_request_body())
|
118
|
-
|
119
|
-
|
120
|
-
|
119
|
+
if response.ok:
|
120
|
+
response_json = response.json()
|
121
|
+
# Add the access token to the list of secrets so it is replaced before logging the response
|
122
|
+
# An argument could be made to remove the prevous access key from the list of secrets, but unmasking values seems like a security incident waiting to happen...
|
123
|
+
access_key = response_json.get(self.get_access_token_name())
|
124
|
+
if not access_key:
|
125
|
+
raise Exception("Token refresh API response was missing access token {self.get_access_token_name()}")
|
126
|
+
add_to_secrets(access_key)
|
127
|
+
self._log_response(response)
|
128
|
+
return response_json
|
129
|
+
else:
|
130
|
+
# log the response even if the request failed for troubleshooting purposes
|
131
|
+
self._log_response(response)
|
132
|
+
response.raise_for_status()
|
121
133
|
except requests.exceptions.RequestException as e:
|
122
134
|
if e.response is not None:
|
123
135
|
if e.response.status_code == 429 or e.response.status_code >= 500:
|
@@ -10,7 +10,7 @@ import dpath.util
|
|
10
10
|
def get_secret_paths(spec: Mapping[str, Any]) -> List[List[str]]:
|
11
11
|
paths = []
|
12
12
|
|
13
|
-
def traverse_schema(schema_item: Any, path: List[str]):
|
13
|
+
def traverse_schema(schema_item: Any, path: List[str]) -> None:
|
14
14
|
"""
|
15
15
|
schema_item can be any property or value in the originally input jsonschema, depending on how far down the recursion stack we go
|
16
16
|
path is the path to that schema item in the original input
|
@@ -56,12 +56,18 @@ def get_secrets(connection_specification: Mapping[str, Any], config: Mapping[str
|
|
56
56
|
__SECRETS_FROM_CONFIG: List[str] = []
|
57
57
|
|
58
58
|
|
59
|
-
def update_secrets(secrets: List[str]):
|
59
|
+
def update_secrets(secrets: List[str]) -> None:
|
60
60
|
"""Update the list of secrets to be replaced"""
|
61
61
|
global __SECRETS_FROM_CONFIG
|
62
62
|
__SECRETS_FROM_CONFIG = secrets
|
63
63
|
|
64
64
|
|
65
|
+
def add_to_secrets(secret: str) -> None:
|
66
|
+
"""Add to the list of secrets to be replaced"""
|
67
|
+
global __SECRETS_FROM_CONFIG
|
68
|
+
__SECRETS_FROM_CONFIG.append(secret)
|
69
|
+
|
70
|
+
|
65
71
|
def filter_secrets(string: str) -> str:
|
66
72
|
"""Filter secrets from a string by replacing them with ****"""
|
67
73
|
# TODO this should perform a maximal match for each secret. if "x" and "xk" are both secret values, and this method is called twice on
|
@@ -232,7 +232,7 @@ airbyte_cdk/sources/streams/http/auth/core.py,sha256=_s9wewvvIcOgYjhHGDj_YHApnF5
|
|
232
232
|
airbyte_cdk/sources/streams/http/auth/oauth.py,sha256=zchPWN1utNg02F93f5b4UFI5OXYo8-QhocbsXhLdG4U,4135
|
233
233
|
airbyte_cdk/sources/streams/http/auth/token.py,sha256=oU1ul0LsGsPGN_vOJOKw1xX2y_XWULRxjqXu7Rivcr8,1940
|
234
234
|
airbyte_cdk/sources/streams/http/requests_native_auth/__init__.py,sha256=RN0D3nOX1xLgwEwKWu6pkGy3XqBFzKSNZ8Lf6umU2eY,413
|
235
|
-
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=
|
235
|
+
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=VfI81zpWYMrzhU0nzN7J1lYz-aZCyHzvv-cLqzm8in0,10125
|
236
236
|
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py,sha256=T0hVF2cBXGgIfrCslvTC1uNm9rNbYjENNl2Cb3mXuSY,961
|
237
237
|
airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py,sha256=HrnA76k4i-YOp7ygDwyMg0Hf80gjJaNSU3GbXiTzxMc,13381
|
238
238
|
airbyte_cdk/sources/streams/http/requests_native_auth/token.py,sha256=hDti8DlF_R5YYX95hg9BPogYtG-KUYtOifrFDv_L3Hk,2456
|
@@ -258,7 +258,7 @@ airbyte_cdk/test/mock_http/request.py,sha256=dd_i47FOGD5iRlU23daotv2gEn5NOVqTBAq
|
|
258
258
|
airbyte_cdk/test/mock_http/response.py,sha256=F09QGG8N3Z8fL_b0rmSKTYoKgku5yZJQCpj0Fwwxu3s,588
|
259
259
|
airbyte_cdk/test/mock_http/response_builder.py,sha256=sc0lU_LN3wjBc4mFFV-3Y5IhYeapRdtB_-EDdHfyArA,7804
|
260
260
|
airbyte_cdk/utils/__init__.py,sha256=qZoNqzEKhIXdN_ZfvXlIGnmiDDjCFy6BVCzzWjUZcuU,294
|
261
|
-
airbyte_cdk/utils/airbyte_secrets_utils.py,sha256=
|
261
|
+
airbyte_cdk/utils/airbyte_secrets_utils.py,sha256=UIu8jzVGstjrlT8iKxvWieiO57rmxuOtx8QphHEzs9Y,3079
|
262
262
|
airbyte_cdk/utils/analytics_message.py,sha256=om0y9U_Y1RNHREi_K8D3mkZgBNfwiOyG71ixOBKZbVs,598
|
263
263
|
airbyte_cdk/utils/constants.py,sha256=QzCi7j5SqpI5I06uRvQ8FC73JVJi7rXaRnR3E_gro5c,108
|
264
264
|
airbyte_cdk/utils/datetime_format_inferrer.py,sha256=gGKDQ3OdY18R5CVFhq4c7zB_E4Cxe6J6SLA29cz3cJM,3954
|
@@ -299,7 +299,7 @@ unit_tests/sources/declarative/test_declarative_stream.py,sha256=Tt3PBIAo7DeQgvX
|
|
299
299
|
unit_tests/sources/declarative/test_manifest_declarative_source.py,sha256=HsDeDgtipkciNOnOeaM1H7eUh1Noq_OVDoFEMugm124,63391
|
300
300
|
unit_tests/sources/declarative/test_yaml_declarative_source.py,sha256=6HhsUFgB7ueN0yOUHWb4gpPYLng5jasxN_plvz3x37g,5097
|
301
301
|
unit_tests/sources/declarative/auth/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
302
|
-
unit_tests/sources/declarative/auth/test_oauth.py,sha256=
|
302
|
+
unit_tests/sources/declarative/auth/test_oauth.py,sha256=NLa7zUhN2pmFsEZFykxpzKVYuw_9luHMkQtFfxP72U4,14039
|
303
303
|
unit_tests/sources/declarative/auth/test_selective_authenticator.py,sha256=RAolWBLCLtibul5wlteQzLAdnUF8vh893qAr9fhoYOk,1315
|
304
304
|
unit_tests/sources/declarative/auth/test_session_token_auth.py,sha256=nKNBx7yGrrvFW9BUwG2xI472Q2sNXl2j1LhWZuUaWmY,6183
|
305
305
|
unit_tests/sources/declarative/auth/test_token_auth.py,sha256=Kg90S04_4WjTUCzwcj62OxnF_TPQcjL_r7-BaeDhxPI,7384
|
@@ -453,11 +453,11 @@ unit_tests/utils/test_datetime_format_inferrer.py,sha256=1EUW1_afccMDrZM6YZyyPqr
|
|
453
453
|
unit_tests/utils/test_mapping_helpers.py,sha256=hqRppuban9hGKviiNFqp2fNdAz77d1_gjvgg8L7-jy8,1408
|
454
454
|
unit_tests/utils/test_rate_limiting.py,sha256=ESrPBH61EZSeAf-hYWnd49igCgkUWnT21rUHPQaOLQM,873
|
455
455
|
unit_tests/utils/test_schema_inferrer.py,sha256=Z2jHBZ540wnYkylIdV_2xr75Vtwlxuyg4MNPAG-xhpk,7817
|
456
|
-
unit_tests/utils/test_secret_utils.py,sha256=
|
456
|
+
unit_tests/utils/test_secret_utils.py,sha256=CdKK8A2-5XVxbXVtX22FK9dwwMeP5KNqDH6luWRXSNw,5256
|
457
457
|
unit_tests/utils/test_stream_status_utils.py,sha256=Xr8MZ2HWgTVIyMbywDvuYkRaUF4RZLQOT8-JjvcfR24,2970
|
458
458
|
unit_tests/utils/test_traced_exception.py,sha256=bDFP5zMBizFenz6V2WvEZTRCKGB5ijh3DBezjbfoYIs,4198
|
459
|
-
airbyte_cdk-0.62.
|
460
|
-
airbyte_cdk-0.62.
|
461
|
-
airbyte_cdk-0.62.
|
462
|
-
airbyte_cdk-0.62.
|
463
|
-
airbyte_cdk-0.62.
|
459
|
+
airbyte_cdk-0.62.2.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
|
460
|
+
airbyte_cdk-0.62.2.dist-info/METADATA,sha256=mg5FUvzFvSF_W3YQZY6V6fUcSoUy4C03oFt2hF6w0FI,11073
|
461
|
+
airbyte_cdk-0.62.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
462
|
+
airbyte_cdk-0.62.2.dist-info/top_level.txt,sha256=edvsDKTnE6sD2wfCUaeTfKf5gQIL6CPVMwVL2sWZzqo,51
|
463
|
+
airbyte_cdk-0.62.2.dist-info/RECORD,,
|
@@ -10,6 +10,7 @@ import pendulum
|
|
10
10
|
import pytest
|
11
11
|
import requests
|
12
12
|
from airbyte_cdk.sources.declarative.auth import DeclarativeOauth2Authenticator
|
13
|
+
from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
|
13
14
|
from requests import Response
|
14
15
|
|
15
16
|
LOGGER = logging.getLogger(__name__)
|
@@ -165,6 +166,32 @@ class TestOauth2Authenticator:
|
|
165
166
|
|
166
167
|
assert ("access_token", 1000) == token
|
167
168
|
|
169
|
+
filtered = filter_secrets("access_token")
|
170
|
+
assert filtered == "****"
|
171
|
+
|
172
|
+
def test_refresh_access_token_missing_access_token(self, mocker):
|
173
|
+
oauth = DeclarativeOauth2Authenticator(
|
174
|
+
token_refresh_endpoint="{{ config['refresh_endpoint'] }}",
|
175
|
+
client_id="{{ config['client_id'] }}",
|
176
|
+
client_secret="{{ config['client_secret'] }}",
|
177
|
+
refresh_token="{{ config['refresh_token'] }}",
|
178
|
+
config=config,
|
179
|
+
scopes=["scope1", "scope2"],
|
180
|
+
token_expiry_date="{{ config['token_expiry_date'] }}",
|
181
|
+
refresh_request_body={
|
182
|
+
"custom_field": "{{ config['custom_field'] }}",
|
183
|
+
"another_field": "{{ config['another_field'] }}",
|
184
|
+
"scopes": ["no_override"],
|
185
|
+
},
|
186
|
+
parameters={},
|
187
|
+
)
|
188
|
+
|
189
|
+
resp.status_code = 200
|
190
|
+
mocker.patch.object(resp, "json", return_value={"expires_in": 1000})
|
191
|
+
mocker.patch.object(requests, "request", side_effect=mock_request, autospec=True)
|
192
|
+
with pytest.raises(Exception):
|
193
|
+
oauth.refresh_access_token()
|
194
|
+
|
168
195
|
@pytest.mark.parametrize(
|
169
196
|
"timestamp, expected_date",
|
170
197
|
[
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
|
5
5
|
import pytest
|
6
|
-
from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets, get_secret_paths, get_secrets, update_secrets
|
6
|
+
from airbyte_cdk.utils.airbyte_secrets_utils import add_to_secrets, filter_secrets, get_secret_paths, get_secrets, update_secrets
|
7
7
|
|
8
8
|
SECRET_STRING_KEY = "secret_key1"
|
9
9
|
SECRET_STRING_VALUE = "secret_value"
|
@@ -121,3 +121,15 @@ def test_secret_filtering():
|
|
121
121
|
update_secrets([SECRET_STRING_VALUE, SECRET_STRING_2_VALUE])
|
122
122
|
filtered = filter_secrets(sensitive_str)
|
123
123
|
assert filtered == f"**** {NOT_SECRET_VALUE} **** ****"
|
124
|
+
|
125
|
+
|
126
|
+
def test_secrets_added_are_filtered():
|
127
|
+
ADDED_SECRET = "only_a_secret_if_added"
|
128
|
+
sensitive_str = f"{ADDED_SECRET} {NOT_SECRET_VALUE}"
|
129
|
+
|
130
|
+
filtered = filter_secrets(sensitive_str)
|
131
|
+
assert filtered == sensitive_str
|
132
|
+
|
133
|
+
add_to_secrets(ADDED_SECRET)
|
134
|
+
filtered = filter_secrets(sensitive_str)
|
135
|
+
assert filtered == f"**** {NOT_SECRET_VALUE}"
|
File without changes
|
File without changes
|
File without changes
|