mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Guard run_user_tool against deactivated user-defined tools
deactivate_unprivileged_tool deliberately only flips the per-user UserDynamicToolAssociation.active flag, leaving DynamicTool.active intact so other users with associations to the same DynamicTool aren't affected (the model schema permits many-to-many, even though the current create path is 1:1). That means a user who deactivates "their" UDT can still resolve it by UUID through the toolbox -- and run it via tools_service._create -- because get_unprivileged_tool_by_uuid doesn't filter by association.active either. Add a runtime preflight in run_user_tool that fails the call when either the underlying tool or the calling user's association is inactive. Also surfaces unauthenticated and unowned errors as clean ValueErrors before reaching the deeper service layer. Tightening the chokepoint (DynamicToolManager.get_unprivileged_tool_by_uuid) to filter by association.active would close this across all entry points but is a meaningful behavior change for the existing UnprivilegedToolsApi endpoints; leaving that for a separate review.
This commit is contained in:
@@ -920,6 +920,31 @@ class AgentOperationsManager:
|
||||
return {"uuid": uuid, "deactivated": True}
|
||||
|
||||
def run_user_tool(self, history_id: str, tool_uuid: str, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
from sqlalchemy import select
|
||||
|
||||
from galaxy.model import UserDynamicToolAssociation
|
||||
|
||||
user = self.trans.user
|
||||
if not user:
|
||||
raise ValueError("User must be authenticated")
|
||||
|
||||
dynamic_tool = self.dynamic_tools_manager.get_unprivileged_tool_by_uuid(user, tool_uuid)
|
||||
if dynamic_tool is None:
|
||||
raise ValueError(f"User-defined tool {tool_uuid!r} not found")
|
||||
# UDT deactivation is per-user by design: deactivate_unprivileged_tool only
|
||||
# flips the user-association, leaving DynamicTool.active intact so other
|
||||
# users sharing the underlying tool aren't affected. The runtime check has
|
||||
# to look at the association, not just dynamic_tool.active.
|
||||
session = self.dynamic_tools_manager.session()
|
||||
assoc_active = session.scalar(
|
||||
select(UserDynamicToolAssociation.active).where(
|
||||
UserDynamicToolAssociation.user_id == user.id,
|
||||
UserDynamicToolAssociation.dynamic_tool_id == dynamic_tool.id,
|
||||
)
|
||||
)
|
||||
if not dynamic_tool.active or not assoc_active:
|
||||
raise ValueError(f"User-defined tool {tool_uuid!r} is deactivated")
|
||||
|
||||
payload = {
|
||||
"history_id": history_id,
|
||||
"tool_uuid": tool_uuid,
|
||||
|
||||
Reference in New Issue
Block a user