mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Refactor: rename user secrets table to custom secrets (#11525)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
26c636d63e
commit
eb616dfae4
@ -31,7 +31,7 @@ from server.utils.conversation_callback_utils import register_callback_processor
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.integrations.provider import ProviderToken, ProviderType
|
||||
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
|
||||
|
||||
@ -250,7 +250,7 @@ class GithubManager(Manager):
|
||||
f'[GitHub] Creating new conversation for user {user_info.username}'
|
||||
)
|
||||
|
||||
secret_store = UserSecrets(
|
||||
secret_store = Secrets(
|
||||
provider_tokens=MappingProxyType(
|
||||
{
|
||||
ProviderType.GITHUB: ProviderToken(
|
||||
|
||||
@ -25,7 +25,7 @@ from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.integrations.gitlab.gitlab_service import GitLabServiceImpl
|
||||
from openhands.integrations.provider import ProviderToken, ProviderType
|
||||
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
|
||||
|
||||
class GitlabManager(Manager):
|
||||
@ -198,7 +198,7 @@ class GitlabManager(Manager):
|
||||
f'[GitLab] Creating new conversation for user {user_info.username}'
|
||||
)
|
||||
|
||||
secret_store = UserSecrets(
|
||||
secret_store = Secrets(
|
||||
provider_tokens=MappingProxyType(
|
||||
{
|
||||
ProviderType.GITLAB: ProviderToken(
|
||||
|
||||
@ -57,7 +57,7 @@ class JiraNewConversationView(JiraViewInterface):
|
||||
raise StartingConvoException('No repository selected for this conversation')
|
||||
|
||||
provider_tokens = await self.saas_user_auth.get_provider_tokens()
|
||||
user_secrets = await self.saas_user_auth.get_user_secrets()
|
||||
user_secrets = await self.saas_user_auth.get_secrets()
|
||||
instructions, user_msg = self._get_instructions(jinja_env)
|
||||
|
||||
try:
|
||||
|
||||
@ -60,7 +60,7 @@ class JiraDcNewConversationView(JiraDcViewInterface):
|
||||
raise StartingConvoException('No repository selected for this conversation')
|
||||
|
||||
provider_tokens = await self.saas_user_auth.get_provider_tokens()
|
||||
user_secrets = await self.saas_user_auth.get_user_secrets()
|
||||
user_secrets = await self.saas_user_auth.get_secrets()
|
||||
instructions, user_msg = self._get_instructions(jinja_env)
|
||||
|
||||
try:
|
||||
|
||||
@ -57,7 +57,7 @@ class LinearNewConversationView(LinearViewInterface):
|
||||
raise StartingConvoException('No repository selected for this conversation')
|
||||
|
||||
provider_tokens = await self.saas_user_auth.get_provider_tokens()
|
||||
user_secrets = await self.saas_user_auth.get_user_secrets()
|
||||
user_secrets = await self.saas_user_auth.get_secrets()
|
||||
instructions, user_msg = self._get_instructions(jinja_env)
|
||||
|
||||
try:
|
||||
|
||||
@ -186,7 +186,7 @@ class SlackNewConversationView(SlackViewInterface):
|
||||
self._verify_necessary_values_are_set()
|
||||
|
||||
provider_tokens = await self.saas_user_auth.get_provider_tokens()
|
||||
user_secrets = await self.saas_user_auth.get_user_secrets()
|
||||
user_secrets = await self.saas_user_auth.get_secrets()
|
||||
user_instructions, conversation_instructions = self._get_instructions(jinja)
|
||||
|
||||
# Determine git provider from repository
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
"""rename user_secrets table to custom_secrets
|
||||
|
||||
Revision ID: 079
|
||||
Revises: 078
|
||||
Create Date: 2025-10-27 00:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '079'
|
||||
down_revision: Union[str, None] = '078'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Rename the table from user_secrets to custom_secrets
|
||||
op.rename_table('user_secrets', 'custom_secrets')
|
||||
|
||||
# Rename the index to match the new table name
|
||||
op.drop_index('idx_user_secrets_keycloak_user_id', 'custom_secrets')
|
||||
op.create_index(
|
||||
'idx_custom_secrets_keycloak_user_id', 'custom_secrets', ['keycloak_user_id']
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Rename the index back to the original name
|
||||
op.drop_index('idx_custom_secrets_keycloak_user_id', 'custom_secrets')
|
||||
op.create_index(
|
||||
'idx_user_secrets_keycloak_user_id', 'custom_secrets', ['keycloak_user_id']
|
||||
)
|
||||
|
||||
# Rename the table back from custom_secrets to user_secrets
|
||||
op.rename_table('custom_secrets', 'user_secrets')
|
||||
@ -31,7 +31,7 @@ from openhands.integrations.provider import (
|
||||
)
|
||||
from openhands.server.settings import Settings
|
||||
from openhands.server.user_auth.user_auth import AuthType, UserAuth
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.settings.settings_store import SettingsStore
|
||||
|
||||
token_manager = TokenManager()
|
||||
@ -52,7 +52,7 @@ class SaasUserAuth(UserAuth):
|
||||
settings_store: SaasSettingsStore | None = None
|
||||
secrets_store: SaasSecretsStore | None = None
|
||||
_settings: Settings | None = None
|
||||
_user_secrets: UserSecrets | None = None
|
||||
_secrets: Secrets | None = None
|
||||
accepted_tos: bool | None = None
|
||||
auth_type: AuthType = AuthType.COOKIE
|
||||
|
||||
@ -119,13 +119,13 @@ class SaasUserAuth(UserAuth):
|
||||
self.secrets_store = secrets_store
|
||||
return secrets_store
|
||||
|
||||
async def get_user_secrets(self):
|
||||
user_secrets = self._user_secrets
|
||||
async def get_secrets(self):
|
||||
user_secrets = self._secrets
|
||||
if user_secrets:
|
||||
return user_secrets
|
||||
secrets_store = await self.get_secrets_store()
|
||||
user_secrets = await secrets_store.load()
|
||||
self._user_secrets = user_secrets
|
||||
self._secrets = user_secrets
|
||||
return user_secrets
|
||||
|
||||
async def get_access_token(self) -> SecretStr | None:
|
||||
@ -148,7 +148,7 @@ class SaasUserAuth(UserAuth):
|
||||
if not access_token:
|
||||
raise AuthError()
|
||||
|
||||
user_secrets = await self.get_user_secrets()
|
||||
user_secrets = await self.get_secrets()
|
||||
|
||||
try:
|
||||
# TODO: I think we can do this in a single request if we refactor
|
||||
|
||||
@ -7,11 +7,11 @@ from dataclasses import dataclass
|
||||
from cryptography.fernet import Fernet
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from storage.database import session_maker
|
||||
from storage.stored_user_secrets import StoredUserSecrets
|
||||
from storage.stored_custom_secrets import StoredCustomSecrets
|
||||
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
|
||||
|
||||
@ -21,20 +21,20 @@ class SaasSecretsStore(SecretsStore):
|
||||
session_maker: sessionmaker
|
||||
config: OpenHandsConfig
|
||||
|
||||
async def load(self) -> UserSecrets | None:
|
||||
async def load(self) -> Secrets | None:
|
||||
if not self.user_id:
|
||||
return None
|
||||
|
||||
with self.session_maker() as session:
|
||||
# Fetch all secrets for the given user ID
|
||||
settings = (
|
||||
session.query(StoredUserSecrets)
|
||||
.filter(StoredUserSecrets.keycloak_user_id == self.user_id)
|
||||
session.query(StoredCustomSecrets)
|
||||
.filter(StoredCustomSecrets.keycloak_user_id == self.user_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
if not settings:
|
||||
return UserSecrets()
|
||||
return Secrets()
|
||||
|
||||
kwargs = {}
|
||||
for secret in settings:
|
||||
@ -45,14 +45,14 @@ class SaasSecretsStore(SecretsStore):
|
||||
|
||||
self._decrypt_kwargs(kwargs)
|
||||
|
||||
return UserSecrets(custom_secrets=kwargs) # type: ignore[arg-type]
|
||||
return Secrets(custom_secrets=kwargs) # type: ignore[arg-type]
|
||||
|
||||
async def store(self, item: UserSecrets):
|
||||
async def store(self, item: Secrets):
|
||||
with self.session_maker() as session:
|
||||
# Incoming secrets are always the most updated ones
|
||||
# Delete all existing records and override with incoming ones
|
||||
session.query(StoredUserSecrets).filter(
|
||||
StoredUserSecrets.keycloak_user_id == self.user_id
|
||||
session.query(StoredCustomSecrets).filter(
|
||||
StoredCustomSecrets.keycloak_user_id == self.user_id
|
||||
).delete()
|
||||
|
||||
# Prepare the new secrets data
|
||||
@ -74,7 +74,7 @@ class SaasSecretsStore(SecretsStore):
|
||||
|
||||
# Add the new secrets
|
||||
for secret_name, secret_value, description in secret_tuples:
|
||||
new_secret = StoredUserSecrets(
|
||||
new_secret = StoredCustomSecrets(
|
||||
keycloak_user_id=self.user_id,
|
||||
secret_name=secret_name,
|
||||
secret_value=secret_value,
|
||||
|
||||
@ -2,8 +2,8 @@ from sqlalchemy import Column, Identity, Integer, String
|
||||
from storage.base import Base
|
||||
|
||||
|
||||
class StoredUserSecrets(Base): # type: ignore
|
||||
__tablename__ = 'user_secrets'
|
||||
class StoredCustomSecrets(Base): # type: ignore
|
||||
__tablename__ = 'custom_secrets'
|
||||
id = Column(Integer, Identity(), primary_key=True)
|
||||
keycloak_user_id = Column(String, nullable=True, index=True)
|
||||
secret_name = Column(String, nullable=False)
|
||||
@ -309,7 +309,7 @@ class TestJiraViewEdgeCases:
|
||||
mock_agent_loop_info,
|
||||
):
|
||||
"""Test conversation creation when user has no secrets"""
|
||||
new_conversation_view.saas_user_auth.get_user_secrets.return_value = None
|
||||
new_conversation_view.saas_user_auth.get_secrets.return_value = None
|
||||
mock_create_conversation.return_value = mock_agent_loop_info
|
||||
mock_store.create_conversation = AsyncMock()
|
||||
|
||||
|
||||
@ -309,7 +309,7 @@ class TestJiraDcViewEdgeCases:
|
||||
mock_agent_loop_info,
|
||||
):
|
||||
"""Test conversation creation when user has no secrets"""
|
||||
new_conversation_view.saas_user_auth.get_user_secrets.return_value = None
|
||||
new_conversation_view.saas_user_auth.get_secrets.return_value = None
|
||||
mock_create_conversation.return_value = mock_agent_loop_info
|
||||
mock_store.create_conversation = AsyncMock()
|
||||
|
||||
|
||||
@ -309,7 +309,7 @@ class TestLinearViewEdgeCases:
|
||||
mock_agent_loop_info,
|
||||
):
|
||||
"""Test conversation creation when user has no secrets"""
|
||||
new_conversation_view.saas_user_auth.get_user_secrets.return_value = None
|
||||
new_conversation_view.saas_user_auth.get_secrets.return_value = None
|
||||
mock_create_conversation.return_value = mock_agent_loop_info
|
||||
mock_store.create_conversation = AsyncMock()
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
from pydantic import SecretStr
|
||||
from storage.saas_secrets_store import SaasSecretsStore
|
||||
from storage.stored_user_secrets import StoredUserSecrets
|
||||
from storage.stored_custom_secrets import StoredCustomSecrets
|
||||
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.integrations.provider import CustomSecret
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -27,8 +27,8 @@ def secrets_store(session_maker, mock_config):
|
||||
class TestSaasSecretsStore:
|
||||
@pytest.mark.asyncio
|
||||
async def test_store_and_load(self, secrets_store):
|
||||
# Create a UserSecrets object with some test data
|
||||
user_secrets = UserSecrets(
|
||||
# Create a Secrets object with some test data
|
||||
user_secrets = Secrets(
|
||||
custom_secrets=MappingProxyType(
|
||||
{
|
||||
'api_token': CustomSecret.from_value(
|
||||
@ -60,8 +60,8 @@ class TestSaasSecretsStore:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_encryption_decryption(self, secrets_store):
|
||||
# Create a UserSecrets object with sensitive data
|
||||
user_secrets = UserSecrets(
|
||||
# Create a Secrets object with sensitive data
|
||||
user_secrets = Secrets(
|
||||
custom_secrets=MappingProxyType(
|
||||
{
|
||||
'api_token': CustomSecret.from_value(
|
||||
@ -87,8 +87,8 @@ class TestSaasSecretsStore:
|
||||
# Verify the data is encrypted in the database
|
||||
with secrets_store.session_maker() as session:
|
||||
stored = (
|
||||
session.query(StoredUserSecrets)
|
||||
.filter(StoredUserSecrets.keycloak_user_id == 'user-id')
|
||||
session.query(StoredCustomSecrets)
|
||||
.filter(StoredCustomSecrets.keycloak_user_id == 'user-id')
|
||||
.first()
|
||||
)
|
||||
|
||||
@ -154,7 +154,7 @@ class TestSaasSecretsStore:
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_existing_secrets(self, secrets_store):
|
||||
# Create and store initial secrets
|
||||
initial_secrets = UserSecrets(
|
||||
initial_secrets = Secrets(
|
||||
custom_secrets=MappingProxyType(
|
||||
{
|
||||
'api_token': CustomSecret.from_value(
|
||||
@ -169,7 +169,7 @@ class TestSaasSecretsStore:
|
||||
await secrets_store.store(initial_secrets)
|
||||
|
||||
# Create and store updated secrets
|
||||
updated_secrets = UserSecrets(
|
||||
updated_secrets = Secrets(
|
||||
custom_secrets=MappingProxyType(
|
||||
{
|
||||
'api_token': CustomSecret.from_value(
|
||||
|
||||
@ -71,7 +71,7 @@ class AuthUserContext(UserContext):
|
||||
results = {}
|
||||
|
||||
# Include custom secrets...
|
||||
secrets = await self.user_auth.get_user_secrets()
|
||||
secrets = await self.user_auth.get_secrets()
|
||||
if secrets:
|
||||
for name, custom_secret in secrets.custom_secrets.items():
|
||||
results[name] = StaticSecret(value=custom_secret.secret)
|
||||
|
||||
@ -28,7 +28,7 @@ from openhands.runtime import get_runtime_cls
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.server.services.conversation_stats import ConversationStats
|
||||
from openhands.storage import get_file_store
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.utils.async_utils import GENERAL_TIMEOUT, call_async_from_sync
|
||||
|
||||
|
||||
@ -109,9 +109,9 @@ def get_provider_tokens():
|
||||
bitbucket_token = SecretStr(os.environ['BITBUCKET_TOKEN'])
|
||||
provider_tokens[ProviderType.BITBUCKET] = ProviderToken(token=bitbucket_token)
|
||||
|
||||
# Wrap provider tokens in UserSecrets if any tokens were found
|
||||
# Wrap provider tokens in Secrets if any tokens were found
|
||||
secret_store = (
|
||||
UserSecrets(provider_tokens=provider_tokens) if provider_tokens else None # type: ignore[arg-type]
|
||||
Secrets(provider_tokens=provider_tokens) if provider_tokens else None # type: ignore[arg-type]
|
||||
)
|
||||
return secret_store.provider_tokens if secret_store else None
|
||||
|
||||
|
||||
@ -71,8 +71,8 @@ from openhands.server.types import LLMAuthenticationError, MissingSettingsError
|
||||
from openhands.server.user_auth import (
|
||||
get_auth_type,
|
||||
get_provider_tokens,
|
||||
get_secrets,
|
||||
get_user_id,
|
||||
get_user_secrets,
|
||||
get_user_settings,
|
||||
get_user_settings_store,
|
||||
)
|
||||
@ -85,8 +85,8 @@ from openhands.storage.data_models.conversation_metadata import (
|
||||
ConversationTrigger,
|
||||
)
|
||||
from openhands.storage.data_models.conversation_status import ConversationStatus
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.data_models.settings import Settings
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.locations import get_experiment_config_filename
|
||||
from openhands.storage.settings.settings_store import SettingsStore
|
||||
from openhands.utils.async_utils import wait_all
|
||||
@ -210,7 +210,7 @@ async def new_conversation(
|
||||
data: InitSessionRequest,
|
||||
user_id: str = Depends(get_user_id),
|
||||
provider_tokens: PROVIDER_TOKEN_TYPE = Depends(get_provider_tokens),
|
||||
user_secrets: UserSecrets = Depends(get_user_secrets),
|
||||
user_secrets: Secrets = Depends(get_secrets),
|
||||
auth_type: AuthType | None = Depends(get_auth_type),
|
||||
) -> ConversationResponse:
|
||||
"""Initialize a new session or join an existing one.
|
||||
|
||||
@ -14,11 +14,11 @@ from openhands.server.settings import (
|
||||
)
|
||||
from openhands.server.user_auth import (
|
||||
get_provider_tokens,
|
||||
get_secrets,
|
||||
get_secrets_store,
|
||||
get_user_secrets,
|
||||
)
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.data_models.settings import Settings
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
from openhands.storage.settings.settings_store import SettingsStore
|
||||
|
||||
@ -32,20 +32,18 @@ app = APIRouter(prefix='/api', dependencies=get_dependencies())
|
||||
|
||||
async def invalidate_legacy_secrets_store(
|
||||
settings: Settings, settings_store: SettingsStore, secrets_store: SecretsStore
|
||||
) -> UserSecrets | None:
|
||||
) -> Secrets | None:
|
||||
"""We are moving `secrets_store` (a field from `Settings` object) to its own dedicated store
|
||||
This function moves the values from Settings to UserSecrets, and deletes the values in Settings
|
||||
This function moves the values from Settings to Secrets, and deletes the values in Settings
|
||||
While this function in called multiple times, the migration only ever happens once
|
||||
"""
|
||||
if len(settings.secrets_store.provider_tokens.items()) > 0:
|
||||
user_secrets = UserSecrets(
|
||||
provider_tokens=settings.secrets_store.provider_tokens
|
||||
)
|
||||
user_secrets = Secrets(provider_tokens=settings.secrets_store.provider_tokens)
|
||||
await secrets_store.store(user_secrets)
|
||||
|
||||
# Invalidate old tokens via settings store serializer
|
||||
invalidated_secrets_settings = settings.model_copy(
|
||||
update={'secrets_store': UserSecrets()}
|
||||
update={'secrets_store': Secrets()}
|
||||
)
|
||||
await settings_store.store(invalidated_secrets_settings)
|
||||
|
||||
@ -120,7 +118,7 @@ async def store_provider_tokens(
|
||||
try:
|
||||
user_secrets = await secrets_store.load()
|
||||
if not user_secrets:
|
||||
user_secrets = UserSecrets()
|
||||
user_secrets = Secrets()
|
||||
|
||||
if provider_info.provider_tokens:
|
||||
existing_providers = [provider for provider in user_secrets.provider_tokens]
|
||||
@ -183,7 +181,7 @@ async def unset_provider_tokens(
|
||||
|
||||
@app.get('/secrets', response_model=GETCustomSecrets)
|
||||
async def load_custom_secrets_names(
|
||||
user_secrets: UserSecrets | None = Depends(get_user_secrets),
|
||||
user_secrets: Secrets | None = Depends(get_secrets),
|
||||
) -> GETCustomSecrets | JSONResponse:
|
||||
try:
|
||||
if not user_secrets:
|
||||
@ -235,8 +233,8 @@ async def create_custom_secret(
|
||||
description=secret_description or '',
|
||||
)
|
||||
|
||||
# Create a new UserSecrets that preserves provider tokens
|
||||
updated_user_secrets = UserSecrets(
|
||||
# Create a new Secrets that preserves provider tokens
|
||||
updated_user_secrets = Secrets(
|
||||
custom_secrets=custom_secrets, # type: ignore[arg-type]
|
||||
provider_tokens=existing_secrets.provider_tokens
|
||||
if existing_secrets
|
||||
@ -290,7 +288,7 @@ async def update_custom_secret(
|
||||
description=secret_description or '',
|
||||
)
|
||||
|
||||
updated_secrets = UserSecrets(
|
||||
updated_secrets = Secrets(
|
||||
custom_secrets=custom_secrets, # type: ignore[arg-type]
|
||||
provider_tokens=existing_secrets.provider_tokens,
|
||||
)
|
||||
@ -330,8 +328,8 @@ async def delete_custom_secret(
|
||||
# Remove the secret
|
||||
custom_secrets.pop(secret_id)
|
||||
|
||||
# Create a new UserSecrets that preserves provider tokens and remaining secrets
|
||||
updated_secrets = UserSecrets(
|
||||
# Create a new Secrets that preserves provider tokens and remaining secrets
|
||||
updated_secrets = Secrets(
|
||||
custom_secrets=custom_secrets, # type: ignore[arg-type]
|
||||
provider_tokens=existing_secrets.provider_tokens,
|
||||
)
|
||||
|
||||
@ -27,7 +27,7 @@ from openhands.storage.data_models.conversation_metadata import (
|
||||
ConversationMetadata,
|
||||
ConversationTrigger,
|
||||
)
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.utils.conversation_summary import get_default_conversation_title
|
||||
|
||||
|
||||
@ -232,7 +232,7 @@ async def setup_init_conversation_settings(
|
||||
settings = await settings_store.load()
|
||||
|
||||
secrets_store = await SecretsStoreImpl.get_instance(config, user_id)
|
||||
user_secrets: UserSecrets | None = await secrets_store.load()
|
||||
user_secrets: Secrets | None = await secrets_store.load()
|
||||
|
||||
if not settings:
|
||||
from socketio.exceptions import ConnectionRefusedError
|
||||
|
||||
@ -30,7 +30,7 @@ from openhands.runtime.base import Runtime
|
||||
from openhands.runtime.impl.remote.remote_runtime import RemoteRuntime
|
||||
from openhands.runtime.runtime_status import RuntimeStatus
|
||||
from openhands.server.services.conversation_stats import ConversationStats
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.files import FileStore
|
||||
from openhands.utils.async_utils import EXECUTOR, call_sync_from_async
|
||||
from openhands.utils.shutdown_listener import should_continue
|
||||
@ -128,7 +128,7 @@ class AgentSession:
|
||||
finished = False # For monitoring
|
||||
runtime_connected = False
|
||||
restored_state = False
|
||||
custom_secrets_handler = UserSecrets(
|
||||
custom_secrets_handler = Secrets(
|
||||
custom_secrets=custom_secrets if custom_secrets else {} # type: ignore[arg-type]
|
||||
)
|
||||
try:
|
||||
@ -316,7 +316,7 @@ class AgentSession:
|
||||
if self.runtime is not None:
|
||||
raise RuntimeError('Runtime already created')
|
||||
|
||||
custom_secrets_handler = UserSecrets(custom_secrets=custom_secrets or {}) # type: ignore[arg-type]
|
||||
custom_secrets_handler = Secrets(custom_secrets=custom_secrets or {}) # type: ignore[arg-type]
|
||||
env_vars = custom_secrets_handler.get_env_vars()
|
||||
|
||||
self.logger.debug(f'Initializing runtime `{runtime_name}` now...')
|
||||
|
||||
@ -4,7 +4,7 @@ from pydantic import SecretStr
|
||||
from openhands.integrations.provider import PROVIDER_TOKEN_TYPE
|
||||
from openhands.server.settings import Settings
|
||||
from openhands.server.user_auth.user_auth import AuthType, get_user_auth
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
from openhands.storage.settings.settings_store import SettingsStore
|
||||
|
||||
@ -39,9 +39,9 @@ async def get_secrets_store(request: Request) -> SecretsStore:
|
||||
return secrets_store
|
||||
|
||||
|
||||
async def get_user_secrets(request: Request) -> UserSecrets | None:
|
||||
async def get_secrets(request: Request) -> Secrets | None:
|
||||
user_auth = await get_user_auth(request)
|
||||
user_secrets = await user_auth.get_user_secrets()
|
||||
user_secrets = await user_auth.get_secrets()
|
||||
return user_secrets
|
||||
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ from openhands.integrations.provider import PROVIDER_TOKEN_TYPE
|
||||
from openhands.server import shared
|
||||
from openhands.server.settings import Settings
|
||||
from openhands.server.user_auth.user_auth import UserAuth
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
from openhands.storage.settings.settings_store import SettingsStore
|
||||
|
||||
@ -19,7 +19,7 @@ class DefaultUserAuth(UserAuth):
|
||||
_settings: Settings | None = None
|
||||
_settings_store: SettingsStore | None = None
|
||||
_secrets_store: SecretsStore | None = None
|
||||
_user_secrets: UserSecrets | None = None
|
||||
_secrets: Secrets | None = None
|
||||
|
||||
async def get_user_id(self) -> str | None:
|
||||
"""The default implementation does not support multi tenancy, so user_id is always None"""
|
||||
@ -73,17 +73,17 @@ class DefaultUserAuth(UserAuth):
|
||||
self._secrets_store = secret_store
|
||||
return secret_store
|
||||
|
||||
async def get_user_secrets(self) -> UserSecrets | None:
|
||||
user_secrets = self._user_secrets
|
||||
async def get_secrets(self) -> Secrets | None:
|
||||
user_secrets = self._secrets
|
||||
if user_secrets:
|
||||
return user_secrets
|
||||
secrets_store = await self.get_secrets_store()
|
||||
user_secrets = await secrets_store.load()
|
||||
self._user_secrets = user_secrets
|
||||
self._secrets = user_secrets
|
||||
return user_secrets
|
||||
|
||||
async def get_provider_tokens(self) -> PROVIDER_TOKEN_TYPE | None:
|
||||
user_secrets = await self.get_user_secrets()
|
||||
user_secrets = await self.get_secrets()
|
||||
if user_secrets is None:
|
||||
return None
|
||||
return user_secrets.provider_tokens
|
||||
|
||||
@ -9,7 +9,7 @@ from pydantic import SecretStr
|
||||
from openhands.integrations.provider import PROVIDER_TOKEN_TYPE
|
||||
from openhands.server.settings import Settings
|
||||
from openhands.server.shared import server_config
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
from openhands.storage.settings.settings_store import SettingsStore
|
||||
from openhands.utils.import_utils import get_impl
|
||||
@ -69,7 +69,7 @@ class UserAuth(ABC):
|
||||
"""Get secrets store"""
|
||||
|
||||
@abstractmethod
|
||||
async def get_user_secrets(self) -> UserSecrets | None:
|
||||
async def get_secrets(self) -> Secrets | None:
|
||||
"""Get the user's secrets"""
|
||||
|
||||
def get_auth_type(self) -> AuthType | None:
|
||||
|
||||
@ -23,7 +23,7 @@ from openhands.integrations.provider import (
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
|
||||
|
||||
class UserSecrets(BaseModel):
|
||||
class Secrets(BaseModel):
|
||||
provider_tokens: PROVIDER_TOKEN_TYPE_WITH_JSON_SCHEMA = Field(
|
||||
default_factory=lambda: MappingProxyType({})
|
||||
)
|
||||
@ -96,7 +96,7 @@ class UserSecrets(BaseModel):
|
||||
) -> dict[str, MappingProxyType | None]:
|
||||
"""Custom deserializer to convert dictionary into MappingProxyType"""
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError('UserSecrets must be initialized with a dictionary')
|
||||
raise ValueError('Secrets must be initialized with a dictionary')
|
||||
|
||||
new_data: dict[str, MappingProxyType | None] = {}
|
||||
|
||||
@ -14,7 +14,7 @@ from pydantic import (
|
||||
from openhands.core.config.llm_config import LLMConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig
|
||||
from openhands.core.config.utils import load_openhands_config
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
|
||||
|
||||
class Settings(BaseModel):
|
||||
@ -30,7 +30,7 @@ class Settings(BaseModel):
|
||||
llm_base_url: str | None = None
|
||||
remote_runtime_resource_factor: int | None = None
|
||||
# Planned to be removed from settings
|
||||
secrets_store: UserSecrets = Field(default_factory=UserSecrets, frozen=True)
|
||||
secrets_store: Secrets = Field(default_factory=Secrets, frozen=True)
|
||||
enable_default_condenser: bool = True
|
||||
enable_sound_notifications: bool = False
|
||||
enable_proactive_conversation_starters: bool = True
|
||||
@ -76,7 +76,7 @@ class Settings(BaseModel):
|
||||
@model_validator(mode='before')
|
||||
@classmethod
|
||||
def convert_provider_tokens(cls, data: dict | object) -> dict | object:
|
||||
"""Convert provider tokens from JSON format to UserSecrets format."""
|
||||
"""Convert provider tokens from JSON format to Secrets format."""
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
|
||||
@ -87,10 +87,10 @@ class Settings(BaseModel):
|
||||
custom_secrets = secrets_store.get('custom_secrets')
|
||||
tokens = secrets_store.get('provider_tokens')
|
||||
|
||||
secret_store = UserSecrets(provider_tokens={}, custom_secrets={}) # type: ignore[arg-type]
|
||||
secret_store = Secrets(provider_tokens={}, custom_secrets={}) # type: ignore[arg-type]
|
||||
|
||||
if isinstance(tokens, dict):
|
||||
converted_store = UserSecrets(provider_tokens=tokens) # type: ignore[arg-type]
|
||||
converted_store = Secrets(provider_tokens=tokens) # type: ignore[arg-type]
|
||||
secret_store = secret_store.model_copy(
|
||||
update={'provider_tokens': converted_store.provider_tokens}
|
||||
)
|
||||
@ -98,7 +98,7 @@ class Settings(BaseModel):
|
||||
secret_store.model_copy(update={'provider_tokens': tokens})
|
||||
|
||||
if isinstance(custom_secrets, dict):
|
||||
converted_store = UserSecrets(custom_secrets=custom_secrets) # type: ignore[arg-type]
|
||||
converted_store = Secrets(custom_secrets=custom_secrets) # type: ignore[arg-type]
|
||||
secret_store = secret_store.model_copy(
|
||||
update={'custom_secrets': converted_store.custom_secrets}
|
||||
)
|
||||
@ -119,7 +119,7 @@ class Settings(BaseModel):
|
||||
return v
|
||||
|
||||
@field_serializer('secrets_store')
|
||||
def secrets_store_serializer(self, secrets: UserSecrets, info: SerializationInfo):
|
||||
def secrets_store_serializer(self, secrets: Secrets, info: SerializationInfo):
|
||||
"""Custom serializer for secrets store."""
|
||||
"""Force invalidate secret store"""
|
||||
return {'provider_tokens': {}}
|
||||
|
||||
@ -5,7 +5,7 @@ from dataclasses import dataclass
|
||||
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.storage import get_file_store
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.files import FileStore
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
@ -16,7 +16,7 @@ class FileSecretsStore(SecretsStore):
|
||||
file_store: FileStore
|
||||
path: str = 'secrets.json'
|
||||
|
||||
async def load(self) -> UserSecrets | None:
|
||||
async def load(self) -> Secrets | None:
|
||||
try:
|
||||
json_str = await call_sync_from_async(self.file_store.read, self.path)
|
||||
kwargs = json.loads(json_str)
|
||||
@ -26,12 +26,12 @@ class FileSecretsStore(SecretsStore):
|
||||
if v.get('token')
|
||||
}
|
||||
kwargs['provider_tokens'] = provider_tokens
|
||||
secrets = UserSecrets(**kwargs)
|
||||
secrets = Secrets(**kwargs)
|
||||
return secrets
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
async def store(self, secrets: UserSecrets) -> None:
|
||||
async def store(self, secrets: Secrets) -> None:
|
||||
json_str = secrets.model_dump_json(context={'expose_secrets': True})
|
||||
await call_sync_from_async(self.file_store.write, self.path, json_str)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
|
||||
|
||||
class SecretsStore(ABC):
|
||||
@ -21,11 +21,11 @@ class SecretsStore(ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def load(self) -> UserSecrets | None:
|
||||
async def load(self) -> Secrets | None:
|
||||
"""Load secrets."""
|
||||
|
||||
@abstractmethod
|
||||
async def store(self, secrets: UserSecrets) -> None:
|
||||
async def store(self, secrets: Secrets) -> None:
|
||||
"""Store secrets."""
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -9,8 +9,8 @@ from openhands.integrations.provider import (
|
||||
ProviderToken,
|
||||
ProviderType,
|
||||
)
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.data_models.settings import Settings
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
|
||||
|
||||
def test_provider_token_immutability():
|
||||
@ -34,8 +34,8 @@ def test_provider_token_immutability():
|
||||
|
||||
|
||||
def test_secret_store_immutability():
|
||||
"""Test that UserSecrets is immutable"""
|
||||
store = UserSecrets(
|
||||
"""Test that Secrets is immutable"""
|
||||
store = Secrets(
|
||||
provider_tokens={ProviderType.GITHUB: ProviderToken(token=SecretStr('test'))}
|
||||
)
|
||||
|
||||
@ -69,7 +69,7 @@ def test_secret_store_immutability():
|
||||
def test_settings_immutability():
|
||||
"""Test that Settings secrets_store is immutable"""
|
||||
settings = Settings(
|
||||
secrets_store=UserSecrets(
|
||||
secrets_store=Secrets(
|
||||
provider_tokens={
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('test'))
|
||||
}
|
||||
@ -78,7 +78,7 @@ def test_settings_immutability():
|
||||
|
||||
# Test direct modification of secrets_store
|
||||
with pytest.raises(ValidationError):
|
||||
settings.secrets_store = UserSecrets()
|
||||
settings.secrets_store = Secrets()
|
||||
|
||||
# Test nested modification attempts
|
||||
with pytest.raises((TypeError, AttributeError)):
|
||||
@ -87,7 +87,7 @@ def test_settings_immutability():
|
||||
)
|
||||
|
||||
# Test model_copy creates new instance
|
||||
new_store = UserSecrets(
|
||||
new_store = Secrets(
|
||||
provider_tokens={
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('new_token'))
|
||||
}
|
||||
@ -140,10 +140,10 @@ def test_provider_handler_immutability():
|
||||
|
||||
|
||||
def test_token_conversion():
|
||||
"""Test token conversion in UserSecrets.create"""
|
||||
"""Test token conversion in Secrets.create"""
|
||||
# Test with string token
|
||||
store1 = Settings(
|
||||
secrets_store=UserSecrets(
|
||||
secrets_store=Secrets(
|
||||
provider_tokens={
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('test_token'))
|
||||
}
|
||||
@ -159,7 +159,7 @@ def test_token_conversion():
|
||||
assert store1.secrets_store.provider_tokens[ProviderType.GITHUB].user_id is None
|
||||
|
||||
# Test with dict token
|
||||
store2 = UserSecrets(
|
||||
store2 = Secrets(
|
||||
provider_tokens={'github': {'token': 'test_token', 'user_id': 'user1'}}
|
||||
)
|
||||
assert (
|
||||
@ -170,14 +170,14 @@ def test_token_conversion():
|
||||
|
||||
# Test with ProviderToken
|
||||
token = ProviderToken(token=SecretStr('test_token'), user_id='user2')
|
||||
store3 = UserSecrets(provider_tokens={ProviderType.GITHUB: token})
|
||||
store3 = Secrets(provider_tokens={ProviderType.GITHUB: token})
|
||||
assert (
|
||||
store3.provider_tokens[ProviderType.GITHUB].token.get_secret_value()
|
||||
== 'test_token'
|
||||
)
|
||||
assert store3.provider_tokens[ProviderType.GITHUB].user_id == 'user2'
|
||||
|
||||
store4 = UserSecrets(
|
||||
store4 = Secrets(
|
||||
provider_tokens={
|
||||
ProviderType.GITHUB: 123 # Invalid type
|
||||
}
|
||||
@ -186,10 +186,10 @@ def test_token_conversion():
|
||||
assert ProviderType.GITHUB not in store4.provider_tokens
|
||||
|
||||
# Test with empty/None token
|
||||
store5 = UserSecrets(provider_tokens={ProviderType.GITHUB: None})
|
||||
store5 = Secrets(provider_tokens={ProviderType.GITHUB: None})
|
||||
assert ProviderType.GITHUB not in store5.provider_tokens
|
||||
|
||||
store6 = UserSecrets(
|
||||
store6 = Secrets(
|
||||
provider_tokens={
|
||||
'invalid_provider': 'test_token' # Invalid provider type
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ def test_client():
|
||||
def create_new_test_conversation(
|
||||
test_request: InitSessionRequest, auth_type: AuthType | None = None
|
||||
):
|
||||
# Create a mock UserSecrets object with the required custom_secrets attribute
|
||||
# Create a mock Secrets object with the required custom_secrets attribute
|
||||
mock_user_secrets = MagicMock()
|
||||
mock_user_secrets.custom_secrets = MappingProxyType({})
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ from openhands.server.routes.secrets import (
|
||||
app as secrets_app,
|
||||
)
|
||||
from openhands.storage import get_file_store
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.secrets.file_secrets_store import FileSecretsStore
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ async def test_load_custom_secrets_names(test_client, file_secrets_store):
|
||||
provider_tokens = {
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('github-token'))
|
||||
}
|
||||
user_secrets = UserSecrets(
|
||||
user_secrets = Secrets(
|
||||
custom_secrets=custom_secrets, provider_tokens=provider_tokens
|
||||
)
|
||||
|
||||
@ -101,7 +101,7 @@ async def test_load_custom_secrets_names_empty(test_client, file_secrets_store):
|
||||
provider_tokens = {
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('github-token'))
|
||||
}
|
||||
user_secrets = UserSecrets(provider_tokens=provider_tokens, custom_secrets={})
|
||||
user_secrets = Secrets(provider_tokens=provider_tokens, custom_secrets={})
|
||||
|
||||
# Store the initial settings
|
||||
await file_secrets_store.store(user_secrets)
|
||||
@ -123,7 +123,7 @@ async def test_add_custom_secret(test_client, file_secrets_store):
|
||||
provider_tokens = {
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('github-token'))
|
||||
}
|
||||
user_secrets = UserSecrets(provider_tokens=provider_tokens)
|
||||
user_secrets = Secrets(provider_tokens=provider_tokens)
|
||||
|
||||
# Store the initial settings
|
||||
await file_secrets_store.store(user_secrets)
|
||||
@ -184,7 +184,7 @@ async def test_update_existing_custom_secret(test_client, file_secrets_store):
|
||||
provider_tokens = {
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('github-token'))
|
||||
}
|
||||
user_secrets = UserSecrets(
|
||||
user_secrets = Secrets(
|
||||
custom_secrets=custom_secrets, provider_tokens=provider_tokens
|
||||
)
|
||||
|
||||
@ -223,7 +223,7 @@ async def test_add_multiple_custom_secrets(test_client, file_secrets_store):
|
||||
provider_tokens = {
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('github-token'))
|
||||
}
|
||||
user_secrets = UserSecrets(
|
||||
user_secrets = Secrets(
|
||||
custom_secrets=custom_secrets, provider_tokens=provider_tokens
|
||||
)
|
||||
|
||||
@ -285,7 +285,7 @@ async def test_delete_custom_secret(test_client, file_secrets_store):
|
||||
provider_tokens = {
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('github-token'))
|
||||
}
|
||||
user_secrets = UserSecrets(
|
||||
user_secrets = Secrets(
|
||||
custom_secrets=custom_secrets, provider_tokens=provider_tokens
|
||||
)
|
||||
|
||||
@ -323,7 +323,7 @@ async def test_delete_nonexistent_custom_secret(test_client, file_secrets_store)
|
||||
provider_tokens = {
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('github-token'))
|
||||
}
|
||||
user_secrets = UserSecrets(
|
||||
user_secrets = Secrets(
|
||||
custom_secrets=custom_secrets, provider_tokens=provider_tokens
|
||||
)
|
||||
|
||||
@ -355,7 +355,7 @@ async def test_add_git_providers_with_host(test_client, file_secrets_store):
|
||||
provider_tokens = {
|
||||
ProviderType.GITHUB: ProviderToken(token=SecretStr('github-token'))
|
||||
}
|
||||
user_secrets = UserSecrets(provider_tokens=provider_tokens)
|
||||
user_secrets = Secrets(provider_tokens=provider_tokens)
|
||||
await file_secrets_store.store(user_secrets)
|
||||
|
||||
# Mock check_provider_tokens to return empty string (no error)
|
||||
@ -394,7 +394,7 @@ async def test_add_git_providers_update_host_only(test_client, file_secrets_stor
|
||||
token=SecretStr('github-token'), host='github.com'
|
||||
)
|
||||
}
|
||||
user_secrets = UserSecrets(provider_tokens=provider_tokens)
|
||||
user_secrets = Secrets(provider_tokens=provider_tokens)
|
||||
await file_secrets_store.store(user_secrets)
|
||||
|
||||
# Mock check_provider_tokens to return empty string (no error)
|
||||
@ -433,7 +433,7 @@ async def test_add_git_providers_invalid_token_with_host(
|
||||
):
|
||||
"""Test adding an invalid token with a host."""
|
||||
# Create initial user secrets
|
||||
user_secrets = UserSecrets()
|
||||
user_secrets = Secrets()
|
||||
await file_secrets_store.store(user_secrets)
|
||||
|
||||
# Mock validate_provider_token to return None (invalid token)
|
||||
@ -456,7 +456,7 @@ async def test_add_git_providers_invalid_token_with_host(
|
||||
async def test_add_multiple_git_providers_with_hosts(test_client, file_secrets_store):
|
||||
"""Test adding multiple git providers with different hosts."""
|
||||
# Create initial user secrets
|
||||
user_secrets = UserSecrets()
|
||||
user_secrets = Secrets()
|
||||
await file_secrets_store.store(user_secrets)
|
||||
|
||||
# Mock check_provider_tokens to return empty string (no error)
|
||||
|
||||
@ -9,7 +9,7 @@ from pydantic import SecretStr
|
||||
from openhands.integrations.provider import ProviderToken, ProviderType
|
||||
from openhands.server.app import app
|
||||
from openhands.server.user_auth.user_auth import UserAuth
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.memory import InMemoryFileStore
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
from openhands.storage.settings.file_settings_store import FileSettingsStore
|
||||
@ -43,7 +43,7 @@ class MockUserAuth(UserAuth):
|
||||
async def get_secrets_store(self) -> SecretsStore | None:
|
||||
return None
|
||||
|
||||
async def get_user_secrets(self) -> UserSecrets | None:
|
||||
async def get_secrets(self) -> Secrets | None:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -14,8 +14,8 @@ from openhands.server.routes.secrets import (
|
||||
from openhands.server.routes.settings import store_llm_settings
|
||||
from openhands.server.settings import POSTProviderModel
|
||||
from openhands.storage import get_file_store
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.data_models.settings import Settings
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.secrets.file_secrets_store import FileSecretsStore
|
||||
|
||||
|
||||
@ -220,9 +220,9 @@ async def test_store_provider_tokens_new_tokens(test_client, file_secrets_store)
|
||||
mock_store = MagicMock()
|
||||
mock_store.load = AsyncMock(return_value=None) # No existing settings
|
||||
|
||||
UserSecrets()
|
||||
Secrets()
|
||||
|
||||
user_secrets = await file_secrets_store.store(UserSecrets())
|
||||
user_secrets = await file_secrets_store.store(Secrets())
|
||||
|
||||
response = test_client.post('/api/add-git-providers', json=provider_tokens)
|
||||
assert response.status_code == 200
|
||||
@ -242,8 +242,8 @@ async def test_store_provider_tokens_update_existing(test_client, file_secrets_s
|
||||
github_token = ProviderToken(token=SecretStr('old-token'))
|
||||
provider_tokens = {ProviderType.GITHUB: github_token}
|
||||
|
||||
# Create a UserSecrets with the provider tokens
|
||||
user_secrets = UserSecrets(provider_tokens=provider_tokens)
|
||||
# Create a Secrets with the provider tokens
|
||||
user_secrets = Secrets(provider_tokens=provider_tokens)
|
||||
|
||||
await file_secrets_store.store(user_secrets)
|
||||
|
||||
@ -268,7 +268,7 @@ async def test_store_provider_tokens_keep_existing(test_client, file_secrets_sto
|
||||
# Create existing secrets with a GitHub token
|
||||
github_token = ProviderToken(token=SecretStr('existing-token'))
|
||||
provider_tokens = {ProviderType.GITHUB: github_token}
|
||||
user_secrets = UserSecrets(provider_tokens=provider_tokens)
|
||||
user_secrets = Secrets(provider_tokens=provider_tokens)
|
||||
|
||||
await file_secrets_store.store(user_secrets)
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ from pydantic import SecretStr
|
||||
from openhands.integrations.provider import ProviderToken, ProviderType
|
||||
from openhands.server.app import app
|
||||
from openhands.server.user_auth.user_auth import UserAuth
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.storage.memory import InMemoryFileStore
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
from openhands.storage.settings.file_settings_store import FileSettingsStore
|
||||
@ -43,7 +43,7 @@ class MockUserAuth(UserAuth):
|
||||
async def get_secrets_store(self) -> SecretsStore | None:
|
||||
return None
|
||||
|
||||
async def get_user_secrets(self) -> UserSecrets | None:
|
||||
async def get_secrets(self) -> Secrets | None:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -10,12 +10,12 @@ from openhands.integrations.provider import (
|
||||
ProviderToken,
|
||||
ProviderType,
|
||||
)
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
|
||||
|
||||
class TestUserSecrets:
|
||||
class TestSecrets:
|
||||
def test_adding_only_provider_tokens(self):
|
||||
"""Test adding only provider tokens to the UserSecrets."""
|
||||
"""Test adding only provider tokens to the Secrets."""
|
||||
# Create provider tokens
|
||||
github_token = ProviderToken(
|
||||
token=SecretStr('github-token-123'), user_id='user1'
|
||||
@ -31,7 +31,7 @@ class TestUserSecrets:
|
||||
}
|
||||
|
||||
# Initialize the store with a dict that will be converted to MappingProxyType
|
||||
store = UserSecrets(provider_tokens=provider_tokens)
|
||||
store = Secrets(provider_tokens=provider_tokens)
|
||||
|
||||
# Verify the tokens were added correctly
|
||||
assert isinstance(store.provider_tokens, MappingProxyType)
|
||||
@ -52,7 +52,7 @@ class TestUserSecrets:
|
||||
assert len(store.custom_secrets) == 0
|
||||
|
||||
def test_adding_only_custom_secrets(self):
|
||||
"""Test adding only custom secrets to the UserSecrets."""
|
||||
"""Test adding only custom secrets to the Secrets."""
|
||||
# Create custom secrets
|
||||
custom_secrets = {
|
||||
'API_KEY': CustomSecret(
|
||||
@ -64,7 +64,7 @@ class TestUserSecrets:
|
||||
}
|
||||
|
||||
# Initialize the store with custom secrets
|
||||
store = UserSecrets(custom_secrets=custom_secrets)
|
||||
store = Secrets(custom_secrets=custom_secrets)
|
||||
|
||||
# Verify the custom secrets were added correctly
|
||||
assert isinstance(store.custom_secrets, MappingProxyType)
|
||||
@ -95,7 +95,7 @@ class TestUserSecrets:
|
||||
custom_secrets_proxy = MappingProxyType({'API_KEY': custom_secret})
|
||||
|
||||
# Test with dict for provider_tokens and MappingProxyType for custom_secrets
|
||||
store1 = UserSecrets(
|
||||
store1 = Secrets(
|
||||
provider_tokens=provider_tokens_dict, custom_secrets=custom_secrets_proxy
|
||||
)
|
||||
|
||||
@ -120,7 +120,7 @@ class TestUserSecrets:
|
||||
'API_KEY': {'secret': 'api-key-123', 'description': 'API key'}
|
||||
}
|
||||
|
||||
store2 = UserSecrets(
|
||||
store2 = Secrets(
|
||||
provider_tokens=provider_tokens_proxy, custom_secrets=custom_secrets_dict
|
||||
)
|
||||
|
||||
@ -146,7 +146,7 @@ class TestUserSecrets:
|
||||
)
|
||||
}
|
||||
|
||||
initial_store = UserSecrets(
|
||||
initial_store = Secrets(
|
||||
provider_tokens=MappingProxyType({ProviderType.GITHUB: github_token}),
|
||||
custom_secrets=MappingProxyType(custom_secret),
|
||||
)
|
||||
@ -212,7 +212,7 @@ class TestUserSecrets:
|
||||
)
|
||||
|
||||
def test_serialization_with_expose_secrets(self):
|
||||
"""Test serializing the UserSecrets with expose_secrets=True."""
|
||||
"""Test serializing the Secrets with expose_secrets=True."""
|
||||
# Create a store with both provider tokens and custom secrets
|
||||
github_token = ProviderToken(
|
||||
token=SecretStr('github-token-123'), user_id='user1'
|
||||
@ -223,7 +223,7 @@ class TestUserSecrets:
|
||||
)
|
||||
}
|
||||
|
||||
store = UserSecrets(
|
||||
store = Secrets(
|
||||
provider_tokens=MappingProxyType({ProviderType.GITHUB: github_token}),
|
||||
custom_secrets=MappingProxyType(custom_secrets),
|
||||
)
|
||||
@ -290,7 +290,7 @@ class TestUserSecrets:
|
||||
}
|
||||
|
||||
# Initialize the store
|
||||
store = UserSecrets(provider_tokens=mixed_provider_tokens)
|
||||
store = Secrets(provider_tokens=mixed_provider_tokens)
|
||||
|
||||
# Verify all tokens are converted to SecretStr
|
||||
assert isinstance(store.provider_tokens, MappingProxyType)
|
||||
@ -322,7 +322,7 @@ class TestUserSecrets:
|
||||
}
|
||||
|
||||
# Initialize the store
|
||||
store = UserSecrets(custom_secrets=custom_secrets_dict)
|
||||
store = Secrets(custom_secrets=custom_secrets_dict)
|
||||
|
||||
# Verify all secrets are converted to CustomSecret objects
|
||||
assert isinstance(store.custom_secrets, MappingProxyType)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user