Lesson Description

The "Applying ABAC Exercise" Lesson is part of the full, Permission Systems that Scale course featured in this preview video. Here's what you'd learn in this lesson:

Kyle instructs students to implement the remaining author and viewer permissions, then walks through updating all the services to use the new ABAC permission checks, replacing the old helper functions with cleaner "permissions.can" calls that automatically handle user context and resource-level conditions.

Preview

Transcript from the "Applying ABAC Exercise" Lesson

[00:00:00]
>> Kyle Cook: So now that we've got all that set up, it looks like we have one error. I just want to check, of course, just get rid of this placeholder code I was using. Now I just want to show you a use case of using this, and then I'm going to have you actually go through and try to implement some of this yourself. So let's just go into a random location where we have a permission. Let's go into our services because that's kind of our biggest location.

[00:00:18]
So we'll go into our services here. And what I want to do, we'll just go to the very, very first function, which is our function for creating a document. And of course we can't use our can function anymore, so instead we're going to be using our permissions. So here, instead of getting all of our user information, I want to get our permissions so we can say constant permissions equals await get user permissions just like that.

[00:00:41]
Again, we don't need to pass anything in because it gets the user automatically for us, and now we can use the can function built into our permissions. You can see here we have that can function. We don't need to pass it to user anymore because we already have that being passed in by default. All we need to do is tell it what resource we want to check, which in our case is the document. We tell it the action we want to check, which in our case is a create action.

[00:01:02]
And that's all that we pretty much need to do for this entire section to be able to check that particular permission for our document to create a user or to create it for our user to create a document. And essentially we'll update all of our permissions to look just like this. A more complicated permission may be, for example, our update permission. So here, let's update this one as well. We're going to paste in this, and here we have our permissions which we need to get so we can say constant permissions is equal to await get user permissions.

[00:01:29]
There we go. And now I want to check the update policy, but I specifically want to check, can they update the document that already exists in our database so I can pass in our document object, and that gives us all that information. And you'll see if we get autocomplete. It only autocompletes for the four fields that we actually care about that we defined inside of our permission section. So if I scroll down, the exact keys that we defined in here, these are the ones that we actually are needing to pass along every single time.

[00:01:55]
And since when we get a document, it already has all those keys, you can see it's working just fine. So what I want you to do is if we come back over here, you'll notice that we have this checkpoint branch, 5.5-Basic-ABAC Checkpoint. Go ahead and check out that code, and what I want you to try to do is to first implement the remaining two permissions for our author and our viewer, and then if you finished that and still have time, go ahead and try to implement some of the permissions throughout the application, kind of like we've done here.

[00:02:29]
Awesome, so hopefully you guys had a chance to go through and implement some of those. I think the first thing that we want to do is just go back to our attribute-based access control, and I want to implement the author and viewer permissions next, just so we can be done with the permission side of things, and then we can start implementing those permissions inside of our code. So let's come in here, we're going to add our author permissions.

[00:02:50]
Pass it in our builder and our user itself, and then we can create a function for that. I'll copy down the editor one because we're going to have a lot of similarities between these two, so there's only a few additional permissions the author has. Obviously they can read the same exact projects as the editor, and when it comes to reading documents, this again is going to look relatively similar, same with update, but we have a few more restrictions on the author's ability to read documents.

[00:03:13]
So they can read documents that are in the correct project, that is assumed that they have to be, but also we must have the correct status, so we can come in here and say that the status must be published, published, or the status must be archived. There we go. So they can't read draft documents essentially by default, but they can read draft documents if they also are the owner of that document. So let's come in here with our draft, and we want to specify that they must be the creator.

[00:03:41]
Is equal to their user ID. Now let's make sure we put the ID up here inside that pick, so we get all that. So now we essentially have three different conditions for reading when it comes to a user that's an author because they can read published or archived documents, and they can read draft documents if they were the ones themselves that created it. Now for update, again this is a little bit more restrictive because they can only update things that have a status of draft, and that they are the creator of, so we can say the creator ID is equal to their ID.

[00:04:09]
Now we can finally move on to the last permission that we need to worry about for them, which is the allow permission for creating, so we can make sure we get that typed in there. There we go. And again, they can only create something that is inside the correct project, so project ID, and that's the only thing we really care about when it comes to creation, they can create whatever else that they want inside of here.

[00:04:34]
Now let's copy those editor permissions one more time so we can write down our viewer permissions, so we're going to say add viewer permissions just like that, and our viewer permissions are going to be relatively straightforward, they can read the exact same projects as everyone else, and when it comes to everything inside here, they can't update or anything, all they can do is read, and they can only read documents that are published, so we can say published, or they can read documents that are archived, so we can change this to archived.

[00:05:02]
So now they have the ability to read only projects that they are allowed to based on their department and they can only read documents that are in those projects and are also published or archived. Now with that done, let's just make sure we implement that up here, add viewer permissions. Make sure we put our break in here because of course I forgot to put a break on each one of these. There we go.

[00:05:26]
Builder, user, and there we go, that's all of our permissions completely handled inside this section. Now let's move back over into our services and finish out handling these services, because there's actually one thing I didn't do properly with my create document service. You notice here we're just checking, can they create documents in general, but inside of our access control, if we look at our author section specifically, you'll notice that we have a condition applied to create.

[00:05:49]
They can only create if the project is the correct project. We obviously don't want them to create a document for or create a document in a project they don't have access to. So inside of here, what we can do is we can actually move this permission below where we create what our new document will look like. So I'm going to take all of this code right here. I'm going to bring it out into its own object.

[00:06:07]
So we'll just come up here and we'll just say const new document is equal to all of that code. This is what creates our new document, and of course we need to make sure we validate our data before we do that, so let's come all the way up here just like that. So this is going to be what our new document data looks like, and now that we know what our new document is, we can pass that to our create function here and check do they have permission to create this, and it's going to check things like the project ID of the actual document.

[00:06:32]
Because if we didn't do this, they could actually access our API, send it directly to a create for a project they don't have access to, and before it would just allow them to do that, but now we're restricting that, they can only create documents in the correct projects. Then down in our create document, we can just pass along that new document data and everything else will work flawlessly inside this system.

[00:06:51]
So we're done with that create document. Let's move on to update document, which we already did right before the break, so we can close down on that one, and now we can move to our delete document service, which just like all of our other ones, all we need to do is get our permissions and then check to make sure it's correct. So I'll just copy all that code up. And inside of here, we'll just do that permission check.

[00:07:09]
So this one is going to be a delete, and to build a check if they can delete a document, we first need to get the document they're going to delete. So we can just say const document is equal to get document by ID. Make sure that we await that. Pass it in the document ID. And then if it's null for some reason, well, then we can just throw some type of error, not found, doesn't really matter what it is, some type of error, however you want to handle that is entirely up to you, but now we're making sure that they can delete this document, so for example, if there's project restrictions or other things, those would all be handled specifically by this function.

[00:07:47]
So that's all of our mutations handled. Now we can move on to the permissions for reading our data as well, which is going to be relatively simple again, kind of like what we had before. Let me copy our permissions up and we can use them directly inside of here. And this is where attribute-based access control really excels over top of role-based access control because we no longer need these helper functions because all of those are handled in the conditions that we applied inside of attribute-based access control.

[00:08:15]
So here we can just say permissions.can pass it in the document because that's what we're specifically checking for. We're checking read and we're checking it specifically for this document, and we don't even need this user object anymore because again that's handled by our get user permissions. Let's go ahead and do the exact same thing down here for our get document with user info. We can essentially take the exact same section we have here and I can just replace it with our permission for reading an individual document, exactly the same code because it was the same between these two functions.

[00:08:45]
Now in this function, we can do the exact same thing for our permissions, so I'll copy them up into here. But we don't have an individual document to check in this particular use case, so we'll remove this, and what this code essentially says is, can a user read at least one document? Doesn't matter what the restrictions are, can they read at least one document? If so, then we're going to return whatever data we want.

[00:09:06]
I think before I was throwing an error here, so I'm just going to throw an error as well. There we go, we have our error being returned to us, and we also look like we still need our user, so I'm going to get our user. There we go, so we can use that in that where clause down here. And if our user is equal to null, then we can just throw some error, so we'll say throw new error. I would just say unauthenticated.

[00:09:36]
There we go. Now one thing that you can do, instead of throwing an error here, you could just return an empty array because they don't have access to any documents, so we'll just return an empty list of documents to them. Either way is going to work perfectly fine. It just depends on what you want for your particular use case. We'll just leave it as an empty array here so we don't have to worry about error handling or anything else like that.

[00:09:55]
So that function entirely taken care of. This function down here is entirely taken care of, and this function up here is taken care of. So we have all of our functions completely taken care of. The only thing we haven't taken care of is this Drizzle query, this database query. We're going to be solving that in the more advanced portion of our attribute-based access control, but for now this is one of the sticking points where our permissions are having to be duplicated between multiple files.

[00:10:17]
Now you'll notice that this documents folder right here is no longer needed. We don't need these helper functions, so I'm actually going to completely delete this file as well as the project version because again, we're not using these files anywhere inside of our code once we swap over to this attribute-based access control. Now let's finish our services by going to the project service because this is going to look very similar to what we did before.

[00:10:40]
So let's go all the way up to create and get rid of these functions we're no longer using. So inside of our create, we need to get our new project, so we can say const new project and we can just set it equal to all of the data coming directly from here, paste that up and down here we can use our new project, so that way we have access to our project data and let's just make sure we validate that data beforehand.

[00:11:08]
That way we don't get all those errors. Now we can check our permissions, so we can say const permissions is equal to await get user permissions. There we go. And then we can do our can check on that. And we want to check, can they do a project create with whatever our new project is going to look like, and there we go. That is all the permissions we need to handle inside this create project service.

[00:11:27]
I'm going to copy this because we're going to use essentially the same thing throughout the rest of our code. Let's get rid of this import that I accidentally added. Now in our update section, again, I can replace this permission right here with this brand new one. This right here is going to be the project we want to update, so let's just come in here, say project is equal to get project by ID. Now we have the project and we want to of course check if the project equals null.

[00:11:59]
Well then we'll just return or throw an error, so we'll say throw new error, not found. Again, you can handle this error differently if you want, but for the simplicity of this code, we'll just throw a simple error. Now that handles all of our permissions for update, if I just change this to say update instead of create. Let's copy this down, do the exact same thing for delete, change this to delete, and we of course need to get our project.

[00:12:23]
So I'll come up here and we'll paste down getting our project as well. Also, all these user checks, that doesn't need to be there anymore. Same thing here, we can completely remove this user check, but the user check in our create is actually still needed because we use this user ID to update the owner ID field, so we'll leave that in there. But the other ones, again, we can remove those extra checks because we're putting them inside of our permission handler already.

[00:12:47]
Now the last few sections that we have to check is our get all project service as well as our individual read. So again, we were originally using the helper function here. We can replace this entirely with that can function. So let's just get that from here. And we'll paste that down and we'll change this to be a read instead. And we're already getting the project and everything else, so that's working just like we expect.

[00:13:10]
And again, this user, we don't even need this object at all anymore because again, get user permissions already gets that for us. Now lastly, we can do the exact same thing up here. So for this particular position, we're not going to pass in a project at all because we're just checking, can they read at least one project, and here if we have some type of error where they can't read anything, we'll just return an empty array, again so we don't have to worry about that extra error handling stuff, so there we're just returning the empty array.

[00:13:34]
Now that handles all of our permissions inside of our service. Again, we still have our permissions duplicated in this one Drizzle query for our database, but again, we'll handle that later when we go to a more advanced version of this attribute-based access control. But when you're on these more simple ones, that's kind of a trade-off that you have to make.

Learn Straight from the Experts Who Shape the Modern Web

  • 250+
    In-depth Courses
  • Industry Leading Experts
  • 24
    Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now