-
Notifications
You must be signed in to change notification settings - Fork 71
feat(cloud): Add CloudCredentials class and bearer token authentication support #916
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
947ceb8
feat(cloud): Add CloudCredentials class and bearer token authenticati…
devin-ai-integration[bot] 7907954
fix: Ensure CloudWorkspace backwards compatibility
devin-ai-integration[bot] 17affbb
fix: Add explicit bearer_token arguments to all api_util callers
devin-ai-integration[bot] 73b525f
refactor: Use direct null checks in get_airbyte_server_instance
devin-ai-integration[bot] ca21dc9
Potential fix for pull request finding 'Unreachable code'
aaronsteers 9e46b24
refactor: Rename CloudCredentials to CloudClientConfig
devin-ai-integration[bot] 365bad1
fix: Add bearer_token=None to all api_util test calls
devin-ai-integration[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| # Copyright (c) 2024 Airbyte, Inc., all rights reserved. | ||
| """Cloud credentials configuration for Airbyte Cloud API authentication. | ||
| This module provides the CloudCredentials class for managing authentication | ||
| credentials when connecting to Airbyte Cloud, OSS, or Enterprise instances. | ||
| Two authentication methods are supported (mutually exclusive): | ||
| 1. OAuth2 client credentials (client_id + client_secret) | ||
| 2. Bearer token authentication | ||
| Example usage with client credentials: | ||
| ```python | ||
| from airbyte.cloud.credentials import CloudCredentials | ||
| credentials = CloudCredentials( | ||
| client_id="your-client-id", | ||
| client_secret="your-client-secret", | ||
| ) | ||
| ``` | ||
| Example usage with bearer token: | ||
| ```python | ||
| from airbyte.cloud.credentials import CloudCredentials | ||
| credentials = CloudCredentials( | ||
| bearer_token="your-bearer-token", | ||
| ) | ||
| ``` | ||
| Example using environment variables: | ||
| ```python | ||
| from airbyte.cloud.credentials import CloudCredentials | ||
| # Resolves from AIRBYTE_CLOUD_CLIENT_ID, AIRBYTE_CLOUD_CLIENT_SECRET, | ||
| # AIRBYTE_CLOUD_BEARER_TOKEN, and AIRBYTE_CLOUD_API_URL environment variables | ||
| credentials = CloudCredentials.from_env() | ||
| ``` | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
|
|
||
| from airbyte._util import api_util | ||
| from airbyte.cloud.auth import ( | ||
| resolve_cloud_api_url, | ||
| resolve_cloud_bearer_token, | ||
| resolve_cloud_client_id, | ||
| resolve_cloud_client_secret, | ||
| ) | ||
| from airbyte.exceptions import PyAirbyteInputError | ||
| from airbyte.secrets.base import SecretString | ||
|
|
||
|
|
||
| @dataclass | ||
| class CloudCredentials: | ||
| """Authentication credentials for Airbyte Cloud API. | ||
| This class encapsulates the authentication configuration needed to connect | ||
| to Airbyte Cloud, OSS, or Enterprise instances. It supports two mutually | ||
| exclusive authentication methods: | ||
| 1. OAuth2 client credentials flow (client_id + client_secret) | ||
| 2. Bearer token authentication | ||
| Exactly one authentication method must be provided. Providing both or neither | ||
| will raise a validation error. | ||
| Attributes: | ||
| client_id: OAuth2 client ID for client credentials flow. | ||
| client_secret: OAuth2 client secret for client credentials flow. | ||
| bearer_token: Pre-generated bearer token for direct authentication. | ||
| api_root: The API root URL. Defaults to Airbyte Cloud API. | ||
| """ | ||
|
|
||
| client_id: SecretString | None = None | ||
| """OAuth2 client ID for client credentials authentication.""" | ||
|
|
||
| client_secret: SecretString | None = None | ||
| """OAuth2 client secret for client credentials authentication.""" | ||
|
|
||
| bearer_token: SecretString | None = None | ||
| """Bearer token for direct authentication (alternative to client credentials).""" | ||
|
|
||
| api_root: str = api_util.CLOUD_API_ROOT | ||
| """The API root URL. Defaults to Airbyte Cloud API.""" | ||
|
|
||
| def __post_init__(self) -> None: | ||
| """Validate credentials and ensure secrets are properly wrapped.""" | ||
| # Wrap secrets in SecretString if they aren't already | ||
| if self.client_id is not None: | ||
| self.client_id = SecretString(self.client_id) | ||
| if self.client_secret is not None: | ||
| self.client_secret = SecretString(self.client_secret) | ||
| if self.bearer_token is not None: | ||
| self.bearer_token = SecretString(self.bearer_token) | ||
|
|
||
| # Validate mutual exclusivity | ||
| has_client_credentials = self.client_id is not None or self.client_secret is not None | ||
| has_bearer_token = self.bearer_token is not None | ||
|
|
||
| if has_client_credentials and has_bearer_token: | ||
| raise PyAirbyteInputError( | ||
| message="Cannot use both client credentials and bearer token authentication.", | ||
| guidance=( | ||
| "Provide either client_id and client_secret together, " | ||
| "or bearer_token alone, but not both." | ||
| ), | ||
| ) | ||
|
|
||
| if has_client_credentials and (self.client_id is None or self.client_secret is None): | ||
| # If using client credentials, both must be provided | ||
| raise PyAirbyteInputError( | ||
| message="Incomplete client credentials.", | ||
| guidance=( | ||
| "When using client credentials authentication, " | ||
| "both client_id and client_secret must be provided." | ||
| ), | ||
| ) | ||
|
|
||
| if not has_client_credentials and not has_bearer_token: | ||
| raise PyAirbyteInputError( | ||
| message="No authentication credentials provided.", | ||
| guidance=( | ||
| "Provide either client_id and client_secret together for OAuth2 " | ||
| "client credentials flow, or bearer_token for direct authentication." | ||
| ), | ||
| ) | ||
|
|
||
| @property | ||
| def uses_bearer_token(self) -> bool: | ||
| """Return True if using bearer token authentication.""" | ||
| return self.bearer_token is not None | ||
|
|
||
| @property | ||
| def uses_client_credentials(self) -> bool: | ||
| """Return True if using client credentials authentication.""" | ||
| return self.client_id is not None and self.client_secret is not None | ||
|
|
||
| @classmethod | ||
| def from_env( | ||
| cls, | ||
| *, | ||
| api_root: str | None = None, | ||
| ) -> CloudCredentials: | ||
| """Create CloudCredentials from environment variables. | ||
| This factory method resolves credentials from environment variables, | ||
| providing a convenient way to create credentials without explicitly | ||
| passing secrets. | ||
| Environment variables used: | ||
| - `AIRBYTE_CLOUD_CLIENT_ID`: OAuth client ID (for client credentials flow). | ||
| - `AIRBYTE_CLOUD_CLIENT_SECRET`: OAuth client secret (for client credentials flow). | ||
| - `AIRBYTE_CLOUD_BEARER_TOKEN`: Bearer token (alternative to client credentials). | ||
| - `AIRBYTE_CLOUD_API_URL`: Optional. The API root URL (defaults to Airbyte Cloud). | ||
| The method will first check for a bearer token. If not found, it will | ||
| attempt to use client credentials. | ||
| Args: | ||
| api_root: The API root URL. If not provided, will be resolved from | ||
| the `AIRBYTE_CLOUD_API_URL` environment variable, or default to | ||
| the Airbyte Cloud API. | ||
| Returns: | ||
| A CloudCredentials instance configured with credentials from the environment. | ||
| Raises: | ||
| PyAirbyteSecretNotFoundError: If required credentials are not found in | ||
| the environment. | ||
| """ | ||
| resolved_api_root = resolve_cloud_api_url(api_root) | ||
|
|
||
| # Try bearer token first | ||
| bearer_token = resolve_cloud_bearer_token() | ||
| if bearer_token: | ||
| return cls( | ||
| bearer_token=bearer_token, | ||
| api_root=resolved_api_root, | ||
| ) | ||
|
|
||
| # Fall back to client credentials | ||
| return cls( | ||
| client_id=resolve_cloud_client_id(), | ||
| client_secret=resolve_cloud_client_secret(), | ||
| api_root=resolved_api_root, | ||
| ) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.