Skip to content
All articles
/1 min readDjangoArchitectureSecurity

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:

views.py
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:

models.py
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.

ESC
↑↓ navigate↵ openBarclayXP