forked from retoor/devplacepy
fix: extend content delete authorization to allow administrators alongside owners across all endpoints
This commit is contained in:
@@ -23,6 +23,20 @@ def upload(name: str, description: str) -> Param:
|
||||
return Param(name=name, location="file", description=description, required=True)
|
||||
|
||||
|
||||
def confirm() -> Param:
|
||||
return Param(
|
||||
name="confirm",
|
||||
location="body",
|
||||
description=(
|
||||
"Set to true ONLY after the user has explicitly confirmed this irreversible action. "
|
||||
"Leave it unset on the first call: the tool refuses and tells you to confirm with the "
|
||||
"user, and only a repeat call with confirm=true actually performs it."
|
||||
),
|
||||
required=False,
|
||||
type="boolean",
|
||||
)
|
||||
|
||||
|
||||
ATTACHMENTS = "Comma separated attachment uids returned by upload_file."
|
||||
TARGET_TYPE = "Target type, e.g. post, comment, project, gist."
|
||||
|
||||
@@ -159,12 +173,13 @@ ACTIONS: tuple[Action, ...] = (
|
||||
name="delete_post",
|
||||
method="POST",
|
||||
path="/posts/delete/{post_slug}",
|
||||
summary="Delete a post",
|
||||
summary="Delete a post (your own, or any post when you are an administrator). Soft delete, confirmation required",
|
||||
params=(
|
||||
path(
|
||||
"post_slug",
|
||||
"Exact post slug copied from a /posts/... link in a feed or listing response; do not build it from the title.",
|
||||
),
|
||||
confirm(),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
@@ -185,8 +200,8 @@ ACTIONS: tuple[Action, ...] = (
|
||||
name="delete_comment",
|
||||
method="POST",
|
||||
path="/comments/delete/{comment_uid}",
|
||||
summary="Delete a comment",
|
||||
params=(path("comment_uid", "Uid of the comment."),),
|
||||
summary="Delete a comment (your own, or any comment when you are an administrator). Soft delete, confirmation required",
|
||||
params=(path("comment_uid", "Uid of the comment."), confirm()),
|
||||
),
|
||||
Action(
|
||||
name="list_projects",
|
||||
@@ -254,12 +269,13 @@ ACTIONS: tuple[Action, ...] = (
|
||||
name="delete_project",
|
||||
method="POST",
|
||||
path="/projects/delete/{project_slug}",
|
||||
summary="Delete a project",
|
||||
summary="Delete a project and all its files (your own, or any project when you are an administrator). Soft delete, confirmation required",
|
||||
params=(
|
||||
path(
|
||||
"project_slug",
|
||||
"Exact project slug copied from a /projects/... link in a listing response; do not build it from the title.",
|
||||
),
|
||||
confirm(),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
@@ -475,10 +491,11 @@ ACTIONS: tuple[Action, ...] = (
|
||||
name="project_delete_file",
|
||||
method="POST",
|
||||
path="/projects/{project_slug}/files/delete",
|
||||
summary="Delete a file or directory (recursive) from a project filesystem",
|
||||
summary="Delete a file or directory (recursive) from a project filesystem (project owner, or any project when you are an administrator). Soft delete, confirmation required",
|
||||
params=(
|
||||
path("project_slug", "Project slug or uid."),
|
||||
body("path", "Relative path to delete.", required=True),
|
||||
confirm(),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
@@ -779,14 +796,14 @@ ACTIONS: tuple[Action, ...] = (
|
||||
name="delete_media",
|
||||
method="POST",
|
||||
path="/media/{uid}/delete",
|
||||
summary="Delete one of your uploaded media attachments",
|
||||
summary="Delete an uploaded media attachment (your own, or anyone's when you are an administrator)",
|
||||
description=(
|
||||
"Removes the attachment from the user's Media tab and from any post, project, or "
|
||||
"gist where it was attached. Show the user the exact media, get explicit "
|
||||
"confirmation, then call again with confirm=true."
|
||||
"gist where it was attached. Soft delete (restorable from admin trash). Show the user "
|
||||
"the exact media, get explicit confirmation, then call again with confirm=true."
|
||||
),
|
||||
requires_auth=True,
|
||||
params=(path("uid", "Attachment uid to delete."),),
|
||||
params=(path("uid", "Attachment uid to delete."), confirm()),
|
||||
),
|
||||
Action(
|
||||
name="view_leaderboard",
|
||||
@@ -871,12 +888,13 @@ ACTIONS: tuple[Action, ...] = (
|
||||
name="delete_gist",
|
||||
method="POST",
|
||||
path="/gists/delete/{gist_slug}",
|
||||
summary="Delete a gist",
|
||||
summary="Delete a gist (your own, or any gist when you are an administrator). Soft delete, confirmation required",
|
||||
params=(
|
||||
path(
|
||||
"gist_slug",
|
||||
"Exact gist slug copied from a /gists/... link in a listing response; do not build it from the title.",
|
||||
),
|
||||
confirm(),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
@@ -932,8 +950,8 @@ ACTIONS: tuple[Action, ...] = (
|
||||
name="delete_attachment",
|
||||
method="DELETE",
|
||||
path="/uploads/delete/{attachment_uid}",
|
||||
summary="Delete an uploaded attachment",
|
||||
params=(path("attachment_uid", "Uid of the attachment."),),
|
||||
summary="Delete an uploaded attachment (your own, or anyone's when you are an administrator). Soft delete, confirmation required",
|
||||
params=(path("attachment_uid", "Uid of the attachment."), confirm()),
|
||||
),
|
||||
Action(
|
||||
name="admin_overview",
|
||||
@@ -1080,8 +1098,8 @@ ACTIONS: tuple[Action, ...] = (
|
||||
name="admin_delete_news",
|
||||
method="POST",
|
||||
path="/admin/news/{uid}/delete",
|
||||
summary="Delete a news article",
|
||||
params=(path("uid", "News uid."),),
|
||||
summary="Delete a news article (admin only). Soft delete, confirmation required",
|
||||
params=(path("uid", "News uid."), confirm()),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
@@ -1165,7 +1183,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/users/{uid}/reset-ai-quota",
|
||||
summary="Reset a user's AI quota (admin only)",
|
||||
description="Clears the AI quota ledger for a specific user, allowing them to use AI features again.",
|
||||
params=(path("uid", "User uid."),),
|
||||
params=(path("uid", "User uid."), confirm()),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
@@ -1174,7 +1192,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/media/{uid}/purge",
|
||||
summary="Permanently remove soft-deleted media (admin only)",
|
||||
description="Permanently deletes a soft-deleted attachment from the database and filesystem.",
|
||||
params=(path("uid", "Attachment uid to purge."),),
|
||||
params=(path("uid", "Attachment uid to purge."), confirm()),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
@@ -1183,6 +1201,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/ai-quota/reset-guests",
|
||||
summary="Reset all guest AI quotas (admin only)",
|
||||
description="Clears the AI quota ledger for all guest sessions.",
|
||||
params=(confirm(),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
@@ -1191,6 +1210,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/ai-quota/reset-all",
|
||||
summary="Reset ALL AI quotas including member quotas (admin only)",
|
||||
description="Clears the AI quota ledger for every user and guest. Use with caution.",
|
||||
params=(confirm(),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
|
||||
@@ -81,6 +81,13 @@ CONTAINER_ACTIONS: tuple[Action, ...] = (
|
||||
"start, stop, restart, pause, resume, delete, or sync.",
|
||||
required=True,
|
||||
),
|
||||
arg(
|
||||
"confirm",
|
||||
"Required only for action=delete: set true ONLY after the user has explicitly "
|
||||
"confirmed destroying the instance. Leave unset otherwise; the delete is refused "
|
||||
"until you pass confirm=true.",
|
||||
kind="boolean",
|
||||
),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
@@ -112,6 +119,13 @@ CONTAINER_ACTIONS: tuple[Action, ...] = (
|
||||
"Command to run, e.g. 'git clone ... && ls'. Runs in /app already; do not prepend 'cd /app'.",
|
||||
required=True,
|
||||
),
|
||||
arg(
|
||||
"confirm",
|
||||
"Required only when the command is destructive (rm, dd, truncate, drop, etc.): set "
|
||||
"true ONLY after the user has explicitly confirmed. Leave unset otherwise; a "
|
||||
"destructive command is refused until you pass confirm=true.",
|
||||
kind="boolean",
|
||||
),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
|
||||
@@ -50,6 +50,9 @@ CONFIRM_REQUIRED = {
|
||||
"admin_reset_all_ai_quota",
|
||||
"admin_reset_guest_ai_quota",
|
||||
"admin_reset_user_ai_quota",
|
||||
}
|
||||
|
||||
CONDITIONAL_CONFIRM = {
|
||||
"container_instance_action",
|
||||
"container_exec",
|
||||
}
|
||||
@@ -200,6 +203,12 @@ def confirmation_error(name: str, arguments: dict[str, Any]) -> ToolInputError |
|
||||
f"such as rm, dd, truncate, or drop): {command!r}. Show the user the exact command, get "
|
||||
"explicit confirmation, then call again with confirm=true."
|
||||
)
|
||||
if name in CONFIRM_REQUIRED:
|
||||
return ToolInputError(
|
||||
"This removes the item as a soft delete: it disappears from every surface and is only "
|
||||
"restorable from the admin trash. Show the user exactly what will be deleted, get "
|
||||
"explicit confirmation, then call again with confirm=true."
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user