mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-09 22:35:54 -05:00
To allow for a simpler dev experience, the new SDK now auto discovers providers and registers them. However, the OAuth system was still requiring these credentials to be hardcoded in the settings object. This PR changes that to verify the env var is present during registration and then allows the OAuth system to load them from the env. ### Changes 🏗️ - **OAuth Registration**: Modified `ProviderBuilder.with_oauth(..)` to check OAuth env vars exist during registration - **OAuth Loading**: Updated OAuth system to load credentials from env vars if not using secrets - **Block Filtering**: Added `is_block_auth_configured()` function to check if a block has valid authorization options configured at runtime - **Test Updates**: Fixed failing SDK registry tests to properly mock environment variables for OAuth registration ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Verified OAuth system checks that env vars exist during provider registration - [x] Confirmed OAuth system can use env vars directly without requiring hardcoded secrets - [x] Tested that blocks with unconfigured OAuth providers are filtered out - [x] All SDK registry tests pass with proper env var mocking #### For configuration changes: - [x] `.env.example` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) - OAuth providers now require their client ID and secret env vars to be set for registration - No changes required to `.env.example` or `docker-compose.yml` --------- Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
183 lines
6.5 KiB
Python
183 lines
6.5 KiB
Python
"""
|
|
Builder class for creating provider configurations with a fluent API.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
from typing import Callable, List, Optional, Type
|
|
|
|
from pydantic import SecretStr
|
|
|
|
from backend.data.cost import BlockCost, BlockCostType
|
|
from backend.data.model import (
|
|
APIKeyCredentials,
|
|
Credentials,
|
|
CredentialsType,
|
|
UserPasswordCredentials,
|
|
)
|
|
from backend.integrations.oauth.base import BaseOAuthHandler
|
|
from backend.integrations.webhooks._base import BaseWebhooksManager
|
|
from backend.sdk.provider import OAuthConfig, Provider
|
|
from backend.sdk.registry import AutoRegistry
|
|
from backend.util.settings import Settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ProviderBuilder:
|
|
"""Builder for creating provider configurations."""
|
|
|
|
def __init__(self, name: str):
|
|
self.name = name
|
|
self._oauth_config: Optional[OAuthConfig] = None
|
|
self._webhook_manager: Optional[Type[BaseWebhooksManager]] = None
|
|
self._default_credentials: List[Credentials] = []
|
|
self._base_costs: List[BlockCost] = []
|
|
self._supported_auth_types: set[CredentialsType] = set()
|
|
self._api_client_factory: Optional[Callable] = None
|
|
self._error_handler: Optional[Callable[[Exception], str]] = None
|
|
self._default_scopes: Optional[List[str]] = None
|
|
self._client_id_env_var: Optional[str] = None
|
|
self._client_secret_env_var: Optional[str] = None
|
|
self._extra_config: dict = {}
|
|
|
|
def with_oauth(
|
|
self,
|
|
handler_class: Type[BaseOAuthHandler],
|
|
scopes: Optional[List[str]] = None,
|
|
client_id_env_var: Optional[str] = None,
|
|
client_secret_env_var: Optional[str] = None,
|
|
) -> "ProviderBuilder":
|
|
"""Add OAuth support."""
|
|
if not client_id_env_var or not client_secret_env_var:
|
|
client_id_env_var = f"{self.name}_client_id".upper()
|
|
client_secret_env_var = f"{self.name}_client_secret".upper()
|
|
|
|
if os.getenv(client_id_env_var) and os.getenv(client_secret_env_var):
|
|
self._client_id_env_var = client_id_env_var
|
|
self._client_secret_env_var = client_secret_env_var
|
|
|
|
self._oauth_config = OAuthConfig(
|
|
oauth_handler=handler_class,
|
|
scopes=scopes,
|
|
client_id_env_var=client_id_env_var,
|
|
client_secret_env_var=client_secret_env_var,
|
|
)
|
|
self._supported_auth_types.add("oauth2")
|
|
else:
|
|
logger.warning(
|
|
f"Provider {self.name.upper()} implements OAuth but the required env "
|
|
f"vars {client_id_env_var} and {client_secret_env_var} are not both set"
|
|
)
|
|
return self
|
|
|
|
def with_api_key(self, env_var_name: str, title: str) -> "ProviderBuilder":
|
|
"""Add API key support with environment variable name."""
|
|
self._supported_auth_types.add("api_key")
|
|
|
|
# Register the API key mapping
|
|
AutoRegistry.register_api_key(self.name, env_var_name)
|
|
|
|
# Check if API key exists in environment
|
|
api_key = os.getenv(env_var_name)
|
|
if api_key:
|
|
self._default_credentials.append(
|
|
APIKeyCredentials(
|
|
id=f"{self.name}-default",
|
|
provider=self.name,
|
|
api_key=SecretStr(api_key),
|
|
title=title,
|
|
)
|
|
)
|
|
return self
|
|
|
|
def with_api_key_from_settings(
|
|
self, settings_attr: str, title: str
|
|
) -> "ProviderBuilder":
|
|
"""Use existing API key from settings."""
|
|
self._supported_auth_types.add("api_key")
|
|
|
|
# Try to get the API key from settings
|
|
settings = Settings()
|
|
api_key = getattr(settings.secrets, settings_attr, None)
|
|
if api_key:
|
|
self._default_credentials.append(
|
|
APIKeyCredentials(
|
|
id=f"{self.name}-default",
|
|
provider=self.name,
|
|
api_key=api_key,
|
|
title=title,
|
|
)
|
|
)
|
|
return self
|
|
|
|
def with_user_password(
|
|
self, username_env_var: str, password_env_var: str, title: str
|
|
) -> "ProviderBuilder":
|
|
"""Add username/password support with environment variable names."""
|
|
self._supported_auth_types.add("user_password")
|
|
|
|
# Check if credentials exist in environment
|
|
username = os.getenv(username_env_var)
|
|
password = os.getenv(password_env_var)
|
|
if username and password:
|
|
self._default_credentials.append(
|
|
UserPasswordCredentials(
|
|
id=f"{self.name}-default",
|
|
provider=self.name,
|
|
username=SecretStr(username),
|
|
password=SecretStr(password),
|
|
title=title,
|
|
)
|
|
)
|
|
return self
|
|
|
|
def with_webhook_manager(
|
|
self, manager_class: Type[BaseWebhooksManager]
|
|
) -> "ProviderBuilder":
|
|
"""Register webhook manager for this provider."""
|
|
self._webhook_manager = manager_class
|
|
return self
|
|
|
|
def with_base_cost(
|
|
self, amount: int, cost_type: BlockCostType
|
|
) -> "ProviderBuilder":
|
|
"""Set base cost for all blocks using this provider."""
|
|
self._base_costs.append(BlockCost(cost_amount=amount, cost_type=cost_type))
|
|
return self
|
|
|
|
def with_api_client(self, factory: Callable) -> "ProviderBuilder":
|
|
"""Register API client factory."""
|
|
self._api_client_factory = factory
|
|
return self
|
|
|
|
def with_error_handler(
|
|
self, handler: Callable[[Exception], str]
|
|
) -> "ProviderBuilder":
|
|
"""Register error handler for provider-specific errors."""
|
|
self._error_handler = handler
|
|
return self
|
|
|
|
def with_config(self, **kwargs) -> "ProviderBuilder":
|
|
"""Add additional configuration options."""
|
|
self._extra_config.update(kwargs)
|
|
return self
|
|
|
|
def build(self) -> Provider:
|
|
"""Build and register the provider configuration."""
|
|
provider = Provider(
|
|
name=self.name,
|
|
oauth_config=self._oauth_config,
|
|
webhook_manager=self._webhook_manager,
|
|
default_credentials=self._default_credentials,
|
|
base_costs=self._base_costs,
|
|
supported_auth_types=self._supported_auth_types,
|
|
api_client_factory=self._api_client_factory,
|
|
error_handler=self._error_handler,
|
|
**self._extra_config,
|
|
)
|
|
|
|
# Auto-registration happens here
|
|
AutoRegistry.register_provider(provider)
|
|
return provider
|