The "Pit of Success" for Object-level Permissions in Django: How to make it easy to get privacy right.
Madelaine Boyd (~madelaine) |
Goals: Attendees leave feeling comfortable if they need to implement object-level privacy in a django app, and have an idea of one could build a similar system in another web app framework.
INTRO [5 minutes]
The title comes from the expression, "to fall into the pit of success," that is, to have the easiest default be the correct course.
Django allows subject-verb permissions, like “Angelica has the ‘eat cake’ permission”, but you can’t say “Angelica has the permission to eat Bob’s cake but not Charlie’s”.
django-rules: fast, in-memory, object-level permissions framework. You define predicates, and then explicitly check them as needed.
What if you forget to check a predicate, or mess up the check? Privacy leak. Not good.
Can we "bake in" these permission checks into the django model managers? Yes!
Getting the "Requester" [3 minutes]
Store the active "requester" (request.user in most cases) as a thread-local variable (in scope for this request, but not other requests)
Checking Permissions: [8 minutes]
Override the following methods:
a. Manager.get_queryset, so we can filter on the returned list from
b. Model.save(), so we can enforce permission checks before creating objects or saving changes to fields
d. QuerySet._fetch_all() - this fetches objects from the database. Our override looks at the returned list and filters out any objects which the requester doesn’t have permission to see.
Raise PermissionDenied if the current requester doesn’t have permission to take a particular action.
What about per-field privacy? (for example, a person's profile photo or username might be publicly available, but not their email address).
- Override the field class
- Implement its
Relations: one-to-many, many-to-many [5 minutes]
For related fields (one-to-one, foreign keys, many to many), implement
contribute_to_related_class. Make sure default_manager is your overridden manager subclass and base_manager is the original django manager class.
Performance [4 minutes]
You know what’s slow? Making DB calls in predicates. Don’t do this! We built a hook to disable DB calls in predicates, and throw an error whenever a database call is made in a predicate, in order to track them down. Advice: Prefetch objects before passing them into perm checks. Another solution: override permission checks after you’ve checked what you’ve needed
Conclusion [4 minutes]
I'm open sourcing this, help me make it better please.
Some django experience, have worked with Models + managers at least a little bit.
Madelaine is at bit.io building an immediately productive, shareable cloud database. Her experience includes building software to tune, fit, and program hearing aids, building a team to improve the end-to-end containerization, deployment, and orchestration of Facebook’s private cloud, mobile infrastructure, compilers, iOS, and front-end product work.
First Impressions: Building Contextually Aware Tutorials https://youtu.be/OiY1cheLpmI?t=156 Mobile, iOS, objective-C, UX
Holding it in @Scale - the systemd-tails on containers, composable services, and runtime fun times. https://www.facebook.com/watch/?v=2132426893697070 Containers, distributed systems