mirror of
https://github.com/langgenius/dify.git
synced 2026-11-12 06:11:12 +08:00
Track backend integration, Ruff cleanup and migration reconciliation in ENG-1271. Retain required copy contracts without implementing frontend interactions.
79 lines
3.8 KiB
Python
79 lines
3.8 KiB
Python
"""Persist ordinary draft OAuth rotation after native external refresh has closed.
|
|
|
|
Admission remains metadata-only. This mutation receives already encrypted
|
|
native output and updates the exact checked credential in one bounded SQL
|
|
write, repeating current actor access and comparing the original native read.
|
|
A revoke, ownership change, deletion or concurrent rotation cannot be silently
|
|
overwritten or returned as a usable refreshed Tool runtime. Published and
|
|
Builder confirmed refresh retain their separate existing owners.
|
|
"""
|
|
|
|
from sqlalchemy import update
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from core.plugin.entities.plugin_daemon import CredentialType as ToolCredentialType
|
|
from core.tools.errors import ToolProviderCredentialValidationError
|
|
from models.credential_permission import CredentialType
|
|
from models.tools import BuiltinToolProvider
|
|
from repositories.credentials.query_repository import apply_credential_visibility_filter_for_actor
|
|
from services.tools.draft_tool_setup import DraftToolRuntimeSelection, tool_credential_fingerprint
|
|
|
|
|
|
def persist_draft_tool_refresh(
|
|
*,
|
|
session_maker: sessionmaker[Session],
|
|
selection: DraftToolRuntimeSelection,
|
|
original_encrypted_credentials: str | None,
|
|
original_expires_at: int,
|
|
original_user_id: str | None,
|
|
refreshed_encrypted_credentials: str,
|
|
refreshed_expires_at: int,
|
|
) -> str:
|
|
"""Commit one authorized unchanged selection and return its opaque version; no external I/O occurs here.
|
|
|
|
The original opaque ciphertext/expiry/owner come from the native constrained
|
|
read, before OAuth I/O. ``None`` represents the actual stored SQL NULL;
|
|
its comparison retains SQLAlchemy's IS NULL semantics rather than a blank
|
|
ciphertext fallback. Conditional UPDATE repeats canonical visibility
|
|
atomically; a failed compare-and-set rolls back and forbids runtime dispatch.
|
|
Neither token plaintext nor a graph/client permission marker enters this owner.
|
|
"""
|
|
credential = selection.credential
|
|
if credential is None:
|
|
raise ToolProviderCredentialValidationError("Tool credential changed or access was revoked during refresh.")
|
|
statement = update(BuiltinToolProvider).where(
|
|
BuiltinToolProvider.id == credential.id,
|
|
BuiltinToolProvider.tenant_id == selection.tenant_id,
|
|
BuiltinToolProvider.provider == credential.provider,
|
|
BuiltinToolProvider.credential_type == ToolCredentialType.OAUTH2,
|
|
BuiltinToolProvider.user_id == original_user_id,
|
|
BuiltinToolProvider.encrypted_credentials == original_encrypted_credentials,
|
|
BuiltinToolProvider.expires_at == original_expires_at,
|
|
)
|
|
statement = apply_credential_visibility_filter_for_actor(
|
|
statement,
|
|
tenant_id=selection.tenant_id,
|
|
model_id_column=BuiltinToolProvider.id,
|
|
model_user_id_column=BuiltinToolProvider.user_id,
|
|
model_visibility_column=BuiltinToolProvider.visibility,
|
|
credential_type=CredentialType.BUILTIN_TOOL_PROVIDER,
|
|
actor_id=selection.actor_id,
|
|
)
|
|
statement = statement.values(
|
|
encrypted_credentials=refreshed_encrypted_credentials,
|
|
expires_at=refreshed_expires_at,
|
|
).returning(BuiltinToolProvider)
|
|
try:
|
|
with session_maker.begin() as session:
|
|
updated = session.scalar(statement.execution_options(synchronize_session=False))
|
|
if updated is None or updated.id != credential.id:
|
|
raise ToolProviderCredentialValidationError(
|
|
"Tool credential changed or access was revoked during refresh."
|
|
)
|
|
fingerprint = tool_credential_fingerprint(updated)
|
|
return fingerprint
|
|
except ToolProviderCredentialValidationError:
|
|
raise
|
|
except Exception:
|
|
raise ToolProviderCredentialValidationError("Tool credential refresh could not be persisted.") from None
|