views:

139

answers:

2

Hi folks,

this is the current code in ASP.NET MVC2 (RTM) System.Web.Mvc.AuthorizeAttribute class :-

public virtual void OnAuthorization(AuthorizationContext filterContext)
{
    if (filterContext == null)
    {
        throw new ArgumentNullException("filterContext");
    }
    if (this.AuthorizeCore(filterContext.HttpContext))
    {
        HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
        cache.SetProxyMaxAge(new TimeSpan(0L));
        cache.AddValidationCallback(
            new HttpCacheValidateHandler(this.CacheValidateHandler), null);
    }
    else
    {
        filterContext.Result = new HttpUnauthorizedResult();
    }
}

so if i'm 'authorized' then do some caching stuff, otherwise throw a 401 Unauthorized response.

Question: What does those 3 caching lines do?

cheers :)

+1  A: 

call to AuthorizeCore will validate if request is authorized. If authorized, it put an AddValidationCallback in order to test if the cached output is still valid according to cache policy. If so, the cached output is sent to the client.

Regarding the 3 lines for caching; well, first at all you should understand that an output cache must be correct or as correct as possible. In order to meassure its "correctness", the system will test if it meets certain conditions (e.g. it has not been modified). This is stuff can be done in the 3 lines..

Sander Pham
Can you please explain what the second line does and why? Next, can u explain what the AddValidationCallback does/is/why, etc. So is what you are saying, that .. if a person adds the [Authorize] attribute, this code is called. BUT, if a personal also adds an [OutputCache] attribute, then the 3 lines above, effect that. If so - how? please refer to some of the MVC code as proof of an explanation.
Pure.Krome
cache.SetProxyMaxAge(new TimeSpan(0L)) is a better alternative for no-cache; it will cause the cache to re-validated, but IT DOESN'T prevent caching. On the other hand, No-cache will cause nothing to be cached at all.
Sander Pham
so your saying that second line stops the output from being cached, if there was some OutputCache. why would they do that?
Pure.Krome
it doesn't prevent the output from being cached, but it causes cache entries to be re-validated.
Sander Pham
+1  A: 

This code exists to allow you to put both [OutputCache] and [Authorize] together on an action without running the risk of having a response that was generated for an authorized user cached and served to a user that is not authorized.

Here's the source code comment from AuthorizeAttribute.cs:

Since we're performing authorization at the action level, the authorization code runs after the output caching module. In the worst case this could allow an authorized user to cause the page to be cached, then an unauthorized user would later be served the cached page. We work around this by telling proxies not to cache the sensitive page, then we hook our custom authorization code into the caching mechanism so that we have the final say on whether a page should be served from the cache.

So just what is this attribute doing? It first disables proxy caching of this response, as proxies can't make the proper determination of which users are or are not authorized to view it. And if a proxy serves the response to an unauthorized user, this is a Very Bad Thing.

Now what about AddValidationCallback? In ASP.NET, the output caching module hooks events that run before the HTTP handler. Since MVC is really just a special HTTP handler, this means that if the output caching module detects that this response has already been cached, the module will just serve the response directly from cache without going through the MVC pipeline at all. This is also potentially a Very Bad Thing if the output cache serves the response to an unauthorized user.

Now take a closer look at CacheValidateHandler:

private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus) {
    validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}

// This method must be thread-safe since it is called by the caching module.
protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext) {
    if (httpContext == null) {
        throw new ArgumentNullException("httpContext");
    }

    bool isAuthorized = AuthorizeCore(httpContext);
    return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
}

This effectively just associates the AuthorizeCore method with the cached response. When the output cache module detects a match, it will re-run the AuthorizeCore method to make sure that the current user really is allowed to see the cached response. If AuthorizeCore returns true, it's treated as a cache hit (HttpValidationStatus.Valid), and the response is served from cache without going through the MVC pipeline. If AuthorizeCore returns false, it's treated as a cache miss (HttpValidationStatus.IgnoreThisRequest), and the MVC pipeline runs as usual to generate the response.

As an aside, since a delegate is formed to AuthorizeCore (thus capturing the particular instance of AuthorizeAttribute) and saved in a static cache, this is why all types subclassing AuthorizeAttribute must be thread-safe.

Levi
@Levi <3 Thank you. Sincerly - thank you :) That all makes complete sence now. BTW, I was using *Reflector* to lean about this. WHY? Caching was not working when I used my own custom `AuthorizeAttribute`. (Code is in a codeplex MVCContrib fork - plugable REST framework), because each Request was calling *those three lines*. As u stated, *the output cache module detects a match it will re-run the AC method* .. which is my problem :( No cookies == no authentication remembers, so I need to authenticate each Request .. and caching is reset? EG: http://bit.ly/bJcntB
Pure.Krome
/me wishes he can call you Levi @ MS to chat :)
Pure.Krome