Put the tenant check in the data layer, not the view
In Brevia, every law firm must only ever see its own cases. Here's why I enforce that in the queryset instead of trusting each view to remember.
Brevia is a practice platform for Nigerian law firms. Firms upload hearing recordings, and the app turns them into searchable transcripts. The data is about as sensitive as data gets: client names, testimony, strategy.
So there’s one rule that can’t bend. A user only ever sees their own firm’s data.
The fragile version
The obvious way to do it is to filter in every view:
class MatterListView(ListAPIView): serializer_class = MatterSerializer
def get_queryset(self): return Matter.objects.filter(firm=self.request.user.firm)That works. Then someone adds a new endpoint at 1am, forgets the .filter(), and one firm can read another firm’s transcripts. Nothing fails loudly. The tests pass because the test database only has one firm in it.
The rule is only as strong as the most tired developer on the team.
Make the safe path the default
I moved the check down into a manager, so the unscoped query is the one you have to go out of your way to write:
class FirmScopedQuerySet(models.QuerySet): def for_user(self, user): # no firm, no data. fail closed if not user.is_authenticated or user.firm_id is None: return self.none() return self.filter(firm_id=user.firm_id)
class Matter(models.Model): firm = models.ForeignKey(Firm, on_delete=models.CASCADE) title = models.CharField(max_length=255) objects = FirmScopedQuerySet.as_manager()Every view now calls Matter.objects.for_user(request.user). Code review gets simpler too: any bare Matter.objects.all() in a view is a smell you can grep for.
Test it with two firms
The bug hides when the test database has one tenant. Every access test in Brevia seeds two firms and asserts that firm A gets a 404, not a 403, when it asks for firm B’s matter. A 403 tells an attacker the ID exists.
It’s a small amount of setup. It also turned “we’re careful about isolation” into something I can prove.
Written by Barclay Ibejiako
Say hello if this was useful.