none
Tasks and Impersonation

    Question

  • From the documentation I am not sure I understand how WindowsIdentity is maintained across threads when using Task objects in .NET 4 (TPL).

    Do I need to wrap each delegate I want to run with another delegate that Impersonates again? E.g I defined these two helper methods:

       // use to wrap impersonation code around a void method
    void Impersonate(Action action, WindowsIdentity identity) {
    WindowsImpersonationContext impersonation = null;
    if (identity != null)
    impersonation = identity.Impersonate();
    try {
    action();
    }
    finally {
    if (impersonation != null)
    impersonation.Undo();
    }
    }

    // use to wrap impersonation code around a function
    TResult Impersonate<TResult>(Func<TResult> func, WindowsIdentity identity) {
    TResult result;
    WindowsImpersonationContext impersonation = null;
    if (identity != null)
    impersonation = identity.Impersonate();
    try {
    result = func();
    }
    finally {
    if (impersonation != null)
    impersonation.Undo();
    }
    return result;
    }

    Or can I simply create the Task object in the impersonated context and it will automatically execute under that impersonation even if the scheduler runs it on some ThreadPool thread later. Like this:

        WindowsIdentity identity = <some logon user code>;
    WindowsImpersonationContext impersonation = identity.Impersonate();
    try {
    Task.Factory.StartNew(() => {
    ... code that may use parameters from current context...
    });
    }
    finally {
    impersonation.Undo();
    }

     

    I would be grateful for any pointers,

    Karl

    Wednesday, July 21, 2010 7:49 PM

Answers

  • Hi Karl-

    By default, if you create the Task object in the impersonated context, when that Task later runs it will do so under the same impersonated context.  This happens for you automatically because of how Task utilizes ExecutionContext, which includes SecurityContext, which includes the WindowsIdentity.  The ExecutionContext is captured when the Task is created, and the body of the task runs under that ExecutionContext.

    I hope that helps.

    Friday, July 23, 2010 4:50 AM

All replies

  • Hi Karl-

    By default, if you create the Task object in the impersonated context, when that Task later runs it will do so under the same impersonated context.  This happens for you automatically because of how Task utilizes ExecutionContext, which includes SecurityContext, which includes the WindowsIdentity.  The ExecutionContext is captured when the Task is created, and the body of the task runs under that ExecutionContext.

    I hope that helps.

    Friday, July 23, 2010 4:50 AM

  • Hi Karl-

    By default, if you create the Task object in the impersonated context, when that Task later runs it will do so under the same impersonated context.  This happens for you automatically because of how Task utilizes ExecutionContext, which includes SecurityContext, which includes the WindowsIdentity.  The ExecutionContext is captured when the Task is created, and the body of the task runs under that ExecutionContext.

    I hope that helps.

    Yes, that helps. Thank you very much!

     

    Friday, July 23, 2010 5:15 PM
  • I can confirm that this is untrue for ASP.NET MVC apps configured with ASP.NET Impersonation. Consider the following code:

    (The caller is DOMAIN\myUserAccount)

    (The MVC app runs in an app pool as DOMAIN\myServiceAccount)

    public class MyController : Controller
    
    {
    
     private static readonly ILog LOG = //init code
    
    
    
     public ActionResult DoTasks()
    
     {
    
     LOG.DebugFormat("I am running as {0}",
    
      WindowsIdentity.GetCurrent().Name);
    
    
    
     Task a = TaskFactory.StartNew(this.CreateTask("A"));
    
     Task b = TaskFactory.StartNew(this.CreateTask("B"));
    
     Task c = TaskFactory.StartNew(this.CreateTask("C"));
    
    
    
     Task.WaitAll(new Task[] { a, b, c });
    
    
    
     // returns an ActionResult
    
     }
    
    
    
     public Task CreateTask(string taskName)
    
     {
    
     LOG.DebugFormat("Creating task {0} as {1}", 
    
      taskName, WindowsIdentity.GetCurrent().Name));
    
    
    
     return new Task(() =>
    
     {
    
      LOG.DebugFormat("I am running as {0}",
    
      WindowsIdentity.GetCurrent().Name);
    
     });
    
     }
    
    }
    
    

    The output of this code is:

    I am running as DOMAIN\myUserAccount

    Creating task A as DOMAIN\myUserAccount

    Creating task B as DOMAIN\myUserAccount

    I am running as DOMAIN\myServiceAccount

    Creating task C as DOMAIN\myUserAccount

    I am running as DOMAIN\myServiceAccount

    I am running as DOMAIN\myServiceAccount


    Tuesday, August 02, 2011 7:17 PM
  • jar349, in your example, what's the value of ExecutionContext.IsFlowSuppressed()?
    Tuesday, August 02, 2011 7:39 PM
  • Stephen,

    Where in my code would you like me to read the value?  Anywhere?  Or somewhere specific?

    Let me know!

    ~ jR

    Tuesday, August 02, 2011 8:52 PM
  • Hi jR-

    At the beginning of your DoTasks method would be a good place to check.

    Also, could you try adding the following to your configuration file:

    <configuration>
      <runtime>
        <alwaysFlowImpersonationPolicy enabled="true"/>
      </runtime>
    </configuration>

    Thanks.

    Tuesday, August 02, 2011 11:41 PM
  • Stephen,

    Thanks for continuing to track this issue - I really appreciate your help.  I placed the call in my DoTasks method and added the runtime configuration snippet to my web.config.

    On execution, the value is false and the behavior that I originally described still occurs.

    Inside my real tasks, I am  delegating the user's credentials to SQL Server.  I didn't want to tell you this because I feared it might lead you to think that the problem is with my configuration of constrained delegation.  When I change my code from using TPL to being synchronous, I get results from the database because everything is correctly configured.  But I think I should mention it now in case there's some strange gotcha about SSPI being unable to pick up the impersonation context for some reason.

    I think you're having me check IsFlowSuppressed() to ensure that the SecurityContext is correctly flowing to my worker threads.  That does seem to be the case even though IsFlowSuppressed() = false.  What other environmental factors could inhibit the flow of my main thread's ExecutionContext onto TPL threads?

    Thanks!!

    ~ jR

    Wednesday, August 03, 2011 3:39 PM
  • Stephen,

    Here's an interesting thing: SecurityContext.IsWindowsIdentityFlowSuppressed() is True!

    How can I resume that flow?

    Thanks!

    ~ jR

    Wednesday, August 03, 2011 4:54 PM
  • Here is a work-around that will work: inside of the lambda, impersonate the user that ASP.NET has already impersonated for you.

    public Action CreateTask(string taskName)
    {
     WindowsIdentity impersonatedUser = WindowsIdentity.GetCurrent();
    
     return () =>
     {
      using (WindowsImpersonationContext ctx = impersonatedUser.Impersonate())
      {
       LOG.DebugFormat("I am running as {0}",
        WindowsIdentity.GetCurrent().Name);
      }
     };
    }
    

    ~ jR
     

    • Proposed as answer by jar349 Wednesday, August 03, 2011 7:40 PM
    Wednesday, August 03, 2011 7:39 PM
  • Do you by any chance have the legacyImpersonationPolicy config switch enabled?
    Thursday, August 04, 2011 5:27 PM
  • I do not.  Would you like me to explicitly set it to false?
    Thursday, August 04, 2011 5:50 PM
  • Hello Stephen,

    I'm running an impersonation issue in my app.

    I was reading this post and I see that you are saying that:

    By default, if you create the Task object in the impersonated context, when that Task later runs it will do so under the same impersonated context

    Now, this is my scenario. I'm running a web application, I have the web.config:

    <identity impersonate="true" userName="myuser" password="mypass" />
    

    In the global.asax in the app_starts method I'm doing sth like this:

    //Watching here the WindowsIdentity.GetCurrent().Name
    //I get "myuser" 
    Task.Factory.StartNew(new Action(()=>{
         //Watching here the WindowsIdentity.GetCurrent().Name 
         //I get the default asp.net user 
    }));
    


    If I modify the values of the runtime element in the Framework .config file like this:

            <legacyImpersonationPolicy enabled="true"/>
            <alwaysFlowImpersonationPolicy enabled="false"/>
    
    


    Then I will get the expected result. The issue is that I cant modify those values since it is a shared web server, and I cant override those values in my web.config since those are not taken into consideraiton (I tested adding the <runctime> element in the web.config and those values are not making any difference in the execution).

    Then I finally used the approach explained bellow by jar349 by explicitly impersonating inside the delegate.

    Now my question is probably more related to what happens with the reference to the WindowsIdentity that the delegate is accessing? Is that creating a string dependency between the main thread and the created task?

     

    Thanks,

    Fernando

    Tuesday, October 25, 2011 5:53 PM
  • Stephen - Is it possible to impersonate in a custom scheduler?  I created a TaskScheduler with a single thread executing under a different user context.  As you stated, the task was executed under the context it was created and not the thread of the scheduler.  I understand, just curious if there is a way to impersonate the threaded execution rather than the individual task creations.   
    • Edited by rcardare Wednesday, May 30, 2012 3:19 PM
    Wednesday, May 30, 2012 2:08 PM