airbyte-cdk 0.62.1__py3-none-any.whl → 0.62.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|