feat: implement plugin installation availability checks and add custom error handling

This commit is contained in:
Yeuoly 2025-05-20 15:23:58 +08:00
parent 153abb181d
commit a6c98d71db
3 changed files with 84 additions and 13 deletions

View File

@ -0,0 +1,5 @@
from services.errors.base import BaseServiceError
class PluginInstallationForbiddenError(BaseServiceError):
pass

View File

@ -104,6 +104,7 @@ class PluginInstallationPermissionModel(BaseModel):
plugin_installation_scope: PluginInstallationScope = PluginInstallationScope.ALL plugin_installation_scope: PluginInstallationScope = PluginInstallationScope.ALL
# If True, restrict plugin installation to the marketplace only # If True, restrict plugin installation to the marketplace only
# Equivalent to ForceEnablePluginVerification
restrict_to_marketplace_only: bool = False restrict_to_marketplace_only: bool = False

View File

@ -17,11 +17,13 @@ from core.plugin.entities.plugin import (
PluginInstallation, PluginInstallation,
PluginInstallationSource, PluginInstallationSource,
) )
from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginUploadResponse from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginUploadResponse, PluginVerification
from core.plugin.impl.asset import PluginAssetManager from core.plugin.impl.asset import PluginAssetManager
from core.plugin.impl.debugging import PluginDebuggingClient from core.plugin.impl.debugging import PluginDebuggingClient
from core.plugin.impl.plugin import PluginInstaller from core.plugin.impl.plugin import PluginInstaller
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from services.errors.plugin import PluginInstallationForbiddenError
from services.feature_service import FeatureService, PluginInstallationScope
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -86,6 +88,35 @@ class PluginService:
logger.exception("failed to fetch latest plugin version") logger.exception("failed to fetch latest plugin version")
return result return result
@staticmethod
def _check_plugin_installation_availability(plugin_verification: Optional[PluginVerification]):
"""
Check the verification of the plugin
"""
features = FeatureService.get_system_features()
if not plugin_verification:
if features.plugin_installation_permission.restrict_to_marketplace_only:
raise PluginInstallationForbiddenError("Plugin installation is restricted to marketplace only")
return
match features.plugin_installation_permission.plugin_installation_scope:
case PluginInstallationScope.OFFICIAL_ONLY:
if plugin_verification.authorized_category != PluginVerification.AuthorizedCategory.Langgenius:
raise PluginInstallationForbiddenError("Plugin installation is restricted to official only")
case PluginInstallationScope.OFFICIAL_AND_SPECIFIC_PARTNERS:
if plugin_verification.authorized_category not in [
PluginVerification.AuthorizedCategory.Langgenius,
PluginVerification.AuthorizedCategory.Partner,
]:
raise PluginInstallationForbiddenError(
"Plugin installation is restricted to official and specific partners"
)
case PluginInstallationScope.NONE:
raise PluginInstallationForbiddenError("Installing plugins is not allowed")
case PluginInstallationScope.ALL:
pass
@staticmethod @staticmethod
def get_debugging_key(tenant_id: str) -> str: def get_debugging_key(tenant_id: str) -> str:
""" """
@ -199,6 +230,8 @@ class PluginService:
# check if plugin pkg is already downloaded # check if plugin pkg is already downloaded
manager = PluginInstaller() manager = PluginInstaller()
features = FeatureService.get_system_features()
try: try:
manager.fetch_plugin_manifest(tenant_id, new_plugin_unique_identifier) manager.fetch_plugin_manifest(tenant_id, new_plugin_unique_identifier)
# already downloaded, skip, and record install event # already downloaded, skip, and record install event
@ -206,7 +239,14 @@ class PluginService:
except Exception: except Exception:
# plugin not installed, download and upload pkg # plugin not installed, download and upload pkg
pkg = download_plugin_pkg(new_plugin_unique_identifier) pkg = download_plugin_pkg(new_plugin_unique_identifier)
manager.upload_pkg(tenant_id, pkg, verify_signature=False) response = manager.upload_pkg(
tenant_id,
pkg,
verify_signature=features.plugin_installation_permission.restrict_to_marketplace_only,
)
# check if the plugin is available to install
PluginService._check_plugin_installation_availability(response.verification)
return manager.upgrade_plugin( return manager.upgrade_plugin(
tenant_id, tenant_id,
@ -251,7 +291,15 @@ class PluginService:
returns: plugin_unique_identifier returns: plugin_unique_identifier
""" """
manager = PluginInstaller() manager = PluginInstaller()
return manager.upload_pkg(tenant_id, pkg, verify_signature) features = FeatureService.get_system_features()
response = manager.upload_pkg(
tenant_id,
pkg,
verify_signature=features.plugin_installation_permission.restrict_to_marketplace_only,
)
# check if the plugin is available to install
PluginService._check_plugin_installation_availability(response.verification)
return response
@staticmethod @staticmethod
def upload_pkg_from_github( def upload_pkg_from_github(
@ -264,13 +312,17 @@ class PluginService:
pkg = download_with_size_limit( pkg = download_with_size_limit(
f"https://github.com/{repo}/releases/download/{version}/{package}", dify_config.PLUGIN_MAX_PACKAGE_SIZE f"https://github.com/{repo}/releases/download/{version}/{package}", dify_config.PLUGIN_MAX_PACKAGE_SIZE
) )
features = FeatureService.get_system_features()
manager = PluginInstaller() manager = PluginInstaller()
return manager.upload_pkg( response = manager.upload_pkg(
tenant_id, tenant_id,
pkg, pkg,
verify_signature, verify_signature=features.plugin_installation_permission.restrict_to_marketplace_only,
) )
# check if the plugin is available to install
PluginService._check_plugin_installation_availability(response.verification)
return response
@staticmethod @staticmethod
def upload_bundle( def upload_bundle(
@ -313,28 +365,33 @@ class PluginService:
) )
@staticmethod @staticmethod
def fetch_marketplace_pkg( def fetch_marketplace_pkg(tenant_id: str, plugin_unique_identifier: str) -> PluginDeclaration:
tenant_id: str, plugin_unique_identifier: str, verify_signature: bool = False
) -> PluginDeclaration:
""" """
Fetch marketplace package Fetch marketplace package
""" """
if not dify_config.MARKETPLACE_ENABLED: if not dify_config.MARKETPLACE_ENABLED:
raise ValueError("marketplace is not enabled") raise ValueError("marketplace is not enabled")
features = FeatureService.get_system_features()
manager = PluginInstaller() manager = PluginInstaller()
try: try:
declaration = manager.fetch_plugin_manifest(tenant_id, plugin_unique_identifier) declaration = manager.fetch_plugin_manifest(tenant_id, plugin_unique_identifier)
except Exception: except Exception:
pkg = download_plugin_pkg(plugin_unique_identifier) pkg = download_plugin_pkg(plugin_unique_identifier)
declaration = manager.upload_pkg(tenant_id, pkg, verify_signature).manifest response = manager.upload_pkg(
tenant_id,
pkg,
verify_signature=features.plugin_installation_permission.restrict_to_marketplace_only,
)
# check if the plugin is available to install
PluginService._check_plugin_installation_availability(response.verification)
declaration = response.manifest
return declaration return declaration
@staticmethod @staticmethod
def install_from_marketplace_pkg( def install_from_marketplace_pkg(tenant_id: str, plugin_unique_identifiers: Sequence[str]):
tenant_id: str, plugin_unique_identifiers: Sequence[str], verify_signature: bool = False
):
""" """
Install plugin from marketplace package files, Install plugin from marketplace package files,
returns installation task id returns installation task id
@ -344,6 +401,8 @@ class PluginService:
manager = PluginInstaller() manager = PluginInstaller()
features = FeatureService.get_system_features()
# check if already downloaded # check if already downloaded
for plugin_unique_identifier in plugin_unique_identifiers: for plugin_unique_identifier in plugin_unique_identifiers:
try: try:
@ -352,7 +411,13 @@ class PluginService:
except Exception: except Exception:
# plugin not installed, download and upload pkg # plugin not installed, download and upload pkg
pkg = download_plugin_pkg(plugin_unique_identifier) pkg = download_plugin_pkg(plugin_unique_identifier)
manager.upload_pkg(tenant_id, pkg, verify_signature) response = manager.upload_pkg(
tenant_id,
pkg,
verify_signature=features.plugin_installation_permission.restrict_to_marketplace_only,
)
# check if the plugin is available to install
PluginService._check_plugin_installation_availability(response.verification)
return manager.install_from_identifiers( return manager.install_from_identifiers(
tenant_id, tenant_id,