MvcSiteMapProvider
MvcSiteMapProvider copied to clipboard
Roles based Menu question
Hi, I am following you instructions on an ASP MVC 5 (4.6.2) site and trying to understand this comment from Stack Overflow.
"Again, the roles
attribute is only for interoperability with ASP.NET and should not be used for pure MVC, since it means you need to duplicate your roles on AuthorizeAttribute anyway."
can you please help, which roles attribute
are you referring to, do you mean the extension?
If I implement this with ASP Identity V2.2 MVC 5, and simply enable the security trimming attribute, would that be enough or are you stating this additional extension is needed.
public static class ControllerContextExtensions
{
public static IEnumerable<string> Roles(this ControllerContext controllerContext)
{
var controllerType = controllerContext.Controller.GetType();
var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
var actionName = controllerContext.RouteData.Values["action"] as string;
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
var authorizeAttribute = FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor)
.Where(f => typeof(AuthorizeAttribute).IsAssignableFrom(f.Instance.GetType()))
.Select(f => f.Instance as AuthorizeAttribute).FirstOrDefault();
string[] roles = { };
if (authorizeAttribute != null && authorizeAttribute.Roles.Length > 0)
{
roles = Array.ConvertAll(authorizeAttribute.Roles.Split(','), r => r.Trim());
}
return roles;
}
}
What I mean is that there is a roles attribute in the .sitemap schema (and also on the ISiteMapNode). This attribute was carried over from Microsoft's ASP.NET implementation and is only meant for use if you have a hybrid MVC/ASP.NET website.
For a pure MVC website, you should do nothing more than configure your security with AuthorizeAttribute
or a subclass of it, and then enable security trimming on MvcSiteMapProvider, which picks it up automatically.
@NightOwl888 awesomeness in a chocolate lava cake :+1:
Wow thats simpler with AuthorizeAttribute
and security trimming=true
, and I do not even need to implement the ControllerContextExtensions, correct?
Can you please recommend / clarify, a component or a way to implement/make the Mapping of Roles to ControllerAction
Flexible-Dynamic, so that it could be administered from a View/Stored in config or DB and not having to touch the code on top of every Action Or Controller, (since this static code approach involves a recompile in production and it kills us.)
- [ ] Some progress I have made is to reflect on the assembly and get the list controllers and actions, but I am stuck on creating the mapping between the Roles and Action Methods
- [ ] also want to port that functionality to your MVCSiteMap lavacake goodness :yum:
And lastly a request, when I have a multi-tenant scenario is there a way to emit or output or render differently the menu, so it looks different for that client.
thanks
Hi, will this work
@NightOwl888 will this satisfy the need for the filter context
protected override void HandleUnauthorizedRequest(AuthorizationContext filterCtx)
{
if (filterCtx.HttpContext.Request.IsAuthenticated)
{
filterCtx.Result = new HttpStatusCodeResult(403);
return;
}
base.HandleUnauthorizedRequest(filterCtx);
}
If you are referring to putting that into a custom AuthorizeAttribute
subclass, then yes that will work fine. The only requirement is that the filterCtx.Request
be set to a non-null value if authorization fails.
I am not sure what approach is best for your application, as making a "mapping" between roles and actions might not scale well.
One approach is to just make a granular set of roles ("EditProject", "ViewProject", "DeleteProject", etc). But keep in mind the roles are normally set in a cookie and will result in more bandwidth usage if you overdo it (not to mention, there is a limit to how big a cookie can be).
Another would be to make a custom AuthorizeAttribute, override the AuthorizeCore method and return true whenever the authorization succeeds. It might not scale well to hit a database for this, though and keep in mind that MvcSiteMapProvider checks all of your visible Menu actions on each page load when you use the @Html.MvcSiteMap().Menu()
.
You can register AuthorizeAttribute
as a global filter so you don't have to decorate every action method with one.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyAuthorizationAttribute());
filters.Add(new HandleErrorAttribute());
FilterProviders.Providers.Insert(0, new GlobalFilterProvider(DependencyResolver.Current));
}
}
Note that if you are using DI you can also make a global filter provider so you can control the lifetime of your DbContext using the DI container.
Great thanks! :cake:
I am using this approach by @NightOwl888 - "Another would be to make a custom AuthorizeAttribute, override the AuthorizeCore method and return true whenever the authorization succeeds. It might not scale well to hit a database" just not to glad about the hitting the DB, wish I could cache it, but every users roles change, so it might be necessary.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyAuthorizationAttribute());
filters.Add(new HandleErrorAttribute());
//no DI for me, so this should do.
//FilterProviders.Providers.Insert(0, new GlobalFilterProvider(DependencyResolver.Current));
}
}