Impersonating another user with the Windows Form Report Viewer control and SQL Server Reporting Services 2005
The Winform Report Viewer control is pretty nifty, but occasionally you'll want to access a remote report server using a different identity than the user logged in at the console. It can be done, but takes a tiny bit of work.
You're going to be calling some native Win32 functions to get this to work, namely Logonuser, DuplicateToken, and CloseHandle
I'm also being a little bit lazy here, and am just copying in the complete source from the Winform I created the example in. I'm sure you can clean this up quite a bit more.
WARNING: Notice how I'm pretty much hard coding user credentials in the sample below? Isn't that a dumb thing to do? In your code you won't do the same thing, right? (Answers at the bottom of this post)
OK, so here's what we're doing:
Creating a Token, and passing it to the LogonUser function while we logon with a different set of credentials
Making a copy of the token
Creating a WindowsIdentity object off the duplicate token
Assigning this WindowsIdentity object (newId) to the Report Viewer control, specifically reportViewer1.ServerReport.ReportServerCredentials.ImpersonationUser
BTW, this sample also assumes that you have already set properties of the Report Viewer control (ProcessingMode, ReportPath, ReportServerUrl etc.) using the IDE....
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using MyCode.localhost;
using System.Xml;
using System.Web.Services.Protocols;
using System.IO;
using System.Diagnostics;
using System.Security.Principal;
using System.Runtime.InteropServices;
namespace SampleCode
{
public partial class Form2 : Form
{
[DllImport("advapi32.dll", SetLastError=true)] public extern static bool LogonUser(String lpszUsername, String lpszDomain,
String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
// Impersonation code STOLEN from Q319615...shame on me, I feel dirty.
IntPtr tokenHandle = new IntPtr(0);
IntPtr dupeTokenHandle = new IntPtr(0);
string[] args = new string[3] { "Domain", "user", "password" };
// args[0] - DomainName
// args[1] - UserName
// args[2] - Password
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
const int SecurityImpersonation = 2;
tokenHandle = IntPtr.Zero;
dupeTokenHandle = IntPtr.Zero;
try
{
// Call LogonUser to obtain an handle to an access token.
bool returnValue = LogonUser(args[1], args[0], args[2],
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
ref tokenHandle);
if (false == returnValue)
{
Console.WriteLine("LogonUser failed with error code : {0}",
Marshal.GetLastWin32Error());
return;
}
// Check the identity.
Console.WriteLine("Before impersonation: "
+ WindowsIdentity.GetCurrent().Name);
bool retVal = DuplicateToken(tokenHandle, SecurityImpersonation, ref dupeTokenHandle);
if (false == retVal)
{
CloseHandle(tokenHandle);
Console.WriteLine("Exception in token duplication.");
return;
}
// The token that is passed to the following constructor must
// be a primary token to impersonate.
WindowsIdentity newId = new WindowsIdentity(dupeTokenHandle);
WindowsImpersonationContext impersonatedUser = newId.Impersonate();
// Check the identity.
Console.WriteLine("After impersonation: "
+ WindowsIdentity.GetCurrent().Name);
// Assign the new identity to the R.V. control and Refresh the control
reportViewer1.ServerReport.ReportServerCredentials.ImpersonationUser = newId;
reportViewer1.RefreshReport();
// Stop impersonating.
impersonatedUser.Undo();
// Check the identity.
Console.WriteLine("After Undo: " + WindowsIdentity.GetCurrent().Name);
// Free the tokens.
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
if (dupeTokenHandle != IntPtr.Zero)
CloseHandle(dupeTokenHandle);
}
catch (Exception ex)
{
Console.WriteLine("Exception occurred. " + ex.Message);
}
}
}
}
(Answers: Yes, I did / Yes, it is / NO, I won't.)
Comments
Anonymous
November 05, 2005
Is Windows Authentication the only option for SQL Reporting Services? We had a similar issue using Cognos but Cognos allows for both Forms and Windows Authentication.Anonymous
November 05, 2005
The comment has been removedAnonymous
October 03, 2006
This is fantastic stuff, extremely useful for my current situation, thanks :)Anonymous
February 01, 2008
Is it just me, or does this not work under Win2K? Works great in XP, but the reportviewer just shows up blank in 2000. Any ideas/fixes/workarounds?Anonymous
May 26, 2008
Using NetworkCredentials is shorter way :)NetworkCredential myCred = newNetworkCredential(<UserName>, <Password>, <DomainName>); reportViewer1.ServerReport.ReportServerCredentials.NetworkCredentials = myCred;Anonymous
September 10, 2008
NetworkCredentials would be nice if the actual property wasn't read only ;)Anonymous
August 04, 2009
@Kahka...NetworkCreds doesn't workAnonymous
August 08, 2011
reportViewer1.ServerReport.ReportServerCredentials.ImpersonationUser = newId property imperonsate is read only.