ASP.NET MVC - Extending the Authorize Attribute - asp.net-mvc

Currently I use [Authorize(Roles = ".....")] to secure my controller actions on my ASP.NET MVC 1 app, and this works fine. However, certain search views need to have buttons that route to these actions that need to be enabled/disabled based on the record selected on the search list, and also the security privs of the user logged in.
Therefore I think I need to have a class accessing a DB table which cross-references these target controller/actions with application roles to determine the state of these buttons. This will, obviously, make things messy as privs will need to be maintained in 2 places - in that class/DB table and also on the controller actions (plus, if I want to change the access to the action I will have to change the code and compile rather than just change a DB table entry).
Ideally I would like to extend the [Authorize] functionality so that instead of having to specify the roles in the [Authorize] code, it will query the security class based on the user, controller and action and that will then return a boolean allowing or denying access. Are there any good articles on this - I can't imagine it's an unusual thing to want to do, but I seem to be struggling to find anything on how to do it (could be Monday-morning brain). I've started some code doing this, looking at article http://schotime.net/blog/index.php/2009/02/17/custom-authorization-with-aspnet-mvc/ , and it seems to be starting off ok but I can't find the "correct" way to get the calling controller and action values from the httpContext - I could possibly fudge a bit of code to extract them from the request url, but that doesn't seem right to me and I'd rather do it properly.
Cheers
MH

I found this on another forum and so will post it here in case anyone finds it useful. Note that how you do this changes depending on whether you are using MVC 1 or 2
the class you create needs to implement
public void OnAuthorization(AuthorizationContext filterContext)
and then you can use
string controllerName = filterContext.RouteData.Values["controller"].ToString();
and the same, substituting "action" for "controller" (make sure you check for nulls in these values first). In MVC 2 this can be changed to filterContext.ActionDescriptor.ActionName and .ActionDescriptor.ControllerDescriptor.ControllerName and you won't have to check for nulls

Related

Using custom routes instead of /controller/action/{id}

I have to make vanity urls for an already existing site. The requirements are:
The existing controllers are a kind of hierarchical structure and can't go away or be overridden in any way. Examples: domain.com/Dog/vanity and domain.com/Cat/vanity.
Keep existing actions. Any existing actions must take priority so that the page for that action is not stomped on by the vanity url.
take future pages and actions into account so that the above requirement is met (a new vanity url is ignored and the action/view executed instead)
To date, I have tried various solutions with routing that allow me to have domain.com/vanity which is nice but the marketing guys don't like because of the different departments within the company. I've tried routing which will override the existing actions and treats them all as vanities (also not feasible). I've a solution in place that programmatically deals with the url that was requested and redirects to a page that actually exists but this is not scalable in any way.
So far, I know that the vanity portion can be treated as a parameter to the action so that I can fire off the default page in the route (Index) but this is, so far, doesn't preserve the structure.
TL;DR: I need to have a solution that allows for domain/controller/vanity structure while also allowing domain/controller/action
Using AttributeRouting for MVC4 you can accomplish a working solution until you ramp up the replacement project. It'll allow you to keep existing routes while adding new, custom ones with little impact.
[Route("my-vanity/is-cool/post/{id}")]
public ActionResult Index(int id)
{
}
The important part is to remember priority, so you write routes that don't overwrite/are overwritten by existing routes. You can steer this to some degree with properties on the attribute. Below is just an example that will put the added route last in priority for the entire site.
[Route("my-vanity/is-cool", SitePrecedence = -1)]
public ActionResult Index()
{
}
ASP.NET WebApi2 have built in support for attribute routing. With it you can define URL's in whatever way you like (instead of following the /controller/action pattern)
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

How do I set permissions for my CRUD?

I am learning ASP.NET MVC3, and I just created a controller for my Model/Context. However, anyone can navigate to these pages and use them. How can I set permissions for these pages?
AuthorizeAttribute will be your first line of defense. You can grant access based on group membership or username. Works a lot like Code Access Security / Principal Permission Attributes but isn't as hard to work with.
Example:
// Allow Everybody in
public ActionResult Index ()
{}
// Allow only Editors/Admin to post edits back to controller.
[HttpPost]
[Authorize(Roles="Admin,Editor"]
public ActionResult Edit(EditViewModel vm)
{}
You can use them at the Class or Method Level, so you can have a base controller that only lets authorized users use certain controllers of your app.
If you find yourself using the same groups or users over and over, I would create an override of the AuthorizeAttribute that has those groups predefined, that way you don't misspell or forget them. This will also DRY up your code, which is always great.
You can use the Authorize attribute to rstrict permission, often this is inherited from the base class. This is the most common and recommended.
You can use the ActionFilter attribute and override the OnActionExecuting and set custom logic in there. Possible, but not the recommended.
There are lots of other ways too, but the prior two are the main ones for MVC.

ASP.NET MVC - CMS Questions

I'm looking at developing an application that will include a CMS. I'm a seasoned web forms developer but only really just moving into MVC.
I have a couple of questions that I hope some of you guys can answer:
First, my current web forms CMS allows users to create a page, and then "drop" any number of user controls onto that page they have created. The way I do this is to create an entry in the DB together with the path and then use the LoadControl method.
I can see I can do this with partial views, but partial views have no code behind. If I've potentially got 100 controls that people can drop onto a page, does this mean that the ViewBag in the controller needs to cater for all 100 controls just in case they are used on the view? For example, a web forms user control will contain logic: rptItems.DataSource = blah; rptItems.DataBind()
With MVC, I'm assuming that logic will be in the view controller and the view would access it by the ViewBag? I'm a little confused at how to do this.
Secondly, how would you handle deep routing?
EG:
Store/Products/Category is fine, but what about Store/Products/Category/Delivery/UK ? Would I need to set up a route in global.asax for each route I need? In web forms, I just called the ReWritePath method and handled the routing myself using regular expressions.
Thanks for the time to read this, and hopefully answer some of my queries
For your second question, (ie, "deep routing"), you can handle this within your controller instead of adding real routes. Each part of the url is available via the RouteData.Values collection inside of your controller action. So, your route may look like
~/Store/Products/Category/{*params}
Assuming typical route configuration, this would call the Category(...) action method on ~/areas/store/controllers/storeController, which could then grap delivery and uk from the RouteData.Values collection.
There are a lot of other approaches to this - storing routes in a database and using associated metadata to find the correct controller and method - but I think this is the simplest. Also, it may be obvious, but if you really only need two parameters beyond 'Category' in your example, you could just use
public ActionResult Category(string category, string region)
{
...
}
and a route:
~/store/{controller}/{action}/{category}/{region}/{*params}
Delivery and UK would be mapped to the the category and region parameters, respectively. Anything beyond uk would still be available via the RouteData.Values collection. This assumes that you don't have more specific routes, like
~/store/{controller}/{action}/{category}/{region}/{foo}/{bar}/{long_url}/{etc}
that would be a better match. ({*params} might conflict with the second route; you'll have to investigate to see if it's a problem.)
For your first question:
You can dynamically generate the view source and return it as a string from the controller, eliminating the need to pass a lot of stuff via ViewBag. If a virtual page from your CMS database requires inclusion of partial views, you would add the references to those components when generating the page. (This may or may not address your problem - if not, please provide more information.)

ASP.Net MVC: Check if URL is Authorized

I'd like to simply check from a Controller whether another URL is authorized.
So for example, I'd like to call into a Controller like so:
[HttpPost]
public ActionResult IsUrlAuthorized(string url)
{
bool isAuthorized = // What do I put here?
return Json(isAuthorized);
}
So I'd like to know what I could call to check on whether the current user is authorized for the passed-in URL or not. I'm guessing the answer has something to do with Routes, which sit a little bit outside MVC?
This is a somewhat similar question but not quite the same thing:
ASP.NET MVC. Check if user is authorized from JavaScript
Since the user may or may not be authorized in general, but may not have the right permissions or role assignments to see a specific URL.
Ideas?
Update: I use standard MVC authorization attributes to lock down my app, so I'll just give an example of what that looks like here. In MVC Routes map to Controllers. A single method on a Controller can be restricted to one or more Roles:
public class HomeController : Controller
{
[Authorize(Roles = "User, Moderator")]
public ActionResult ListRecentPosts()
{
. . .
}
}
Or, an entire Controller can be restricted to one or more roles:
[Authorize(Roles = "Admin")]
public class AdminController : Controller
. . .
The actual URL that any of these controller methods responds to is based on a default mapping in a standard MVC app:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
But, you can be nice to your users and make URLs guessable by adding a lot more Routes - as a result, a Controller method can have many names that point to it. You can't just assume and infer the controller name from the URL (even if it maps out that way for half the URLs in the site).
So presumably I either need a way to ask the Routing engine directly whether a URL is authorized for the current user, or a 2-step of asking the Routing engine for which Controller and Method, then ask if those are authorized - hopefully not by using Reflection and matching Roles directly as that again would appear to assume too much.
Update 2: The way this came up is I have an Account strip at the top of my app. Its state can change by selecting one of several accounts you're authorized as. Depending on where you are in the app, the account you chose might have authorization to view this page - and you might be in the middle of filling out a form you don't want to lose. So the naive approach - just refresh when they pick another account - is harmful, and a waste of the user's time even if there is no form and they're just reading a page that's all text.
While that convenience to the user is nice, the user is going to fairly assume that pages they can't see as a user who shouldn't have permission really are denied (and, it would be harmful to leave them on a page that's forbidden - actions taken from it will fail). So I need to know whether to redirect away based on their new permissions.
One of the things I love about .Net is the way many of its best libraries decompose so well, so you can easily recompose things that are part of its normal functionality, or a new twist. Both the Routing module and MVC appear to be very well constructed, so I have to suspect this can be done.
The cheap hack is to ensure that my authorization module returns a consistent redirect status code when a user isn't authorized, and when the user changes their account in the account strip, fire 2 AJAX calls: One to change account, and then a second to the current page over AJAX just to check the HTTP Status Code. 200 OK means leave the page as is, Redirect means follow the redirect. Obviously this is a little ugly, involves an extra HTTP call, creates a false hit in the logs, and makes an assumption about how authorization is handled across the app.
There could be a secondary concern - the page might be authorized, but just change how it works or looks. This particular app has no change in look based on account (besides the account strip itself), and I can handle functionality changes by just providing a custom event that forms listen to - they can reload any relevant data from the server in response to it.
Using UrlAuthorization.CheckUrlAccessForPrincipal only works if you're only using URL authorization. But for MVC using Routing, we highly recommend that you don't use URL authorization to secure an app.
Instead, we recommend using Authorization attributes on the controller class. The reason is there could be multiple URLs that call the same controller action. It's always better to secure the resource at the the resource and not just at the entry ways.
In this particular case, you'd have to get an instance of the controller given the URL. THat's a little tricky as you'll basically have to run the MVC pipeline from the point where you have the URL to the point where you have the controller. It's possible, but seems heavyweight.
I wonder if there isn't a better and simpler way to accomplish your goals. What is it you're really trying to do?
UPDATE: Based on your scenario, it sounds like this is an initial check just for UI purposes. Perhaps all you need to do is make an asynchronous Ajax request to the URL and check the HTTP Status code. If it's a 401 status code, you know the user is not authorized. That seems like the safest bet.
How about UrlAuthorizationModule.CheckUrlAccessForPrincipal method.
UrlAuthorizationModule.CheckUrlAccessForPrincipal Method (System.Web.Security)

ASP.NET MVC How many levels deep should a view or URL be?

I am still learning ASP.NET MVC. With webforms, I would create a new folder let's call it admin. In there I might have many pages for create_product, edit_product, etc. So the URL might look like http://somesite.com/admin/create_product.aspx.
But with MVC it is a little different. I am trying to see what would be the best way to do this.
Would doing http://somesite.com/admin/product/create be right? Or should it just be http://somesite.com/product/create? If I do it as the first way, do I put everything in the "admin" controller or should it be separated into a "product" controller?
I know this is probably subjective or personal choice, but I would like to get some advise.
Thanks.
Part of the benefit of ASP.NET MVC (and more generally, the URL Routing Engine common to all of ASP.NET in .NET 3.5 SP1) is that the URLs can be flexibly configured to map to any folder / file structure you prefer. That means it's much easier than it was in the days of WebForms to modify your URLs after you've started building your project.
To your specific questions:
One Admin Controller vs. Product Controller - In general, the guidance is to keep controllers focused so that they are easier to test and maintain. For that reason, I would suggest using a single controller per object type (like Product) with your CRUD actions. Examples in your case:
/admin/product/create
/admin/product/edit/34 or /admin/product/edit/red-shoes (if name is unique)
In either case, the Create, Edit, Deatils actions will all be in the ProductController. You may just have custom routes for the "admin actions" (like Create and Edit) that limit their usage (and add the "admin" text to the URL), and then the Details action would be usable by all visitors to your site.
Securing Admin Views - One important fact to remember with MVC: all requests go directly to controllers, not views. That means the old "secure a directory with web.config" does not apply (usually) to MVC for securing your Admin. Instead, you should now apply security directly to the controllers. This can easily be achieved by using attributes to Controller classes like:
[Authorize] - Just checks that the user is logged-in
[Authorize(Roles = "Admin")] - Limit to specific user roles
[Authorize(Users = "Joe")] - Limit to specific users
You can even create a custom route for "Admin" views in your site and limit access to those views by enforcing your authorization check in the URL routing, like this:
routes.MapRoute(
"Admin",
"Admin/{controller}/{action}",
new { controller = "Product", action = "Index" },
new { authenticated= new AuthenticatedConstraint()}
);
Where AuthenticatedConstraint looks something like:
using System.Web;
using System.Web.Routing;
public class AuthenticatedConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.IsAuthenticated;
}
}
Good details on Stephen Walther's blog:
ASP.NET MVC Tip #30 – Create Custom Route Constraints
For admin stuff, just mark with [Authorize] attribute. To ensure only admins can use it, do something like [Authorize(Roles = "Admin")]. Check out this question
Also, /product/create is most common, I think :)
I3Dx definitely has the right guidance for the Authorize attribute, this is essential for keeping controller secure, you can apply to a controller or individual actions.
As far as the URL depth, I would not worry about the depth, I would be more concerned that the route made logical sense for example:
domain.com/admin/products/edit/1
domain.com/admin/groups/edit/1
domain.com/products/view/1
domain.com/groups/view/1
This way you know what is happening with each route. it is obvious that one is an admin and one is an end user.
The easyest way to check is to get someone to read your URL and ask them what they would expect to see.
Hope this helps.
OH and one last thing, for client side routes we often use "slugs" rather than ids so that it is more readable. So when someone creates a product we slugify the name so it can be used in the route such as:
domain.com/products/view/big-red-bucket
rather than
domain.com/products/view/1

Resources