You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

builtin_tools_manage_service.py 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. import json
  2. import logging
  3. from collections.abc import Mapping
  4. from pathlib import Path
  5. from typing import Any, Optional
  6. from sqlalchemy.orm import Session
  7. from configs import dify_config
  8. from constants import HIDDEN_VALUE, UNKNOWN_VALUE
  9. from core.helper.name_generator import generate_incremental_name
  10. from core.helper.position_helper import is_filtered
  11. from core.helper.provider_cache import NoOpProviderCredentialCache, ToolProviderCredentialsCache
  12. from core.plugin.entities.plugin import ToolProviderID
  13. from core.tools.builtin_tool.provider import BuiltinToolProviderController
  14. from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
  15. from core.tools.entities.api_entities import (
  16. ToolApiEntity,
  17. ToolProviderApiEntity,
  18. ToolProviderCredentialApiEntity,
  19. ToolProviderCredentialInfoApiEntity,
  20. )
  21. from core.tools.entities.tool_entities import CredentialType
  22. from core.tools.errors import ToolProviderNotFoundError
  23. from core.tools.plugin_tool.provider import PluginToolProviderController
  24. from core.tools.tool_label_manager import ToolLabelManager
  25. from core.tools.tool_manager import ToolManager
  26. from core.tools.utils.encryption import create_provider_encrypter
  27. from core.tools.utils.system_oauth_encryption import decrypt_system_oauth_params
  28. from extensions.ext_database import db
  29. from extensions.ext_redis import redis_client
  30. from models.tools import BuiltinToolProvider, ToolOAuthSystemClient, ToolOAuthTenantClient
  31. from services.plugin.plugin_service import PluginService
  32. from services.tools.tools_transform_service import ToolTransformService
  33. logger = logging.getLogger(__name__)
  34. class BuiltinToolManageService:
  35. __MAX_BUILTIN_TOOL_PROVIDER_COUNT__ = 100
  36. __DEFAULT_EXPIRES_AT__ = 2147483647
  37. @staticmethod
  38. def delete_custom_oauth_client_params(tenant_id: str, provider: str):
  39. """
  40. delete custom oauth client params
  41. """
  42. tool_provider = ToolProviderID(provider)
  43. with Session(db.engine) as session:
  44. session.query(ToolOAuthTenantClient).filter_by(
  45. tenant_id=tenant_id,
  46. provider=tool_provider.provider_name,
  47. plugin_id=tool_provider.plugin_id,
  48. ).delete()
  49. session.commit()
  50. return {"result": "success"}
  51. @staticmethod
  52. def get_builtin_tool_provider_oauth_client_schema(tenant_id: str, provider_name: str):
  53. """
  54. get builtin tool provider oauth client schema
  55. """
  56. provider = ToolManager.get_builtin_provider(provider_name, tenant_id)
  57. verified = not isinstance(provider, PluginToolProviderController) or PluginService.is_plugin_verified(
  58. tenant_id, provider.plugin_unique_identifier
  59. )
  60. is_oauth_custom_client_enabled = BuiltinToolManageService.is_oauth_custom_client_enabled(
  61. tenant_id, provider_name
  62. )
  63. is_system_oauth_params_exists = verified and BuiltinToolManageService.is_oauth_system_client_exists(
  64. provider_name
  65. )
  66. result = {
  67. "schema": provider.get_oauth_client_schema(),
  68. "is_oauth_custom_client_enabled": is_oauth_custom_client_enabled,
  69. "is_system_oauth_params_exists": is_system_oauth_params_exists,
  70. "client_params": BuiltinToolManageService.get_custom_oauth_client_params(tenant_id, provider_name),
  71. "redirect_uri": f"{dify_config.CONSOLE_API_URL}/console/api/oauth/plugin/{provider_name}/tool/callback",
  72. }
  73. return result
  74. @staticmethod
  75. def list_builtin_tool_provider_tools(tenant_id: str, provider: str) -> list[ToolApiEntity]:
  76. """
  77. list builtin tool provider tools
  78. :param tenant_id: the id of the tenant
  79. :param provider: the name of the provider
  80. :return: the list of tools
  81. """
  82. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  83. tools = provider_controller.get_tools()
  84. result: list[ToolApiEntity] = []
  85. for tool in tools or []:
  86. result.append(
  87. ToolTransformService.convert_tool_entity_to_api_entity(
  88. tool=tool,
  89. tenant_id=tenant_id,
  90. labels=ToolLabelManager.get_tool_labels(provider_controller),
  91. )
  92. )
  93. return result
  94. @staticmethod
  95. def get_builtin_tool_provider_info(tenant_id: str, provider: str):
  96. """
  97. get builtin tool provider info
  98. """
  99. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  100. # check if user has added the provider
  101. builtin_provider = BuiltinToolManageService.get_builtin_provider(provider, tenant_id)
  102. if builtin_provider is None:
  103. raise ValueError(f"you have not added provider {provider}")
  104. entity = ToolTransformService.builtin_provider_to_user_provider(
  105. provider_controller=provider_controller,
  106. db_provider=builtin_provider,
  107. decrypt_credentials=True,
  108. )
  109. entity.original_credentials = {}
  110. return entity
  111. @staticmethod
  112. def list_builtin_provider_credentials_schema(provider_name: str, credential_type: CredentialType, tenant_id: str):
  113. """
  114. list builtin provider credentials schema
  115. :param credential_type: credential type
  116. :param provider_name: the name of the provider
  117. :param tenant_id: the id of the tenant
  118. :return: the list of tool providers
  119. """
  120. provider = ToolManager.get_builtin_provider(provider_name, tenant_id)
  121. return provider.get_credentials_schema_by_type(credential_type)
  122. @staticmethod
  123. def update_builtin_tool_provider(
  124. user_id: str,
  125. tenant_id: str,
  126. provider: str,
  127. credential_id: str,
  128. credentials: dict | None = None,
  129. name: str | None = None,
  130. ):
  131. """
  132. update builtin tool provider
  133. """
  134. with Session(db.engine) as session:
  135. # get if the provider exists
  136. db_provider = (
  137. session.query(BuiltinToolProvider)
  138. .where(
  139. BuiltinToolProvider.tenant_id == tenant_id,
  140. BuiltinToolProvider.id == credential_id,
  141. )
  142. .first()
  143. )
  144. if db_provider is None:
  145. raise ValueError(f"you have not added provider {provider}")
  146. try:
  147. if CredentialType.of(db_provider.credential_type).is_editable() and credentials:
  148. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  149. if not provider_controller.need_credentials:
  150. raise ValueError(f"provider {provider} does not need credentials")
  151. encrypter, cache = BuiltinToolManageService.create_tool_encrypter(
  152. tenant_id, db_provider, provider, provider_controller
  153. )
  154. original_credentials = encrypter.decrypt(db_provider.credentials)
  155. new_credentials: dict = {
  156. key: value if value != HIDDEN_VALUE else original_credentials.get(key, UNKNOWN_VALUE)
  157. for key, value in credentials.items()
  158. }
  159. if CredentialType.of(db_provider.credential_type).is_validate_allowed():
  160. provider_controller.validate_credentials(user_id, new_credentials)
  161. # encrypt credentials
  162. db_provider.encrypted_credentials = json.dumps(encrypter.encrypt(new_credentials))
  163. cache.delete()
  164. # update name if provided
  165. if name and name != db_provider.name:
  166. # check if the name is already used
  167. if (
  168. session.query(BuiltinToolProvider)
  169. .filter_by(tenant_id=tenant_id, provider=provider, name=name)
  170. .count()
  171. > 0
  172. ):
  173. raise ValueError(f"the credential name '{name}' is already used")
  174. db_provider.name = name
  175. session.commit()
  176. except Exception as e:
  177. session.rollback()
  178. raise ValueError(str(e))
  179. return {"result": "success"}
  180. @staticmethod
  181. def add_builtin_tool_provider(
  182. user_id: str,
  183. api_type: CredentialType,
  184. tenant_id: str,
  185. provider: str,
  186. credentials: dict,
  187. expires_at: int = -1,
  188. name: str | None = None,
  189. ):
  190. """
  191. add builtin tool provider
  192. """
  193. try:
  194. with Session(db.engine) as session:
  195. lock = f"builtin_tool_provider_create_lock:{tenant_id}_{provider}"
  196. with redis_client.lock(lock, timeout=20):
  197. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  198. if not provider_controller.need_credentials:
  199. raise ValueError(f"provider {provider} does not need credentials")
  200. provider_count = (
  201. session.query(BuiltinToolProvider).filter_by(tenant_id=tenant_id, provider=provider).count()
  202. )
  203. # check if the provider count is reached the limit
  204. if provider_count >= BuiltinToolManageService.__MAX_BUILTIN_TOOL_PROVIDER_COUNT__:
  205. raise ValueError(f"you have reached the maximum number of providers for {provider}")
  206. # validate credentials if allowed
  207. if CredentialType.of(api_type).is_validate_allowed():
  208. provider_controller.validate_credentials(user_id, credentials)
  209. # generate name if not provided
  210. if name is None or name == "":
  211. name = BuiltinToolManageService.generate_builtin_tool_provider_name(
  212. session=session, tenant_id=tenant_id, provider=provider, credential_type=api_type
  213. )
  214. else:
  215. # check if the name is already used
  216. if (
  217. session.query(BuiltinToolProvider)
  218. .filter_by(tenant_id=tenant_id, provider=provider, name=name)
  219. .count()
  220. > 0
  221. ):
  222. raise ValueError(f"the credential name '{name}' is already used")
  223. # create encrypter
  224. encrypter, _ = create_provider_encrypter(
  225. tenant_id=tenant_id,
  226. config=[
  227. x.to_basic_provider_config()
  228. for x in provider_controller.get_credentials_schema_by_type(api_type)
  229. ],
  230. cache=NoOpProviderCredentialCache(),
  231. )
  232. db_provider = BuiltinToolProvider(
  233. tenant_id=tenant_id,
  234. user_id=user_id,
  235. provider=provider,
  236. encrypted_credentials=json.dumps(encrypter.encrypt(credentials)),
  237. credential_type=api_type.value,
  238. name=name,
  239. expires_at=expires_at
  240. if expires_at is not None
  241. else BuiltinToolManageService.__DEFAULT_EXPIRES_AT__,
  242. )
  243. session.add(db_provider)
  244. session.commit()
  245. except Exception as e:
  246. session.rollback()
  247. raise ValueError(str(e))
  248. return {"result": "success"}
  249. @staticmethod
  250. def create_tool_encrypter(
  251. tenant_id: str,
  252. db_provider: BuiltinToolProvider,
  253. provider: str,
  254. provider_controller: BuiltinToolProviderController,
  255. ):
  256. encrypter, cache = create_provider_encrypter(
  257. tenant_id=tenant_id,
  258. config=[
  259. x.to_basic_provider_config()
  260. for x in provider_controller.get_credentials_schema_by_type(db_provider.credential_type)
  261. ],
  262. cache=ToolProviderCredentialsCache(tenant_id=tenant_id, provider=provider, credential_id=db_provider.id),
  263. )
  264. return encrypter, cache
  265. @staticmethod
  266. def generate_builtin_tool_provider_name(
  267. session: Session, tenant_id: str, provider: str, credential_type: CredentialType
  268. ) -> str:
  269. db_providers = (
  270. session.query(BuiltinToolProvider)
  271. .filter_by(
  272. tenant_id=tenant_id,
  273. provider=provider,
  274. credential_type=credential_type.value,
  275. )
  276. .order_by(BuiltinToolProvider.created_at.desc())
  277. .all()
  278. )
  279. return generate_incremental_name(
  280. [provider.name for provider in db_providers],
  281. f"{credential_type.get_name()}",
  282. )
  283. @staticmethod
  284. def get_builtin_tool_provider_credentials(
  285. tenant_id: str, provider_name: str
  286. ) -> list[ToolProviderCredentialApiEntity]:
  287. """
  288. get builtin tool provider credentials
  289. """
  290. with db.session.no_autoflush:
  291. providers = (
  292. db.session.query(BuiltinToolProvider)
  293. .filter_by(tenant_id=tenant_id, provider=provider_name)
  294. .order_by(BuiltinToolProvider.is_default.desc(), BuiltinToolProvider.created_at.asc())
  295. .all()
  296. )
  297. if len(providers) == 0:
  298. return []
  299. default_provider = providers[0]
  300. default_provider.is_default = True
  301. provider_controller = ToolManager.get_builtin_provider(default_provider.provider, tenant_id)
  302. credentials: list[ToolProviderCredentialApiEntity] = []
  303. encrypters = {}
  304. for provider in providers:
  305. credential_type = provider.credential_type
  306. if credential_type not in encrypters:
  307. encrypters[credential_type] = BuiltinToolManageService.create_tool_encrypter(
  308. tenant_id, provider, provider.provider, provider_controller
  309. )[0]
  310. encrypter = encrypters[credential_type]
  311. decrypt_credential = encrypter.mask_tool_credentials(encrypter.decrypt(provider.credentials))
  312. credential_entity = ToolTransformService.convert_builtin_provider_to_credential_entity(
  313. provider=provider,
  314. credentials=decrypt_credential,
  315. )
  316. credentials.append(credential_entity)
  317. return credentials
  318. @staticmethod
  319. def get_builtin_tool_provider_credential_info(tenant_id: str, provider: str) -> ToolProviderCredentialInfoApiEntity:
  320. """
  321. get builtin tool provider credential info
  322. """
  323. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  324. supported_credential_types = provider_controller.get_supported_credential_types()
  325. credentials = BuiltinToolManageService.get_builtin_tool_provider_credentials(tenant_id, provider)
  326. credential_info = ToolProviderCredentialInfoApiEntity(
  327. supported_credential_types=supported_credential_types,
  328. is_oauth_custom_client_enabled=BuiltinToolManageService.is_oauth_custom_client_enabled(tenant_id, provider),
  329. credentials=credentials,
  330. )
  331. return credential_info
  332. @staticmethod
  333. def delete_builtin_tool_provider(tenant_id: str, provider: str, credential_id: str):
  334. """
  335. delete tool provider
  336. """
  337. with Session(db.engine) as session:
  338. db_provider = (
  339. session.query(BuiltinToolProvider)
  340. .where(
  341. BuiltinToolProvider.tenant_id == tenant_id,
  342. BuiltinToolProvider.id == credential_id,
  343. )
  344. .first()
  345. )
  346. if db_provider is None:
  347. raise ValueError(f"you have not added provider {provider}")
  348. session.delete(db_provider)
  349. session.commit()
  350. # delete cache
  351. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  352. _, cache = BuiltinToolManageService.create_tool_encrypter(
  353. tenant_id, db_provider, provider, provider_controller
  354. )
  355. cache.delete()
  356. return {"result": "success"}
  357. @staticmethod
  358. def set_default_provider(tenant_id: str, user_id: str, provider: str, id: str):
  359. """
  360. set default provider
  361. """
  362. with Session(db.engine) as session:
  363. # get provider
  364. target_provider = session.query(BuiltinToolProvider).filter_by(id=id).first()
  365. if target_provider is None:
  366. raise ValueError("provider not found")
  367. # clear default provider
  368. session.query(BuiltinToolProvider).filter_by(
  369. tenant_id=tenant_id, user_id=user_id, provider=provider, is_default=True
  370. ).update({"is_default": False})
  371. # set new default provider
  372. target_provider.is_default = True
  373. session.commit()
  374. return {"result": "success"}
  375. @staticmethod
  376. def is_oauth_system_client_exists(provider_name: str) -> bool:
  377. """
  378. check if oauth system client exists
  379. """
  380. tool_provider = ToolProviderID(provider_name)
  381. with Session(db.engine).no_autoflush as session:
  382. system_client: ToolOAuthSystemClient | None = (
  383. session.query(ToolOAuthSystemClient)
  384. .filter_by(plugin_id=tool_provider.plugin_id, provider=tool_provider.provider_name)
  385. .first()
  386. )
  387. return system_client is not None
  388. @staticmethod
  389. def is_oauth_custom_client_enabled(tenant_id: str, provider: str) -> bool:
  390. """
  391. check if oauth custom client is enabled
  392. """
  393. tool_provider = ToolProviderID(provider)
  394. with Session(db.engine).no_autoflush as session:
  395. user_client: ToolOAuthTenantClient | None = (
  396. session.query(ToolOAuthTenantClient)
  397. .filter_by(
  398. tenant_id=tenant_id,
  399. provider=tool_provider.provider_name,
  400. plugin_id=tool_provider.plugin_id,
  401. enabled=True,
  402. )
  403. .first()
  404. )
  405. return user_client is not None and user_client.enabled
  406. @staticmethod
  407. def get_oauth_client(tenant_id: str, provider: str) -> Mapping[str, Any] | None:
  408. """
  409. get builtin tool provider
  410. """
  411. tool_provider = ToolProviderID(provider)
  412. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  413. encrypter, _ = create_provider_encrypter(
  414. tenant_id=tenant_id,
  415. config=[x.to_basic_provider_config() for x in provider_controller.get_oauth_client_schema()],
  416. cache=NoOpProviderCredentialCache(),
  417. )
  418. with Session(db.engine).no_autoflush as session:
  419. user_client: ToolOAuthTenantClient | None = (
  420. session.query(ToolOAuthTenantClient)
  421. .filter_by(
  422. tenant_id=tenant_id,
  423. provider=tool_provider.provider_name,
  424. plugin_id=tool_provider.plugin_id,
  425. enabled=True,
  426. )
  427. .first()
  428. )
  429. oauth_params: Mapping[str, Any] | None = None
  430. if user_client:
  431. oauth_params = encrypter.decrypt(user_client.oauth_params)
  432. return oauth_params
  433. # only verified provider can use official oauth client
  434. is_verified = not isinstance(
  435. provider_controller, PluginToolProviderController
  436. ) or PluginService.is_plugin_verified(tenant_id, provider_controller.plugin_unique_identifier)
  437. if not is_verified:
  438. return oauth_params
  439. system_client: ToolOAuthSystemClient | None = (
  440. session.query(ToolOAuthSystemClient)
  441. .filter_by(plugin_id=tool_provider.plugin_id, provider=tool_provider.provider_name)
  442. .first()
  443. )
  444. if system_client:
  445. try:
  446. oauth_params = decrypt_system_oauth_params(system_client.encrypted_oauth_params)
  447. except Exception as e:
  448. raise ValueError(f"Error decrypting system oauth params: {e}")
  449. return oauth_params
  450. @staticmethod
  451. def get_builtin_tool_provider_icon(provider: str):
  452. """
  453. get tool provider icon and it's mimetype
  454. """
  455. icon_path, mime_type = ToolManager.get_hardcoded_provider_icon(provider)
  456. icon_bytes = Path(icon_path).read_bytes()
  457. return icon_bytes, mime_type
  458. @staticmethod
  459. def list_builtin_tools(user_id: str, tenant_id: str) -> list[ToolProviderApiEntity]:
  460. """
  461. list builtin tools
  462. """
  463. # get all builtin providers
  464. provider_controllers = ToolManager.list_builtin_providers(tenant_id)
  465. with db.session.no_autoflush:
  466. # get all user added providers
  467. db_providers: list[BuiltinToolProvider] = ToolManager.list_default_builtin_providers(tenant_id)
  468. # rewrite db_providers
  469. for db_provider in db_providers:
  470. db_provider.provider = str(ToolProviderID(db_provider.provider))
  471. # find provider
  472. def find_provider(provider):
  473. return next(filter(lambda db_provider: db_provider.provider == provider, db_providers), None)
  474. result: list[ToolProviderApiEntity] = []
  475. for provider_controller in provider_controllers:
  476. try:
  477. # handle include, exclude
  478. if is_filtered(
  479. include_set=dify_config.POSITION_TOOL_INCLUDES_SET, # type: ignore
  480. exclude_set=dify_config.POSITION_TOOL_EXCLUDES_SET, # type: ignore
  481. data=provider_controller,
  482. name_func=lambda x: x.identity.name,
  483. ):
  484. continue
  485. # convert provider controller to user provider
  486. user_builtin_provider = ToolTransformService.builtin_provider_to_user_provider(
  487. provider_controller=provider_controller,
  488. db_provider=find_provider(provider_controller.entity.identity.name),
  489. decrypt_credentials=True,
  490. )
  491. # add icon
  492. ToolTransformService.repack_provider(tenant_id=tenant_id, provider=user_builtin_provider)
  493. tools = provider_controller.get_tools()
  494. for tool in tools or []:
  495. user_builtin_provider.tools.append(
  496. ToolTransformService.convert_tool_entity_to_api_entity(
  497. tenant_id=tenant_id,
  498. tool=tool,
  499. labels=ToolLabelManager.get_tool_labels(provider_controller),
  500. )
  501. )
  502. result.append(user_builtin_provider)
  503. except Exception as e:
  504. raise e
  505. return BuiltinToolProviderSort.sort(result)
  506. @staticmethod
  507. def get_builtin_provider(provider_name: str, tenant_id: str) -> Optional[BuiltinToolProvider]:
  508. """
  509. This method is used to fetch the builtin provider from the database
  510. 1.if the default provider exists, return the default provider
  511. 2.if the default provider does not exist, return the oldest provider
  512. """
  513. with Session(db.engine) as session:
  514. try:
  515. full_provider_name = provider_name
  516. provider_id_entity = ToolProviderID(provider_name)
  517. provider_name = provider_id_entity.provider_name
  518. if provider_id_entity.organization != "langgenius":
  519. provider = (
  520. session.query(BuiltinToolProvider)
  521. .where(
  522. BuiltinToolProvider.tenant_id == tenant_id,
  523. BuiltinToolProvider.provider == full_provider_name,
  524. )
  525. .order_by(
  526. BuiltinToolProvider.is_default.desc(), # default=True first
  527. BuiltinToolProvider.created_at.asc(), # oldest first
  528. )
  529. .first()
  530. )
  531. else:
  532. provider = (
  533. session.query(BuiltinToolProvider)
  534. .where(
  535. BuiltinToolProvider.tenant_id == tenant_id,
  536. (BuiltinToolProvider.provider == provider_name)
  537. | (BuiltinToolProvider.provider == full_provider_name),
  538. )
  539. .order_by(
  540. BuiltinToolProvider.is_default.desc(), # default=True first
  541. BuiltinToolProvider.created_at.asc(), # oldest first
  542. )
  543. .first()
  544. )
  545. if provider is None:
  546. return None
  547. provider.provider = ToolProviderID(provider.provider).to_string()
  548. return provider
  549. except Exception:
  550. # it's an old provider without organization
  551. return (
  552. session.query(BuiltinToolProvider)
  553. .where(BuiltinToolProvider.tenant_id == tenant_id, BuiltinToolProvider.provider == provider_name)
  554. .order_by(
  555. BuiltinToolProvider.is_default.desc(), # default=True first
  556. BuiltinToolProvider.created_at.asc(), # oldest first
  557. )
  558. .first()
  559. )
  560. @staticmethod
  561. def save_custom_oauth_client_params(
  562. tenant_id: str,
  563. provider: str,
  564. client_params: Optional[dict] = None,
  565. enable_oauth_custom_client: Optional[bool] = None,
  566. ):
  567. """
  568. setup oauth custom client
  569. """
  570. if client_params is None and enable_oauth_custom_client is None:
  571. return {"result": "success"}
  572. tool_provider = ToolProviderID(provider)
  573. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  574. if not provider_controller:
  575. raise ToolProviderNotFoundError(f"Provider {provider} not found")
  576. if not isinstance(provider_controller, (BuiltinToolProviderController, PluginToolProviderController)):
  577. raise ValueError(f"Provider {provider} is not a builtin or plugin provider")
  578. with Session(db.engine) as session:
  579. custom_client_params = (
  580. session.query(ToolOAuthTenantClient)
  581. .filter_by(
  582. tenant_id=tenant_id,
  583. plugin_id=tool_provider.plugin_id,
  584. provider=tool_provider.provider_name,
  585. )
  586. .first()
  587. )
  588. # if the record does not exist, create a basic record
  589. if custom_client_params is None:
  590. custom_client_params = ToolOAuthTenantClient(
  591. tenant_id=tenant_id,
  592. plugin_id=tool_provider.plugin_id,
  593. provider=tool_provider.provider_name,
  594. )
  595. session.add(custom_client_params)
  596. if client_params is not None:
  597. encrypter, _ = create_provider_encrypter(
  598. tenant_id=tenant_id,
  599. config=[x.to_basic_provider_config() for x in provider_controller.get_oauth_client_schema()],
  600. cache=NoOpProviderCredentialCache(),
  601. )
  602. original_params = encrypter.decrypt(custom_client_params.oauth_params)
  603. new_params: dict = {
  604. key: value if value != HIDDEN_VALUE else original_params.get(key, UNKNOWN_VALUE)
  605. for key, value in client_params.items()
  606. }
  607. custom_client_params.encrypted_oauth_params = json.dumps(encrypter.encrypt(new_params))
  608. if enable_oauth_custom_client is not None:
  609. custom_client_params.enabled = enable_oauth_custom_client
  610. session.commit()
  611. return {"result": "success"}
  612. @staticmethod
  613. def get_custom_oauth_client_params(tenant_id: str, provider: str):
  614. """
  615. get custom oauth client params
  616. """
  617. with Session(db.engine) as session:
  618. tool_provider = ToolProviderID(provider)
  619. custom_oauth_client_params: ToolOAuthTenantClient | None = (
  620. session.query(ToolOAuthTenantClient)
  621. .filter_by(
  622. tenant_id=tenant_id,
  623. plugin_id=tool_provider.plugin_id,
  624. provider=tool_provider.provider_name,
  625. )
  626. .first()
  627. )
  628. if custom_oauth_client_params is None:
  629. return {}
  630. provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
  631. if not provider_controller:
  632. raise ToolProviderNotFoundError(f"Provider {provider} not found")
  633. if not isinstance(provider_controller, BuiltinToolProviderController):
  634. raise ValueError(f"Provider {provider} is not a builtin or plugin provider")
  635. encrypter, _ = create_provider_encrypter(
  636. tenant_id=tenant_id,
  637. config=[x.to_basic_provider_config() for x in provider_controller.get_oauth_client_schema()],
  638. cache=NoOpProviderCredentialCache(),
  639. )
  640. return encrypter.mask_tool_credentials(encrypter.decrypt(custom_oauth_client_params.oauth_params))