views:

2254

answers:

2

I have a report saved on a SQL2005 reporting server, and I want to return a rendered PDF of this report. I've figured this out when working with a local *.rdlc file (and I've blogged about it), but not when the *.rdl resides on a reporting server. I am getting a 401 Not Authorized error at the line...

reportViewer.ServerReport.SetParameters(reportDefinition.ReportParameters);

Here's the method used to render the report.

public byte[] Render(IReportDefinition reportDefinition)
{
    var reportViewer = new ReportViewer();
    byte[] renderedReport;
    try
    {
        var credentials = new WindowsImpersonationCredentials();
        reportViewer.ServerReport.ReportServerUrl = new Uri("http://myssrsbox", UrlKind.Absolute);
        reportViewer.ServerReport.ReportServerCredentials = credentials;
        reportViewer.ServerReport.ReportPath = reportDefinition.Path;
        // Exception is thrown on the following line...
        reportViewer.ServerReport.SetParameters(reportDefinition.ReportParameters);

        string mimeType;
        string encoding;
        string filenameExtension;
        string[] streams;
        Warning[] warnings;

        renderedReport = reportViewer.ServerReport.Render(reportDefinition.OutputType, reportDefinition.DeviceInfo, out mimeType, out encoding, out filenameExtension, out streams, out warnings);
    }
    catch (Exception ex)
    {
        // log the error...
        throw;
    }
    finally
    {
        reportViewer.Dispose();
    }
    return renderedReport;
}

The other thing that you're missing is the WindowsImpersonationCredentials class.

public class WindowsImpersonationCredentials : IReportServerCredentials
{
    public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
    {
        authCookie = null;
        userName = password = authority = null;
        return false;
    }

    public WindowsIdentity ImpersonationUser
    {
        get { return WindowsIdentity.GetCurrent(); }
    }

    public ICredentials NetworkCredentials
    {
        get { return null; }
    }

    public override string ToString()
    {
        return String.Format("WindowsIdentity: {0} ({1})", this.ImpersonationUser.Name, this.ImpersonationUser.User.Value);
    }
}

Other things you may need to know...

  • This is running on an intranet, and impersonation is turned on.
  • Logging indicates that the impersonation user is being set correctly.
  • This does work when running in Visual Studio (http://localhost:devport), and it does work when running on my development box (http://localhost/myApplication). It does not work when running on our test or production servers.
  • I have tried solutions both with and without system.net.defaultProxy settings in web.config. Neither worked.

What am I doing wrong? Is it a server setting? Is it code? Is it web.config?

+3  A: 

We did finally figure out the problem. Our network administrators have disabled double-hopping, so while the impersonation was correctly connecting as domain\jmeyer, the application was still attempting to connect to the SRS box with domain\web01$. Why is it set up like this? Because double-hopping is a massive security hole. (Or so I was told. Does this sound like something you would read on The Daily WTF?)

Our solution was to create a generic domain\ssrs_report_services user, and connect with that user with the following network credentials

public class CustomCredentials : IReportServerCredentials
{
    public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
    {
        authCookie = null;
        userName = password = authority = null;
        return false;
    }

    public WindowsIdentity ImpersonationUser
    {
        get { return null; }
    }

    public ICredentials NetworkCredentials
    {
        get { return new NetworkCredential("ssrs_report_services", "password", "domain") ; }
    }    
}

The above is the classic example solution that you can find all over the internets.

Jarrett Meyer
In fact, I don't think double hopping is "allowed" under any configuration. It's not clear to me why your dev environment was working, but it was probably configured differently.
Michael Maddox
As it was explained to me, it works on dev because it doesn't count as a hop to go from localhost (as client) -> localhost (as IIS) -> SRS
Jarrett Meyer
FYI, the admins can configure delegation on a per server basis. Check out: http://serverfault.com/questions/16364/is-there-a-way-to-get-kerberos-credentials-to-delegate-twice-why-not
Brian Vander Plaats
+1  A: 

"Double hopping" is allowed - swith on Kerberos authentication ... (so long as it is working properly!)

Unfortunately, we developers are constrained quite a bit by the environment and have no say in the infrastructure. Thanks for giving me something new to read about, though.
Jarrett Meyer