Report Builder 2.0 security extension issues

One of the ways to add more flexibility to your SQL Server Reporting Services (SSRS) is to extend SSRS with custom extensions.
SSRS supports a wide variety of extensions that serve different purposes, i.e. the custom data extension to manipulate a connectionstring or a custom security extension to enable Forms Authentication.

On my current project we’re making extensive use of a few custom extensions, including a custom security extension. In our environment we were unable to use integrated security on the Report Server, so we decided use Forms Authentication and therefore the need for a custom security extension quickly arose.
While security extension are a great way to implement tailormade security solutions, extensions can cause quite a few headaches as well.
In this blog I will address an issue we came across while using ReportBuilder 2.0 in combination with our security extension.

When we started our latest project for SSRS 2008 we already had a similar solution running on SSRS 2005, so we thought that a simple copy-paste of the extensions would give us a headstart.
And indeed, after a little bit of tweaking we got both Report Server and Report Manager running.. until we decided to start ReportBuilder 2.0 (RB2.0).

In our SSRS 2005 solution we’re using ReportBuilder as well, but we’re still on version 1.0 here.
If you have worked with both, there is no need to tell that RB2.0 is a lot more sophisticated than RB1.0 (this might explain why the download is a lot bigger as well).
And while RB1.0 cooperates with the security extension quite nicely, RB2.0 is a whole different story.

While working with RB2.0 in Forms Authentication mode we experienced some very unstable behavior when using the wizard to create a new dataset. The first few screens were very responsive and stable, but after third, fourth screen RB2.0 would suddenly freeze and stop responding.
And with ‘stop responding’ I mean you have to actually kill the process, unless you want to look at a wizard all day long.

So what is happening when you let RB2.0 run in Forms Authentication?
As taken from MSDN (in a nutshell):

 

image

1. A client application calls the Web service method LogonUser to authenticate a user.

2. The Web service makes a call to the LogonUser method of your security extension, specifically, the class that implements IAuthenticationExtension.

3. Your implementation of LogonUser validates the user name and password in the user store or security authority.

4. Upon successful authentication, the Web service creates a cookie and manages it for the session.

5. The Web service returns the authentication ticket to the calling application on the HTTP header.

When the Web service successfully authenticates a user through the security extension, it generates a cookie that is used for subsequent requests. The cookie may not persist within the custom security authority because the report server does not own the security authority. The cookie is returned from the LogonUser Web service method and is used in subsequent Web service method calls and in URL access.

 

So, in short, after we have submitted our credentials to the RB2.0 we should have a cookie that is able to keep us ‘logged in’ during the process.
Since extensions can be a bit tedious to debug, we added some logtraces to find out what was happening while the wizard of Report Builder freezes.
When we checked our logfile, the following message showed up:

   1: <html><head><title>Object moved</title></head><body>

   2: <h2>Object moved to <a href="/ReportServer/logon.aspx?ReturnUrl=%2fReportServer %2fReportService2005.asmx">here</a>.</h2>

   3: </body></html>

RB2.0 uses the ReportService2005.asmx webservice to communicate with the Report Server.
The Report Server will only redirect us to logon.aspx when the authentication cookie is missing.
This is the kind of behavior you get when the cookie has reached its timeout, which was not the case in our scenario.

After doing some research it seemed like RB2.0 does not send the cookie with every request, unlike the documentation seems to suggest.
This behavior has puzzled us quite a bit, because most of the requests include the authentication cookie, but with some requests RB2.0 doesn’t seem to set the cookie properly.
When RB2.0 fails to send the cookie with the request, Forms Authentication will attempt to redirect (HTTP 302) and if you are in the middle of a wizard at this point, RB2.0 will stop responding.

We have ‘solved’ this issue by preventing RB2.0 from getting redirected.
The report server calls the GetUserInfo method for each request to retrieve the current user identity.
Our original code looked like this:

   1: public void GetUserInfo(out IIdentity userIdentity, out IntPtr userId)  

   2: {  

   3:     if (HttpContext.Current != null  

   4:         && HttpContext.Current.User != null)  

   5:     {  

   6:         userIdentity = HttpContext.Current.User.Identity;  

   7:     }  

   8:     else  

   9:     {  

  10:         userIdentity = new GenericIdentity("Temporary user");

  11:     }  

  12:   

  13:     userId = IntPtr.Zero;  

  14: }

If there is no current HttpContext available, a generic identity will be returned (instead of the identity of the current user).
This will cause the Report Server to send a HTTP 302 redirect, which RB2.0 really dislikes.

   1: public void GetUserInfo(out IIdentity userIdentity, out IntPtr userId)  

   2: {  

   3:     if (HttpContext.Current != null  

   4:         && HttpContext.Current.User != null)  

   5:     {  

   6:         userIdentity = HttpContext.Current.User.Identity;  

   7:     }  

   8:     else  

   9:     {  

  10:         userIdentity = null;  

  11:     }  

  12:   

  13:     userId = IntPtr.Zero;  

  14: }  

If we change the ‘else’ branch to return a null identity, this will cause the Report Server to send a HTTP 500 response.
Somehow this response triggers RB2.0 to invoke the LogonUser method on the Report Server.
This will yield a brand new authentication cookie, without RB2.0 being redirected.
And even though we’re still not convinced that this is the perfect solution for our problem, it’s working out pretty well so far.