Why does HasChildNodes in MvcSiteMap v4 trigger HandleUnauthorizedRequest for each unauthorized node?

StackOverflow https://stackoverflow.com/questions/23429355

  •  14-07-2023
  •  | 
  •  

Question

I'm upgrading from v3 to v4 of MvcSiteMap, and it seems just using the property Html.MvcSiteMap().SiteMap.CurrentNode.HasChildNodes triggers a hit on HandleUnauthorizedRequest in AuthorizeAttribute for every unathorized child node in the list.

  1. Why should this happen? I would expect HandleUnauthorizedRequest to be triggered for a separate http request, not just interrogating whether a node exists.

  2. What is the best way to distinguish between a 'genuine' unauthorized http request and simply checking an unauthorized sitemap node? My best guess so far is to check whether the controller and action match, but it seems a little unnecessary:

    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            var httpRouteData = ((MvcHandler)filterContext.HttpContext.CurrentHandler).RequestContext.RouteData;
            var filterRouteData = filterContext.RequestContext.RouteData;
    
            var isHttpRequestUnauth = (httpRouteData.Values["Controller"] == filterRouteData.Values["Controller"] &&
                httpRouteData.Values["Action"] == filterRouteData.Values["Action"]);
    
            if (isHttpRequestUnauth)
                throw new System.Web.HttpException(403, string.Format("Access denied for path '{0}'. ", filterContext.HttpContext.Request.RawUrl));
            else
                base.HandleUnauthorizedRequest(filterContext);
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
    
Was it helpful?

Solution

HandleUnauthorizedRequest is only called by the MVC AuthorizeAttribute in the case where the authorization check fails. It is meant only for setting the handler of the request, not actually to provide the check whether the user is authorized. That said, MvcSiteMapProvider doesn't call HandleUnauthorizedRequest directly - it calls OnAuthorization.

The default implementation of AuthorizeAttribute.OnAuthorization makes the check already, so I am unsure what you hope to accomplish by comparing the controller and action again in HandleUnauthorizedRequest since unauthorized users cannot reach that path unless you override the implementation of OnAuthorization as well (or you rely on output caching entirely).

Anyway, to answer your question, in v3 and early revisions of v4 MvcSiteMapProvider used Reflection.Emit to generate a class on the fly that inherited from AuthorizeAttribute or any subclass of AuthorizeAttribute as described in this post. The subclass added public access to the AuthorizeCore method so it could be called by MvcSiteMapProvider. However, that approach had performance issues and also could not be used with sealed overloads of AuthorizeAttribute.

Since then, it has evolved to use the one and only public member of AuthorizeAttribute - OnAuthorization - to do the check. The author of the above post made an error in his assertion that Reflection.Emit was the only way it could be done because he didn't take into account using a subclass of HttpContext.Response that overrides the output caching members. We compromised on using the result of HandleUnauthorizedAttribute (setting the filterContext.Result property to a non-null value) as the way to determine whether or not the security check works.

Unfortunately, there is not a way to make a solution that works 100% of the time because AuthorizeAttribute was only designed to be used in the context of a request for the current page, but this is the solution that we compromised on because it requires the least amount of code to maintain, performs the best, and uses direct method calls instead of workarounds. If you use the typical method of overloading AuthorizeCore for custom logic, it will work perfectly. On the other hand, if you overload OnAuthorization or HandleUnauthorizedRequest, you need to ensure that the filterRequest.Result property is set to non-null for unauthorized and null for authorized.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top