1
0
mirror of synced 2025-12-31 06:05:12 -05:00
Files
airbyte/airbyte-cdk/python/airbyte_cdk/sources/declarative/auth/token_provider.py
Joe Reuter 58cc540c6b 🚨 Low code CDK: Add session token authenticator (#28050)
This PR adds a new authenticator: The SessionTokenAuthenticator. The existing authenticator under the same name is renamed to LegacySessionTokenAuthenticator.
2023-07-19 17:10:24 +02:00

89 lines
3.0 KiB
Python

#
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
#
import datetime
from abc import abstractmethod
from dataclasses import InitVar, dataclass
from typing import Any, List, Mapping, Optional, Union
import dpath.util
import pendulum
import requests
from airbyte_cdk.models import Level
from airbyte_cdk.sources.declarative.decoders.decoder import Decoder
from airbyte_cdk.sources.declarative.decoders.json_decoder import JsonDecoder
from airbyte_cdk.sources.declarative.exceptions import ReadException
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
from airbyte_cdk.sources.declarative.requesters.requester import Requester
from airbyte_cdk.sources.declarative.types import Config
from airbyte_cdk.sources.http_logger import format_http_message
from airbyte_cdk.sources.message import MessageRepository, NoopMessageRepository
from isodate import Duration
from pendulum import DateTime
class TokenProvider:
@abstractmethod
def get_token(self) -> str:
pass
@dataclass
class SessionTokenProvider(TokenProvider):
login_requester: Requester
session_token_path: List[str]
expiration_duration: Optional[Union[datetime.timedelta, Duration]]
parameters: InitVar[Mapping[str, Any]]
message_repository: MessageRepository = NoopMessageRepository()
_decoder: Decoder = JsonDecoder(parameters={})
_next_expiration_time: Optional[DateTime] = None
_token: Optional[str] = None
def get_token(self) -> str:
self._refresh_if_necessary()
if self._token is None:
raise ReadException("Failed to get session token, token is None")
return self._token
def _refresh_if_necessary(self) -> None:
if self._next_expiration_time is None or self._next_expiration_time < pendulum.now():
self._refresh()
def _refresh(self) -> None:
response = self.login_requester.send_request()
if response is None:
raise ReadException("Failed to get session token, response got ignored by requester")
self._log_response(response)
session_token = dpath.util.get(self._decoder.decode(response), self.session_token_path)
if self.expiration_duration is not None:
self._next_expiration_time = pendulum.now() + self.expiration_duration
self._token = session_token
def _log_response(self, response: requests.Response) -> None:
self.message_repository.log_message(
Level.DEBUG,
lambda: format_http_message(
response,
"Login request",
"Obtains session token",
None,
is_auxiliary=True,
),
)
@dataclass
class InterpolatedStringTokenProvider(TokenProvider):
config: Config
api_token: Union[InterpolatedString, str]
parameters: Mapping[str, Any]
def __post_init__(self) -> None:
self._token = InterpolatedString.create(self.api_token, parameters=self.parameters)
def get_token(self) -> str:
return str(self._token.eval(self.config))