[Low Code CDK] configurable oauth request payload (#13993)
* configurable oauth request payload * support interpolation for dictionaries that are not new subcomponents * rewrite a declarative oauth authenticator that performs interpolation at runtime * formatting * whatever i don't know why factory gets flagged w/ the newline change * we java now * remove duplicate oauth * add some comments * parse time properly from string interpolation * move declarative oauth to its own package in declarative module * add changelog info
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
#
|
||||
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
|
||||
#
|
||||
@@ -0,0 +1,90 @@
|
||||
#
|
||||
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
import pendulum
|
||||
import requests
|
||||
from airbyte_cdk.sources.declarative.auth import DeclarativeOauth2Authenticator
|
||||
from requests import Response
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
resp = Response()
|
||||
|
||||
config = {
|
||||
"refresh_endpoint": "refresh_end",
|
||||
"client_id": "some_client_id",
|
||||
"client_secret": "some_client_secret",
|
||||
"refresh_token": "some_refresh_token",
|
||||
"token_expiry_date": pendulum.now().subtract(days=2).to_rfc3339_string(),
|
||||
"custom_field": "in_outbound_request",
|
||||
"another_field": "exists_in_body",
|
||||
}
|
||||
|
||||
|
||||
class TestOauth2Authenticator:
|
||||
"""
|
||||
Test class for OAuth2Authenticator.
|
||||
"""
|
||||
|
||||
def test_refresh_request_body(self):
|
||||
"""
|
||||
Request body should match given configuration.
|
||||
"""
|
||||
scopes = ["scope1", "scope2"]
|
||||
oauth = DeclarativeOauth2Authenticator(
|
||||
token_refresh_endpoint="{{ config['refresh_endpoint'] }}",
|
||||
client_id="{{ config['client_id'] }}",
|
||||
client_secret="{{ config['client_secret'] }}",
|
||||
refresh_token="{{ config['refresh_token'] }}",
|
||||
config=config,
|
||||
scopes=["scope1", "scope2"],
|
||||
token_expiry_date="{{ config['token_expiry_date'] }}",
|
||||
refresh_request_body={
|
||||
"custom_field": "{{ config['custom_field'] }}",
|
||||
"another_field": "{{ config['another_field'] }}",
|
||||
"scopes": ["no_override"],
|
||||
},
|
||||
)
|
||||
body = oauth.get_refresh_request_body()
|
||||
expected = {
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": "some_client_id",
|
||||
"client_secret": "some_client_secret",
|
||||
"refresh_token": "some_refresh_token",
|
||||
"scopes": scopes,
|
||||
"custom_field": "in_outbound_request",
|
||||
"another_field": "exists_in_body",
|
||||
}
|
||||
assert body == expected
|
||||
|
||||
def test_refresh_access_token(self, mocker):
|
||||
oauth = DeclarativeOauth2Authenticator(
|
||||
token_refresh_endpoint="{{ config['refresh_endpoint'] }}",
|
||||
client_id="{{ config['client_id'] }}",
|
||||
client_secret="{{ config['client_secret'] }}",
|
||||
refresh_token="{{ config['refresh_token'] }}",
|
||||
config=config,
|
||||
scopes=["scope1", "scope2"],
|
||||
token_expiry_date="{{ config['token_expiry_date'] }}",
|
||||
refresh_request_body={
|
||||
"custom_field": "{{ config['custom_field'] }}",
|
||||
"another_field": "{{ config['another_field'] }}",
|
||||
"scopes": ["no_override"],
|
||||
},
|
||||
)
|
||||
|
||||
resp.status_code = 200
|
||||
mocker.patch.object(resp, "json", return_value={"access_token": "access_token", "expires_in": 1000})
|
||||
mocker.patch.object(requests, "request", side_effect=mock_request, autospec=True)
|
||||
token = oauth.refresh_access_token()
|
||||
|
||||
assert ("access_token", 1000) == token
|
||||
|
||||
|
||||
def mock_request(method, url, data):
|
||||
if url == "refresh_end":
|
||||
return resp
|
||||
raise Exception(f"Error while refreshing access token with request: {method}, {url}, {data}")
|
||||
@@ -53,13 +53,23 @@ def test_factory():
|
||||
|
||||
def test_interpolate_config():
|
||||
content = """
|
||||
authenticator:
|
||||
class_name: airbyte_cdk.sources.streams.http.requests_native_auth.token.TokenAuthenticator
|
||||
token: "{{ config['apikey'] }}"
|
||||
authenticator:
|
||||
class_name: airbyte_cdk.sources.declarative.auth.oauth.DeclarativeOauth2Authenticator
|
||||
client_id: "some_client_id"
|
||||
client_secret: "some_client_secret"
|
||||
token_refresh_endpoint: "https://api.sendgrid.com/v3/auth"
|
||||
refresh_token: "{{ config['apikey'] }}"
|
||||
refresh_request_body:
|
||||
body_field: "yoyoyo"
|
||||
interpolated_body_field: "{{ config['apikey'] }}"
|
||||
"""
|
||||
config = parser.parse(content)
|
||||
authenticator = factory.create_component(config["authenticator"], input_config)()
|
||||
assert authenticator._tokens == ["verysecrettoken"]
|
||||
assert authenticator._client_id._string == "some_client_id"
|
||||
assert authenticator._client_secret._string == "some_client_secret"
|
||||
assert authenticator._token_refresh_endpoint._string == "https://api.sendgrid.com/v3/auth"
|
||||
assert authenticator._refresh_token._string == "verysecrettoken"
|
||||
assert authenticator._refresh_request_body._mapping == {"body_field": "yoyoyo", "interpolated_body_field": "{{ config['apikey'] }}"}
|
||||
|
||||
|
||||
def test_list_based_stream_slicer_with_values_refd():
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
|
||||
#
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
import pendulum
|
||||
import requests
|
||||
from airbyte_cdk.sources.streams.http.requests_native_auth import MultipleTokenAuthenticator, Oauth2Authenticator, TokenAuthenticator
|
||||
from requests import Response
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
resp = Response()
|
||||
|
||||
|
||||
def test_token_authenticator():
|
||||
"""
|
||||
@@ -96,34 +98,40 @@ class TestOauth2Authenticator:
|
||||
"""
|
||||
scopes = ["scope1", "scope2"]
|
||||
oauth = Oauth2Authenticator(
|
||||
token_refresh_endpoint=TestOauth2Authenticator.refresh_endpoint,
|
||||
client_id=TestOauth2Authenticator.client_id,
|
||||
client_secret=TestOauth2Authenticator.client_secret,
|
||||
refresh_token=TestOauth2Authenticator.refresh_token,
|
||||
scopes=scopes,
|
||||
token_refresh_endpoint="refresh_end",
|
||||
client_id="some_client_id",
|
||||
client_secret="some_client_secret",
|
||||
refresh_token="some_refresh_token",
|
||||
scopes=["scope1", "scope2"],
|
||||
token_expiry_date=pendulum.now().add(days=3),
|
||||
refresh_request_body={"custom_field": "in_outbound_request", "another_field": "exists_in_body", "scopes": ["no_override"]},
|
||||
)
|
||||
body = oauth.get_refresh_request_body()
|
||||
expected = {
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": "client_id",
|
||||
"client_secret": "client_secret",
|
||||
"refresh_token": "refresh_token",
|
||||
"client_id": "some_client_id",
|
||||
"client_secret": "some_client_secret",
|
||||
"refresh_token": "some_refresh_token",
|
||||
"scopes": scopes,
|
||||
"custom_field": "in_outbound_request",
|
||||
"another_field": "exists_in_body",
|
||||
}
|
||||
assert body == expected
|
||||
|
||||
def test_refresh_access_token(self, mocker):
|
||||
oauth = Oauth2Authenticator(
|
||||
token_refresh_endpoint=TestOauth2Authenticator.refresh_endpoint,
|
||||
client_id=TestOauth2Authenticator.client_id,
|
||||
client_secret=TestOauth2Authenticator.client_secret,
|
||||
refresh_token=TestOauth2Authenticator.refresh_token,
|
||||
token_refresh_endpoint="refresh_end",
|
||||
client_id="some_client_id",
|
||||
client_secret="some_client_secret",
|
||||
refresh_token="some_refresh_token",
|
||||
scopes=["scope1", "scope2"],
|
||||
token_expiry_date=pendulum.now().add(days=3),
|
||||
refresh_request_body={"custom_field": "in_outbound_request", "another_field": "exists_in_body", "scopes": ["no_override"]},
|
||||
)
|
||||
resp = Response()
|
||||
resp.status_code = 200
|
||||
|
||||
mocker.patch.object(requests, "request", return_value=resp)
|
||||
resp.status_code = 200
|
||||
mocker.patch.object(resp, "json", return_value={"access_token": "access_token", "expires_in": 1000})
|
||||
mocker.patch.object(requests, "request", side_effect=mock_request, autospec=True)
|
||||
token = oauth.refresh_access_token()
|
||||
|
||||
assert ("access_token", 1000) == token
|
||||
@@ -142,3 +150,9 @@ class TestOauth2Authenticator:
|
||||
oauth(prepared_request)
|
||||
|
||||
assert {"Authorization": "Bearer access_token"} == prepared_request.headers
|
||||
|
||||
|
||||
def mock_request(method, url, data):
|
||||
if url == "refresh_end":
|
||||
return resp
|
||||
raise Exception(f"Error while refreshing access token with request: {method}, {url}, {data}")
|
||||
|
||||
Reference in New Issue
Block a user