Skip to content

Commit 6260248

Browse files
maxi297octavia-squidington-iii
and
octavia-squidington-iii
authored
feat(OAuth): allow for access_token without expiration from the API (airbytehq#324)
Co-authored-by: octavia-squidington-iii <contact@airbyte.com>
1 parent a32fea4 commit 6260248

File tree

3 files changed

+38
-1
lines changed

3 files changed

+38
-1
lines changed

airbyte_cdk/sources/declarative/auth/oauth.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
from dataclasses import InitVar, dataclass, field
6-
from datetime import timedelta
6+
from datetime import datetime, timedelta
77
from typing import Any, List, Mapping, MutableMapping, Optional, Union
88

99
from airbyte_cdk.sources.declarative.auth.declarative_authenticator import DeclarativeAuthenticator
@@ -232,8 +232,13 @@ def get_refresh_request_headers(self) -> Mapping[str, Any]:
232232
return self._refresh_request_headers.eval(self.config)
233233

234234
def get_token_expiry_date(self) -> AirbyteDateTime:
235+
if not self._has_access_token_been_initialized():
236+
return AirbyteDateTime.from_datetime(datetime.min)
235237
return self._token_expiry_date # type: ignore # _token_expiry_date is an AirbyteDateTime. It is never None despite what mypy thinks
236238

239+
def _has_access_token_been_initialized(self) -> bool:
240+
return self._access_token is not None
241+
237242
def set_token_expiry_date(self, value: Union[str, int]) -> None:
238243
self._token_expiry_date = self._parse_token_expiration_date(value)
239244

airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py

+3
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ def _parse_token_expiration_date(self, value: Union[str, int]) -> AirbyteDateTim
261261
262262
:return: expiration datetime
263263
"""
264+
if not value and not self.token_has_expired():
265+
# No expiry token was provided but the previous one is not expired so it's fine
266+
return self.get_token_expiry_date()
264267

265268
if self.token_expiry_is_time_of_expiration:
266269
if not self.token_expiry_date_format:

unit_tests/sources/declarative/auth/test_oauth.py

+29
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ def test_initialize_declarative_oauth_with_token_expiry_date_as_timestamp(
301301
client_id="{{ config['client_id'] }}",
302302
client_secret="{{ config['client_secret'] }}",
303303
token_expiry_date=timestamp,
304+
access_token_value="some_access_token",
304305
refresh_token="some_refresh_token",
305306
config={
306307
"refresh_endpoint": "refresh_end",
@@ -313,6 +314,34 @@ def test_initialize_declarative_oauth_with_token_expiry_date_as_timestamp(
313314
assert isinstance(oauth._token_expiry_date, AirbyteDateTime)
314315
assert oauth.get_token_expiry_date() == ab_datetime_parse(expected_date)
315316

317+
def test_given_no_access_token_but_expiry_in_the_future_when_refresh_token_then_fetch_access_token(
318+
self,
319+
) -> None:
320+
expiry_date = ab_datetime_now().add(timedelta(days=1))
321+
oauth = DeclarativeOauth2Authenticator(
322+
token_refresh_endpoint="https://refresh_endpoint.com/",
323+
client_id="some_client_id",
324+
client_secret="some_client_secret",
325+
token_expiry_date=expiry_date.isoformat(),
326+
refresh_token="some_refresh_token",
327+
config={},
328+
parameters={},
329+
grant_type="client",
330+
)
331+
332+
with HttpMocker() as http_mocker:
333+
http_mocker.post(
334+
HttpRequest(
335+
url="https://refresh_endpoint.com/",
336+
body="grant_type=client&client_id=some_client_id&client_secret=some_client_secret&refresh_token=some_refresh_token",
337+
),
338+
HttpResponse(body=json.dumps({"access_token": "new_access_token"})),
339+
)
340+
oauth.get_access_token()
341+
342+
assert oauth.access_token == "new_access_token"
343+
assert oauth._token_expiry_date == expiry_date
344+
316345
@pytest.mark.parametrize(
317346
"expires_in_response, token_expiry_date_format",
318347
[

0 commit comments

Comments
 (0)