views:

360

answers:

3

I have an application that needs to do a lot of impersonation, due to moving and creating many files across protected network shares. I've created a simple static class that has a method takes a user, domain, password and a delegate that contains code you need run under the impersonation context.

The problem I've been running into is when the CLR tries to bind to a referenced assembly under this context. A FileLoadException is thrown with an 'Access is denied' message.

I'm led to believe that this is due to the impersonated user not having sufficient privileges to the *.DLL file under the filesystem. For example, I can write benign code just before the impersonation block, which accomplishes nothing other than to load types from the problem assembly before the context switches to the impersonated user, and this works great! However I don't really consider this to be an elegant solution – am I going to need to begin worrying about what types I can use under impersonation, making sure I put random typeof() statements beforehand?

What makes this more frustrating is that I don't run into this problem on my local development machine. It's when the assemblies get shipped off to the beta environment does this problem occur. And I don't have access to read the file permissions from the beta environment to try and mimic them on my location machine.

In any case, I tried this solution:

// Defined above:
// System.Security.Principal.WindowsIdentity identity;
// System.Security.Principal.WindowsImpersonationContext context;

context = identity.Impersonate();

int tries = 0;
while ( true )
{
    try
    {
        contextAction();
    }
    catch ( FileLoadException ex )
    {
        if ( tries > MAX_TRIES )
        {
            // don't allow an infinite loop
            throw;
        }
        if ( String.IsNullOrEmpty( ex.FileName ) )
        {
            // if this is null/empty, we can't really recover
            throw;
        }
        context.Undo(); // return to current logon
        try
        {
            var assemblyName = new AssemblyName( ex.FileName );
            Assembly.Load( assemblyName );
            tries++;
            continue;
        }
        finally
        {
            context = identity.Impersonate(); // re-impersonate
        }
    }   
    finally
    {
        // return to your current windows logon
        context.Undo();
    }
}

No dice. I still get the 'Access is denied' exception, except now from the line starting with Assembly.Load.

One interesting thing to note is I was getting the same exception from the build server. This above solution fixed it on the build server. Not in our beta environment.

What am I missing here? Thanks.

A: 

I am not sure what the issue is, but Windows Impersonation does not work well when one is Impersonating an Administrator from a non admin user account. It may be that your end user is an non admin & when you try to impersonate, it fails leading to file access error.

Ganesh R.
+1  A: 

I'm having the same problem. It seems to be an issue with the impersonation "session" not being cleaned up properly. Loading the problem assembly prior to calling your logic works fine. Attempting to load the assembly after undo'ing and disposing the impersonation identity and context results in another FileLoadException.

Another thread shows sample code where they close the token handles, but doing that didn't make any difference in my test code.

EDIT

Calling System.Security.Principal.WindowsIdentity.GetCurrent() before and after the impersonation code returns the same information, even though something must have changed to no longer allow the problem assembly to be loaded.

EDIT #2 (NOW WITH 100% MORE SOLUTION!)

We just discovered a separate, far more serious problem in our web app. The process of creating an identity and closing the handle was not correct. We were having major issues in our multi-threaded app, which eventually brought our web site to a grinding halt when all the handles ran out. After quite a bit of discussion with fellow developers and online research (including tips from the second-to-last post in this thread), I improved the code so that the token handles are properly cleaned up.

This seems to have also solved our problem with loading assemblies in an impersonation context. I can't seem to reproduce the error now, no matter how hard I try. I'll post our improved impersonation code here, so you can see if it works in your application. Note the lock block in GetIdentity; that is very important for a multi-threaded application.

// LogonType = 8     // LOGON32_LOGON_NETWORK_CLEARTEXT
// LogonProvider = 0    // LOGON32_PROVIDER_DEFAULT

[DllImport ( "advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
private static extern bool LogonUser( string userName, string domain,
    string password, int logonType, int logonProvider, ref IntPtr accessToken );

[DllImport ( "kernel32.dll", SetLastError = true )]
private static extern bool CloseHandle( IntPtr handle );

private static readonly object Locker = new Object ();

private static WindowsIdentity GetIdentity( string username, string domain, string password )
{
    lock ( Locker )
    {
     IntPtr token = IntPtr.Zero;
     if ( LogonUser ( username, domain, password,
      (int) LogonType, (int) LogonProvider, ref token ) )
     {
      // using the token to create an instance of WindowsIdentity class
      var identity = new WindowsIdentity ( token );
      CloseHandle ( token ); // the WindowsIdentity object duplicates this token internally
      return identity;
     }

     throw new SecurityException ( string.Format (
      "Invalid username/password (domain: '{0}', username: '{1}')",
      domain, username ) );
    }
}

public static T ExecuteAction<T>( string username, string domain, string password,
    Func<T> contextAction )
{
    var identity = GetIdentity ( username, domain, password );
    var context = identity.Impersonate ();
    try
    {

     return contextAction ();
    }
    finally
    {
     context.Undo ();
     context.Dispose ();
    }
}
MikeWyatt
A: 

You could put the 'offending' assemblies in the GAC.

Or dynamically add inheritable Read&Execute permissions for the user to-be-impersonated on the directory containing the assemblies just before impersonating the user. I'm assuming that you control the location of the offending assemblies?

Mikkel Christensen