Introduction
This article addresses a simple approach to applying impersonation to threading;
by default when impersonation is applied, it does not extend to threads spawned
after impersonation is set. It is, however, possible to apply impersonation such
that threads spawned by the application are spawned under the impersonated user.
This article and the accompanying code will provide an example in which a user
is impersonated and threads are subsequently spawned under the identity of the
impersonated user.
Figure 1: Demonstration Application
In order to run the application effectively, you will need to have a test user
account on the machine so that you can impersonate that account. If you need to,
open up the control panel, go to user accounts, and add another user to the
machine.
Getting Started:
In order to get started, unzip the included project and save it to your hard
drive. There is a single project included in the download.
Figure 2: Solution Explorer for the Demo Application
The application project is called "ImpersonationTest". This application contains
a single class (Impersonation.vb) and a form used to test impersonation. If you
want to use impersonation as provided herein, just copy the contents of the
impersonation class into your project.
Running the Application
Given that you have at least one account to impersonate on your local system,
run the application. Key in the domain (or machine name), the user ID, and the
password.
To begin, click on the Impersonate With Threads button. This will set the login
the user provided using the domain, user ID, and password provided and set up an
impersonation context, and call impersonate on the windows identity that you
have provided. Once that is done, the method called with set the current thread
principal to the impersonated user. With that done, any threads spawned will run
under the impersonated user's identity.
You can test that this is the case by clicking next on the button that is
labeled, "Get Current Identities for All". That method will report on the
current principal Windows identity and the current thread principal identity.
After confirming that both the current principal Windows identity and the
current thread principal identity are both set to the impersonated user's
account, click on the button labeled, "Undo User Impersonation". This will
restore the current logged in user as the current principal Windows identity but
it will do nothing to the current thread principal. Click on the "Get Current
Identities for All" button again and you will see that the current principal
Windows identity is set back to the logged in user but the current thread
principal is still set to the impersonated user.
Now, click the button labeled, "Undo Threading Impersonation". After that, click
the "Get Current Identities for All" button again and you will see that both the
current principal Windows identity and the current thread principal are both set
back to the current logged user.
You can now click the "Impersonate Without Threads" button and check the
identities again. This button just sets impersonation to the supplied user but
does not set the thread principal to match and so you will see any threads
spawned from this state will run under the currently logged in user rather than
the impersonated user.
Code: Impersonation.vb
The Impersonation class does the work of setting up and removing
impersonation. The class uses four Win32 API calls to handle logging in a user
and removing a logged in, impersonated
user. These Win32 API calls are well documented and there is nothing unique
about them as they are included in this project.
The class begins with a few imports related to Windows security, threading, and
interop. I will point out here that the class must be declared as Friend. You
can review the Win32 calls in the first region of the code.
Imports System.Web
Imports System.Security.Principal
Imports System.Security.Permissions
Imports System.Runtime.InteropServices
Imports System.Threading
Friend Class Impersonate
#Region "Win32
API Methods"
Const LOGON32_LOGON_INTERACTIVE As Integer =
2
Const LOGON32_PROVIDER_DEFAULT As Integer =
0
Declare Function LogonUserA Lib "advapi32.dll" (ByVal lpszUsername As String,
_
ByVal lpszDomain As String,
_
ByVal lpszPassword As String,
_
ByVal dwLogonType As Integer,
_
ByVal dwLogonProvider As Integer,
_
ByRef phToken As IntPtr) As Integer
Declare Auto Function DuplicateToken Lib "advapi32.dll" (
_
ByVal ExistingTokenHandle As IntPtr,
_
ByVal ImpersonationLevel As Integer,
_
ByRef DuplicateTokenHandle As IntPtr) As Integer
Declare Auto Function RevertToSelf Lib "advapi32.dll" () As Long
Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Long
#End Region
#Region "Default
Constructor"
Private Sub New()
MyBase.New()
End Sub
#End Region
The class methods region contains the methods of interest; the first function
provided is entitled, "ImpersonateValidUser". It accepts three arguments as the
user name, the domain, and the user's password. This function will establish the
passed in user as the current Windows identity through impersonation if that
user can successfully be authenticated. This function will not have impact upon
any threads that are subsequently spawned following the establishment of the
impersonation context and any such threads will run under the identity of the
current logged in user.
#Region "Class
Methods"
''' <summary>
'''
Impersonate a user without allowing threads to run under the impersonated user
''' </summary>
''' <param
name="userName"></param>
''' <param
name="domain"></param>
''' <param
name="password"></param>
''' <returns></returns>
''' <remarks></remarks>
Friend Shared Function ImpersonateValidUser(ByVal userName As String,
_
ByVal domain As String,
ByVal password As String) As
WindowsImpersonationContext
Dim impersonationContext As WindowsImpersonationContext = Nothing
Dim tempWindowsIdentity As WindowsIdentity
Dim token As IntPtr = IntPtr.Zero
Dim tokenDuplicate As IntPtr = IntPtr.Zero
Try
If RevertToSelf() Then
If LogonUserA(userName,
domain, password, LOGON32_LOGON_INTERACTIVE, _
LOGON32_PROVIDER_DEFAULT, token) <> 0 Then
If DuplicateToken(token,
2, tokenDuplicate) <> 0 Then
tempWindowsIdentity = New WindowsIdentity(tokenDuplicate)
impersonationContext = tempWindowsIdentity.Impersonate()
End If
End If
End If
Return impersonationContext
Catch ex As Exception
EventLog.WriteEntry(Application.ProductName,
ex.Message)
Finally
If Not tokenDuplicate.Equals(IntPtr.Zero) Then
CloseHandle(tokenDuplicate)
End If
If Not token.Equals(IntPtr.Zero) Then
CloseHandle(token)
End If
End Try
Return Nothing
End Function
The next method in the class will do exactly the same thing as is accomplished
in the previous method but it will also set the current thread principal to the
impersonated user. Threads spawned after this call is successfully made will run
under the context of the impersonated user.
''' <summary>
'''
Impersonate a user and set the thread current principal to that impersonated
'''
user. Afterwards, you can launch threads that will run under the impersonated
''' user.
''' </summary>
''' <param
name="userName"></param>
''' <param
name="domain"></param>
''' <param
name="password"></param>
''' <returns></returns>
''' <remarks></remarks>
Friend Shared Function ImpersonateValidUserAndSetThreadPrincipal(ByVal userName AsString, ByVal domain As String, ByVal password As String) As WindowsImpersonationContext
Dim impersonationContext As WindowsImpersonationContext = Nothing
Dim tempWindowsIdentity As WindowsIdentity
Dim token As IntPtr = IntPtr.Zero
Dim tokenDuplicate As IntPtr = IntPtr.Zero
Dim user As IIdentity
Dim principal As WindowsPrincipal
Try
If RevertToSelf() Then
If LogonUserA(userName,
domain, password, LOGON32_LOGON_INTERACTIVE, _
LOGON32_PROVIDER_DEFAULT, token) <> 0 Then
If DuplicateToken(token,
2, tokenDuplicate) <> 0 Then
tempWindowsIdentity = New WindowsIdentity(tokenDuplicate)
impersonationContext = tempWindowsIdentity.Impersonate()
'
apply impersonation to threading
user = New WindowsIdentity(token, "NTLM",
WindowsAccountType.Normal, True)
principal = New WindowsPrincipal(user)
Thread.CurrentPrincipal
= principal
End If
End If
End If
Return impersonationContext
Catch ex As Exception
EventLog.WriteEntry(Application.ProductName,
ex.Message)
Finally
If Not tokenDuplicate.Equals(IntPtr.Zero) Then
CloseHandle(tokenDuplicate)
End If
If Not token.Equals(IntPtr.Zero) Then
CloseHandle(token)
End If
End Try
Return Nothing
End Function
The last method of interest in the class is used to restore the current thread
principal to the logged in user after impersonation is removed.
''' <summary>
'''
After calling undo on impersonation, call this method to restore the thread
''' current principal to the
logged on user.
''' </summary>
''' <remarks></remarks>
Friend Shared Sub RestoreThreadPrincipal()
Try
Dim principal As WindowsPrincipal
principal = New WindowsPrincipal(WindowsIdentity.GetCurrent)
Thread.CurrentPrincipal
= principal
Catch ex As Exception
EventLog.WriteEntry(Application.ProductName,
ex.Message)
End Try
End Sub
#End Region
End Class
Code: Form1.vb
The code behind the demo application basically consists of the button click
event handlers used to add and remove impersonation and to report on the status
of that impersonation.
The class begins with a few imports to support threading and Windows security.
Imports System.Security.Principal
Imports System.Security
Imports System.Security.Permissions
Imports System.Threading
Imports System.Text
''' <summary>
''' Test application used to confirm
that threads are launched and started under
''' an impersonated identity
''' </summary>
''' <remarks></remarks>
Public Class Form1
There are two class
wide variables declared, one is an impersonation context object and the other is
a thread.
#Region "Class
Scope VVariable Declarations"
Private IPers As System.Security.Principal.WindowsImpersonationContext
Private t1 As Thread
#End Region
The constructor for the demonstration form sets the impersonation context object
to nothing to begin with.
#Region "Constructor"
Public Sub New()
InitializeComponent()
IPers = Nothing
End Sub
#End Region
The remainder of the
functions/subroutines in the demo form class are annotated below.
#Region "Event
Handlers"
''' <summary>
'''
Impersonates another authorized user and spawns a single thread under the
'''
impersonated identity.
'''
'''
To test this, if you do not have a user to impersonate on the machine, add a new
'''
user and provide that user's information in the form fields (domain, user id,
and
''' password)
''' </summary>
''' <param
name="sender"></param>
''' <param
name="e"></param>
''' <remarks></remarks>
Private Sub btnImpersonate_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnImpersonate.Click
Try
IPers = Impersonate.ImpersonateValidUserAndSetThreadPrincipal(txtUserId.Text,
txtDomain.Text, txtPassword.Text)
t1 = New Thread(AddressOf ThreadTask)
t1.IsBackground = True
t1.Start()
Catch ex As Exception
MessageBox.Show(ex.Message, "Impersonation
Error")
End Try
End Sub
''' <summary>
'''
Impersonates a user but does not apply impersonation to the current thread
''' principal
''' </summary>
''' <param
name="sender"></param>
''' <param
name="e"></param>
''' <remarks></remarks>
Private Sub btnImpersonateWOthreads_Click(sender As System.Object,
e As
System.EventArgs) Handles btnImpersonateWOthreads.Click
Try
IPers = Impersonate.ImpersonateValidUser(txtUserId.Text,
txtDomain.Text,
txtPassword.Text)
t1 = New Thread(AddressOf ThreadTask)
t1.IsBackground = True
t1.Start()
Catch ex As Exception
MessageBox.Show(ex.Message, "Impersonation
Error")
End Try
End Sub
''' <summary>
'''
Removes Impersonation by reverting back to logged in user but does not
'''
revert the thread principal back to the logged in user
''' </summary>
''' <param
name="sender"></param>
''' <param
name="e"></param>
''' <remarks></remarks>
Private Sub btnUndoImpersonation_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnUndoImpersonation.Click
If Not IPers Is Nothing Then
'
undo impersonation at the level of the current windows identity
IPers.Undo()
End If
End Sub
''' <summary>
'''
Removes impersonation by reverting back to the logged in user and also
'''
reverts the thread principal back to the logged in user
''' </summary>
''' <param
name="sender"></param>
''' <param
name="e"></param>
''' <remarks></remarks>
Private Sub btnUndoThreadImpersonation_Click(sender As System.Object,
e As
System.EventArgs) Handles btnUndoThreadImpersonation.Click
If Not IPers Is Nothing Then
'
undo impersonation at the level of the current windows identity
IPers.Undo()
'
removes prior thread impersonation by setting it back to the
'
current windows identity
Impersonate.RestoreThreadPrincipal()
End If
End Sub
''' <summary>
'''
Inform on the current windows identity and the current thread principal identity
''' - confirms that the
'''
threads are running under the impersonated identity or not
''' </summary>
''' <param
name="sender"></param>
''' <param
name="e"></param>
''' <remarks></remarks>
Private Sub btnWhoIsUser_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnWhoIsUser.Click
Dim sb As New StringBuilder
sb.Append("Current
Principal: " &
System.Security.Principal.WindowsIdentity.GetCurrent().Name().ToString()
&
Environment.NewLine)
sb.Append("Current
Thread Principal: " &
Thread.CurrentPrincipal.Identity.Name().ToString())
MessageBox.Show(sb.ToString(), "Current
Identities")
End Sub
#End Region
#Region "Class
Methods"
''' <summary>
'''
Useless long running task -
'''
This just provides a repeating task that can run under the thread
''' </summary>
''' <remarks></remarks>
Private Sub ThreadTask()
Dim counter As Integer =
0
Dim endVal As Integer =
3
'
unending loop to represent long running task
Do Until counter
= endVal
If counter
= (endVal - 1) Then
counter = 0
Else
counter += 1
End If
Loop
End Sub
#End Region
End Class
Summary
The article addresses a simple approach that may be used to extend impersonation
to include threading; by default, establishing an impersonation context has no
effect upon the account under which threads are spawned. However, with a little
bit of extra efforts it is not difficult to include threads within the
impersonation. Depending upon what the threads are doing and accessing, it may
be essential to enforce the impersonation at the level of the threads