fix(permissions): comprehensive live permission system tests + delete permission fixes

- Add tests/test_permission_system_live.py: 33 live tests against real PostgreSQL
  testing RBAC, ABAC, RLS, cross-tenant isolation, guest access, entity sharing,
  field-level permissions, role invalidation, group permissions, membership suspension

- fix(contacts): delete route uses contacts:delete instead of contacts:write
  The delete_contact and delete_contact_person routes were checking contacts:write
  permission instead of contacts:delete, allowing users without delete permission
  to delete contacts.

- fix(contacts): DeleteContactCommand passes is_system_admin to service
  DeleteContactCommand.run() was not passing is_system_admin from the session
  to contact_service.delete_contact(), causing system admins to be blocked
  by the row-level admin access check.

- fix(contacts): allow deletion of tenant-owned contacts
  contact_service.delete_contact() required admin-level entity access for ALL
  contacts, including tenant-owned ones (owner_id=None). Tenant-owned contacts
  can now be deleted by any user with contacts:delete permission (already
  verified by the route via require_permission).
This commit is contained in:
Agent Zero
2026-08-06 09:49:07 +02:00
parent bf60e8090a
commit 67015ef82b
4 changed files with 1154 additions and 5 deletions
+2 -2
View File
@@ -184,7 +184,7 @@ async def delete_contact(
hard: bool = Query(False, description="GDPR hard-delete"),
db: AsyncSession = Depends(get_db),
redis: aioredis.Redis = Depends(get_redis_dep),
current_user: dict = Depends(require_permission("contacts:write")),
current_user: dict = Depends(require_permission("contacts:delete")),
):
"""Soft-delete (or hard-delete with ?hard=true) a contact via DeleteContactCommand."""
cmd = DeleteContactCommand(contact_id=contact_id, hard=hard)
@@ -248,7 +248,7 @@ async def delete_contact_person(
contact_id: str,
person_id: str,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("contacts:write")),
current_user: dict = Depends(require_permission("contacts:delete")),
):
"""Delete a contact person."""
tenant_id = uuid.UUID(current_user["tenant_id"])