You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by el...@apache.org on 2026/04/26 11:15:03 UTC
(airflow) branch main updated: fix(providers/airbyte): make OAuth credentials optional for Airbyte OSS deployments (#64986)
This is an automated email from the ASF dual-hosted git repository.
eladkal pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new bd6b2d58365 fix(providers/airbyte): make OAuth credentials optional for Airbyte OSS deployments (#64986)
bd6b2d58365 is described below
commit bd6b2d5836546c9ffac3498b20990fba672fde66
Author: Aldo <ao...@gmail.com>
AuthorDate: Sun Apr 26 08:14:55 2026 -0300
fix(providers/airbyte): make OAuth credentials optional for Airbyte OSS deployments (#64986)
The AirbyteHook unconditionally requires OAuth client credentials
(client_id/client_secret) and calls /v1/applications/token on every
request. This endpoint does not exist on Airbyte OSS v2.x without
Keycloak/Enterprise auth, breaking the provider for all self-hosted
OSS deployments.
When client_id and client_secret are not provided in the connection,
the hook now creates the API session without authentication
(security=None), which the airbyte-api SDK already supports.
Fixes #47161
---
providers/airbyte/docs/connections.rst | 45 ++++++++++++++--------
.../src/airflow/providers/airbyte/hooks/airbyte.py | 43 ++++++++++++++++-----
.../tests/unit/airbyte/hooks/test_airbyte.py | 44 ++++++++++++++++++++-
3 files changed, 106 insertions(+), 26 deletions(-)
diff --git a/providers/airbyte/docs/connections.rst b/providers/airbyte/docs/connections.rst
index 1f2a00643ed..5d41d2096ea 100644
--- a/providers/airbyte/docs/connections.rst
+++ b/providers/airbyte/docs/connections.rst
@@ -19,30 +19,45 @@
Airbyte Connection
==================
-The Airbyte connection type use the Airbyte API Python SDK to authenticate to the server.
+The Airbyte connection type uses the Airbyte API Python SDK to communicate with the server.
+
+Authentication is **optional**: when Client ID and Client Secret are provided, the hook
+authenticates via OAuth2 Client Credentials (required for Airbyte Cloud and Enterprise).
+When they are omitted, the hook connects without authentication, which is suitable for
+Airbyte OSS deployments that do not have auth enabled.
+
+Host (required)
+ The fully qualified host URL of the Airbyte server.
+
+ * Airbyte Cloud: ``https://api.airbyte.com/v1/``
+ * Airbyte OSS (with auth): ``http://localhost:8000/api/public/v1/``
+ * Airbyte OSS (without auth): ``http://localhost:8000/api/v1/``
-Host(required)
- The full qualified host domain to connect to the Airbyte server.
- If you are using Airbyte Cloud: ``https://api.airbyte.com/v1/``
- If you are using Airbyte OSS: ``http://localhost:8000/api/public/v1/``
Be aware: If you're changing the API path, you must update the value accordingly.
Token URL (optional)
- The prefix for URL used to create the access token.
- If you are using Airbyte Cloud: ``v1/applications/token`` (default value)
- If you are using Airbyte OSS: ``/api/public/v1/applications/token```
+ The prefix for the URL used to create the access token. Only used when
+ Client ID and Client Secret are provided.
+
+ * Airbyte Cloud: ``v1/applications/token`` (default value)
+ * Airbyte OSS: ``/api/public/v1/applications/token``
+
Be aware: If you're changing the API path, you must update the value accordingly.
-Client ID (required)
- The Client ID to connect to the Airbyte server.
- You can find this information in the Settings / Applications page in Airbyte UI.
+Client ID (optional)
+ The Client ID for OAuth2 authentication.
+ Required for Airbyte Cloud and Enterprise deployments.
+ You can find this in Settings > Applications in the Airbyte UI.
+ Leave blank for Airbyte OSS deployments without auth enabled.
-Client Secret (required)
- The Client Secret to connect to the Airbyte server.
- You can find this information in the Settings / Applications page in Airbyte UI.
+Client Secret (optional)
+ The Client Secret for OAuth2 authentication.
+ Required for Airbyte Cloud and Enterprise deployments.
+ You can find this in Settings > Applications in the Airbyte UI.
+ Leave blank for Airbyte OSS deployments without auth enabled.
Extra (optional)
- Specify custom proxies in json format.
+ Specify custom proxies in JSON format.
Following default requests parameters are taken into account:
* ``proxies``
diff --git a/providers/airbyte/src/airflow/providers/airbyte/hooks/airbyte.py b/providers/airbyte/src/airflow/providers/airbyte/hooks/airbyte.py
index 4094b83d316..a30012d8394 100644
--- a/providers/airbyte/src/airflow/providers/airbyte/hooks/airbyte.py
+++ b/providers/airbyte/src/airflow/providers/airbyte/hooks/airbyte.py
@@ -75,12 +75,37 @@ class AirbyteHook(BaseHook):
return conn_params
def create_api_session(self) -> AirbyteAPI:
- """Create Airbyte API session."""
- credentials = SchemeClientCredentials(
- client_id=self.conn["client_id"],
- client_secret=self.conn["client_secret"],
- token_url=self.conn["token_url"],
- )
+ """
+ Create Airbyte API session.
+
+ When ``client_id`` and ``client_secret`` are provided (via the
+ connection's *Login* and *Password* fields), the SDK authenticates
+ with OAuth2 Client Credentials — required for Airbyte Cloud and
+ Enterprise deployments.
+
+ When credentials are **not** provided, the session is created
+ without authentication. This is the correct mode for Airbyte OSS
+ deployments that do not have the ``/v1/applications/token``
+ endpoint enabled.
+ """
+ client_id = self.conn["client_id"]
+ client_secret = self.conn["client_secret"]
+
+ security: Security | None = None
+ if client_id and client_secret:
+ credentials = SchemeClientCredentials(
+ client_id=client_id,
+ client_secret=client_secret,
+ token_url=self.conn["token_url"],
+ )
+ security = Security(client_credentials=credentials)
+ else:
+ self.log.info(
+ "No client_id/client_secret provided in connection '%s'. "
+ "Creating Airbyte API session without authentication "
+ "(suitable for Airbyte OSS without auth enabled).",
+ self.airbyte_conn_id,
+ )
client = None
if self.conn["proxies"]:
@@ -90,7 +115,7 @@ class AirbyteHook(BaseHook):
return AirbyteAPI(
server_url=self.conn["host"],
- security=Security(client_credentials=credentials),
+ security=security,
client=client,
)
@@ -104,8 +129,8 @@ class AirbyteHook(BaseHook):
],
"relabeling": {
"host": "Server URL",
- "login": "Client ID",
- "password": "Client Secret",
+ "login": "Client ID (optional)",
+ "password": "Client Secret (optional)",
"schema": "Token URL",
},
"placeholders": {},
diff --git a/providers/airbyte/tests/unit/airbyte/hooks/test_airbyte.py b/providers/airbyte/tests/unit/airbyte/hooks/test_airbyte.py
index f75fc1cb76a..030c5de5bdb 100644
--- a/providers/airbyte/tests/unit/airbyte/hooks/test_airbyte.py
+++ b/providers/airbyte/tests/unit/airbyte/hooks/test_airbyte.py
@@ -36,6 +36,7 @@ class TestAirbyteHook:
conn_type = "airbyte"
airbyte_conn_id = "airbyte_conn_id_test"
airbyte_conn_id_with_proxy = "airbyte_conn_id_test_with_proxy"
+ airbyte_conn_id_with_credentials = "airbyte_conn_id_test_with_credentials"
connection_id = "conn_test_sync"
job_id = 1
host = "http://test-airbyte:8000/public/v1/api/"
@@ -70,6 +71,16 @@ class TestAirbyteHook:
extra=self._mock_proxy,
)
)
+ create_connection_without_db(
+ Connection(
+ conn_id=self.airbyte_conn_id_with_credentials,
+ conn_type=self.conn_type,
+ host=self.host,
+ port=self.port,
+ login="test-client-id",
+ password="test-client-secret",
+ )
+ )
self.hook = AirbyteHook(airbyte_conn_id=self.airbyte_conn_id)
self.hook_with_proxy = AirbyteHook(airbyte_conn_id=self.airbyte_conn_id_with_proxy)
@@ -235,6 +246,35 @@ class TestAirbyteHook:
assert hook.airbyte_api is not None
assert hook.airbyte_api.sdk_configuration.client.proxies == self._mock_proxy["proxies"]
+ def test_create_api_session_without_credentials(self):
+ """Test that a session without OAuth credentials creates an unauthenticated client."""
+ # The default connection (self.airbyte_conn_id) has no login/password
+ hook = AirbyteHook(airbyte_conn_id=self.airbyte_conn_id)
+ assert hook.airbyte_api is not None
+ # SDK should have been created with security=None
+ assert hook.airbyte_api.sdk_configuration.security is None
+
+ def test_create_api_session_with_credentials(self):
+ """Test that a session with OAuth credentials uses client_credentials auth."""
+ hook = AirbyteHook(airbyte_conn_id=self.airbyte_conn_id_with_credentials)
+ assert hook.airbyte_api is not None
+ security = hook.airbyte_api.sdk_configuration.security
+ assert security is not None
+ assert security.client_credentials is not None
+ assert security.client_credentials.client_id == "test-client-id"
+ assert security.client_credentials.client_secret == "test-client-secret"
+
+ @mock.patch("airbyte_api.jobs.Jobs.create_job")
+ def test_submit_sync_connection_without_credentials(self, create_job_mock):
+ """Test that sync submission works without OAuth credentials."""
+ mock_response = mock.Mock()
+ mock_response.job_response = self._mock_sync_conn_success_response_body
+ create_job_mock.return_value = mock_response
+
+ # Use the default hook which has no credentials
+ resp = self.hook.submit_sync_connection(connection_id=self.connection_id)
+ assert resp == self._mock_sync_conn_success_response_body
+
def test_get_ui_field_behaviour(self):
"""
Test the UI field behavior configuration for Airbyte connections.
@@ -243,8 +283,8 @@ class TestAirbyteHook:
"hidden_fields": ["extra", "port"],
"relabeling": {
"host": "Server URL",
- "login": "Client ID",
- "password": "Client Secret",
+ "login": "Client ID (optional)",
+ "password": "Client Secret (optional)",
"schema": "Token URL",
},
"placeholders": {},