Hello.
I'm trying to implement row-based security checks for Django models. The idea is that when I access model manager I specify some additional info which is used in database queries so that only allowed instances are fetched from database.
For example, we can have two models: Users and, say, Items. Each Item belongs to some User and User may be connected to many Items. And let there be some restrictions, according to which a user may see or may not see Items of another User. I want to separate this restrictions from other query elements and write something like:
items = Item.scoped.forceRule('user1').all() # all items visible for 'user1'
or
# show all items of 'user2' visible by 'user1'
items = Item.scoped.forceRule('user1').filter(author__username__exact = 'user2')
To acheive this I made following:
class SecurityManager(models.Manager):
def forceRule(self, onBehalf) :
modelSecurityScope = getattr(self.model, 'securityScope', None)
if modelSecurityScope :
return super(SecurityManager, self).get_query_set().filter(self.model.securityScope(onBehalf))
else :
return super(SecurityManager, self).get_query_set()
def get_query_set(self) :
#
# I need to know that 'onBehalf' parameter here
#
return super(SecurityManager, self).get_query_set()
class User(models.Model) :
username = models.CharField(max_length=32, unique=True)
class Item(models.Model) :
author = models.ForeignKey(User)
private = models.BooleanField()
name = models.CharField(max_length=32)
scoped = SecurityManager()
@staticmethod
def securityScope(onBehalf) :
return Q(author__username__exact = onBehalf) | Q(bookmark__private__exact = False)
For shown examples it works fine, but dies on following:
items = Item.scoped.forceRule('user1').filter(author__username__exact = 'user2') # (*)
items2 = items[0].author.item_set.all() # (**)
Certainly, items2
is populated by all items of 'user2', not only those which conform the rule. That is because when all() is executed SecurityManager.get_query_set()
has no information about the restriction set. Though it could. For example, in forceRule() I could add a field for every instance and then, if I could access that field from manager, apply the rule needed.
So, the question is - is there any way to pass an argument provided to forceRule()
in statement (*)
to manager, called in statement (**)
.
Or another question - am I doing strange things that I shouldn't do at all?
Thank you.