Avoid logging sensitive information with ELMAH

ELMAH is probably de-facto the most commonly used solution to create an automatic logging infrastructure in your ASP.NET application. One of the problem you can encounter using it, is that this component logs a lot of information, and particularly all POST variables of the request. If some error occours in login or registration page, you will end with user password written in clear text inside the elmah Database.

To avoid this problem you need to use a feature of ELMAH called Filtering, that permits you to intercept error logging, here is an example.

void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs args)
{
    Filter(args);
}

void ErrorMail_Filtering(object sender, ExceptionFilterEventArgs args)
{
    Filter(args);
}

void Filter(ExceptionFilterEventArgs args)
{
    if (args.Context != null)
    {
        HttpContext context = (HttpContext)args.Context;
        if (context.Request != null && context.Request.Form.AllKeys.Count() > 0)
        {
            foreach (var key in context.Request.Form.AllKeys)
            {
                if (context.Request.Form.AllKeys.Any
                    (
                        k => sensitiveKeys.Any(sk => k.IndexOf(sk, StringComparison.OrdinalIgnoreCase) >= 0)
                    ))
                {
                    //I've got sentitive information
                    ElmahSensitiveHandle(args, context);
                }
            }
        }
    }
}

Actually the first two events intercepts both error log and error mail and delegates handling to the single Filter function. As you can see Filter function only checks if some key in Form parameters are sensitive. This is determined by a series of string value, saved in sensitiveKeys list, that represents all the string that I want to avoid being logged in clear text. Es:

private List<String> sensitiveKeys = new List<string>() { "password", "pwd" };

The routine does absolutely nothing if there are no sensitve keys, but when a sensitive key is found, it calls the ElmahSensitiveHandle function.

private void ElmahSensitiveHandle(ExceptionFilterEventArgs args, HttpContext context)
{
    var newError = new Elmah.Error(args.Exception, (HttpContext)args.Context);
    foreach
            (var key in context.Request.Form.OfType<String>().Where
                (
                    k => sensitiveKeys.Any(sk => k.IndexOf(sk, StringComparison.OrdinalIgnoreCase) >= 0)
                )
            )
    {
        newError.Form.Set(key, "*******************");
    }

    Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(newError);
    args.Dismiss();
}

Last line tells ELMAH not to log the error, and this actually prevents the logging. But if you still want to log the error and only remove the sensitive part, you can simply create another elmah error object with Error static method of Elmah object. After the error object is created, a simple foreach permits me to identify all sensitive keys and replace in the error object with a series of asterisks.

Finally using the Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(newError) you can make elmah log this new error object, that has no more any sensitive information in clear text.

Gian Maria.

Signalr Error: the connection id is the incorrect format

I’m working with Signalr in a real project and I found a problem with authentication. This is the Repro Steps for the bug:

1) the user open a browser tab to a public page that uses SignalR
2) he opens another tab in the browser and navigate to a private page that requires authentication
3) he logins into the system and Signalr in the first tab stops working.

The page stops to receive server calls and if I call some Hub function from javascript I got no javascript error in Developer Console, but done function is never called. To troubleshoot connection error you should handle $.connection.hub.error function that gets called whenever a communication error happens. Now I was able to find that the error is a status 500 with a page that tells “the connection id is the incorrect format”

The reason is described in this StackOverflow post, basically the solution consist on a simple stop/restart of the connection, but in my scenario, when the code calls a function from javascript, nor the done() or fail() method gets called, so I have no way to react. Luckily enough, I’ve structured my code to centralize all function calls in an api.js file. Es.

self.api.Myfunction(self.parama(), self.paramb())
    .done(function (data) {
        //success...

The caller simply calls functions on an api object that returns a promise, caller can use done() and fail() to take appropriate decision based on the result of the call and caller code completely ignore how the call is done to the server. As an example this is how Myfunction is defined in the api.js file:

self.Myfunction = function (parama, paramb)
 {
    return self.callMessageHub(function () {
        var retvalue = self.messageHub.server.myfunction(parama, paramb);
        return retvalue;
    });

Actually it simply delegates to a callMessageHub internal function that is used to wrap all calls to the Signalr Hub, here is the full definition of the function.

self.callMessageHub = function (functionToCall) {
    lastFunctionToCall = functionToCall;
    var def = $.Deferred();
    lastDeferred = def;

    functionToCall()
        .done(function (data) {
            def.resolve(data);
        })
        .fail(function (error) {
            self.globalError('', '', error);
            def.reject();
        })
        .always(function () {
            lastFunctionToCall = undefined;
            lastDeferred = undefined;
        });

    

This code assumes that Hub functions are not called concurrently, and this is not a problem in my scenario. First of all this method stores the actual function call in a global object called lastFunctionToCall, then it declare a deferred with $.Deferred() call and store it inside a global variable called lastDeferred; finally the code calls the original function. The key factor is: if the user opened another tabs and login to the system, the call to the Hub will fails and nor the .done() nor the .fail() function will be called, but we can handle with $.connection.hub.error

$.connection.hub.error(function (error) {
    self.globalError('', '', error);
    if (error.status === 500) {
        $.connection.hub.stop();
        $.connection.hub.start()
            .done(function ()
            {
                if (lastFunctionToCall !== undefined) {
                    lastFunctionToCall()
                    .done(function (data) {
                        lastDeferred.resolve(data);
                    })
                    .fail(function (error) {
                        self.globalError('', '', error);
                        lastDeferred.reject();
                    });
                }

            });
    }
});

This code simply check for an hub error of type 500 and try to stop and restart again the hub. Once the hub connection is started again, if we have a lastFunctionToCall different from undefined, it means that we have done a call to the server after the authentication is changed and the call failed, but since I’ve stored a reference both to the original function to call and to the deferred returned to the caller, O can do another call to the function and signal to the original deferred object.

The net effect is that the caller should not worry about disconnection from the hub due to change to the login status, because the Api.js file takes care of error handling and automatic retry of the function call. Everything is transparent to the caller.

Actually this code uses a couple of global javascript variables, to make everything more robust, we should use a variable on the Api object and add support for concurrent calls.

Gian Maria.

Could not load file or assembly Microsoft.ReportViewer.WebForms

This error: Could not load file or assembly Microsoft.ReportViewer.WebForms sometimes shows up when you start working with old projects and you have only the latest version of Visual Studio installed, or when you deploy a web site or program and forget to include all dependencies.

In a project I’m working to I’m using Visual Studio 11 Beta in a Virtual Machine,  I opened a project that actually uses VS2010 but was created originally with VS2005, I rebuild every project, but I got the above error when I started one of sites included in the solution inside IIS. The reason is clear, the old site still uses old assemblies of Report Viewer, but in my machine only VS11 beta is installed so I need to reinstall the runtime of Report Viewer to make it work. For any other person that is incurred in this problem, you can find the Report Viewer installer of VS2008 at this address but if you have a really old site that uses Visual Studio 2005 Report Viewer this is the link, finally this is the link for 2010 version.

If you want to know the exact version you need you should look at the version, Version=8.0.0.0 is Visual Studio 2005 version, Version=9.0.0.0 is Visual studio 2008 and finally Version=10.0.0.0 is Visual Studio 2010 Version. In my example the exact error is

Could not load file or assembly ‘Microsoft.ReportViewer.WebForms, Version=8.0.0.0, Culture=neutral

This means that I need Report Viewer of Visual Studio 2005 installed to make it work.

Gian Maria.

How to test SSL based WCF services

I usually work with WCF service that needs to be secured with Certificates or simply using HTTPS. The usual question from other dev in the team is “how can I simulate HTTPS to work with WCF?”

The problem arise that to test https sites, people usually work with IIS self issued certificates.

SNAGHTMLa82f3d

Figure 1: Self-Signed certificate in IIS

This works great for sites, you can use the auto signed certificate in your sites in https binding, then when you navigate to the site you usually got an error because the certificate is not issued for the right site. What I need is usually to modifiy the hosts file in windows, creating an alias of www.mydomain.com to 127.0.0.1, so I can directly point to the right address with WCF client application and can simply manage to use the local or remote service simply modifying the hosts file.

Sadly enough, WCF does not tolerate problem in certificates and this makes useless working with Self-Signed Certificate. To be able to use a WCF Service secured with SSL in your dev machine you should issue yourself a valid certificate. The solution is using the SelfSSL.exe tool that comes with the IIS6 Resource Kit Tools.

Once installed you can simply go to installation folder with Administrator Command Prompt (you need to launch the command prompt as administrator or it wont work) and simply create a valid certificate with this command line

Selfssl /N:CN=www.codewrecks.com /V :2000 /S:3

as you can see with the option /N:CN you are able to specify the Common Name you want to use, the /V option is used to specify the duraction in years of the certificate, and the /S: is used to specify the Id of the site you want to change, just select the “site” node on IIS7 administration console to see the ID assigned to each site.

image

Figure 2: How to find Site ID from IIS administration console

Now your site should have https binding enabled and it should use the autogenerated certificate, but if you browse the site your browser is still telling you that the certificate is not valid. If you open your certificate from the IIS administration console (just select the root node with your computer name and select Server Certificates from the IIS section on the right and double click on the desidered certificate) you can verify the error.

SNAGHTMLb106f5

Figure 3: The certificate is not valid because the certification authority is not trusted

The problem is that the Certification Authority of this certificate is not trusted, so you need to export this certificate to a file, just right-click the certificate and choose “Export”. (remember that you should choose a password to export the certificate)

Now open the mmc certification snap in (just Start->Run->mmc.exe certmgr.msc) and choose to import the certificate you just exported

image

Figure 4: Import the certificate from the Certificates manager.

Choose the file you previously exported, insert the password you used for the export, then proceed to import the certificates. At the end of the operation, if you open again the certificate from the IIS administration console, everything should be ok.

image

Figure 5: The certificate is now ok and can be used to secure your sites

If this does not works, and you still got errors when you browse your local site through Https, probably the certificate was imported in the wrong path, just import the exported certificate again as shown in figure 4, but when you are asked for the location where you want to import the certificate press the button browse (Figure 6)

image

Figure 6: Import the certificate to a specified location.

Now choose the location as shown in Figure 7 and everything should work ok.

SNAGHTMLbcf691

Figure 7: Choose the exact location where to import the certificate.

Now modify the hosts file, mount your WCF service and verify with WCF test client that everything is ok.

Gian Maria.

The strangest bug of IE I’ve ever seen

We have a web application written in asp.net and we experienced really strange problem after recent modification. We have a strange behavior in IE9 with redirects and after some inspection we found a really strange fact (that almost make us lost 2 hours of work). To recreate this situation simply create a new page in an asp.net site, and put this code in it.

   1: <form id="form1" runat="server">

   2: <div>

   3:     <label id="queuecount"  />

   4:     <asp:ImageButton ID="ImageButton1" runat="server" />

   5:     <a href="http://www.microsoft.com">microsoft</a>

   6: </div>

   7: </form>

Now simply run this page with fiddler opened, you should see a simple broken image and a standard anchor tag. Now click on the link and look at what happened with fiddler.

image

Internet explorer issue the request to www.microsoft.com, but immediately after he request the original asp.ner page in POST. The order of the two requests varies, sometimes the test.aspx POST is done before requesting the link, and the user navigates to www.microsoft.com, but if the order is reversed (as appears in the above image) the user remains on the original page wondering on what is happening.

Using Firefox, Opera or Chrome do not produce any strange result, it seems only a bug of Internet Explorer. If you remove the HTML label tag or the image button everything works correctly, but it seems that if you have a label and an imagebutton, a simple anchor link in the page causes an unnecessary postback to the same page.

Gian Maria.