atom feed6 messages in com.googlegroups.elmah[ELMAH] Re: Implementing more advance...
FromSent OnAttachments
Scott MitchellAug 3, 2009 10:58 am 
Atif AzizAug 3, 2009 11:57 am 
Scott MitchellAug 4, 2009 9:21 am 
Scott MitchellAug 14, 2009 11:02 am 
Atif AzizAug 20, 2009 4:03 pm 
Atif AzizAug 29, 2009 10:36 am.zip
Subject:[ELMAH] Re: Implementing more advanced authorization rules for web-based ELMAH error log
From:Atif Aziz (aziz@gmail.com)
Date:Aug 29, 2009 10:36:19 am
List:com.googlegroups.elmah
Attachments:
authdemo.zip - 1.3MB

Hi Scott,

I investigated this further and it looks like you're right. The session state is not available at the time *IRequestAuthorizationHandler*'s * Authorize* method is called. I guess this is to be expected. ELMAH's authorization occurs right before it returns one of its request handlers back to ASP.NET and as you know, ASP.NET relies on the handler indicating sessions state requirements via *IRequiresSessionState* and * IReadOnlySessionState*. It's a bit of a catch 22 scenario. The good news is that I have managed to hack a workaround that doesn't require any changes to ELMAH 1.1 and attached is a working example built on top of the demo supplied with ELMAH. Following is an explanation of the solution that requires 3 classes in all (see the *App_Code* directory).

There are really two reasons why session state is not available during authorization. One, and as we discovered, it's just too plain early. Two, none of the ELMAH handlers use session state so they don't implement * IRequiresSessionState* or *IReadOnlySessionState*. Even if * IRequestAuthorizationHandler.Authorize* wasn't being called too early, the session state would have never been made available by ASP.NET because the handlers don't advertise their need for it. Consequently, the first thing that needed fixing was implementing those interfaces on the handlers. Unfortunately, this would mean forking ELMAH's sources and maintaining a modified version. To avoid that, the solution takes a wrapper approach. The first class is pretty straightforward and generic:

public sealed class SessionRequringHandler : IHttpHandler, IRequiresSessionState { public IHttpHandler InnerHandler { get; private set; } public SessionRequringHandler(IHttpHandler handler) { InnerHandler = handler; } public void ProcessRequest(HttpContext context) { InnerHandler.ProcessRequest(context); } public bool IsReusable { get { return InnerHandler.IsReusable; } } }

It takes any *IHttpHandler* during construction and delegates to it. Because this class advertises session-requirement, it can cause session state to be summoned during a request when the actual handler wouldn't have. Now on to the next class, which is also dead straightforward:

public class ErrorLogHandlerFactory : Elmah.ErrorLogPageFactory { public override IHttpHandler GetHandler( HttpContext context, string requestType, string url, string pathTranslated) { var handler = base.GetHandler(context, requestType, url, pathTranslated); return new SessionRequringHandler(handler); } }

This is a handler factory that inherits from ELMAH's handler factory and I've named it the same. It first calls the base implementation and then takes the returned handler and wraps it up in an instance of * SessionRequiringHandler*. Then instead of returning the original handler, the latter one is returned and which will cause ASP.NET to make session state available. To make this work, you'll need to register the new handler factory in lieu of the original one from EMAH; that is, for *elmah.axd* (or whatever path you've chosen instead) in the *httpHandlers* section in * web.config*.

The final piece of the puzzle is the authorization handler itself and here's the implementation:

public class ElmahAuthorizationHandler : HttpModuleBase, IRequestAuthorizationHandler { protected override void OnInit(HttpApplication application) { application.PreRequestHandlerExecute += delegate { var context = application.Context; if (IsFlaggedRequest(context)) CompleteAuthorization(context); }; base.OnInit(application); } protected override bool SupportDiscoverability { get { return true; } } public virtual bool Authorize(HttpContext context) { FlagRequest(context); return true; } protected virtual void CompleteAuthorization(HttpContext context) { // Forbid more than 100 requests within a given session. if (context.Session == null) throw new Exception("Session unavailable."); var count = (int)(context.Session["ElmahRequestCount"] ?? 0) + 1; if (count > 100) throw new HttpException(403, "Unauthorized request."); context.Session["ElmahRequestCount"] = count; } protected virtual void FlagRequest(HttpContext context) context.Items[GetType()] = true; } protected virtual bool IsFlaggedRequest(HttpContext context) return (bool)(context.Items[GetType()] ?? false); } }

What the handler basically does is that it defers the decision to authorize. When the *Authorize* method is called, it merely flags the request as one requiring an authorization check later in the processing and tells ELMAH to let it go through for now as if it is authorized. When the call returns, ELMAH returns a handler for the request. This handler then gets wrapped by our new *ErrorLogHandlerFactory* (the one overriding the original one from ELMAH) into another one requiring session state (*SessionRequiringHandler*). When ASP.NET sees the handler and that it implements *IRequiresSessionState*, it goes ahead and makes the session state available to the request context. Later when the handler is just about to be executed, ASP.NET raises the * PreRequestHandlerExecute* event, to which *ElmahAuthorizationHandler*subscribes. It is then during this event that the real authorization check is conducted, the one based on session state. I've chosen to implement * IRequiresSessionState* on *RequiringSessionState* to have a read-write access to session state for demo purposes but you can decide to use * IReadOnlySessionState* if it is good enough for your case.

Hope this helps.

- Atif

On Fri, Aug 14, 2009 at 8:02 PM, Scott Mitchell <scot@gmail.com>wrote:

Atif, earlier this week I was at the client's site and was trying to implement this approach. I created an HTTP Module that extended HttpModuleBase and implemented IRequestAuthorizationHandler's Authorize method. However, I noticed that when the Authorize method is invoked that HttpContext.Current.Session is always null. I assume this has to do with when in the pipeline this method is called. Do you have any proposed workaround?

The issue is that we are storing credentials in a session variable, so I cannot perform any sort of authorization check because I don't know who is visiting the page! :-) This app has its roots in a classic ASP app and still has a good chunk of its core functionality in classic ASP, which is part of the reason the credentials are stored in session. But long story short is that changing this approach is not feasible at this point in the game. I was hoping there might be some suggestion you'd have as to how to get this going.

My guess is that the Authorize method is being called during the MapRequestHandler stage of the HTTP pipeline, but that session isn't enabled until the AcquireRequestState stage, which happens later.

Thanks

On Aug 3, 11:57 am, Atif Aziz <aziz@gmail.com> wrote:

Hi Scott, > There is a little known feature in ELMAH that allows custom authorization but unfortunately which has not been documented so far. It's very simple to use, however. All you need to do is add a regular ASP.NET<http://asp.net/>HTTP
module that implements the IRequestAuthorizationHandler interface from the

Elmahnamespace. The interface is dead simple and contains only a single method: > public interface IRequestAuthorizationHandler {

bool Authorize(HttpContext context); }

You can find this definition starting line 212 of ErrorLogPageFactory.cs

of

release 1.0<

http://code.google.com/p/elmah/source/browse/tags/REL-1.0/src/Elmah/E...>.

The Authorize method is called everytime any request takes place via the > ErrorLogPageFactoryhandler so it can be used to secure the error log page, details, RSS feeds, style sheet or what have you and using any complexity of rules you desire. Returning true from Authorize will naturally authorize the request and otherwise forbid it. Also note that if you authorize a request, all other security still applies. That is, if you've not enabled remote viewing by configuration then authorizing by your rules won't make the page accessible.

Another thing to keep in mind is that if you want your ELMAH-specific authorization module to work in medium trust then you should inherit from

SupportDiscoverability<

http://code.google.com/p/elmah/source/browse/tags/REL-1.0/src/Elmah/H...>to

return > true.

The reason this bit has not been advertized is that it hasn't received as much design scrutiny and field feedback as I would've like but all the machinery is there nonetheless and modular enough to develop and distribute stand-alone authorization modules for ELMAH. If you want to take a crack at it, I'd be happy to guide you through any bits that still may seem foggy. And if it works out well in your case, it'd be great if you can contribute a wiki page surrounding this subject. ;)

- Atif

On Mon, Aug 3, 2009 at 7:59 PM, Scott Mitchell

<scot@gmail.com>wrote:

Is there any guidance on how to implement more advanced authorization rules for the web-based ELMAH error log? In short, I want to be able to display the ELMAH error log to a certain subset of users, but that subset is defined by business rules in the application and not by user or role.

I've tried one approach, which was to:

* Disable web viewing for elmah.axd * Create an ASP.NET <http://asp.net/> page named ViewLog.aspx * Add authorization rules in Page_Load. If these rules passed then I did a Server.Transfer to elmah.axd

While the above certainly shows the log to only those authorized users (whilst prohibiting access via ~/elmah.axd), there are two issues:

(1) The CSS doesn't come cleanly through. I got it looking pretty good by examining the markup/CSS rendered by elmah.axd and copying and pasting what I could into ViewLog.aspx, but it's still not perfect. (2) Whenever I view the error log from ViewLog.aspx I see the error log, but an exception occurs. I apologize as I can't tell you what exception is firing, exactly, as the code is at a client's site, but it happens everytime the page is loaded (either by visiting it directly or by having the page auto-refresh). (If needed, I can recreate a sample environment and let you know.) The error doesn't impede viewing the error log, but it does appear in the log.

Is there a way to achieve what I'm after? If not, is there any interest in adding support for such to a future version of ELMAH?

Thanks