mirror of
https://github.com/langgenius/dify.git
synced 2026-11-12 06:11:12 +08:00
202 lines
8.1 KiB
Python
202 lines
8.1 KiB
Python
"""Resolve dynamic parameter credentials before invoking the plugin daemon."""
|
|
|
|
from collections.abc import Mapping, Sequence
|
|
from typing import Any, Literal, Protocol
|
|
|
|
from constants import HIDDEN_VALUE
|
|
from core.plugin.entities.parameters import PluginParameterOption
|
|
from core.plugin.entities.plugin_daemon import CredentialType
|
|
from core.tools.plugin_tool.provider import PluginToolProviderController
|
|
from core.tools.utils.encryption import create_tool_provider_encrypter
|
|
from core.trigger.provider import PluginTriggerProviderController
|
|
from core.trigger.utils.encryption import create_trigger_provider_encrypter
|
|
from models.provider_ids import TriggerProviderID
|
|
from services.credentials.query import ToolCredentialSnapshot
|
|
from services.trigger.subscription_service import TriggerSubscriptionSnapshot
|
|
from services.trigger.trigger_subscription_builder_service import TriggerSubscriptionBuilderQuery
|
|
|
|
|
|
class PluginParameterCredentials(Protocol):
|
|
def get_tool(
|
|
self, *, workspace_id: str, provider: str, credential_id: str | None
|
|
) -> ToolCredentialSnapshot | None: ...
|
|
|
|
|
|
class PluginParameterSubscriptions(Protocol):
|
|
def get_for_provider(
|
|
self, *, tenant_id: str, provider_id: TriggerProviderID, subscription_id: str | None
|
|
) -> TriggerSubscriptionSnapshot | None: ...
|
|
|
|
|
|
class PluginParameterGateway(Protocol):
|
|
def get_tool_provider(self, *, tenant_id: str, provider: str) -> PluginToolProviderController: ...
|
|
|
|
def get_trigger_provider(
|
|
self, *, tenant_id: str, provider_id: TriggerProviderID
|
|
) -> PluginTriggerProviderController: ...
|
|
|
|
def fetch_options(
|
|
self,
|
|
*,
|
|
tenant_id: str,
|
|
user_id: str,
|
|
plugin_id: str,
|
|
provider: str,
|
|
action: str,
|
|
parameter: str,
|
|
credentials: Mapping[str, Any],
|
|
credential_type: str,
|
|
plugin_unique_identifier: str,
|
|
) -> Sequence[PluginParameterOption]: ...
|
|
|
|
|
|
class PluginParameterService:
|
|
"""Keep plugin and credential I/O outside repository-owned read sessions.
|
|
|
|
TODO: The shared RSA key provider still owns a scoped database session;
|
|
remove that dependency when its tenant key reads return detached values.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
credentials: PluginParameterCredentials,
|
|
subscriptions: PluginParameterSubscriptions,
|
|
builders: TriggerSubscriptionBuilderQuery,
|
|
plugins: PluginParameterGateway,
|
|
) -> None:
|
|
self._credentials = credentials
|
|
self._subscriptions = subscriptions
|
|
self._builders = builders
|
|
self._plugins = plugins
|
|
|
|
def get_dynamic_select_options(
|
|
self,
|
|
*,
|
|
tenant_id: str,
|
|
user_id: str,
|
|
plugin_id: str,
|
|
provider: str,
|
|
action: str,
|
|
parameter: str,
|
|
credential_id: str | None,
|
|
provider_type: Literal["tool", "trigger"],
|
|
) -> Sequence[PluginParameterOption]:
|
|
"""Use a selected credential, or the provider's existing default lookup.
|
|
|
|
A trigger credential ID may also identify a builder owned by this user.
|
|
Only a missing/expired builder falls back to a persisted subscription.
|
|
"""
|
|
provider_name = provider if "/" in provider else f"{plugin_id}/{provider}"
|
|
credentials: Mapping[str, Any] = {}
|
|
credential_type = CredentialType.UNAUTHORIZED.value
|
|
if provider_type == "tool":
|
|
tool = self._plugins.get_tool_provider(tenant_id=tenant_id, provider=provider_name)
|
|
plugin_unique_identifier = tool.plugin_unique_identifier
|
|
if tool.need_credentials:
|
|
record = self._credentials.get_tool(
|
|
workspace_id=tenant_id, provider=tool.entity.identity.name, credential_id=credential_id
|
|
)
|
|
if record is None:
|
|
raise ValueError(f"Builtin provider {provider} not found when fetching credentials")
|
|
encrypter, _ = create_tool_provider_encrypter(tenant_id=tenant_id, controller=tool)
|
|
credentials = encrypter.decrypt(record.credentials)
|
|
credential_type = record.credential_type
|
|
else:
|
|
provider_id = TriggerProviderID(provider_name)
|
|
builder = (
|
|
self._builders.get_subscription_builder(
|
|
tenant_id=tenant_id,
|
|
user_id=user_id,
|
|
provider_id=provider_id,
|
|
subscription_builder_id=credential_id,
|
|
)
|
|
if credential_id
|
|
else None
|
|
)
|
|
subscription = (
|
|
None
|
|
if builder is not None
|
|
else self._require_subscription(
|
|
tenant_id=tenant_id, provider_id=provider_id, subscription_id=credential_id
|
|
)
|
|
)
|
|
trigger = self._plugins.get_trigger_provider(tenant_id=tenant_id, provider_id=provider_id)
|
|
plugin_unique_identifier = trigger.plugin_unique_identifier
|
|
if builder is not None:
|
|
credentials = builder.credentials
|
|
credential_type = builder.credential_type or CredentialType.UNAUTHORIZED.value
|
|
else:
|
|
assert subscription is not None
|
|
encrypter, _ = create_trigger_provider_encrypter(
|
|
tenant_id=tenant_id,
|
|
controller=trigger,
|
|
credential_id=subscription.id,
|
|
credential_type=subscription.credential_type,
|
|
)
|
|
credentials = encrypter.decrypt(subscription.credentials)
|
|
credential_type = subscription.credential_type or CredentialType.UNAUTHORIZED.value
|
|
|
|
return self._plugins.fetch_options(
|
|
tenant_id=tenant_id,
|
|
user_id=user_id,
|
|
plugin_id=plugin_id,
|
|
provider=provider_name,
|
|
action=action,
|
|
credentials=credentials,
|
|
credential_type=credential_type,
|
|
parameter=parameter,
|
|
plugin_unique_identifier=plugin_unique_identifier,
|
|
)
|
|
|
|
def get_dynamic_select_options_with_credentials(
|
|
self,
|
|
*,
|
|
tenant_id: str,
|
|
user_id: str,
|
|
plugin_id: str,
|
|
provider: str,
|
|
action: str,
|
|
parameter: str,
|
|
credential_id: str,
|
|
credentials: Mapping[str, Any],
|
|
) -> Sequence[PluginParameterOption]:
|
|
"""Resolve hidden values from the owned subscription without persisting edits."""
|
|
provider_name = provider if "/" in provider else f"{plugin_id}/{provider}"
|
|
provider_id = TriggerProviderID(provider_name)
|
|
subscription = self._require_subscription(
|
|
tenant_id=tenant_id, provider_id=provider_id, subscription_id=credential_id
|
|
)
|
|
trigger = self._plugins.get_trigger_provider(tenant_id=tenant_id, provider_id=provider_id)
|
|
encrypter, _ = create_trigger_provider_encrypter(
|
|
tenant_id=tenant_id,
|
|
controller=trigger,
|
|
credential_id=subscription.id,
|
|
credential_type=subscription.credential_type,
|
|
)
|
|
original_credentials = encrypter.decrypt(subscription.credentials)
|
|
resolved_credentials = {
|
|
key: original_credentials.get(key) if value == HIDDEN_VALUE else value for key, value in credentials.items()
|
|
}
|
|
return self._plugins.fetch_options(
|
|
tenant_id=tenant_id,
|
|
user_id=user_id,
|
|
plugin_id=plugin_id,
|
|
provider=provider_name,
|
|
action=action,
|
|
credentials=resolved_credentials,
|
|
credential_type=subscription.credential_type or CredentialType.UNAUTHORIZED.value,
|
|
parameter=parameter,
|
|
plugin_unique_identifier=trigger.plugin_unique_identifier,
|
|
)
|
|
|
|
def _require_subscription(
|
|
self, *, tenant_id: str, provider_id: TriggerProviderID, subscription_id: str | None
|
|
) -> TriggerSubscriptionSnapshot:
|
|
subscription = self._subscriptions.get_for_provider(
|
|
tenant_id=tenant_id, provider_id=provider_id, subscription_id=subscription_id
|
|
)
|
|
if subscription is None:
|
|
raise ValueError(f"Subscription {subscription_id} not found")
|
|
return subscription
|