WindowsImpersonationContext made easy

Updated August 13, 2006 to reflect Sander’s comment.


On my old blog (in Dutch) I commented that I thought the WindowsImpersonationContext was clumsy and I wrote a replacement that you can use as follows:


Console.WriteLine(“Current user: “ + WindowsIdentity.GetCurrent().Name);
WrapperImpersonationContext context = new WrapperImpersonationContext(domain, username, password);
context.Enter();
// Execute code under other uses context
Console.WriteLine(“Current user: “ + WindowsIdentity.GetCurrent().Name);
context.Leave();
Console.WriteLine(“Current user: “ + WindowsIdentity.GetCurrent().Name);


Recently a visitor noted that the code wasn’t quite right (missng a few using statements, and I found some other small issues. The correct code (including namespace references) is below. It takes care of all that nasty calls into the Win32 API. The one thing you need to be aware of is that it requires permissions to call into a DLL (i.e. run unsafe code), which is why I added the attributes that indicate this. Unfortunately that renders this class useless in a hosted environment, unless you strong sign the assembly and pursuade the host to allow your assembly to run in Full or High trust. This however is a problem you will run into regardless your use of this class. As soon as you call LogonUser you need at least High trust. If this is something that should be possible under lower trust by default it’s up to the folks in Redmond to add this functionality to .NET and handle it as such.


using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Permissions;
using System.ComponentModel;

public class WrapperImpersonationContext
{
   [DllImport("advapi32.dll", SetLastError = true)]
   public static extern 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);

   private const int LOGON32_PROVIDER_DEFAULT = 0;
   private const int LOGON32_LOGON_INTERACTIVE = 2;

   private string m_Domain;
   private string m_Password;
   private string m_Username;
   private IntPtr m_Token;

   private WindowsImpersonationContext m_Context = null;


   protected bool IsInContext
   {
      get { return m_Context != null; }
   }

   public WrapperImpersonationContext(string domain, string username, string password)
   {
      m_Domain = domain;
      m_Username = username;
      m_Password = password;
   }

   [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
   public void Enter()
   {
      if (this.IsInContext) return;
      m_Token = new IntPtr(0);
      try
      {
         m_Token = IntPtr.Zero;
         bool logonSuccessfull = LogonUser(
            m_Username,
            m_Domain,
            m_Password,
            LOGON32_LOGON_INTERACTIVE,
            LOGON32_PROVIDER_DEFAULT,
            ref m_Token);
         if (logonSuccessfull == false)
         {
            int error = Marshal.GetLastWin32Error();
            throw new Win32Exception(error);
         }
         WindowsIdentity identity = new WindowsIdentity(m_Token);
         m_Context = identity.Impersonate();
      }
      catch (Exception exception)
      {
         // Catch exceptions here
      }
   }


   [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
   public void Leave()
   {
       if (this.IsInContext == false) return;
       m_Context.Undo();

       if (m_Token != IntPtr.Zero) CloseHandle(m_Token);
       m_Context = null;
   }
}


 

6 thoughts on “WindowsImpersonationContext made easy

  1. Sander Post author

    according to the compiler you need to compile with /unsafe for this class to work. However, this applies only to the method FormatMessage, since it uses the C++ type pointer IntPtr* Arguments.
    But the class doesn’t use this method anyway. So by removing FormatMessage, you don’t need permissions to run unsafe code.

    Reply
  2. Michiel van Otegem Post author

    Sander, you are absolutely right. That’s what you get when copying from a class that does more… sorry. That said, you will still need High or Full trust, because otherwise the call to LogonUser is still not allowed. Not having to add the /usafe compile switch is a plus, but it doesn’t matter once you deploy this.

    Reply
  3. Sincan Post author

    Hey Tilps, I actually just found the issue and since I posetd the problem, I figured I’d post the solution. The WindowsIdentity.GetCurrent() returns a WindowsIdentity. The code example on MSDN was calling WindowsIdentity.GetCurrent().Name and posting it to the console. This call to GetCurrent() still creates an object in memory and since we don’t have a reference we can’t properly dispose of it. To remedy this, every time the example used GetCurrent().Name, I stored the WindowsIdentity reference, used its Name property, then called Dispose() on it. If you re-use the same reference for each of the three calls, then you must call Dispose() in between each re-assignment or a reference will be left in memory. Sorry about the posts but I hate to post a question and not post an answer for future blog readers!

    Reply
  4. Michiel van Otegem

    Erik,

    I originally wrote this code for a Microsoft Content Management Server implementation. MS CMS runs ASP.NET. In ASP.NET every thread comes from the thread pool and gets the identity of the caller, which is either IUSR for anonymous users, or the Windows Account in case of Windows Integrated security. This was an intranet scenario, so all users were using their Windows account. We never encountered issues with people have the wrong identity, which is logical because each thread has its own context. This is a very longer answer to say: yes, I believe it is thread safe (but I can’t be 100% sure).

    Regards,
    Michiel

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

− 1 = 8

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>