7

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?

Jarrett Meyer
  • 19,333
  • 6
  • 58
  • 52
  • Does the impersonation user have access to the report server -- that report in particular? – DCNYAM Sep 17 '09 at 14:37
  • Have you tried running IIS under the impersonation user on your dev machine (localhost) to closer emulate what is happening on your test server? It sounds like a flat out permissions issue with your impersonation user against the reporting server or reporting server database. I assume the impersonation user is a domain account. – Michael Maddox Sep 17 '09 at 15:03
  • @NYSystemsAnalyst - Yes, the impersonation user has access to the appropriate directory on the report server. – Jarrett Meyer Sep 17 '09 at 15:50

2 Answers2

6

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
  • 19,333
  • 6
  • 58
  • 52
  • 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 Sep 17 '09 at 16:36
  • 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 Sep 17 '09 at 17:18
  • 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 Aug 05 '10 at 14:38
  • By creating a new domain user, doesn't that mean that you won't be able to restrict the reports down to a user level though as everyone using you app will be authenticated using the same credentials? Thanks – Gareth Jul 06 '15 at 13:08
2

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

  • 1
    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 Sep 22 '09 at 10:14