fix(#356): DSAR-Sammlung auf Contract-Zugriff umgestellt — 4 Core→Plugin-Imports (mail/tasks/calendar/kommunikation) nutzen jetzt get_contract(); MailContract exponiert MailAccount; ImportError-Fallback-Semantik unverändert; Checker 4→0 Verstöße; DSAR-Suite 4/4 passed
Check Cross-Plugin Imports / check (push) Has been cancelled

fixes #356
This commit is contained in:
Agent Zero
2026-08-27 17:38:06 +02:00
parent 385521eddc
commit 092c2d20fb
3 changed files with 44 additions and 21 deletions
+31 -18
View File
@@ -172,6 +172,7 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
from app.models.contact import Contact
from app.models.notification import Notification
from app.models.user import User
from app.plugins.builtins.contracts import get_contract
uid = PyUUID(user_id)
tid = PyUUID(tenant_id)
@@ -256,13 +257,16 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
# ── Categories promised by the dsgvo-export route docstring ──
# (G1-b: mail accounts, tasks, calendar entries, comm messages)
try:
from app.plugins.builtins.mail.models import MailAccount
mail_contract = get_contract("mail")
if mail_contract is None:
raise ImportError("mail plugin not active")
mail_account_m = mail_contract.MailAccount
mail_accounts = (
await db.execute(
sa_select(MailAccount).where(
MailAccount.tenant_id == tid,
MailAccount.user_id == uid,
sa_select(mail_account_m).where(
mail_account_m.tenant_id == tid,
mail_account_m.user_id == uid,
).limit(500)
)
).scalars().all()
@@ -280,14 +284,17 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
pass
try:
from app.plugins.builtins.tasks.models import Task as TaskModel
tasks_contract = get_contract("tasks")
if tasks_contract is None:
raise ImportError("tasks plugin not active")
task_m = tasks_contract.Task
tasks = (
await db.execute(
sa_select(TaskModel).where(
sa_or_(TaskModel.owner_id == uid, TaskModel.assigned_to == uid),
TaskModel.tenant_id == tid,
TaskModel.deleted_at.is_(None),
sa_select(task_m).where(
sa_or_(task_m.owner_id == uid, task_m.assigned_to == uid),
task_m.tenant_id == tid,
task_m.deleted_at.is_(None),
).limit(1000)
)
).scalars().all()
@@ -305,14 +312,17 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
pass
try:
from app.plugins.builtins.calendar.models import CalendarEntry as CalEntry
cal_contract = get_contract("calendar")
if cal_contract is None:
raise ImportError("calendar plugin not active")
cal_entry_m = cal_contract.CalendarEntry
cal_entries = (
await db.execute(
sa_select(CalEntry).where(
CalEntry.tenant_id == tid,
CalEntry.owner_id == uid,
CalEntry.deleted_at.is_(None),
sa_select(cal_entry_m).where(
cal_entry_m.tenant_id == tid,
cal_entry_m.owner_id == uid,
cal_entry_m.deleted_at.is_(None),
).limit(1000)
)
).scalars().all()
@@ -330,13 +340,16 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
pass
try:
from app.plugins.builtins.kommunikation.models import CommMessage
komm_contract = get_contract("kommunikation")
if komm_contract is None:
raise ImportError("kommunikation plugin not active")
comm_msg_m = komm_contract.CommMessage
comm_messages = (
await db.execute(
sa_select(CommMessage).where(
CommMessage.sender_id == uid,
CommMessage.tenant_id == tid,
sa_select(comm_msg_m).where(
comm_msg_m.sender_id == uid,
comm_msg_m.tenant_id == tid,
).limit(1000)
)
).scalars().all()
+4 -3
View File
@@ -17,20 +17,21 @@ instead of importing from internal modules directly.
from __future__ import annotations
from app.plugins.builtins.contracts import get_contract_registry
from app.plugins.builtins.mail.models import Mail
from app.plugins.builtins.mail.models import Mail, MailAccount
class MailContract:
"""Public API surface for the mail plugin.
Exposes the ``Mail`` ORM model so that other plugins can query the
mails table without importing from ``mail.models`` directly.
Exposes ORM models so that other plugins can query the mail tables
without importing from ``mail.models`` directly.
"""
contract_name = "mail"
# ─── models ───
Mail = Mail
MailAccount = MailAccount
@classmethod
def get_function(cls, name: str):