Skip to content

Commit c1b914e

Browse files
committed
fix: Auto-delete API credentials when uninstalling plugin
Automatically remove plugin provider credentials on uninstall to prevent orphaned keys from being restored when plugin is reinstalled. Fixes #27531
1 parent ddad246 commit c1b914e

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

api/controllers/console/workspace/plugin.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,3 +826,29 @@ def get(self):
826826
return jsonable_encoder(
827827
{"readme": PluginService.fetch_plugin_readme(tenant_id, args.plugin_unique_identifier, args.language)}
828828
)
829+
830+
831+
class ParserUninstall(BaseModel):
832+
plugin_installation_id: str = Field(..., description="Plugin installation ID")
833+
834+
835+
console_ns.schema_model(
836+
ParserUninstall.__name__, ParserUninstall.model_json_schema(ref_template=DEFAULT_REF_TEMPLATE_SWAGGER_2_0)
837+
)
838+
839+
840+
@console_ns.route("/workspaces/current/plugin/uninstall")
841+
class PluginUninstallApi(Resource):
842+
@console_ns.expect(console_ns.models[ParserUninstall.__name__])
843+
@setup_required
844+
@login_required
845+
@account_initialization_required
846+
@plugin_permission_required(install_required=True)
847+
def post(self):
848+
_, tenant_id = current_account_with_tenant()
849+
args = ParserUninstall.model_validate(console_ns.payload)
850+
851+
try:
852+
return {"success": PluginService.uninstall(tenant_id, args.plugin_installation_id)}
853+
except PluginDaemonClientSideError as e:
854+
raise ValueError(e)

api/services/plugin/plugin_service.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,38 @@ def install_from_marketplace_pkg(tenant_id: str, plugin_unique_identifiers: Sequ
505505

506506
@staticmethod
507507
def uninstall(tenant_id: str, plugin_installation_id: str) -> bool:
508+
from extensions.ext_database import db
509+
from models.provider import ProviderCredential
510+
from sqlalchemy import select
511+
508512
manager = PluginInstaller()
513+
514+
# Get plugin info before uninstalling to delete associated credentials
515+
try:
516+
plugins = manager.list_plugins(tenant_id)
517+
plugin = next((p for p in plugins if p.installation_id == plugin_installation_id), None)
518+
519+
if plugin:
520+
plugin_id = plugin.plugin_id
521+
logger.info(f"Deleting credentials for plugin: {plugin_id}")
522+
523+
# Delete provider credentials that match this plugin
524+
credentials = db.session.scalars(
525+
select(ProviderCredential).where(
526+
ProviderCredential.tenant_id == tenant_id,
527+
ProviderCredential.provider_name.like(f"{plugin_id}/%"),
528+
)
529+
).all()
530+
531+
for cred in credentials:
532+
db.session.delete(cred)
533+
534+
db.session.commit()
535+
logger.info(f"Deleted {len(credentials)} credentials for plugin: {plugin_id}")
536+
except Exception as e:
537+
logger.warning(f"Failed to delete credentials: {e}")
538+
# Continue with uninstall even if credential deletion fails
539+
509540
return manager.uninstall(tenant_id, plugin_installation_id)
510541

511542
@staticmethod

0 commit comments

Comments
 (0)