Saturday, August 2, 2008

Using ReportViewer control with SQL Server 2005 Reporting Services Custom Security Extension

Russell's Blog

I've been playing around with using different forms of authentication / impersonation with the Report Viewer controls, and I thought I'd post the fruits of my efforts. Here we go:

Using Forms Authentication with the Winform Report Viewer control is easy -- Just pass in the creds and you're all done:

reportViewer1.ServerReport.ReportServerCredentials.SetFormsCredentials(null, "userName", "password", "domainName");
this.reportViewer1.RefreshReport();

Doing the same thing with the Webform Report Viewer control is kind of a pain in the tail. You actually have to implement an interface called IReportServerCredentials and write/borrow another subclass that handles all the cookie related stuff when dealing with Forms Auth.

I took most of the following code from the following help topic, btw:

http://msdn2.microsoft.com/en-us/library/microsoft.reporting.webforms.ireportservercredentials.aspx

Anyway, first create the code for your Forms Auth Login page:

MyReportingService svc = new MyReportingService();
svc.Url = "
http://myServer/reportserver/reportexecution2005.asmx";
try
{
svc.LogonUser("myUserName", "MyPassword", null);
Cookie myAuthCookie = svc.AuthCookie;
if (myAuthCookie == null)
{
Message.Text = "Logon failed";
}
else
{
HttpCookie cookie = new HttpCookie(myAuthCookie.Name, myAuthCookie.Value);
Response.Cookies.Add(cookie);
string returnUrl = Request.QueryString["ReturnUrl"];
if (returnUrl == null !returnUrl.StartsWith("/"))
Message.Text = "Return url is missing or invalid!";
else
Response.Redirect("
http://myServer/appFolder/default.aspx");
}
}
catch (Exception ex)
{
Message.Text = "Logon failed: " + ex.Message;
}

The code above calls LogonUser() against the SSRS web service so that we can get the Forms Auth cookie back from SSRS itself. Then, we stick the cookie into Response.Cookies, and forward the user to a page which has a ReportViewer control on it (in this case, http://myServer/appFolder/default.aspx)

OK, so now we're sitting on the page which has the Report Viewer control itself.

In Page_Load, we first see if the previous cookie exists...if it doesn't, we send you right back to the logon form:


HttpCookie cookie = Request.Cookies["sqlAuthCookie"];
if (cookie == null)
{
Response.Redirect("/appFolder/logon.aspx?ReturnUrl=" + HttpUtility.UrlEncode(Request.RawUrl));

}
else
{

ReportViewer1.ProcessingMode = ProcessingMode.Remote;
ReportViewer1.ServerReport.ReportServerUrl = new Uri("
http://myServer/reportserver");
ReportViewer1.ServerReport.ReportPath = "/Report Project2/Report1";

Cookie authCookie = new Cookie(cookie.Name, cookie.Value);
authCookie.Domain = "myServer";
ReportViewer1.ServerReport.ReportServerCredentials =
new MyReportServerCredentials(authCookie);
}

If the cookie IS there, we set a few properties on the Report Viewer control, then set the ReportServerCredentials property of the control equal to our authCookie. Here is where the implementation of IReportServerCredentials and that other "cookie-handling class" come in.

First, here's the implementation of IReportServerCredentials. It allows us to create a MyReportServerCredentials object:

class MyReportServerCredentials : IReportServerCredentials
{
private Cookie m_authCookie;

public MyReportServerCredentials(Cookie authCookie)
{
m_authCookie = authCookie;
}

public WindowsIdentity ImpersonationUser
{
get
{
return null; // Use default identity.
}
}

public ICredentials NetworkCredentials
{
get
{
return null; // Not using NetworkCredentials to authenticate.
}
}

public bool GetFormsCredentials(out Cookie authCookie,
out string user, out string password, out string authority)
{
authCookie = m_authCookie;
user = password = authority = null;
return true; // Use forms credentials to authenticate.
}
}


As I mentioned earlier, we also have to subclass the myServer.ReportExecutionService class and override a few methods in order to do the cookie-related work:

public class MyReportingService : myServer.ReportExecutionService
{
private Cookie m_authCookie;

public Cookie AuthCookie
{
get
{
return m_authCookie;
}
}

protected override WebRequest GetWebRequest(Uri uri)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Credentials = base.Credentials;
request.CookieContainer = new CookieContainer();
if (m_authCookie != null)
request.CookieContainer.Add(m_authCookie);
return request;
}

protected override WebResponse GetWebResponse(WebRequest request)
{
WebResponse response = base.GetWebResponse(request);
string cookieName = response.Headers["RSAuthenticationHeader"];
if (cookieName != null)
{
HttpWebResponse webResponse = (HttpWebResponse)response;
m_authCookie = webResponse.Cookies[cookieName];
}
return response;
}
}

Parameters can also be passed to ReportViewer and we can also Show/Hide them as needed:


Cookie authCookie = new Cookie(cookie.Name, cookie.Value);
authCookie.Domain = ConfigurationManager.AppSettings["serverName"].ToString();
reportViewer1.ServerReport.ReportServerCredentials = new MyReportServerCredentials(authCookie);
//Checking available parameter list
ReportParameterInfoCollection rparams = reportViewer1.ServerReport.GetParameters();
List paramList = new List();
//Hide parameters if user is External user i.e not Internal
if (isClientID != "false")
{
foreach (ReportParameterInfo rpinfo in rparams)
{
switch (rpinfo.Name)
{
case "ClientID":
paramList.Add(new Microsoft.Reporting.WebForms.ReportParameter("ClientID", isClientID, false));
break;
case "IsExported":
paramList.Add(new Microsoft.Reporting.WebForms.ReportParameter("IsExported","true", false));
break;
case "ServerType":
paramList.Add(new Microsoft.Reporting.WebForms.ReportParameter("ServerType", "true", false));
break;
case "IsReleased":
paramList.Add(new Microsoft.Reporting.WebForms.ReportParameter("IsReleased", "true", false));
break;
}
}
this.reportViewer1.ServerReport.SetParameters(paramList);
}

...and that's it. You may actually have better luck using the URL at the top of the page for your code copying and pasting as it contains HTML you can use for you logon page, too.

My biggest problem with this whole scenario was actually *finding* the help topic I needed...It would have nice if I could have searched on "forms authentication report viewer control" and been directed to the topic in question (grump, grump).

No comments: