1

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);
        }
    }
    
GuyB
  • 101
  • 7

1 Answers1

1

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.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • That's really helpful. My problem is that I am overriding HandleUnauthorizedRequest to intercept users who are authenticated but not authorised, throwing an exception so that it gets [handled by Application_Error](http://stackoverflow.com/questions/7501810/net-mvc-how-to-fire-a-error-for-application-error-manage-them). I don't want to call an exception for every unauthed node every time I call HasChildNodes, which is why I am trying to filter out those authorization failures by comparing controller/action (not for purposes of authorization again). I'm still not sure how to work around this. – GuyB May 06 '14 at 09:23
  • I see your dilemma. I believe what you want can be done by creating a subclass of [HttpStatusCodeResult](http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/HttpStatusCodeResult.cs) and throwing your exception in the ExecuteResult method. Rather than throwing the exception in HandleUnauthorizedRequest, you could just set it to the correct handler, and have your handler throw the exception. MvcSiteMapProvider only checks to ensure there is a value set in filterContext.Result, but it doesn't actually execute the handler like MVC does. – NightOwl888 May 06 '14 at 10:46
  • See [my answer here](http://stackoverflow.com/questions/7501810/net-mvc-how-to-fire-a-error-for-application-error-manage-them/23493421#23493421) for an example. – NightOwl888 May 06 '14 at 11:29
  • Throwing exception in subclass of HttpStatusCodeResult works perfectly. I'd upvote both answers if I had enough rep :) – GuyB May 07 '14 at 09:03