Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions airbyte_cdk/sources/declarative/auth/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
client_secret_name: Union[InterpolatedString, str] = "client_secret"
expires_in_name: Union[InterpolatedString, str] = "expires_in"
refresh_token_name: Union[InterpolatedString, str] = "refresh_token"
scopes_name: Union[InterpolatedString, str] = "scope"
refresh_request_body: Optional[Mapping[str, Any]] = None
refresh_request_headers: Optional[Mapping[str, Any]] = None
grant_type_name: Union[InterpolatedString, str] = "grant_type"
Expand Down Expand Up @@ -108,6 +109,7 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None:
self._refresh_token_name = InterpolatedString.create(
self.refresh_token_name, parameters=parameters
)
self._scopes_name = InterpolatedString.create(self.scopes_name, parameters=parameters)
if self.refresh_token is not None:
self._refresh_token: Optional[InterpolatedString] = InterpolatedString.create(
self.refresh_token, parameters=parameters
Expand Down Expand Up @@ -229,6 +231,9 @@ def get_refresh_token(self) -> Optional[str]:
def get_scopes(self) -> List[str]:
return self.scopes or []

def get_scopes_name(self) -> str:
return self._scopes_name.eval(self.config) # type: ignore # eval returns a string in this context

def get_access_token_name(self) -> str:
return self.access_token_name.eval(self.config) # type: ignore # eval returns a string in this context

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,14 @@ definitions:
"crm.objects.contacts.read",
"crm.schema.contacts.read",
]
scopes_name:
title: Scopes Property Name
description: The name of the property to use for scopes in the token refresh request body. Per RFC 6749, the default is "scope" (singular).
type: string
default: "scope"
examples:
- scope
- scopes
token_expiry_date:
title: Token Expiry Date
description: The access token expiry date.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.

# generated by datamodel-codegen:
# filename: declarative_component_schema.yaml

Expand Down Expand Up @@ -1898,6 +1896,12 @@ class OAuthAuthenticator(BaseModel):
examples=[["crm.list.read", "crm.objects.contacts.read", "crm.schema.contacts.read"]],
title="Scopes",
)
scopes_name: Optional[str] = Field(
"scope",
description='The name of the property to use for scopes in the token refresh request body. Per RFC 6749, the default is "scope" (singular).',
examples=["scope", "scopes"],
title="Scopes Property Name",
)
token_expiry_date: Optional[str] = Field(
None,
description="The access token expiry date.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2818,6 +2818,9 @@ def create_oauth_authenticator(
refresh_request_headers=InterpolatedMapping(
model.refresh_request_headers or {}, parameters=model.parameters or {}
).eval(config),
scopes_name=InterpolatedString.create(
model.scopes_name or "scope", parameters=model.parameters or {}
).eval(config),
scopes=model.scopes,
token_expiry_date_format=model.token_expiry_date_format,
token_expiry_is_time_of_expiration=bool(model.token_expiry_date_format),
Expand All @@ -2841,6 +2844,7 @@ def create_oauth_authenticator(
refresh_request_headers=model.refresh_request_headers,
refresh_token_name=model.refresh_token_name or "refresh_token",
refresh_token=model.refresh_token,
scopes_name=model.scopes_name or "scope",
scopes=model.scopes,
token_expiry_date=model.token_expiry_date,
token_expiry_date_format=model.token_expiry_date_format,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def build_refresh_request_body(self) -> Mapping[str, Any]:
payload[self.get_refresh_token_name()] = self.get_refresh_token()

if self.get_scopes():
payload["scopes"] = self.get_scopes()
payload[self.get_scopes_name()] = self.get_scopes()

if self.get_refresh_request_body():
for key, val in self.get_refresh_request_body().items():
Expand Down Expand Up @@ -484,6 +484,10 @@ def get_refresh_token(self) -> Optional[str]:
def get_scopes(self) -> List[str]:
"""List of requested scopes"""

@abstractmethod
def get_scopes_name(self) -> str:
"""The name of the property to use for scopes in the token request"""

@abstractmethod
def get_token_expiry_date(self) -> AirbyteDateTime:
"""Expiration date of the access token"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def __init__(
client_id_name: str = "client_id",
client_secret_name: str = "client_secret",
refresh_token_name: str = "refresh_token",
scopes_name: str = "scope",
scopes: List[str] | None = None,
token_expiry_date: AirbyteDateTime | None = None,
token_expiry_date_format: str | None = None,
Expand All @@ -59,6 +60,7 @@ def __init__(
self._client_id = client_id
self._refresh_token_name = refresh_token_name
self._refresh_token = refresh_token
self._scopes_name = scopes_name
self._scopes = scopes
self._access_token_name = access_token_name
self._expires_in_name = expires_in_name
Expand Down Expand Up @@ -102,6 +104,9 @@ def get_access_token_name(self) -> str:
def get_scopes(self) -> list[str]:
return self._scopes # type: ignore[return-value]

def get_scopes_name(self) -> str:
return self._scopes_name

def get_expires_in_name(self) -> str:
return self._expires_in_name

Expand Down Expand Up @@ -154,6 +159,7 @@ def __init__(
self,
connector_config: Mapping[str, Any],
token_refresh_endpoint: str,
scopes_name: str = "scope",
scopes: List[str] | None = None,
access_token_name: str = "access_token",
expires_in_name: str = "expires_in",
Expand Down Expand Up @@ -221,6 +227,7 @@ def __init__(
client_secret=self._client_secret,
refresh_token=self.get_refresh_token(),
refresh_token_name=self._refresh_token_name,
scopes_name=scopes_name,
scopes=scopes,
token_expiry_date=self.get_token_expiry_date(),
access_token_name=access_token_name,
Expand Down
4 changes: 2 additions & 2 deletions unit_tests/sources/declarative/auth/test_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_refresh_request_body(self):
refresh_request_body={
"custom_field": "{{ config['custom_field'] }}",
"another_field": "{{ config['another_field'] }}",
"scopes": ["no_override"],
"scope": ["no_override"],
},
parameters=parameters,
grant_type="{{ config['grant_type'] }}",
Expand All @@ -69,7 +69,7 @@ def test_refresh_request_body(self):
"client_id": "some_client_id",
"client_secret": "some_client_secret",
"refresh_token": "some_refresh_token",
"scopes": scopes,
"scope": scopes,
"custom_field": "in_outbound_request",
"another_field": "exists_in_body",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def test_refresh_request_body(self):
refresh_request_body={
"custom_field": "in_outbound_request",
"another_field": "exists_in_body",
"scopes": ["no_override"],
"scope": ["no_override"],
},
)
body = oauth.build_refresh_request_body()
Expand All @@ -165,7 +165,7 @@ def test_refresh_request_body(self):
"client_id": "some_client_id",
"client_secret": "some_client_secret",
"refresh_token": "some_refresh_token",
"scopes": scopes,
"scope": scopes,
"custom_field": "in_outbound_request",
"another_field": "exists_in_body",
}
Expand Down Expand Up @@ -216,14 +216,15 @@ def test_refresh_request_body_with_keys_override(self):
client_secret="some_client_secret",
refresh_token_name="custom_refresh_token_key",
refresh_token="some_refresh_token",
scopes_name="custom_scopes_key",
scopes=["scope1", "scope2"],
token_expiry_date=ab_datetime_now() + timedelta(days=3),
grant_type_name="custom_grant_type",
grant_type="some_grant_type",
refresh_request_body={
"custom_field": "in_outbound_request",
"another_field": "exists_in_body",
"scopes": ["no_override"],
"custom_scopes_key": ["no_override"],
},
)
body = oauth.build_refresh_request_body()
Expand All @@ -232,7 +233,7 @@ def test_refresh_request_body_with_keys_override(self):
"custom_client_id_key": "some_client_id",
"custom_client_secret_key": "some_client_secret",
"custom_refresh_token_key": "some_refresh_token",
"scopes": scopes,
"custom_scopes_key": scopes,
"custom_field": "in_outbound_request",
"another_field": "exists_in_body",
}
Expand Down