fix(audit): P0-P3 audit fixes — 838 ruff errors → 0, 30 F821 bugs fixed, 118 files changed

- P0: hooks.py 3-tuple fix, trigger_dispatcher Contract, contacts/plugin unregister_actions_by_owner
- P0: 5 test files — check_permission mocks removed, hardcoded DB credential → env var
- P1: attachment_service DmsFile via Contract helper, restore_registry/history_hooks dedup
- P1: mail/plugin restore unregister, mcp_client datetime.now(UTC), saved_views/filters patterns
- P1: ProtectedRoute fail-closed, 13 test assertion fixes (bcrypt, DB-URLs, SECRET_KEYs)
- P2: deprecated notifications → post_system_message (3 files), forgejo Base, report_generator lazy import
- P2: webhooks permissions, deps.py/roles.py plugin perms removed, import_export default
- P2: address/tags/entity_links patterns removed, worker.py Contract-Umgehungen fixed
- P2: 28 frontend TODOs (hardcoded constants, deprecated notification API)
- P3: dead code, duplicates, deprecated imports, private attr, __import__ inline
- P3: 8 frontend TODOs (LucideIcons, inline styles, XSS, i18n)
- ruff: 838 → 0 (612 auto-fix + 246 manual + 27 F821 regression fix)
- F821: 30 → 0 (AutomationDefinition, DmsFile, user_id, Path, Any, String)
- Contract-Umgehungen: 2 neue gefunden (worker.py:169, worker.py:280) und gefixt
This commit is contained in:
Agent Zero
2026-08-16 01:17:18 +02:00
parent 3d9b76cea4
commit abbe7a18fc
306 changed files with 5912 additions and 1827 deletions
+113 -3
View File
@@ -4,18 +4,98 @@ from __future__ import annotations
import asyncio
import logging
from datetime import UTC
from typing import Any
from app.plugins.base import BasePlugin
from app.plugins.manifest import PluginManifest, PluginRouteDef, FrontendMenuItem, FrontendPageRoute, FrontendDetailTab, FrontendSettingsPage
from app.plugins.manifest import (
FrontendDetailTab,
FrontendMenuItem,
FrontendPageRoute,
FrontendSettingsPage,
PluginManifest,
PluginRouteDef,
)
logger = logging.getLogger(__name__)
async def _mail_restore_handler(
db, entity, action: str, snapshot: dict, context: dict,
) -> dict:
"""Special restore handler for Mail entities (moved from core, P0-7 fix).
Mail restore has IMAP semantics:
- delete: move back from trash to original folder (if folder still exists)
- update: revert metadata fields
- create: soft-delete (undo send only works for drafts)
"""
import uuid
from datetime import datetime
from sqlalchemy import select
user_id = context.get("user_id")
tenant_id = context.get("tenant_id")
if action == "delete":
if entity is None:
raise ValueError("Mail entity not found for restore")
entity.deleted_at = None
if user_id:
entity.updated_by = user_id if hasattr(entity, "updated_by") else None
original_folder_id = snapshot.get("folder_id")
if original_folder_id and hasattr(entity, "folder_id"):
try:
folder_uuid = uuid.UUID(str(original_folder_id))
from app.plugins.builtins.mail.models import MailFolder
folder_q = select(MailFolder).where(
MailFolder.id == folder_uuid,
MailFolder.tenant_id == tenant_id,
MailFolder.deleted_at.is_(None),
)
folder_result = await db.execute(folder_q)
folder = folder_result.scalar_one_or_none()
if folder:
entity.folder_id = folder_uuid
else:
logger.warning(
"Original mail folder %s no longer exists, "
"restoring mail without folder assignment",
original_folder_id,
)
except (ValueError, Exception) as e:
logger.warning("Failed to restore mail folder: %s", e)
await db.flush()
return {"id": str(entity.id), "restored": True, "entity_type": "mail"}
elif action == "update":
if entity is None:
raise ValueError("Mail entity not found for restore")
from app.core.restore_registry import _DEFAULT_EXCLUDED
excluded = _DEFAULT_EXCLUDED | {
"message_id", "rfc822_size", "raw_path", "account_id", "folder_id",
}
for key, value in snapshot.items():
if hasattr(entity, key) and key not in excluded:
setattr(entity, key, value)
await db.flush()
return {"id": str(entity.id), "restored": True, "entity_type": "mail"}
elif action == "create":
if entity is None:
raise ValueError("Mail entity not found for restore")
entity.deleted_at = datetime.now(UTC)
await db.flush()
return {"id": str(entity.id), "restored": True, "entity_type": "mail", "note": "soft-deleted (undo create)"}
raise ValueError(f"Unsupported action for mail restore: {action}")
async def _auto_sync_loop() -> None:
"""Background loop: process pending sync queue, then sync all active mail accounts every 5 minutes."""
from app.plugins.builtins.mail.services import auto_sync_all_accounts, process_sync_queue
from app.core.db import get_session_factory
from app.plugins.builtins.mail.services import auto_sync_all_accounts, process_sync_queue
while True:
try:
@@ -78,9 +158,29 @@ class MailPlugin(BasePlugin):
async def on_activate(
self, db, service_container, event_bus
) -> None:
"""Activate plugin: register events + start auto-sync background task."""
"""Activate plugin: register events, restore, history + start auto-sync."""
await super().on_activate(db, service_container, event_bus)
# Register restore config for Mail entities (P0-7 fix)
from app.core.restore_registry import RestoreConfig, get_restore_registry
from app.plugins.builtins.mail.models import Mail
get_restore_registry().register(RestoreConfig(
entity_type="mail",
model_class=Mail,
restore_permission="mail:write",
excluded_fields=frozenset({"message_id", "rfc822_size", "raw_path", "account_id", "folder_id"}),
special_handler=_mail_restore_handler,
))
# Register history hooks for Mail entities (P0-8 fix)
from app.core.history_hooks import register_history_hooks
from app.core.hooks import get_hook_registry
register_history_hooks(
get_hook_registry(), "mail",
"mail.after_create", "mail.after_update", "mail.after_delete",
owner_tag="mail",
)
if self._auto_sync_task is None or self._auto_sync_task.done():
self._auto_sync_task = asyncio.create_task(_auto_sync_loop())
logger.info("Mail plugin: auto-sync background task started")
@@ -117,4 +217,14 @@ class MailPlugin(BasePlugin):
self._auto_sync_task = None
logger.info("Mail plugin: auto-sync background task stopped")
# Unregister history hooks (free functions, not bound methods)
from app.core.hooks import get_hook_registry
get_hook_registry().unregister_actions_by_owner("mail.after_create", "mail")
get_hook_registry().unregister_actions_by_owner("mail.after_update", "mail")
get_hook_registry().unregister_actions_by_owner("mail.after_delete", "mail")
# Unregister restore config for Mail entities
from app.core.restore_registry import get_restore_registry
get_restore_registry().unregister("mail")
await super().on_deactivate(db, service_container, event_bus)