7.2 Securing Our Application
Not every user in our CMS is an admin, so we need to secure the different parts of our admin system based upon a user's role.
1.Introduction2 lessons, 05:56
2.Managing Posts3 lessons, 38:13
3.Tag Management2 lessons, 27:29
4.Storing Data3 lessons, 47:22
5.Testing Functionality2 lessons, 33:06
6.User Management3 lessons, 1:11:48
7.Security and User Authentication2 lessons, 34:17
8.Enhancing the User Experience2 lessons, 36:38
9.The Front-End2 lessons, 36:01
10.Conclusion1 lesson, 01:22
7.2 Securing Our Application
So let's get started with the user controllers. So let's go to our user controller, because really the only people that should be creating and deleting users are admins, but we also need to allow anyone to be able to edit their own information. At the control level, let's do authorize, just to ensure that everything within this controller requires the user to be logged in. Then we can go to the index. We really don't want anyone to see this except the administrator, so we can use the authorize attribute once again, and then we can set Roles equal to admin. And that's going to work there. So let's just copy that authorize attribute, and we can paste it on all of the other action methods. Unfortunately, I don't think we can set authorize and then Roles equal admin at the controller level, and then specify the editor and author roles on the edit action method. It would be nice if we could do that, but I don't think that that works. So for the edits, we want admin then we want editor, and then we want author. So let's copy that and we will paste it on the other edits method, and then back to delete we will use the authorize attribute and just have of admin as the role. Oh and you know, what we need to dispose of our user repository in the admin controller. So let's copy this code, let's go back to our admin controller, and let's paste this. So after the logout action method, we will paste in the dispose method. We can change this to underscore users. We don't have a role repository, so we can get rid of that. Okay, so, now we are properly disposing of our resources there. Now, we're not done in this controller. Because as it is right now, if you are an admin or an editor or an author, you could edit any user. You just have to simply supply the username as part of the URL. We want to limit this so that if the user is in the editor or the author rolls, that the logged in user has to match the username. And that's easy enough to do. First of all, we need to get the name of the currently logged in user. And so let's create a variable called current user, and to get this information, we use a property on our controller called user. This has a property called identity. And then this has a property called name, and this gives us the name of the currently logged in user. So then we can also check to see if the current user is in the admin role. So let's do an if statement. If not we are going to use to this user property again. This has a method called IsInRole and then we can specify admin. So if the user is not in the admin role, and if the current user is not equal to the user name, then this user does not have the authority to edit this user with this user name. So we can use the string.equals method, and this needs to have a not there, so if not string.equals we will supply the current user, we will also supply the username, and we want to compare these with ignoring the case. So we can use a string comparison and, for some reason, we don't have these system names. Basically, we do now. And, we can use the current culture, ignore case. So, if the user's not authorized to perform this operation, then we can return a new HTTP unauthorized result. And that will take the user back to the login form. And that's really all that we need to do for this method. Now for the other edits method, we need to do something a little bit more. But we can start with the code that we have written here. So let's paste that code in, but let's also create another variable, one that is the result of calling IsInRole for the admin, because we're going to need that information in another part of this method, and the reason why we would do this is because this hits the database every time we call the IsInRole method. It is going to hit the database, to find if the user is in that role. So var is admin equals, and then user is enroll. Then we can use this variable in place of this call to is in role. Now, we don't have the user name here, but we can easily add that as a perimeter because we have that as part of our URL. So we can add a string user name. And then we need to scroll down. Because if we update the user, then we need to change the behavior of this method. Because currently this action method is going to redirect to the index action, it's going to list all or our users. If the currently logged in user is not an admin, then they do not have the authority to view that. So we could send them back to just the index of our admin controller. So inside of this if statement, we can add another if statement and this is where we can use that is admin. If the user is admin, then we will simply redirect to the index action. Otherwise, we will return RedirectToAction and we will specify index, but we will also specify admin. And that should be sufficient for this controller. I don't think we need to do anything for delete, because only admins should be able to do that. Now one other thing. We don't technically need to have this authorize attribute here, because we only have three roles. We have admin, editor, and author. So if we wanted to, we could get rid of that authorize attribute for the edits methods, because at the controller level we say that the user has to be authorized. But for me personally, I like to have this here just so that while I'm reading the code, I can see that okay, only admins, editors and authors have the authority to do something within this edit method. And then if we decide that we add some more roles, and we want to limit that to just these three, then we don't have to do anything with our code. It's already there. That should be everything with the user controller. Let's go to the tag controller and let's add some security here. Now really the only people that should be modifying the tags are admins and editors. We can make this really easy on ourselves. We can just use the authorize attribute at the controller level, and we can specify the roles as admin and editor. We don't have to add any extra logic to the methods because really there's nothing here for authors to do. So we can count that off of our list. But now let's open up the post controller, and this is going to require us to modify not just some, but quite a bit of our code, and we'll start with the indexed action method. Now currently we are using the get on method, which will retrieve all of our posts and we are sending those to the view. And that's fine if the user is an admin or an editor, but if they are an author, we only want to show them their posts. They have no business seeing anybody else's. So we can change this code, so that if the currently logged in user is not in the author role, then we will go ahead and we will execute the code that we have. Otherwise, we will need to write some other code. Now, we know that we should make things asynchronous, and the new code that we are going to write will be asynchronous. So let's just go ahead, and let's just make some modifications to make this GetAll method asynchronous as well. So let's first of all change this index action method to be asynchronous, and it will return a task of ActionResult. And then let's go ahead and change this method to be GetAllAsync, and lets add await here. Now of course we need to modify the interface for our ipost repository. So let's just go to that definition and we will change that code there. So let's make this return a task of i innumerable in post and lookey there. We already have the using statement for the tasks namespace, and we change that to get all async. And now we can change the actual implementation. So let's go down to our data folder. Let's open up the post repository, and let's go to the get all method. And here, we are going to first of all add async, and it will a return a task of enumerable of post. And the name is get AllAsync. Really the only thing that we need to do is add await here, but we do need to make this asynchronous, so instead of calling to/array, we are going to call a method called to/array Async, although that is not here, but we can use that method by adding system.data.entity, and then that two array async is going to be there. So, while we're here, let's write another method. One that will allow us to get the posts by an author. So we can call this get posts by author async, and we need the author ID, so that will be our parameter, and we need a where clause, so db.posts.include.where, and where the author ID equals the parameter author ID, and that should be fine there. So, let's go back to our controller, and the only way that we can get the ID of the user is to use our user repository. If we get a CMS user object, then we have the author ID. So we need our user repository. So let's add another statement here. Let's change this to, IUserRepository. Ant let's just call this, underscore users. And then, inside of our constructor, we will set underscore users equal to userRepository. Of course, we don't have this parameter yet, but let's add one. So we will have an IPostRepository and an IUserRepository. And we also need to change our default constructor to create a new user repository. And now we have our repository. So we can use that to inside of the index action method. So we need to retrieve the currently logged in user's CMS object. So we can do var user equals await and underscore users, GetUserByNameAsync, and then we just need to get to the username. User.Identity.name, and really, we are going to have to do this multiple times, so it makes sense to go ahead and just turn this into a little helper method. So let's go to the bottom of this controller class, and let's add a private async. It's going to return a CMS user, and let's call it GetLoggedInUser. And it is simply going to return this await_users.GetUserByNameAsync, and this needs to be a task of CMS User. So we have our currently logged in user easily accessible now, so let's go back to the index method. And we will say, await and GetLoggedInUser, and then we can get to the posts. And we use the await keyword, our underscore repository and get post by other, but I don't see that there. The reason why is because we don't have that on our interface. So let's go to our repository and let's copy this signature, and then let's just paste that into the interface and everything should be good to go. So if we go back to post controller, now we should see this in intelligence get posts by author a sync. We want to pass in using the user.ID. And then we will return view passing in our posts. Although we can't really do that because we have two post variables. So lets, do this. Lets take this statement, put this inside the call to return view, and then we can have this post. Now we could do the same here, but since this is a little bit more typing, I would prefer to see this in a single statement, and then just use the posts variable there. Otherwise, this statement could be just a little bit unruly. So that's our index statement. If the user is not an author, they will see all of the posts. But if they are, then we will show them only the posts that they are allowed to see. The next method that we need to modify is the create method for post requests, because down here whenever we assign the author ID, we hard coded that so that we wouldn't run into any errors. So we need to change this so that it is the ID of the currently logged in user. So we first of all need to make this method asynchronous, and then we need to retrieve the current user. Now we should do this after we check the model state, because if the model state isn't valid, then there's no reason to get the user. So after we check the model state, then we want to var user, and we will await and GetLoggedInUser, and then we will use the user's ID property down here where we assign the Author ID. And I think that's all that we need to do here. So we can move on to the edit method. Let's go ahead and let's make this asynchronous as well. And, let's see, the first thing we do is get the post and then we check to see if it's null, and we probably still want to do that at the very beginning. After if we check if post is null, then we can get the currently logged in user. So far user equals await, and then GetLoggedInUser. And really, we only need to do this if the user is an author. If user.IsInRole and author, then we will get the logged in user, because then we need to check to see if the user's ID is equal to the post's author ID. So, we can use another if statement. If post.AuthorID is not equal to user.ID, then this user is not authorized to edit the post. So, return new HTTP unauthorized. Otherwise everything should just go right on through to returning view, passing in the post. And we will kind of do something similar in the other edits method. So let's scroll on down. Once again, we will check to see if the model state is valid first. And if it is, then we will check to see if the user is an author. If they are, then we will get the user, but we also need to get the post. So let's first of all make this method asynchronous, and it will return task of ActionResult. And then we need to get the post. So if our post equals, and we will use our repository Get and then pass in the post ID. Now it's possible that post will be null here, and instead of checking to see if post is null, let's just wrap this with a try and a catch. In fact, our catch is not going to have any code, because if post is null, then we don't have to worry about it because the edit method handles that for us. Because if you'll remember, it throws multiple types of exceptions. So we'll be good to go there. And then finally for the delete method, we need the authorized attributes and we want to specify the roles of admin and editor. And since we have two delete methods we will do the same thing for the other one as well. So let's just scroll through and make sure everything is properly secured. So we have authorize for admin and editor on delete. For the edits methods, we don't have an authorized attribute, but we do have logic that checks to see if the user is the author, and if the post's author ID equals the user ID. So let's make sure that we do that once again, and we do. And then, inside of the create method, we simply get the user and use its ID for the author ID. We don't do anything for the other creates method. For the index method, we check if the user is not an author, then we just pass in all of the posts. Otherwise, we get the posts for the given user. So, everything is okay except we need the authorized attribute at the controller level. The only other thing that comes to mind is that we need to dispose of our user repository. So, let's open up the user controller and let's just grab that code and paste it in. Actually, we can use the admin controller since we've already done that, so let's copy that code. Let's go to the post repository. Let's paste in that overridden disposed method. So okay, our admin area is completely secured. In order to do anything we have to be logged in, and then if we want to do certain things, we have to be in the appropriate role. Now we have just a few little extra things that we need to do. We need to add the Ajax calls for deleting things. We also need to add a menu, so that we can navigate through the admin area a little easier. We will do that in the next lesson.