Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Low code CDK: Allow query param / body injection for api key authenticator #26953

Merged
merged 10 commits into from
Jun 7, 2023

Conversation

flash1293
Copy link
Contributor

@flash1293 flash1293 commented Jun 2, 2023

What

Part of #20790

This PR deprecates the header param of the api key authenticator and introduces a request_option param that allows to also inject the auth key into a query param or the body

How

To allow this I had to change the class hierarchy a bit - the important part is that the simple retriever can spread in authenticator-defined query params and body params into outgoing requests. To do so the interface of the authenticator known to the retriever has to include methods for getting query params and body params (not for headers as this is already handled by the current setup using the Session class from the requests package). Currently this is set to AuthBase. As every authenticator can theoretically be able to do this, DeclarativeAuthenticator is extended with the required methods and the authenticator type in the requester is changed to DeclarativeAuthenticator.

This removes a bunch of double inheritance for other declarative connectors which is a nice side effect.

As an image:
Screenshot 2023-06-02 at 15 36 05

Note that the python class ApiKeyAuthenticator is changed to only support the request option, the deprecated header property is handled in the component factory to isolate this behavior to the yaml layer.

Theoretically, custom authenticators used to work fine as long as they implemented AuthBase, now they have to implement DeclarativeAuthenticator. I checked existing custom authenticators and they all do this already, I guess it was implied that a custom authenticator needs to do this in the first place (it just didn't bear any weight so far). I clarified this in the description.

🚨 User Impact 🚨

Low code CDK users can now inject the token from the api key authenticator in request params or the body:

type: ApiKeyAuthenticator
token: "{{ config.token }}"
inject_into:
   type: "RequestOption"
   field_name: "authKey"
   inject_into: "request_parameter"

The header property is still supported, but should not be used anymore.

The ApiKeyAuthenticator class from airbyte_cdk.sources.declarative.auth.token does not support the header parameter anymore.

@octavia-squidington-iii octavia-squidington-iii added the CDK Connector Development Kit label Jun 2, 2023
@flash1293 flash1293 marked this pull request as ready for review June 5, 2023 10:06
@flash1293 flash1293 requested a review from a team as a code owner June 5, 2023 10:06
@flash1293 flash1293 requested a review from girarda June 5, 2023 10:06
@@ -191,6 +197,8 @@ def request_headers(
self.requester.get_request_headers,
self.paginator.get_request_headers,
self.stream_slicer.get_request_headers,
# auth headers are handled separately by passing the authenticator to the HttpStream constructor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open question: would it be preferable to pass None to the HttpStream constructor and handle request headers like the query params and body for consistency?

Copy link
Contributor Author

@flash1293 flash1293 Jun 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to split this out from the main change as it would require changes to the other authenticators as well. Let's get the changes in to unblock downstream changes and come back to it

if model.inject_into
else RequestOption(
inject_into=RequestOptionType.header,
field_name=InterpolatedString.create(model.header, parameters=model.parameters).eval(config),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the field_name evaluated here instead of in the authenticator?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, that's not necessary

@@ -110,14 +109,23 @@ definitions:
- "Token token={{ config['api_key'] }}"
header:
title: Header Name
description: The name of the HTTP header that will be set to the API key.
description: The name of the HTTP header that will be set to the API key. This setting is deprecated, use inject_into instead. If header and inject_into is defined at the same time, header will be ignored.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we fail if both are set to avoid suprises?

"""
Interface used to associate which authenticators can be used as part of the declarative framework
"""

def get_request_params(self) -> MutableMapping[str, Any]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this method returning a MutableMapping?

"""HTTP request parameter to add to the requests"""
return {}

def get_request_body_data(self) -> Union[Mapping, str]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the type is incomplete. It should be Union[Mapping[str, Any], str]

"""Form-encoded body data to set on the requests"""
return {}

def get_request_body_json(self) -> Mapping:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the type is incomplete

def get_request_body_data(self) -> Union[Mapping, str, None]:
return self._get_request_options(RequestOptionType.body_data)

def get_request_body_json(self) -> Union[Mapping, None]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Optional[Mapping[str, Any]]

def get_request_params(self) -> Union[MutableMapping[str, Any], None]:
return self._get_request_options(RequestOptionType.request_parameter)

def get_request_body_data(self) -> Union[Mapping, str, None]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Optional[Union[Mapping[str, Any], str]

options[self._field_name.eval(self.config)] = self.token
return options

def get_request_params(self) -> Union[MutableMapping[str, Any], None]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Optional[Mapping[str, Any]]

@flash1293
Copy link
Contributor Author

@girarda Adjusted the PR, could you have another look?

  • Fails if both inject_into and header are defined
  • Cleaned up the types

@flash1293
Copy link
Contributor Author

flash1293 commented Jun 7, 2023

/test connector=connectors/source-zenloop local_cdk=1

🕑 connectors/source-zenloop https://github.com/airbytehq/airbyte/actions/runs/5198472485
✅ connectors/source-zenloop https://github.com/airbytehq/airbyte/actions/runs/5198472485
No Python unittests run

Build Passed

Test summary info:

=========================== short test summary info ============================
SKIPPED [1] ../usr/local/lib/python3.9/site-packages/connector_acceptance_test/tests/test_core.py:100: The previous and actual specifications are identical.
SKIPPED [1] ../usr/local/lib/python3.9/site-packages/connector_acceptance_test/tests/test_core.py:598: The previous and actual discovered catalogs are identical.
SKIPPED [1] ../usr/local/lib/python3.9/site-packages/connector_acceptance_test/tests/test_core.py:695: This tests currently leads to too much failures. We need to fix the connectors at scale first.
============ 38 passed, 3 skipped, 41 warnings in 81.23s (0:01:21) =============

@flash1293
Copy link
Contributor Author

flash1293 commented Jun 7, 2023

/test connector=connectors/source-lokalise local_cdk=1

🕑 connectors/source-lokalise https://github.com/airbytehq/airbyte/actions/runs/5198558454
✅ connectors/source-lokalise https://github.com/airbytehq/airbyte/actions/runs/5198558454
No Python unittests run

Build Passed

Test summary info:

=========================== short test summary info ============================
SKIPPED [1] ../usr/local/lib/python3.9/site-packages/connector_acceptance_test/plugin.py:63: Skipping TestIncremental.test_two_sequential_reads: This connector does not implement incremental sync
SKIPPED [1] ../usr/local/lib/python3.9/site-packages/connector_acceptance_test/tests/test_core.py:100: The previous and actual specifications are identical.
SKIPPED [1] ../usr/local/lib/python3.9/site-packages/connector_acceptance_test/tests/test_core.py:598: The previous and actual discovered catalogs are identical.
SKIPPED [1] ../usr/local/lib/python3.9/site-packages/connector_acceptance_test/tests/test_core.py:695: This tests currently leads to too much failures. We need to fix the connectors at scale first.
======================== 35 passed, 4 skipped in 51.16s ========================

@flash1293 flash1293 merged commit c4981a7 into master Jun 7, 2023
17 checks passed
@flash1293 flash1293 deleted the flash1293/query-parameter-authenticator branch June 7, 2023 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CDK Connector Development Kit
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants