Close the gaps between the documented contract (API-STANDARD, TENANCY, ENTITLEMENTS) and the implementation, and remove committed build artifacts. Security: - verify + process inbound webhooks (HMAC/handler verify, size limit, timestamp tolerance, idempotency via InboundEvent.external_id) - real 2FA login flow (pre-auth challenge; tokens only after verify/recovery) - wire HybridPermission into security/audit views; add API-key rotate and users/<id>/permissions|roles endpoints - tenant-scoped throttling on by default; webhook replay protection - verify MercadoPago webhook signatures - login brute-force guard, configurable password policy, real ALTCHA PoW Correctness: - apply verified billing webhooks idempotently (subscription/entitlement/ invoice/PaymentTransaction); scheduled payment lifecycle jobs - capture audit update diffs automatically; add audit retention purge - working notification retries, per-channel rate limits, log retention - pluggable virus scanner, upload-size limit, pipeline toggle - feature rollout %/environment targeting; settings-driven registrations - workflow guards (instance cap, route depth, self-assignment, clone on re-entry) - wire every previously-dead INFRASYNTH_* setting; drop truly dead ones Delivery: - README + CHANGELOG; CI format check + coverage gate - keep test media out of the tree; untrack .coverage, __pycache__, egg-info, docs/ and invoice artifacts
71 lines
2.1 KiB
Python
71 lines
2.1 KiB
Python
"""Abstract model mixins for tenant-owned and global+override resources.
|
|
|
|
Using a mixin keeps the ``tenant`` field and managers consistent across every
|
|
app without repeating them. Concrete models still declare their own ``db_table``
|
|
and constraints. The mixin is the sanctioned way for another app to depend on
|
|
``infrasynth.tenancy`` (see the dependency graph in ``PLAN.md`` §3).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from django.db import models
|
|
|
|
from .context import get_current_tenant
|
|
from .managers import AllObjectsManager, GlobalOrTenantManager, TenantManager
|
|
|
|
__all__ = ["TenantOwnedModel", "GlobalOrTenantModel"]
|
|
|
|
|
|
class TenantOwnedModel(models.Model):
|
|
"""Base for every tenant-owned model (``TENANCY.md`` §4).
|
|
|
|
Provides a non-null ``tenant`` FK, the fail-closed default ``TenantManager``,
|
|
and the unscoped ``all_objects`` escape hatch. On save, an unset tenant is
|
|
filled from the bound context so writes inside a request/task land in the
|
|
right tenant; a write with neither is rejected by the database.
|
|
"""
|
|
|
|
tenant = models.ForeignKey(
|
|
"tenancy.Tenant",
|
|
on_delete=models.CASCADE,
|
|
related_name="+",
|
|
editable=False,
|
|
)
|
|
|
|
objects = TenantManager()
|
|
all_objects = AllObjectsManager()
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
def save(self, *args: Any, **kwargs: Any) -> None:
|
|
if self.tenant_id is None:
|
|
tenant = get_current_tenant()
|
|
if tenant is not None:
|
|
self.tenant = tenant
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
class GlobalOrTenantModel(models.Model):
|
|
"""Base for resources that exist globally and can be overridden per tenant.
|
|
|
|
``tenant IS NULL`` is the platform default; a non-null tenant is that
|
|
tenant's override. The default manager only ever exposes the global rows
|
|
plus the current tenant's rows, never another tenant's.
|
|
"""
|
|
|
|
tenant = models.ForeignKey(
|
|
"tenancy.Tenant",
|
|
on_delete=models.CASCADE,
|
|
null=True,
|
|
blank=True,
|
|
related_name="+",
|
|
)
|
|
|
|
objects = GlobalOrTenantManager()
|
|
all_objects = AllObjectsManager()
|
|
|
|
class Meta:
|
|
abstract = True
|