 Cool, hi everyone. My name is Jack and today I'm going to be talking about advanced permissioning in Django. It's the first time I've been using Google Notes, so let me see how this, actually don't even know how this works. Did that work? Bingo. Cool. So, the reason why I'm giving this talk is because I was recently part of a project and in that project I, along with a friend, had to implement a pretty complicated permissioning system that involved object level permissionings, it involved, it was a REST framework project that was using React on the front end, but all the permissioning was done on the back end and I didn't know how to do any of this. I faffed around on it for a while, I screwed it up a couple of times before I got it right. Luckily I learned a lot in the process and it turned into this talk which is really a lot of lessons learned from building a pretty complicated permissioning framework or permissioning system in Django. Django projects are pretty simple in terms of permissioning. You have more or less three types of users. You have anonymous users or logged out users. You have logged in users who have some level of permissioning, some increased access to logged out users and then you have super users who can do everything. And it's just those three, it's pretty straightforward. You don't have any object permissions and you don't have any grouping of users. You just have three user tiers and the way that you would normally approach that is you would just have it on the model itself. You have a super user flag that Django gives you for free, logged in and logged out, Django gives you for free and that's pretty easy. What I was building had more tiers of users than that. Logged out users could do very little, a certain class of users were not super users but were admins and they could edit content for a group of users on the site but not for everyone on the site. They can edit some content but not everyone's content and they can also manage the users that had access to that content. They could create new users and assign them to that content so they had super user type permissions for certain objects but no permissions at all for other objects. And then finally, regular users could view content but only content that was available to the groups that they were a part of. So yeah, that's where I learned doing all of this. It talks about building a permission management system in Django project but hopefully the concepts will be abstract enough that it will help for other projects. The permissioning in Django is based on permissioning in Linux and I think that the kind of rules and principles that I'm going to go through will be useful if you're building in Django, it will be useful if you're building in Flask, it will be useful if you're implementing permissioning in a lot of different user facing projects. And also for what it's worth if you want to butt in with like a question or anything, feel free. I think it's like a small enough group that's cool so just get my attention. I don't consider myself to be any kind of like super expert on this stuff. I just spent a heck of a lot of time working on it last year and also a heck of a lot of, I lost a heck of a lot of sleep thinking about it because it really for a while until I started figuring it out as kind of a nightmare. So yeah, who is this talk for? It's really for three groups, for developers who are considering adding more fine grained permissioning to an existing Django project that right now only has simple permissioning. For developers who already use complicated permissioning or object level permissioning but have run into problems managing that permission state, which is kind of common if you use something like Django Guardian to do object level permissioning. And it's also for people who just want to better understand how permissions in Django work, both out of the box and also using add-on packages. So I guess before we dive in, it's good to define what permissions are. Does anyone have a good definition for permissions or? In Django, permissions can apply to individual users or they can apply to groups of users. And the fact that you have a choice as to where you want to put permissions, whether you want to assign them at an individual user level or at a group level is super useful. It can also be a source of a lot of difficulty and creating a lot of complicated state that you have to mentally manage if you don't do it right. So we'll be digging into that in a bit. It's all about access who can see what on a site and permissions are all about control, who can do what. And the way that Django defines permissions in the docs is really the definition that I like best. They say that permissions are binary flags designating whether or not a user can perform a certain task. So that covers both things that we're concerned about. It covers who can do what and who can see what. Because who can see what is just a task like any other task. The two important parts or at least the two parts that stuck out to me in this definition of permissions is the word binary, which seems obvious if you've done a lot of work with permissions before. But I don't think that a lot of, I know I at the beginning didn't think about permissioning in a binary way, but it helps to. You either can do something or you can't do something and there's no middle state between the two that simplifies a lot. And then the other one is the phrase to perform a certain task. Performing a certain task, this stuck out to me because it is really the mental map that you create for permissioning. It's not just about control and not just about access. Permissions are assigned and evaluated on a per task basis, as opposed to on a per page basis or anything like that. So yeah, one other important thing that this definition sets up is an implicit separation between the permissions and the tasks. And if you're building a complicated permissions management system, one of the most important things that I found is preserving this separation. Django doesn't force you to, Rest Framework doesn't force you to, Guardian doesn't force you to, but it's really, really important to. So my first tip is permissions, they're related to tasks, but they should be kept totally separate from tasks. By keeping them separate, it makes it a lot easier to avoid unexpected outcomes, to extend your permissions management system later and maybe most importantly to write tests around your permissions management system. If your permissions are controlling anything that really is important, that certain people do have access to or don't have access to, having a good test framework around that is equally important. And it's hard to do if you don't separate the permissions from the tasks. So yeah, this separation, it's also true for users and groups. It's kind of similar to the separation between logic and views, where you want to keep your logic out of your views when you can. But that's one that everyone knows and kind of follows. This is one that's a little bit less common to follow, at least in the projects that I've worked on and looked at. So we talked about what permissions are, what's permissioning? It's a system for managing permissions. That's like probably the most obvious of anything that I'm going to stay today. But it's also an important distinction where if permissions are your state, permissioning is your state flow. It's your system for managing the state. If you have a project that's relatively simple and has simple permissioning requirements, managing that state is easy. You can do it in your head. If your project is more complicated, it requires a more complicated system to manage that state. I write a lot of JavaScript as well as Python. It's almost like moving from jQuery to React, where it's hard at the beginning. But once you understand both systems, you realize that even though React is more complicated, it's also a hell of a lot better for managing complicated state. Permissions are kind of like that. If you only have a simple requirements for permissions, you really don't have to worry about it too much, because there's not much state to manage. But once there is a lot of state to manage, it gets important that you're thoughtful about how you build these things. So let's talk about Django permissions and what you get out of the box. Permissions in Django, this is all pretty straightforward. It's tied to and actually a part of the Django authentication system. It makes sense. Your permissions are based on the user. So to have it tied to the authentication system, the user auth system makes a ton of sense. It includes the concept of groups. So permissions in Django, they can be tied to an individual user. They can also be tied to a group of users. So if you have a site that has like a paid members section, rather than assigning the privileges to the people in that section, you can put them all in a group called paid members and assign the permissions to that group and everyone inside the group will have it. If you've worked with Django, you know this, it's pretty straightforward. It's also super useful, because if you want to change permissions for your paid members, you can do it once for the whole group rather than having to run every user through that individually. In a minute, I'll show you why it's also something that you have to be super careful with, because it's not always straightforward what's gonna happen when you manage permissions on users and also permissions on groups. Third thing, it allows permissions to be associated with models and so it's tied to the model system in Django. It doesn't have to be, but it usually is. And you get for free with Django three different types of permissions for each of your models. Yeah? The what's that? Yeah, that's actually one of the things that I'm gonna get at in a minute, because I scratched my head on this for like literally three days before I figured out how to do it. But no, that's 100% true. The three that you get for free are add, change, and delete. It makes implicit that if you are logged in, you can read everything on the site or you don't have access to the model at all. So in this particular setup, read permissions are implicit, but in most complex permissioning systems, especially if you have certain parts of content that you want some people, but not everyone to read, you wanna make it explicit. And towards the end of this, I'll show you how to do that. Yeah. So just to kind of map out what it looks like in a typical project, logged out users, let's say, can do nothing. Super users can do whatever they want. They have add, change, and delete permissions. This is just like a little kind of throwaway project that I'm gonna be using as a sample for some of the code that I'm gonna show today. But you've got a site with users and articles, and users can publish articles, super straightforward. And regular users on the site, when they're logged in, they can add articles, but they can't do anything else. They can't change articles, they can't delete articles. So since the permissions that we're using for this setup come free with Django, our models file is gonna look like just any other models file. You don't have to do anything abnormal to it. And here is just a simple function-based view for editing an article. That's the one that we're looking at right now. Super users are the only users who can edit existing articles. So we check if the user is a super user. If they're not, we raise a 404, and if they are, then we execute the rest of that view logic so that they can edit the article. Does anyone see any problems with this? Yep, that's exactly right. The good news is this will 100% work, and if you are only implementing, if you only have simple permissioning requirements on your site, no big deal, this is just fine. But you just broke rule number one. Permissions, while related to tasks, should be kept separate from tasks. We don't wanna check if the user is or isn't a super user. We want to check if the user has or doesn't have the specific permission that they would need to interact with the view. In this particular case, it's a distinction without a difference, but you could imagine wanting to add a third class, maybe editors to the site, where it all of a sudden wouldn't be a distinction without a difference where you would want to be able to test for that permission explicitly. So, it's pretty easy to keep the permission separate from the tasks here, rather than having the view check whether or not the user is a super user. Instead, you check whether or not the user has permission to change articles. Pretty straightforward. If you look, I'm just gonna go to the Django source code for a minute, and if you look at the source code for the has perm call that we just used here on this last slide, the first thing that it does is it checks if the active user is or isn't a super user, and if the active user is a super user, it's always going to return true. It almost is like it don't check any other permissions. This person's a super user. They can do whatever they want. They have run of the place. And you'll also notice that the has perm function takes an optional object argument. We'll get to that in a minute. It's important when you implement object level permissioning later. Django gives you the foundation for that, even though it doesn't give you any implementation for it. But it hints at how easy it is to implement object level permissioning into an existing project that you have. So yeah, and then this is just the last slide that we're on. This is what we were just looking at a moment ago, the right way to check if a user has the ability to change articles. So let's get a little bit more complex and add that third class of users, the editors. And we want, actually before we add the editors, let's say we want to just change things up and we want users to be able to change the articles that they wrote, but nothing else. So the only thing that's different here, super users can still do everything, run of the mill users can still only add articles, but they can now also edit the articles that they've added. No problem. Let's take the earlier view that checks if the user has the appropriate model permission, but we also run a check to see if the user created the article instance and we let that user do the edit if they did. Let that sit for just a sec. There's one problem here. It breaks rule one. It doesn't separate the permissions logic from the task logic. So if we wanted to do it in a way that doesn't break rule one, we'll add a new file, permissions.py, we'll call it, and we'll define a function in here. You could do this as class based or function based. It's probably, for what it's worth, better to do it class based, but it's a little bit easier, clearer to explain if you do it function based. And yeah, we define the function has perm or is author and it does exactly what you expect it to. It takes a user object, it takes the permission string and optionally an instance and it returns true or false depending on whether or not the user has the permission to change that instance. And yeah, we implement it in the views and we are now good. A couple of quick notes. This pattern of separating out permissions into a separate file, it's kind of a little bit barred from Django REST framework. It's how they do permissions. They do a really pretty good job of separating concerns between permissions and tasks or between permissions and views in the case of Django REST framework. Does anyone here use DRF, by the way? Cool. Like I mentioned a couple of minutes ago, one big difference is that in Django REST framework, the permissions are always defined as classes, which like I said is probably a better way to do it. It's just a little bit less clear to explain it that way. But if you're familiar with doing class based views and using object oriented programming in Python, you would maybe want to go that way. So all the code that we've looked at so far has been around checking permissions, which is important. But you also wanna build a system to manage the assignment of those permissions and the revocation of those permissions. That's part of a permissioning system. So we're gonna take a look at how you would do that by creating a third type of user, the editor. And editors have the ability to do whatever they want to articles, but they can't do anything at all to any other object instance type. And by doing what we already did by separating out the permissions check, we're already most of the way to implementing this. There are two ways to assign and, oh, this is getting back to the users and the group thing. There are two ways to assign and revoke permissions on a user. The first way is the most straightforward of the two. You operate directly on the user object. So here I did it in a file called utils.py. You can do whatever you want. You typically are gonna wanna have two opposite functions, one to assign the permissions and one to revoke the permissions. So you don't get into a weird state where people have permissions that you don't want them to and it's difficult to take them away. You can also make this a single function that takes a Boolean and just a directional Boolean. Granting permissions to users who already have them or removing permissions from users who don't. Doing it, it's item potents are doing it multiple times. It's not going to do behave in a way that you don't expect it to, there's no problem there. Here's a tip, it's worth taking the time to create a screen that shows which users have what permissions. I don't use the Django admin, so I don't know if this comes for free in the Django admin or not, but if it doesn't or if you're not using the Django admin, I spent a lot of time in weird states not quite knowing who had permission to what. It is definitely worth taking the couple hours it takes to be able to see that on a screen who has permissions to what, it makes it a lot easier. And any time you want, and then one thing that I didn't do in this utility file is I didn't put a check that the person who is granting editor status has permission to do that. That's also if you're doing like a complicated permissioning system, you would probably want to make sure that it takes the user object of who's doing it and making sure that they can do it as well. Simple, simple. And yeah, the revoke function is just the opposite. It takes the perms away instead of giving them. The other way to assign permissions is through the Django's groups model. And with this approach, you assign the permissions to a group and then you add the user to that group. Rather than having permissions explicitly, the user doesn't have any permissions explicitly. The user has the permissions implicitly by virtue of the fact that they're a member of a group that has those permissions. I got into a weird state with thinking about groups because in the project that I was working on, you would have groups of users who could edit the user object of other users. So the way that I kind of came to thinking about groups, and it might be obvious if you've, it might be obvious to everyone but me, but it wasn't at the time, is that groups are a group of users and a group has a set of permissions. That set of permissions might be permissions to perform actions on a totally different set of user objects, but it's a group of users that have a set of permissions. I don't know if I did a great job explaining that, but I feel like it's important. Rule two in terms of designing like a permissioning system that has what Django gives you with users and groups is you can assign permissions to users or you can assign permissions to groups, but you probably shouldn't do both. There is nothing in Django that will keep you from doing both. It's just probably a good idea to make the assertion that you're only going to do one or the other because the danger of not doing it that way is you have users that have certain permissions and you know that they have certain permissions and you revoke those permissions and expect them to no longer have access to a certain set of assets, but they still do because they're part of a group that has those permissions and it works the opposite way too where you might remove them from the group that has those permissions, but if they have them explicitly on the user object, they might still have them and it's really a lot easier to manage that state like not as a system, but as a human if you decide to do it one way or the other. Assigning permissions at the group level has some advantages. First of all, the advantages to assigning permissions at the user level is that it's easier and it's more explicit if you only do it that way and someone has a permission, by removing that permission you will always remove that permission 100% of the time. At the group level, if they're part of two groups and there's permission overlap between those two groups, you can remove them from one group and they might still have a permission that you don't want them to have because they're part of another group. So doing it at the user level is more explicit and arguably safer for that reason. Doing it at the group level, the advantages there is you can create groups of users where those permissions overlap. It gets more important when you have overlapping permissions and you have users, different cohorts of users that might have some of the same permissions and you don't want to accidentally revoke some of the permissions that they have. And yeah, the other advantage to operating on groups as opposed to users is it's easier to change what permissions an entire cohort of users have. So if you have a group of users called admins and then you create a new model and you want them to have access to that model, if they're all part of a group, it's really easy. You just give that group access to that model. If they're not all part of a group, you have to figure out who should have access to that model and make sure that each individual does. So it's harder, especially when you don't have a stable project and you're constantly adding stuff to it. And like I said, keep in mind there's no limitation to using both. It's not like a capital R rule, but you probably don't want to use both. So yeah, when you assign permissions directly to users, a simple map of that looks like this. I just created going on our project, we have user one who's the editor in chief and user two who's the editor. And the editor in chief has permissions to permission one and permission two. And the editor has permission two. Pretty easy. We'll have four total functions to manage these permissions. Two, to grant and revoke permissions to an editor in chief. And two, to grant and revoke permissions to normal editors. If we want to add user three, an additional editor, fairly straightforward, just run the function to grant editor perms and we're done. It gets a little trickier going to the example out of set of why it's easier to use groups in some cases. This gets a little bit tricky when you want to add new permission for editors. If we want to add this permission three to editors, we need to change the function that grants it to editors, but we also need to make sure that the two people that currently have those editor permissions run through that function again so that they have it. In this simple example, it's not that big of a deal because you can manage all of this state in your head pretty easily, but in more complicated systems, it can get out of control pretty quick. If you imagine that there were a lot more users, too many to fit on the screen, and there was an additional type of user, like a writer, for example. And writers had permission overlap with editors where writers had permission three, but not permission two. Then there's no way to know if someone's both an editor and a writer, or if that person is only an editor. So if you want to take an editor and turn them, I'm sorry, if you wanted to take a, yeah, exactly. If you wanted to take editor privileges away from someone, if they're also a writer, you would be taking the writer privileges away from that person too. And that's where groups can make things easier. So a group map looks like this. You've got group one, editors in chief, group two, editors. You want to add a new editor, you add them to the group. You want to add a new permission to editors. That's easy too, you just add a new permission to the editors group. And when you want to add that group of writers that only has permission three, that becomes easy as well. And you have no problem with someone who's both in group two and group three. If you take group two away from them, they'll still have permission three because they're still part of group three. So again, there's no right or wrong way to go whether you want to do it directly on the user or whether you want to do it on the group. I personally like doing it on the group in most projects. But if you have very fine grained permissions where you have, let's say, 100 users and of those 100 users, there are like 80 different sets of permissions. Having a separate group for each of those sets of permissions probably doesn't make much sense. If you've got 100 users and there are five or six sets of permissions, having those in groups probably does make a lot of sense. The one big thing that Django permissions doesn't do out of the box is it doesn't let permissions be associated with individual model instances. Why would you want this functionality? If you want certain users, I mean, you spoke to it earlier, if you want certain users to be able to see a set of articles but not all articles, you can't do that out of the box with Django. But while Django doesn't support object permissions out of the box, it does provide the foundation for it. So I didn't copy it, so I have to. It's a little bit small to read. But in the Django docs, it lays out the foundation. Django has a permission framework that's a foundation for object permissions, though there's not any implementation for it at the core, means that checking for object permissions will always return false but that the function is there. An authentication backend will receive the word blah, blah, blah. So yeah, the foundation is there. And out of all of the tools that extend vanilla Django to implement object permissioning, Django Guardian is the one with the most active community and probably the most well-maintained. It's, yeah. It makes use of an additional authentication backend in Django and it's a really good project. The documentation is a little bit sparse in areas, but I love using it. So using our examples from before, let's add object permission to it. So we'll take an example where you have your site of users and articles and you want to add a paywall to it so that some users can, some articles are behind the paywall and other articles aren't. Now, obviously there's a trivial way to implement this that doesn't require object level permissioning where you have users that are either paid users or unpaid users, but we're going to do it with object level permissioning because that's what we're here for. First, we've got to add a new permission to the model and this gets to the view level permission that we added at the bottom of the models file right here. Django out of the box gives you the change permission, the add permission, and the delete permission. It doesn't give you the view permission, but that's what we want. So we add it as a permissions tuple at the bottom of, in the meta of our model. And we'll also create an article view view and in that article view we check, we don't have to change anything, we just check if the user has the perm or not, we cast the instance with it. This is how you assign and revoke object level permissioning to a user. It's a shortcut that Guardian gives you to pull the sign perms where you pass the permission, the user object, and the instance. It works for group objects as well where you pass the permission, the group object, and the instance, and we are then done. We can still use the same has perm call that Django gives us to check whether or not a user has object level perms. We just have to pass the object along with it. So now we know how Django permissions work out of the box, how to extend them using object level permissioning and the two key rules for keeping your permissioning system sane, to keep permissions separate from tasks and to either use permissions directly on users or indirectly on groups, but not both. So we'll take a look at some code using Django REST framework and see how this all fits in. I'm going to start with a class-based view for articles. I'm not going to explain too much how views in Django REST framework work, but basically you just define an object, a query set. You define a serializer, and you define permission classes, a tuple of however many permission classes that you want. And you inherit from different views that REST framework gives you for free. And those views give you the different types of calls. So list create view gives you the ability to post to it or get from it. And if you get from it, you get multiple objects. Retrieve, update, destroy. Let's you operate on a single object, and you can potentially have get perms, change perms, so put patch perms and delete perms. The serializer class is where the serialization and deserialization occur, but the permissions are checked in the view. So how permissions are determined in the REST framework? They're always defined as a list of permission classes. And before you run the main body of each permission, before running the main body of the view, each permission in the entire list is checked. And if any of them come back as permission denied, it will not give you access to the view. So you can string multiple permissions together. And if any of them fail, then you're not going to have access to whatever resource it is that you're trying to get. The permissions that REST framework gives you for free, they're all pretty straightforward. Allow any is authenticated, is admin user. For what it's worth, is admin user. For a while, I thought that that was the same as is super user. It's obvious that it's not, but it actually corresponds with the is staff field in the Django user model, not the is super user field. Is authenticated or read only is the same as is authenticated, but logged out users have view permission. And then Django model permissions is where it gets interesting. Django model permissions is where it starts to use the view permission that we assign to the model, and also the change permission, the delete permission, and the add permission that Django gives you for free. Django object permissions, they check if a user has object level permissions, in addition to checking if a user has model permissions. So you have to pass both, which is another thing that was a head scratcher for me for a while, where I was assigning users object level permissions to stuff, and they couldn't access the things that they were being assigned to, because they need model level permissions and also object level permissions to it. And all of these permission classes inherit from a base permission class that REST framework also provides. And that base permission class has just two methods, has permission, and has object permission. And for base permission, they both return true, but you can extend this or any of the other permissions that REST framework gives you to do custom permissioning. And when you are going after an object, as long as these two things return true, you'll have access. If either one of them returns false, you won't. So back to the permission classes that Django REST framework gives to us, there's one thing that will keep us from using any of these permission classes for our example. We have that additional non-default permission type, the can view. And Django doesn't give us that for free. We added that later. So we need to add a custom permission for our view. This, for what it's worth, is the Django object permissions class in REST framework. And the interesting thing here, if you take a look at the perms map there, you see the add, change, and delete, the three permissions that Django gives you for free. We need to update that perms map so that it also has view perms for get, options, and head. And we're going to do that right there. First of all, we're going to make it so that none of the methods come back as safe methods. By default, get, options, and head are safe methods. And then we're going to require view perms for get, options, and head. And we subclassed from the Django object permissions class that I just had up there. And now it should work. And we use our custom object permissions instead of the REST frameworks object permissions in our two class-based views. And we will have the behavior that we want, where we can assign permissions on an object level and assign them granularly based on the type of request, based on whether it's an add, a get, a change, or delete. So yeah, just to wrap up, awesome. Permissioning can be complicated, but it can also be really simple if you take the time to understand it. It's a lot easier if you put restrictions on yourself by following two simple rules. Rule one, permissions should be kept separate from tasks. Rule two, you can assign permissions to users or to groups, but you probably shouldn't do both. Django, Django Guardian, and Django REST framework give you everything that you need if you're in a Django project. They also provide a really good roadmap for how you should go about building this if you're in a non-Jango project. But what they don't do is they don't force you to follow or even understand the two rules that make all of this a lot easier. So the next steps, if you want to implement some of this into your own project, is first off, start separating your permissioning logic from your task logic. Then decide if you want to implement permissions directly on user objects or indirectly using groups. It's an important decision, and it's not the type of decision that you can easily go back and forth between the two. Use classes for your permissions logic so that they're more reusable and extendable. And finally, implement object-level permissioning if and when you need to. Cool. Any questions? For anyone who wants to ask a question, if you're sitting at the desk, you can use a mic. There's a little silver button. Just be sure to turn it off when you're done. And for anyone against the wall, just speak loudly. But we have about a little over five minutes for questions. Yeah. Is there any reason why you didn't use setting a content type whenever creating a permission? In the Django documentation, it explicitly says content types whenever you're adding a known one. You definitely should do that. I didn't just because it's a little bit more clear that you're passing a permission class. But that content type is just a unique string on the Django permissions model. So in these examples, it was the same. But if you were doing permission system that managed permissions for more than just one model, that's a really good point. You should definitely do that. And I should update the slides to reflect that. I wasn't sure if you were only doing it because you were only using it on a single map or a single model. I was doing it for clarity, but it's the type of thing that I should have mentioned. OK. Thanks. Are those slides available online? Not right now, but they will be five minutes from now when I share them. OK. Fair enough. So I'll just post them on my Twitter, I guess. Yeah. There's no good reason why I have. The reason is because that's how I've always done it. PK is the convention, but I've always explicitly put an ID on all of my models. And I don't know why I haven't stopped doing that. But I just haven't. So any other questions? I remember towards the beginning, you mentioned something about Linux, about this being similar to the permissions on Linux. Like the UMass values and all that. Like I'm not familiar with Django at all. Can you tell me if does Linux have anything to do with this? I guess the command is. Linux has nothing to do with it. This is how I understand it. And I'm no Linux nerd or anything. But the way that I understand it is that it has nothing to do with it other than that it provided the inspiration for how Django should go about githubing groups. All right, thank you. Cool. Last chance. Thanks, everyone.