CodeSnip: Impersonation in ThreadPool Worker Threads
page 2 of 3
by J. Ambrose Little
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 36175/ 101

Sharing Identity Between Threads

Now that the easy, or rather, intuitive part is out of the way, let's get to the meat of our problem.  The problem we're addressing is that when you are running under an impersonated identity as we set up on the previous page, that identity will not be shared by threads in the ThreadPool.  So if you are using the ThreadPool to do a little asynchronous programming, you will likely want to share the impersonated identity that your application is running under with the ThreadPool; otherwise, you may get permission denied errors because the ThreadPool will run under the default ASP.NET process identity, which is the ASPNET account, by default.

Sharing the impersonated identity, I was glad to find, was actually easier than it sounds.  First, you need to create a static (Shared in VB.NET) member of type System.Security.Principal.WindowsIdentity.  You could just add this member to any ol' web form class, but it would make it easiest to just add it to your global class in the Global.asax.cs/vb file as below.
[C#]

public class Global : HttpApplication
{
  internal static System.Security.Principal.WindowsIdentity ApplicationIdentity;

  protected void Application_Start(Object sender, EventArgs e)
  {
    ApplicationIdentity = 
      System.Security.Principal.WindowsIdentity.GetCurrent();
  }
}

[VB.NET]
Public Class Global 
  Inherits System.Web.HttpApplication

  Shared Friend ApplicationIdentity As System.Security.Principal.WindowsIdentity

  Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
    ApplicationIdentity = _
      System.Security.Principal.WindowsIdentity.GetCurrent()
  End Sub
End Class

The static/Shared keyword means that the member will be shared between multiple threads, which is exactly what we're after.  But that's not all we have to do.  You'll note that we also need to set that identity, which we can do in almost any place in the application, but again, it makes most sense to do it in the Global class, so we just set it to be the current identity of the application. 

The next step is to actually do some asynchronous work in which we'll need to use this.  For this article, I created a web form, added a Label and a Button to it, and then added the following code.

private void Button1_Click(object sender, System.EventArgs e)
{
  this.Label1.Text += "Main Thread Identity: " + 
    Global.ApplicationIdentity.Name + "<br />";
  System.Threading.ThreadPool.QueueUserWorkItem(
    new System.Threading.WaitCallback(this.DoSomething));
  System.Threading.Thread.Sleep(2000);
}

private void DoSomething(object blah)
{
  this.Label1.Text += "Worker Thread Identity Pre-Impersonation: " + 
    System.Security.Principal.WindowsIdentity.GetCurrent().Name + "<br />";
  this.TryFileAccess();
  System.Security.Principal.WindowsImpersonationContext wi = 
    Global.ApplicationIdentity.Impersonate();
  this.Label1.Text += "Worker Thread Identity During Impersonation: " + 
    System.Security.Principal.WindowsIdentity.GetCurrent().Name + "<br />";
  this.TryFileAccess();
  wi.Undo();
  this.Label1.Text += "Worker Thread Identity Post-Impersonation: " + 
    System.Security.Principal.WindowsIdentity.GetCurrent().Name + "<br />";
}

The key things to note here are that we need to create a new WindowsPrincipal object using our shared application identity, then set the current thread's CurrentPrincipal object to be our new principal, and lastly, we need to call Impersonate on our shared identity.  Once this is done, the worker thread will be impersonating the same identity that our application uses.

You may also note that I called Undo on the impersonation context.  You'll probably want to do this in order to restore the worker thread's identity.  I haven't tested, but since the threads are pooled, if you change the identity and release them back to the pool without undoing the impersonation, I imagine it'd keep that identity, which may cause unexpected results.  Also, the call to Thread.Sleep is simply there to ensure that our worker thread has time to execute before the page is rendered and cleaned up--you normally would not want to put that in your asynchronous code (it kind of defeats the purpose).

Now, originally, I just tested by printing out the name of the current identity, but I wanted to take it a step further and ensure that the impersonation was actually taking place as expected, so I added a Test.txt file to my application's root and set the security permissions on it to explicitly deny access to the ASPNET identity.  You can do this by simply removing the Users group's access to the file, assuming ASPNET is not in any other groups that have access and is not explicitly granted access.

I then created the following simple method to try to open the file and read its contents.

private void TryFileAccess()
{
 System.IO.StreamReader sr = null;
 try
 {
  sr = System.IO.File.OpenText(Server.MapPath(this.Request.ApplicationPath + "/Test.txt"));
  this.Label1.Text += "File says: " + sr.ReadToEnd() + "<br />";
 }
 catch (Exception ex)
 {
  this.Label1.Text += "Error reading file: " + ex.ToString() + "<br />";
 }
 finally
 {
  if (sr != null)
   sr.Close();
 }
}

As you can see, it is fairly straightforward; I just open a file using a StreamReader and read the contents of the file as a string, appending it onto my Label control for display.  I catch any exceptions and print them to the screen using the same approach.  And finally, I ensure the file gets closed. 

As an aside, you should always use a finally statement when accessing files to ensure they get closed properly.  In C#, you can also use the using statement,  but if you need to handle exceptions generated from the code, it's best to stick with the try-catch-finally.

So, running this code produced the following results for me:
Main Thread Identity: GRENDEL\IUSR_GRENDEL
Worker Thread Identity Pre-Impersonation: GRENDEL\ASPNET
Error reading file: System.UnauthorizedAccessException: Access to the path "H:\Projects\SomeSolution\SomeWeb\Test.txt" is denied. at System.IO.__Error.WinIOError(Int32 errorCode, String str) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync, String msgPath, Boolean bFromProxy) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize) at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize) at System.IO.StreamReader..ctor(String path) at System.IO.File.OpenText(String path) at SomeWeb.WebForm1.TryFileAccess() in h:\Projects\SomeSolution\SomeWeb\webform1.aspx.cs:line 80
Worker Thread Identity During Impersonation: GRENDEL\IUSR_GRENDEL
File says: Blah blah blah
Worker Thread Identity Post-Impersonation: GRENDEL\ASPNET

You can see by this that it does indeed work as expected:  When running under the default/non-impersonated identity of the worker thread (ASPNET), I get an access denied error trying to read my file.  But after I impersonate the shared application identity, I'm able to access the file as expected.  Also note that after calling Undo, it does in fact revert to the default process identity.


View Entire Article

User Comments

Title: didn't work   
Name: Aisha
Date: 2009-07-21 12:35:25 PM
Comment:
Thanks for the tutorial.
I tried both way. First i did myself and second used your cs file. I didn't get any error about Impersonate() but my user didn't change it remain IUSR even after Impersonate() so i couldn't reach my file..
I use identity impersonate=true in my config file..
Using .net 2005 c#
Any idea?
Title: Director   
Name: H Malhotra
Date: 2006-08-17 4:52:50 PM
Comment:
Fixed our production issue.

Thanks
Title: Sweet   
Name: Matt
Date: 2006-08-09 12:55:07 PM
Comment:
Thanks for this. It's amazing how simple this code is but yet so complicated to find on the internet!
Title: The Answer To My Problem   
Name: Jeff
Date: 2006-05-25 11:13:47 AM
Comment:
This was the exact answer that I needed. It looks like .NET 2.0 offers us the HostingEnvironment class to work with, but our application isn't there yet.
Thanks!
Title: Problem inside a thread   
Name: Jack Freudenheim
Date: 2006-03-01 3:56:31 PM
Comment:
I can get your example to work perfectly when I do it from within an aspx page, but when I tried doing the impersonation from within a thread like this:

Thread thread = new Thread( new ThreadStart(myThread.Run));
thread.Start();

I get a security exception, "Unable to impersonate user".

Any ideas? I'm stuck and getting this to work would save me!
Title: Yer Welcome   
Name: J. Ambrose Little
Date: 2006-02-22 10:53:07 AM
Comment:
I'm glad I could help, Nois.
Title: Excellent.   
Name: Deepak
Date: 2006-02-16 10:01:06 AM
Comment:
Its really excellent article.I was really searching for this for 7-8 days but was not able to locate proper code.This article was really helpful and solved my acute problem.
Title: Good Idea   
Name: Ambrose
Date: 2005-03-23 1:30:36 PM
Comment:
Stefan,

Sounds like a great idea for an article (hint hint). Did you do any benchmarks to see how much running in separate contexts affected speed? I imagine it wouldn't be noticeable under most circumstances, and if it were asynchronous, it wouldn't matter at all really.
Title: Nice   
Name: Stefan
Date: 2005-03-23 12:57:31 PM
Comment:
Very nice. I did something similar with a RemoteProxy and a custom attribute, so you would have something like

[Impersonate]
private void DoSomething(object blah)
{
...
}

The proxy would then wrap the DoSomething call with the impersonation calls (Impersonate -> DoSomething -> Undo). Like this the app developer does not really need to know how to do the impersonation (simply tag the method that needs impersonation and you are done).

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2022 ASPAlliance.com  |  Page Processed at 2022-08-14 9:48:15 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search