"""租户授权权限类。 X-Tenant-Id 只选择请求上下文;已认证用户必须拥有该租户的有效 membership。 公开端点显式使用 AllowAny,因此不会被此默认权限类改变。 """ from django.conf import settings from rest_framework.permissions import BasePermission class TenantMembershipPermission(BasePermission): """Require an active user membership for the requested tenant.""" message = "当前用户无权访问该租户" code = "tenant_membership_required" def has_permission(self, request, view): user = getattr(request, "user", None) if not user or not user.is_authenticated: return False # API keys are already cryptographically bound to their tenant. Keep # their existing header-optional contract, but reject an explicit # attempt to select a different tenant. auth = getattr(request, "auth", None) if auth is not None and auth.__class__.__name__ == "APIKey": key_tenant = getattr(auth, "tenant", None) requested = request.META.get("HTTP_X_TENANT_ID") return key_tenant is not None and ( not requested or requested == key_tenant.code ) # Superusers keep the historical bootstrap contract: they are operators # for every seeded tenant (seed_initial_data + the 0002 backfill). if getattr(user, "is_superuser", False): return True tenant_code = request.META.get( "HTTP_X_TENANT_ID", getattr(settings, "TENANT_DEFAULT", "default") ) if not tenant_code: return False from .models import Tenant, TenantMembership # An unknown (or inactive) tenant is a *parameter* error, not an # authorization failure. Answering it here would mask it as 403; the # view layer returns 400「无法识别租户」 instead. if not Tenant.objects.filter(code=tenant_code, is_active=True).exists(): return True return TenantMembership.objects.filter( user=user, tenant__code=tenant_code, tenant__is_active=True, is_active=True, ).exists()