sprint2: 8 services + 8 routes visibility filter + BaseSearchProvider + owned_mixin on models
Check Cross-Plugin Imports / check (push) Has been cancelled

This commit is contained in:
Agent Zero
2026-07-29 01:52:47 +02:00
parent 479ee04834
commit 9fc84b7905
25 changed files with 977 additions and 211 deletions
+38 -1
View File
@@ -17,6 +17,7 @@ from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.webhook import Webhook
from app.core.visibility import apply_visibility_filter, check_single_entity_access
logger = logging.getLogger(__name__)
@@ -64,6 +65,8 @@ async def list_webhooks(
db: AsyncSession,
tenant_id: uuid.UUID,
event: str | None = None,
user_id: uuid.UUID | None = None,
is_system_admin: bool = False,
) -> list[Webhook]:
"""List webhooks for a tenant, optionally filtered by event."""
stmt = select(Webhook).where(
@@ -71,6 +74,10 @@ async def list_webhooks(
)
if event:
stmt = stmt.where(Webhook.events.any(event))
if user_id and not is_system_admin:
stmt = await apply_visibility_filter(
db, stmt, "webhook", Webhook, user_id, tenant_id, is_system_admin
)
stmt = stmt.order_by(Webhook.created_at.desc())
result = await db.execute(stmt)
return list(result.scalars().all())
@@ -80,6 +87,8 @@ async def get_webhook(
db: AsyncSession,
tenant_id: uuid.UUID,
webhook_id: uuid.UUID,
user_id: uuid.UUID | None = None,
is_system_admin: bool = False,
) -> Webhook | None:
"""Get a single webhook by ID."""
stmt = select(Webhook).where(
@@ -87,7 +96,16 @@ async def get_webhook(
Webhook.tenant_id == tenant_id,
)
result = await db.execute(stmt)
return result.scalar_one_or_none()
webhook = result.scalar_one_or_none()
if webhook is None:
return None
if user_id and not is_system_admin:
has_access = await check_single_entity_access(
db, "webhook", webhook.id, user_id, tenant_id, "read", is_system_admin
)
if not has_access:
raise PermissionError("No access")
return webhook
async def create_webhook(
@@ -107,6 +125,7 @@ async def create_webhook(
timeout_seconds=data.get("timeout_seconds", 30),
created_by=user_id,
updated_by=user_id,
owner_id=user_id,
)
db.add(webhook)
await db.flush()
@@ -120,12 +139,20 @@ async def update_webhook(
webhook_id: uuid.UUID,
data: dict[str, Any],
user_id: uuid.UUID | None = None,
is_system_admin: bool = False,
) -> Webhook | None:
"""Update an existing webhook subscription."""
webhook = await get_webhook(db, tenant_id, webhook_id)
if webhook is None:
return None
if not is_system_admin:
has_access = await check_single_entity_access(
db, "webhook", webhook.id, user_id, tenant_id, "write", is_system_admin
)
if not has_access:
raise PermissionError("No access")
update_fields = ["url", "events", "secret", "is_active", "retry_count", "timeout_seconds"]
for field in update_fields:
if field in data:
@@ -143,6 +170,8 @@ async def delete_webhook(
db: AsyncSession,
tenant_id: uuid.UUID,
webhook_id: uuid.UUID,
user_id: uuid.UUID | None = None,
is_system_admin: bool = False,
) -> bool:
"""Delete a webhook subscription."""
stmt = select(Webhook).where(
@@ -153,6 +182,14 @@ async def delete_webhook(
webhook = result.scalar_one_or_none()
if webhook is None:
return False
if not is_system_admin:
has_access = await check_single_entity_access(
db, "webhook", webhook.id, user_id, tenant_id, "admin", is_system_admin
)
if not has_access:
raise PermissionError("No access")
await db.delete(webhook)
await db.flush()
return True