Await, Async, Mvc and Impersonation

I’ve been using AsyncControllers in Asp.net mvc for quite a while now.   I’ve also been using the Tasks Parallel Library sparingly.  Only recently have I used them together, with and without the new .Net 4.5 async and await keywords.  Throw unmanaged impersonation into the mix and you’re in for a smorgasbord of technologies that don’t necessarily play well together.

I want to tell you a story, but I must first set the stage for you.

  • I was developing an internal web app for my company TempWorks Software.
  • I Wanted to make use of concurrency wherever possible.
  • The data layer needed several different elevated credentials in varying contexts, other than that of the AppPoolIdentity.

Our last release to production web product, WebCenter 6 used AsyncControllers, taking advantage of the AsyncManager, rather than Task’s with continuation. If you are unfamiliar with either, have a look at this concise outline of the evolution of Mvc asynchronous controllers here. This time, I chose to use the more straightforward, Task with Task.ContinueWith.

The first problem I encountered was trying to make my impersonation context flow through my tasks and continuations. Imagine something like the following inside one of my Actions (the important thing to note is how I am doing impersonation).

FeatureEditorViewModel viewModel;

using (ElevatedUser.Impersonate())
{
    var features = LicensingService
        .GetFeatures(false);

    viewModel = new FeatureEditorViewModel(features, versionID, productID);
}

return View(viewModel);

The IDisposable ElevatedUser object you see above is a derivative of the code in the following article: Windows Impersonation Using C#. The important part about this is that it is IDisposable in my class, and that it is calling unmananged code via DllImport to do impersonation.

Interestingly enough, I never noticed any problem with this until I deployed to production servers. The reason was, that because I was using IIS Express, my AppPoolIdentity was in fact MY user account, and I happened to have permissions on the object I was accessing. When I deployed to production, the AppPoolIdentity was no longer me, it was the App Pool user account, thus making all data calls under the wrong context.

I scoured the net in search of answers, and discovered that I had a few options.

1. Change the aspnet.config file (there are 2 of them if you have an x64 machine), setting the value “legacyImpersonationPolicy” to enabled=false, and “alwaysFlowImpersonationPolicy” to enabled=true
2. Do the same as above except in the context of the app pool only (I discovered later on that this does not work)
3. Capture the impersonation context outside of my thread, and then re-impersonate inside of my thread

I chose option 3 because I wanted to adhere to the least privilege principal. I wanted keep elevated credentials to the absolute minimum so as not to expose any overlooked security holes in my application.

I came up with my own TPL extension method class that I could use in place of the default TPL methods.

using System;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;

public static class TaskFactoryExtensions
{

    public static Task StartNewImpersonated(this TaskFactory taskFactory, Action action)
    {
        var identity = WindowsIdentity.GetCurrent();
        return taskFactory.StartNew(() =>
        {
            using (identity.Impersonate()) 
            {
                action();
            }
        });
    }

    public static Task<TResult> StartNewImpersonated<TResult>(this TaskFactory taskFactory, Func<TResult> function)
    {
        var identity = WindowsIdentity.GetCurrent();
        return taskFactory.StartNew<TResult>(() =>
        {
            using (identity.Impersonate())
            {
                return function();
            }
        });
    }

    public static Task ContinueWithImpersonated<TResult>(this Task<TResult> task, Action<Task<TResult>> continuationAction)
    {
        var identity = WindowsIdentity.GetCurrent();

        return task.ContinueWith((antecedent) =>
        {
            using (identity.Impersonate())
            {
                continuationAction(antecedent);
            }
        });
    }

    public static Task<TNewResult> ContinueWithImpersonated<TResult, TNewResult>(this Task<TResult> task, Func<Task<TResult>, TNewResult> continuationFunction)
    {
        var identity = WindowsIdentity.GetCurrent();

        return task.ContinueWith<TNewResult>((antecedent) =>
        {
            using (identity.Impersonate())
            {
                return continuationFunction(antecedent);
            }
        });
    }

    public static Task ContinueWhenAllImpersonated(this TaskFactory taskFactory, Task[] tasks, Action<Task[]> continuationAction)
    {
        var identity = WindowsIdentity.GetCurrent();

        return taskFactory.ContinueWhenAll(tasks, (antecedent) =>
        {
            using (identity.Impersonate())
            {
                continuationAction(antecedent);
            }
        });
    }

    public static Task<TResult> ContinueWhenAllImpersonated<TResult>(this TaskFactory taskFactory, Task[] tasks, Func<Task[], TResult> continuationFunction)
    {
        var identity = WindowsIdentity.GetCurrent();

        return taskFactory.ContinueWhenAll<TResult>(tasks, (antecedent) =>
        {
            using (identity.Impersonate())
            {
                return continuationFunction(antecedent);
            }
        });
    }

    public static void ForAllImpersonated<TSource>(this ParallelQuery<TSource> source, Action<TSource> action)
    {

        var identity = WindowsIdentity.GetCurrent();

        source.ForAll(new Action<TSource>((tsource) =>
        {
            using (identity.Impersonate())
            {
                action(tsource);
            }
        }));
        
    }

}

This worked great aside from the fact that everytime I needed to use a varied overload in the TPL, I needed to code a new extension method in my class.

Enter .Net 4.5… Async, await.

Before converting my app to 4.5 and the “new way” of doing things, I ingested as many Kindle books as I could find on the topic. Here is my short list:

So here I was at a cross-roads. I now had enough knowledge to start using the async and await keywords in place of “continuations” using ContinueWith and various Wait techniques. At this point however, I theorized that if I was to substitute usage of ContinueWith (or in fact, my very own ContinueWithImpersonated as noted above) with await, that the impersonation context would be lost.

I converted a couple of methods to async/await and threw in a couple of debug breakpoints. To much chagrin, I was right, the impersonation context was lost AFTER my await keyword.

Back to the drawing board! My options were few, and I still didn’t want to turn on the aspnet.config options that I mentioned earlier.

I then diagrammed my architecture:

* Controller
* Actions -> call services via Tasks (impersonate here)
* Services -> call repositories via Tasks
* Repositories

It became very apparent that because I couldn’t persist my impersonation context through tasks and continuation, that I had to move where I was doing my impersonation.

My arch then looked like this:

* Controller
* Actions -> call services via Tasks
* Services -> call repositories via Tasks
* Repositories (impersonate here)

So, now I would have to rewrite how I was calling my database, so that impersonation was done there.

I am using a variation of Oh So Simple SQL, so I created some extension method overloads to handle this.

    public static class RepositoryExtensions
    {
        // public List<O> FetchAll<O>();
        public static List<O> FetchAll<O>(this QueryBuilder<StoredProcedure> baseQuery, IElevatedUser elevatedUser)
        {
            using (elevatedUser.Impersonate())
            {
                return baseQuery.FetchAll<O>();
            }
        }
        public static List<O> FetchAll<O>(this QueryBuilder<Query> baseQuery, IElevatedUser elevatedUser)
        {
            using (elevatedUser.Impersonate())
            {
                return baseQuery.FetchAll<O>();
            }
        }

        //public O Fetch<O>();
        public static O Fetch<O>(this QueryBuilder<StoredProcedure> baseQuery, IElevatedUser elevatedUser)
        {
            using (elevatedUser.Impersonate())
            {
                return baseQuery.Fetch<O>();
            }
        }
        public static O Fetch<O>(this QueryBuilder<Query> baseQuery, IElevatedUser elevatedUser)
        {
            using (elevatedUser.Impersonate())
            {
                return baseQuery.Fetch<O>();
            }
        }

        //public void Execute();
        public static void Execute(this QueryBuilder<StoredProcedure> baseQuery, IElevatedUser elevatedUser)
        {
            using (elevatedUser.Impersonate())
            {
                baseQuery.Execute();
            }
        }
        public static void Execute(this QueryBuilder<Query> baseQuery, IElevatedUser elevatedUser)
        {
            using (elevatedUser.Impersonate())
            {
                baseQuery.Execute();
            }
        }

    }

My IElevatedUser object looks like this:

    public interface IElevatedUser : IDisposable
    {

        IElevatedUser Impersonate();
        IElevatedUser Impersonate(string account, string password);

        string Account
        {
            get;
            set;
        }

        string Password
        {
            get;
            set;
        }

    }

The moral of the story is 2-fold.

1. Impersonation doesn’t flow through the TPL automatically
2. Make sure you are doing impersonation at the right place

I’ve also compiled the impersonation classes as a gist available here.

6 thoughts on “Await, Async, Mvc and Impersonation

  1. gregg dourgarian

    Martha an icon for async?…maybe for IE8 or WindowsMe but definitely not async.

    On a more serious note, one of the hardest things for anyone to do is empathize with the ‘other’. The ‘other’ in your context being the user, the oft forgotten end-user. Impersonation done right overcomes many difficulties that prevent the developer from empathizing with the user. Impersonation is a big deal.

    Not easy to accomplish, as you’ve demonstrated, but still a big deal for the overall success of a software product.

    Reply
  2. Pingback: Some Super Talented and Inspiring People | Software Blog

  3. Pingback: Impersonation and asynchrony in ASP.NET WebAPI | BlogoSfera

  4. george

    Great article, thanks for sharing. The link to the impersonation classes does not work. Do you still have that gist up somewhere? thanks!

    Reply

Leave a comment