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

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