dfunckt / django-rules

Awesome Django authorization, without the database
MIT License
1.86k stars 149 forks source link

How do you map predicates to objects and/or users? #173

Open realnot opened 1 year ago

realnot commented 1 year ago

I'm trying to build this permission mapping

Users Permissions Object
Bob Can edit book Lord of the rings
Bob Can edit book Harry Potter
Bob Can read book Lord of the rings
Bob Can read book Harry Potter
Tim Can edit book Lord of the rings
Tim Can edit book Harry Potter
Tim Can read book Lord of the rings
Tim Can read book Harry Potter

But from the documentation I don't see how "Bob and Tim can edit Lord of the rings". Bob is not in any group and is not the owner of the book. Basically what is missing is a table to map Users, Permissions, Objects.

Predicates can be created from any callable that accepts anything from zero to two positional arguments

All examples show a predicate with user and object as arguments, but not a "permission". How do you test against above matrix? One way would be create a table like:

Users Permissions Content Type Object ID
1 (Bob) 1 (Can edit book) 1 (books) 1 (Lord of the Rings)
1 (Bob) 1 (Can edit book) 1 (books) 2 (Harry Potter)
1 (Bob) 2 (Can read book) 1 (books) 1 (Lord of the Rings)
1 (Bob) 2 (Can read book) 1 (books) 2 (Harry Potter)
1 (Bob) 3 (Can drive cars) 2 (cars) 1 (Ferrari SF90)
1 (Bob) 3 (Can drive cars) 2 (cars) 2 (Fiat 500)
1 (Bob) 4 (Can fix cars) 2 (cars) 1 (Ferrari)
1 (Bob) 4 (Can fix cars) 2 (cars) 2 (Fiat 500)
2 (Tim) 1 (Can edit book) 1 (books) 1 (Lord of the Rings)
2 (Tim) 1 (Can edit book) 1 (books) 2 (Harry Potter)
2 (Tim) 2 (Can read book) 1 (books) 1 (Lord of the Rings)
2 (Tim) 2 (Can read book) 1 (books) 2 (Harry Potter)
2 (Tim) 3 (Can drive cars) 2 (cars) 1 (Ferrari SF90)
2 (Tim) 3 (Can drive cars) 2 (cars) 2 (Fiat 500)
2 (Tim) 4 (Can fix cars) 2 (cars) 1 (Ferrari)
2 (Tim) 4 (Can fix cars) 2 (cars) 2 (Fiat 500)

And from the view you have:

1) required permissions 2) user from request 3) current object_id (from get_object) which take you to step 4 4) get content_type with object_id

Now you have to test that all permissions you retrieve from the table are in required permission defined by view. Is the right approach or there's another way?

suspiciousRaccoon commented 6 months ago

I would probably change it so you have all 4 CRUD methods on the same row with a BooleanField for each:

class BookPermission(RulesModel):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    book = models.ForeignKey(Book, on_delete=models.CASCADE)

    can_add = models.BooleanField(default=False)
    can_view = models.BooleanField(default=False)
    can_change = models.BooleanField(default=False)
    can_delete = models.BooleanField(default=False)

It's unrelated to django-rules but I would also recommend avoiding object_id + content_type. You can do this in django with GenericForeignKeys but you should leave them as a last resort. This article talks about the alternatives,