1

I want to implement kind of row level security for my model in Django. I want to filter out data as low as it's possible based on some requirement.

Now, I know I could create specified managers for this model as docs says but it seems for me that it needs to be static. I also know that I can create just method that will return queryset as I want but I'll be not sufficient, I mean the possibility to just get all data is still there and the simplest mistake can lead to leak of them.

So I found this post but as author said - it's not safe nor pretty to mess around with global states. This post is nearly 10 years old so I hope that maybe someone has come up with a better generic solution.

Here is piece of example to visualise what I need:

models.py:

class A(models.Model): 
    ...

class B(models.Model): 
    user = models.ForeignKey(User) 
    a = models.ForeignKey(A)

And I want to create global functionality for getting objects of A only if instance of B with user as logged in user exists.

I came up with solution to just override get_queryset() of A manager like so:

managers.py

class AManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset(b__user=**and_here_i_need_a_user**)

but I can't find hot to parametrize it.

==== EDIT ====

Another idea is to simply not allow to get querysets of A explicitly but only via related field from B but I can't find any reference how to accomplish that. Has anyone done something like that?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
kebie
  • 485
  • 5
  • 16
  • This is not a thing that you can - or should want to - do. There are plenty of circumstances in which there is no "logged in user". – Daniel Roseman Feb 20 '19 at 16:59
  • Sure but with generic solution I could very easly handle that exception. – kebie Feb 20 '19 at 17:01
  • But that's my point. There is simply no such thing as a generic solution. It's a fundamental separation of concern that a Manager doesn't know anything about requests. – Daniel Roseman Feb 20 '19 at 17:02
  • Ok, so is there any other way to implement that? I mean rls is pretty widely used, there should be possibility to do that at lower level than middleware – kebie Feb 20 '19 at 17:06

1 Answers1

0

So you're sort of on the right track. How about something like this...

class AQuerySet(models.QuerySet):
    def filter_by_user(self, user, *args, **kwargs):
        user_filter = Q(b__user=user)
        return self.filter(user_filter, *args, **kwargs)

class AManager(models.Manager):
    queryset_class = AQuerySet

    def filter_by_user(self, user, *args, **kwargs):
        return self.get_queryset().filter_by_user(user, *args, **kwargs)

class A(models.Model):
    objects = AManager()
    # ...

then you can use it like this:

A.objects.filter_by_user(get_current_user(), some_filter='some_value')
Julian
  • 1,078
  • 5
  • 17