Need Role-Based Editing0:00
In the previous episode, we implemented a policy for updating tickets, and we only allow users to edit their own tickets, they can't edit someone else's. But of course, there are times when we want a user to be able to edit someone else's ticket. Like, for example, if that user is a manager. That makes perfect sense, managers should be able to edit, well, whatever they want to edit, or at least in this system, I think that they should. But managers really aren't the only users that should have that capability. Like, for example, if we had an admin. A manager isn't necessarily an admin, so their permissions aren't going to align for everything,
Like, for example, if we had an Admin. A Manager isn't necessarily an Admin, so their permissions aren't going to align for everything, but for updating a Ticket, yeah, that would probably be something that they would have in common. And if there were any other kind of User that had special permissions for updating Tickets, then we would need to check for that as well. And of course, this is unsustainable, because as more and more types of Users are added, we have to add those checks. So of course, that's where the roles come into play. So that if a User is in a Role that is ticket update, then yes, they can update that Ticket.
Using Sanctum Token Abilities1:05
So of course, that's where the roles come into play. So that if a User is in a role that is ticket update, then yes, they can update that ticket. But roles have their own problems. Like, for example, we would have to implement a role system, or we would need to use a package that, well, sets one up for us. But in our case, we are using Sanctum Tokens, and Sanctum Tokens have a permission system built in. It's the abilities that we can assign to a token whenever we create that token. So inside of our AuthController, whenever we create a token, when a User signs in, we are assigning abilities.
So inside of our AuthController, whenever we create a token, when a User signs in, we are assigning abilities. Now, as it is right now, they have complete and total control over everything, because, well, that's the ability that we assigned. But let's say that a manager signed in. We could say that they would have a ticketCreate ability, a ticketUpdate ability, and things like that. And if just a normal User signed in, they would be able to create tickets, but they might have a slightly different ability, so that they can only update their own tickets. So of course, the abilities that we assign here are going to be different based upon
might have a slightly different ability, so that they can only update their own tickets. So of course, the abilities that we assign here are going to be different based upon the User that signs in. And one of the really nice things about this idea of token abilities is that we don't have to limit it just to our purposes. If for whatever reason, we wanted to allow users to create tokens with certain permissions, we can do that. If you create a new Laravel application using Jetstream, and you opt into the API stuff, out of the box, users have the ability to create new tokens and assign permissions. So this concept of token abilities is very flexible.
Implement Abilities Class2:41
out of the box, users have the ability to create new tokens and assign permissions. So this concept of token abilities is very flexible. We can use it for just about any kind of purpose that we might need. The only problem in our case is that we will have different abilities for different types of users. So whenever a User signs in, and we create that token, and we assign the abilities, what I want is something like this, where we would have an Abilities class, where we would call getAbilities. We could pass in the User, and then that would return the abilities for that particular user. So let's implement that.
We could pass in the User, and then that would return the abilities for that particular User. So let's implement that. Inside of the app folder, let's create a permissions folder. And I'm going to version this, although when it comes to abilities, I'm not sure that we need to version it, but I can see where we might want to. So I'm going to version this, and I'm just going to call it abilities.php. And the name of the case is AppPermissionsV1. And we're going to make this a final class, because I don't think we want to use this as a base class for anything. And here, we will define several string constants, because whenever we need to check for an ability,
as a base class for anything. And here, we will define several string constants, because whenever we need to check for an ability, we would have to check a string. And we don't want to keep typing the same string over and over again throughout our application, because we're human, we are going to make a mistake. So we want some kind of constant value that we can use to take all of, not necessarily the guesswork, but definitely the human error out of working with strings. So this abilities class is going to have a lot of constants. So in this case, we're going to have a constant called CreateTicket, and its value is going to be the ability name, TicketCreate.
So in this case, we're going to have a constant called CreateTicket, and its value is going to be the ability name, TicketCreate. That is going to be the ability. So then we would need an UpdateTicket, a ReplaceTicket, and a DeleteTicket. And of course, we need to update those string values. But these are very generic. The idea of TicketUpdate means that you can update any ticket that you need. So what we need to do then is have something that just normal users would have, like UpdateOwnTicket. And DeleteOwnTicket, so that the ability would actually be something like this, TicketOwnUpdate. But then, you know, managers would need to have the ability to manage user accounts.
And DeleteOwnTicket, so that the ability would actually be something like this, TicketOwnUpdate. But then, you know, managers would need to have the ability to manage user accounts. So we could do something like this, to where we would have, once again, Create, Update, and Replace, but it would be for the users. And then we would change the string values to be UserCreate, UserUpdate, Replace, and Delete. So then we would have our static method called GetAbilities. We would get the user supplied to us, because we would need to see if the user is a manager. And we don't have this flag yet, so we're going to need to add this to our model. But if the user is a manager, then we can return all of the abilities that they would
And we don't have this flag yet, so we're going to need to add this to our model. But if the User is a manager, then we can return all of the abilities that they would need, such as CreateTicket, Update, and all of that stuff. They would also need to be able to do the same thing for the user resources. But if the User is not a manager, then for right now, we could just have an else statement. As we add other classes of users, then we can, of course, make this whatever we need it to be. So for just normal users, they should be able to Create a ticket, they should be able to UpdateOwnTicket, and DeleteOwnTicket. I don't think that they need to do anything else.
Update Policy With Abilities6:21
UpdateOwnTicket, and DeleteOwnTicket. I don't think that they need to do anything else. So that means, inside of our ticket policy, what we can do then is first of all check if the user has a token that can, and we will use our Abilities class to UpdateTicket. Then we would return true. But then we also need to check if the user's token can UpdateTheirOwnTicket, then we return a statement that we had before, otherwise we return false. Now of course, we need to update our database. So let's do this. Let's go to our UserModel, and you know what, we don't need to do anything there.
Add Manager Flag and Seed6:57
So let's do this. Let's go to our User model, and you know what, we don't need to do anything there. We do, however, need to go to our UserMigration. So let's open up CreateUsersTable. Let's add a Boolean field called isManager, and let's give this a default of false. But we also need a manager user. So let's go to our database seeder. We'll leave everything else as it is. But now let's create a manager user. So the email is arbitrary.
But now let's create a Manager User. So the email is arbitrary. We can use whatever we want. I'm going to use manager@manager.com. The password we will encrypt using bcrypt, the name I'm going to set as just the manager. And then we will set the isManager to true. And that should be good enough. So now let's just wipe out the database and start completely over. So we'll use php artisan db:wipe.
So now let's just wipe out the database and start completely over. So we'll use php artisan db:wipe. That will wipe everything out so that then we can run the migration. And then we can seed the database. And that should get us all set up as far as our database is concerned. Let's go to our AuthController. We do need a use statement for this Abilities class. So let's bring that in. Everything else should be okay now. So we just need to make sure that this works.
Test User vs Manager Access8:20
Everything else should be okay now. So we just need to make sure that this works. But now that we have a different set of users, we need to look. So let's grab this user with an ID of 1. Let's grab that email address. Let's go to Postman. And we want to sign in with that user information. So the email address is whatever we copied from the database. Password is, of course, password. So we will send that request.
Password is, of course, password. So we will send that request. We got our token. So let's copy that. Let's go to our bearer variable, paste that in, save it. So now we should be able to get the tickets for that User. So let's do that. Let's get the tickets for the User number 1. And we can see that the ticket with an ID of 2 is one that we should be able to edit. So let's just change the status.
And we can see that the ticket with an ID of 2 is one that we should be able to edit. So let's just change the status. It is set to active right now. So let's change that to completed. Let's go to the patch ticket. That was the ticket with an ID of 2. The status, we will set to C for completed. We don't want to include anything else. We want just that status. So whenever we send that, we see, yes, indeed, that was successfully updated.
We want just that status. So whenever we send that, we see, yes, indeed, that was successfully updated. So let's try to edit the ticket with an ID of 1. We should not be able to. So I'm not even going to worry to see what the status was, because that is exactly what we get. You are not authorized to update that resource. So that works just fine. But let's sign out. And let's sign in as our manager.
But let's sign out. And let's sign in as our manager. So we will change email, once again, to manager@manager.com. We will send that request. We will get a new token. So let's copy that. We want to put that inside of our bearer variable. And so now we should be able to edit that ticket. I guess we should be sure to see what that value is to begin with. So the ticket with an ID of 1 is active as well.
I guess we should be sure to see what that value is to begin with. So the ticket with an ID of 1 is active as well. So let's say that that is going to be canceled. So let's change that to an X. Let's send that. We should, yep, that worked just fine. So our managers can update all of the tickets that they need. The users can only update their own tickets. So with our update policy finished, we can just use this as a basis for everything else.
So with our update policy finished, we can just use this as a basis for everything else. And for example, for deleting, all we need to do is check if the token has the ability to delete the ticket or delete own ticket. All the other logic is going to be the same. And the replace is going to be a lot simpler because the typical user does not have the ability to replace the ticket. So this will be a little bit different in that we will check if the token has the replace ticket ability. And if so, then we return true, otherwise we return false.
has the replace ticket ability. And if so, then we return true, otherwise we return false. And really creating is going to be essentially the same thing. Let's call this store just because we're keeping it in line with the convention of being the name of the method on the controller. So we have delete, replace, store goes next. The ability is create ticket, and that should be fine. So we should be able to go to the TicketController and just apply these policies. So let's see, for the store method, we are finding that user. I guess I took out the code to actually create that ticket.
So let's see, for the store method, we are finding that user. I guess I took out the code to actually create that ticket. We need to add that back in. But for right now, what I'm concerned with is to use the isAble. Is that what we called it? Let's scroll down to the, it's this. Okay, so yeah, so we will check to see if the user can store this. We will pass in null here because we don't have a ticket. But then let's add the toDoCreateTicket there. Let's scroll on down to the replace.
But then let's add the toDo create Ticket there. Let's scroll on down to the replace. So we fetch the ticket. Before we update that, we need to check our policy for replace. And then essentially the same thing for delete. So once again, check our policy. But we are checking the delete policy, and we should be good to go. And one of the really nice things about using token abilities is that we can be as granular as we need, for example, for updating a ticket. Managers should be able to update everything about a ticket, but
granular as we need, for example, for updating a Ticket. Managers should be able to update everything about a Ticket, but a typical user should only be able to update certain fields. Because otherwise, they would be able to replace everything, and we don't want to provide that functionality to them. So in the next episode, we will look at how we can do that. So in the next episode, we will look at how we can do that.
