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.
@@ -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
- self._log_response(response)
119
- response.raise_for_status()
120
- return response.json()
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 0.62.1
3
+ Version: 0.62.2
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://github.com/airbytehq/airbyte
6
6
  Author: Airbyte
@@ -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=sknTSDgwPX3IhHRqEnxp9tuRE5Iu8jU_dyIKsFad1wY,9257
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=q3aDl8T10ufGbeqnUPqbZLxQcHdkf2kDfQK_upWzBbI,2894
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=pNUzscHDR8K7tedF7v_lSMVSOEOeq9bAlxHG2cttdU0,12854
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=XKe0f1RHYii8iwE6ATmBr5JGDI1pzzrnZUGdUSMJQP4,4886
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.1.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
460
- airbyte_cdk-0.62.1.dist-info/METADATA,sha256=9m_kiUnGlScq9ANU1dhvx00D_p5inrTOtJ-09MkF1vo,11073
461
- airbyte_cdk-0.62.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
462
- airbyte_cdk-0.62.1.dist-info/top_level.txt,sha256=edvsDKTnE6sD2wfCUaeTfKf5gQIL6CPVMwVL2sWZzqo,51
463
- airbyte_cdk-0.62.1.dist-info/RECORD,,
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}"