Search code examples
imageresizer

Correct way to skip authorization with ImageResizer


HttpConext object has SkipAutorization property that is used to disable the authorization check in UrlAuthorizationModule, which is a part of standard asp.net pipeline.

ImageResizer calls UrlAuthorizationModule.CheckUrlAccessForPrincipal directly, outside of the normal asp.net pipeline. As a result the SkipAutorization property is not honoured.

A workaround to that would be:

protected void Application_Start(object sender, EventArgs e)
{
  // Ask ImageResizer not to re-check authorization if it's skipped 
  // by means of the context flag
  Config.Current.Pipeline.OnFirstRequest += 
      (m, c) =>
      {
          Config.Current.Pipeline.AuthorizeImage +=
              (module, context, args) =>
                  {
                      if (context.SkipAuthorization)
                      {
                          args.AllowAccess = true;
                      }
                  };
      };
}

The outer OnFirstRequest here is to make sure that the AuthorizeImage subscription is happening after all plugins has been loaded so it's last in chain to execute.

I don't like this workaround because it's quite implementation dependent. For example if ImageResizer plugins loading is moved from onFirstRequest to elsewhere it will break.

It would be nice if this is fixed in ImageResizer itself. I would suggest changing the additional Autorization check in InterceptModule to something along these lines:

//Run the rewritten path past the auth system again, using the result as the default "AllowAccess" value
bool isAllowed = true;
if (canCheckUrl) try {
        isAllowed =  conf.HonourSkipAutorization && app.Context.SkipAuthorization 
                     || UrlAuthorizationModule.CheckUrlAccessForPrincipal(virtualPath, user, "GET");
    } catch (NotImplementedException) { } //For MONO support

Would that be appropriate, or is there a better solution?

In the last part of the question, I'll describe my use case, reading is entirely optional, but it gives perspective how this query came to be.

In an asp.net application I have an HttpHandler that serves pdf documents. It accepts document id and security information in url and headers (I'm using OAuth) and it performs all the security checks and if they succeed the pdf document path is retrieved from the database, and the file is served to the client by Response.WriteFile.

I need to provide preview of a pdf page as an image, and I'm using ImageResize with the PdfRenderer plugin for that.

Unfortunately the path of the pdf is not know until my file handler have worked, and this is too late for ImageResizer to act on the request since all the magic happens in PostAuthorizeRequest which is (obviously) before a handler runs.

To work around this I re-wrote my HttpHandler as HttpModule, where it's executed on BeginRequest. If the authorization checks fail, the request is severed right there. If they are ok, then I use PathRewrite to point to the resulting pdf and at the same time write the proper Content-Type and other headers to the response. At the same time I set context.SkipAutorization flag, because, since the pdf files can't be accessible via a direct url as per web.config configuration, the pipeline would not even get to the PostAuthorizeRequest if authorization is not skipped. It is safe to skip authorization in this case, since all required check has already been performed by the module.

So this allows the execution flow to get to ImageResizer. But then Image resizer decides that it wants to re-check the authorization on the pdf url. Which fails unless you apply the workaround above.

What is the rationale for this re-check? In the scenario above, when ImageResizer has work to do, the image that it is to serve is not what appears in the URL and the auth check has been already done by the asp.net pipeline, now when we are in PostAuthorizeRequest. In which cases is this re-check useful?


Solution

  • Update: The latest version of ImageResizer respects the HttpContext.SkipAuthorization boolean, making the event handler no longer necessary.


    Your work-around is exactly the right way to deal with this, and is forwards-comaptible.

    The re-check exists because

    1. Url rewriting is very common, encouraged, and even implemented by certain ImageResizer plugins (such as FolderResizeSyntax and ImageHandlerSyntax).
    2. Url rewriting after the Authorize stage allows UrlAuthorization to be circumvented completely.

    HttpContext.SkipAuthorization should be respected by ImageResizer; and probably will be in a future release.

    That said, your workaround involving AuthorizeImage is actually exactly what I would suggest. I don't see how it could be more fragile than SkipAuthorization by itself; and in fact should work regardless of how ImageResizer reorders events in the future.

    ImageResizer respects the order of events in the pipeline - your V2 with authorization happening before PostAuthorize is exactly correct (although it could be moved to PreAuthorize, if you wished to support additional front-end resizing during BeginRequest).

    Also, using RewritePath for serving the original PDF is far more efficient than calling WriteFile, especially on IIS6+, as you probably discovered.