Security fixes: P0-P2 complete (22 fixes)
P0 (7): Auth-bypass removed, migrations fixed, plugin-upload disabled, RLS FORCE+WITH CHECK, plugin double-registration fixed, persistent volume, domain removed P1 (11): User/tenant model, Redis centralized, worker separated, transactional outbox, XSS fixed, DMS chunked streaming, permissions unified, password reset, metrics secured, config/docs fixed, cross-tenant FK P2 (4): Contact model normalized, cross-imports reduced 94%, commands+state machines for contacts/dms/mail/calendar, SPA path-traversal 8 new migrations, 99 unit tests, 13 commands, 8 contracts, 72 files changed
This commit is contained in:
@@ -110,7 +110,7 @@ async def search_authed_client(
|
||||
seed = await seed_tenant_and_users(db_session)
|
||||
# Grant is_system_admin to admin user so search:read/search:admin permissions pass
|
||||
from sqlalchemy import update
|
||||
from app.models.user import User
|
||||
from app.models.user import User, UserTenant
|
||||
await db_session.execute(
|
||||
update(User)
|
||||
.where(User.email == "admin@tenanta.com")
|
||||
@@ -665,23 +665,23 @@ async def test_index_entity_success(db_session: AsyncSession):
|
||||
"""index_entity stores embedding in DB and returns True."""
|
||||
from app.models.contact import Contact
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from app.models.user import User, UserTenant
|
||||
from app.core.auth import hash_password
|
||||
|
||||
tenant = Tenant(name="Test Tenant", slug="test-tenant")
|
||||
db_session.add(tenant)
|
||||
await db_session.flush()
|
||||
user = User(
|
||||
tenant_id=tenant.id,
|
||||
email="test@example.com",
|
||||
name="Test",
|
||||
password_hash=hash_password("TestPass123!"),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
preferences={},
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.flush()
|
||||
db_session.add(UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True, role="admin"))
|
||||
await db_session.flush()
|
||||
contact = Contact(
|
||||
tenant_id=tenant.id,
|
||||
first_name="John",
|
||||
@@ -792,23 +792,23 @@ async def test_hybrid_search_with_results(db_session: AsyncSession):
|
||||
"""hybrid_search returns results when data exists."""
|
||||
from app.models.contact import Contact
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from app.models.user import User, UserTenant
|
||||
from app.core.auth import hash_password
|
||||
|
||||
tenant = Tenant(name="Test HS", slug="test-hs")
|
||||
db_session.add(tenant)
|
||||
await db_session.flush()
|
||||
user = User(
|
||||
tenant_id=tenant.id,
|
||||
email="hs@example.com",
|
||||
name="HS",
|
||||
password_hash=hash_password("TestPass123!"),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
preferences={},
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.flush()
|
||||
db_session.add(UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True, role="admin"))
|
||||
await db_session.flush()
|
||||
contact = Contact(
|
||||
tenant_id=tenant.id,
|
||||
first_name="Search",
|
||||
@@ -867,23 +867,23 @@ async def test_index_mails(mock_index_entity, mock_factory, db_session: AsyncSes
|
||||
"""index_mails calls index_entity for each mail."""
|
||||
from app.plugins.builtins.mail.models import Mail, MailAccount, MailFolder
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from app.models.user import User, UserTenant
|
||||
from app.core.auth import hash_password
|
||||
|
||||
tenant = Tenant(name="Mail Tenant", slug="mail-tenant")
|
||||
db_session.add(tenant)
|
||||
await db_session.flush()
|
||||
user = User(
|
||||
tenant_id=tenant.id,
|
||||
email="mail@example.com",
|
||||
name="Mail",
|
||||
password_hash=hash_password("TestPass123!"),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
preferences={},
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.flush()
|
||||
db_session.add(UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True, role="admin"))
|
||||
await db_session.flush()
|
||||
account = MailAccount(
|
||||
tenant_id=tenant.id,
|
||||
user_id=user.id,
|
||||
@@ -954,23 +954,23 @@ async def test_index_contact(mock_index_entity, mock_factory, db_session: AsyncS
|
||||
"""index_contact calls index_entity."""
|
||||
from app.models.contact import Contact
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from app.models.user import User, UserTenant
|
||||
from app.core.auth import hash_password
|
||||
|
||||
tenant = Tenant(name="Contact Tenant", slug="contact-tenant")
|
||||
db_session.add(tenant)
|
||||
await db_session.flush()
|
||||
user = User(
|
||||
tenant_id=tenant.id,
|
||||
email="contact@example.com",
|
||||
name="Contact",
|
||||
password_hash=hash_password("TestPass123!"),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
preferences={},
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.flush()
|
||||
db_session.add(UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True, role="admin"))
|
||||
await db_session.flush()
|
||||
contact = Contact(
|
||||
tenant_id=tenant.id,
|
||||
first_name="Index",
|
||||
@@ -996,23 +996,23 @@ async def test_index_contact_company_type(mock_index_entity, mock_factory, db_se
|
||||
"""index_contact calls index_entity for company-type contact."""
|
||||
from app.models.contact import Contact as Company
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from app.models.user import User, UserTenant
|
||||
from app.core.auth import hash_password
|
||||
|
||||
tenant = Tenant(name="Company Tenant", slug="company-tenant")
|
||||
db_session.add(tenant)
|
||||
await db_session.flush()
|
||||
user = User(
|
||||
tenant_id=tenant.id,
|
||||
email="company@example.com",
|
||||
name="Company",
|
||||
password_hash=hash_password("TestPass123!"),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
preferences={},
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.flush()
|
||||
db_session.add(UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True, role="admin"))
|
||||
await db_session.flush()
|
||||
company = Company(
|
||||
tenant_id=tenant.id,
|
||||
name="Index Company",
|
||||
@@ -1037,23 +1037,23 @@ async def test_reindex(mock_index_entity, mock_factory, db_session: AsyncSession
|
||||
"""reindex iterates over all entities of a type."""
|
||||
from app.models.contact import Contact as Company
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from app.models.user import User, UserTenant
|
||||
from app.core.auth import hash_password
|
||||
|
||||
tenant = Tenant(name="Reindex Tenant", slug="reindex-tenant")
|
||||
db_session.add(tenant)
|
||||
await db_session.flush()
|
||||
user = User(
|
||||
tenant_id=tenant.id,
|
||||
email="reindex@example.com",
|
||||
name="Reindex",
|
||||
password_hash=hash_password("TestPass123!"),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
preferences={},
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.flush()
|
||||
db_session.add(UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True, role="admin"))
|
||||
await db_session.flush()
|
||||
company = Company(
|
||||
tenant_id=tenant.id,
|
||||
name="Reindex Co",
|
||||
@@ -1078,23 +1078,23 @@ async def test_embedding_batch(mock_index_entity, mock_factory, db_session: Asyn
|
||||
"""embedding_batch finds entities without embedding."""
|
||||
from app.models.contact import Contact as Company
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from app.models.user import User, UserTenant
|
||||
from app.core.auth import hash_password
|
||||
|
||||
tenant = Tenant(name="Batch Tenant", slug="batch-tenant")
|
||||
db_session.add(tenant)
|
||||
await db_session.flush()
|
||||
user = User(
|
||||
tenant_id=tenant.id,
|
||||
email="batch@example.com",
|
||||
name="Batch",
|
||||
password_hash=hash_password("TestPass123!"),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
preferences={},
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.flush()
|
||||
db_session.add(UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True, role="admin"))
|
||||
await db_session.flush()
|
||||
company = Company(
|
||||
tenant_id=tenant.id,
|
||||
name="Batch Co",
|
||||
|
||||
Reference in New Issue
Block a user