Search code examples
asp.net-mvc-5rolesmvcsitemapprovider

MvcSiteMapProvider not picking up roles for menu


I am using MVC5, windows authentication, structuremap DI and custom role provider.

MvcSiteMapProvider for MVC5 is not picking up the roles to show menu based on user role. When security trimming is enabled it shows only menu items which do not have any roles attribute defined.

I have ActionFilterAttribute implemented and used on controller for authorization. Controller correctly redirects the user to unauthorized page based on roles but menu is not picking the role attributes to hide the menu.

Custom RoleProvider has implementation for GetRolesForUser and GetUsersInRole.

Any suggestion will be helpful.

Also want to know where to look for roles attributes in SiteMapNodeModel. I am thinking of customizing to look for permission in the HtmlHelper while building the menu.

Note: Same implementation is working fine in MVC4. Once upgraded to MVC5 and it does not work.

Thanks


Solution

  • MvcSiteMapProvider for MVC5 is not picking up the roles to show menu based on user role. When security trimming is enabled it shows only menu items which do not have any roles attribute defined.

    As per the documentation, the roles attribute is not for use with MVC. It is for interop with ASP.NET pages.

    It only functions when the security framework implements IPrincipal and IIdentity as Membership and Identity do.

    I have ActionFilterAttribute implemented and used on controller for authorization. Controller correctly redirects the user to unauthorized page based on roles but menu is not picking the role attributes to hide the menu.

    This is most likely your issue. Security trimming only looks for AuthorizeAttribute and subclasses of AuthorizeAttribute. If you have subclassed ActionFilterAttribute, it will not be used to hide links from navigation.

    Of course, for the standard AuthorizeAttribute to work, you need to implement IPrincipal and IIdentity or use one of the pre-built security frameworks that does the same.

    Alternatively, you could build your own IAclModule or ISiteMapNodeVisibilityProvider if you have completely custom security.

    Also want to know where to look for roles attributes in SiteMapNodeModel. I am thinking of customizing to look for permission in the HtmlHelper while building the menu.

    You would not need to look for roles from the SiteMapNodeModel. Instead, you should get the roles from the current context and make the changes to the menu templates in /Views/Shared/DisplayTemplates/ accordingly.

    If you are using a framework that supports IPrincipal and IIdentity, you could just use:

    @if (User.IsInRole("SomeRole"))
    {
        ...
    }
    

    Also see the following:

    If you want to get the current roles that are configured for an action method, you can build an extension method to read the roles from the current AuthorizeAttribute. 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.

    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;
        }
    }
    

    Usage

    In view:

    { var roles = this.ViewContext.Controller.ControllerContext.Roles(); }
    

    In controller:

    var roles = this.ControllerContext.Roles();
    

    To get the roles from the SiteMapNodeModel:

        var siteMap = MvcSiteMapProvider.SiteMaps.Current;
        var siteMapNode = siteMap.FindSiteMapNodeFromKey(SiteMapNodeModel.Key);
        var roles = siteMapNode.Roles;