I have a C# windows application that I need to impersonate a userid other than the one that the user logged on with when I access files on a file share. I use impersonation to switch to this user and then switch back. I also need to revert back to the logged on user to access local files. I get a System.ExecutionEngineException that is not being caught by the application and it just stops. Here is the code. Any ideas why I am getting this error or suggestions on how to do it better.
Edit: The application is using the XAF framework by DevExpress.
public static class Impersonation
{
#region Fields
public static WindowsImpersonationContext newUser;
public static string domain;
public static string userName;
public static string password;
#endregion
// group type enum
private enum SECURITY_IMPERSONATION_LEVEL : int
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}
// obtains user token
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
// closes open handes returned by LogonUser
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private extern static bool CloseHandle(IntPtr handle);
// creates duplicate token handle
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
#region Methods
public static void RevertUser()
{
newUser.Undo();
}
#region ImpersonateApexApplicationUser
/// <summary>
/// Attempts to impersonate a user. If successful, returns
/// a WindowsImpersonationContext of the new users identity.
/// </summary>
/// <param name="sUsername">Username you want to impersonate</param>
/// <param name="sDomain">Logon domain</param>
/// <param name="sPassword">User's password to logon with</param></param>
/// <returns></returns>
public static void ImpersonateApexApplicationUser()
{
// initialize tokens
IntPtr pExistingTokenHandle = new IntPtr(0);
IntPtr pDuplicateTokenHandle = new IntPtr(0);
pExistingTokenHandle = IntPtr.Zero;
pDuplicateTokenHandle = IntPtr.Zero;
try
{
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
if (!LogonUser(userName, domain, Encryption.Decrypt(password), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref pExistingTokenHandle))
throw new Exception(string.Format("LogonUser() failed with error code {0}", Marshal.GetLastWin32Error()));
else
if (!DuplicateToken(pExistingTokenHandle, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, ref pDuplicateTokenHandle))
{
int errorCode = Marshal.GetLastWin32Error();
CloseHandle(pExistingTokenHandle); // close existing handle
throw new Exception(string.Format("DuplicateToken() failed with error code: {0}", errorCode));
}
else
{
// create new identity using new primary token
WindowsIdentity newId = new WindowsIdentity(pDuplicateTokenHandle);
newUser = newId.Impersonate();
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
// close handle(s)
if (pExistingTokenHandle != IntPtr.Zero)
CloseHandle(pExistingTokenHandle);
if (pDuplicateTokenHandle != IntPtr.Zero)
CloseHandle(pDuplicateTokenHandle);
}
}
#endregion
}
I call it like this once at the beginning of the program:
Impersonation.domain = "xxxx";
Impersonation.userName = "xxxx;
Impersonation.password = "xxxx";
Impersonation.ImpersonateApexApplicationUser();
and then:
Impersonation.ImpersonateApexApplicationUser();
//file share access code
Impersonation.RevertUser();
Related
this is my code and I want to know how to use network credentials in my code
string filePath = Path.Combine(#"\\192.168.5.90\uploads", newfilename);
using (var filestream = new FileStream(filePath, FileMode.Create,FileAccess.Write))
{
await uploadfile.CopyToAsync(filestream);
}
return Ok(newfilename);
Windows uses the identity of the user running the process of the app for this authentication.
You will need to impersonate an alternative user and execute the code that writes that file in that impersonation context. Look into WindowsIdentity.RunImpersonated method
https://learn.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.runimpersonated?view=netcore-3.1
you can use this link
You can use a impersonator instead:
using (var impersonator = new Impersonator(username, password))
{
File.Copy(source, destination, true);
}
this is a copy past from our implementation, so please adjust your domain name
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
public class Impersonator : IDisposable
{
/// <summary>
/// The Impersonator class is used to access a network share with other credentials.
/// </summary>
private readonly WindowsImpersonationContext _impersonatedUser;
private readonly IntPtr _userHandle;
/// <summary>
/// Constructor
/// </summary>
/// <param name="username">The user of the network share</param>
/// <param name="password">The password of the network share</param>
public Impersonator(string username, string password, string userDomain = "YOURDOMAIN")
{
_userHandle = new IntPtr(0);
bool returnValue = LogonUser(username, userDomain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
ref _userHandle);
if (!returnValue)
throw new ApplicationException(
"The applications wasn't able to impersonate the user with the specified credentials!");
var newId = new WindowsIdentity(_userHandle);
_impersonatedUser = newId.Impersonate();
}
#region IDisposable Members
public void Dispose()
{
if (_impersonatedUser != null)
{
_impersonatedUser.Undo();
CloseHandle(_userHandle);
}
}
#endregion
#region Interop imports/constants
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_LOGON_SERVICE = 3;
public const int LOGON32_PROVIDER_DEFAULT = 0;
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern bool LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType,
int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
#endregion
}
I am currently doing some maintenance on a C# CodedUITest. It is testing the frontend on our software which is installed in validated mode (=> which means it can only be used by the "ProtectedUser" User and Administrators).
The problem is that we obviously want to perform some clicks on the interface but we are using our "TestAgent" User who doesn't have the rights to interact with the software's interface. Instead we want to Impersonate the "ProtectedUser" User when interacting with the interface.
At some point the following code is used:
using (new TestUtils.Tools.Impersonator(".", "ProtectedUser", "password"))
{
Microsoft.VisualStudio.TestTools.UITesting.Mouse.StartDragging(SoftwareBrowserSlider);
Microsoft.VisualStudio.TestTools.UITesting.Mouse.StopDragging(SoftwareBrowserTreeView, x, y);
}
My problem is that I can't seem to get the impersonation to work to do frontend interactions such as mouse clicks or drags. But the Impersonator class works just fine when copying or deleting files. Does anyone know if it is even possible to Impersonate another local user and then perform a click or a drag as the said user?
Here is my Impersonator class:
public class Impersonator : IDisposable
{
public bool isImpersonating { get; private set; }
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
#region DllImports
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(
string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool ImpersonateLoggedOnUser(IntPtr hToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(
IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
#endregion
public Impersonator(string domainName = ".",
string userName = "ProtectedUser",
string password = "password")
{
ImpersonateValidUser(domainName, userName, password);
}
public void Dispose()
{
UndoImpersonation();
}
private void ImpersonateValidUser(string domain, string userName, string password)
{
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
try
{
if(!RevertToSelf())
{
ThrowLastWin32Exception();
}
if(LogonUser(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) == 0)
{
ThrowLastWin32Exception();
}
if (DuplicateToken(token, 2, ref tokenDuplicate) == 0)
{
ThrowLastWin32Exception();
}
isImpersonating = ImpersonateLoggedOnUser(tokenDuplicate);
}
finally
{
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
}
}
private void ThrowLastWin32Exception()
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
private void UndoImpersonation()
{
if (impersonationContext != null)
{
impersonationContext.Undo();
}
}
}
Within a windows service I am facing an issue with impersonating a user.
The impersonation finds place in a System.Timers.Timer Elapsed event.
The timer ticks every 3 seconds. When the imersonation fails and I get "Unable to impersonate user" I do a retry by restarting the timer and it continues and impersonation goes on.
After 3 to 4 days of running this service the impersonation fails again and a retry doesn't work and the application is stuck within the elapsed event.
Is it possible that the Timer ran out of threads?(Threadpool)
How can I prevent a failed impersonation or how do I handle this?
Impersonation code :
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
String lpszUsername,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
private static IntPtr tokenHandle = new IntPtr(0);
private static WindowsImpersonationContext impersonatedUser;
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
// If you incorporate this code into a DLL, be sure to demand that it
// runs with FullTrust.
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Impersonate(string domainName, string userName, string password)
{
//try
{
// Use the unmanaged LogonUser function to get the user token for
// the specified user, domain, and password.
const int LOGON32_PROVIDER_DEFAULT = 0;
// Passing this parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
tokenHandle = IntPtr.Zero;
// ---- Step - 1
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(userName,domainName,password,LOGON32_LOGON_INTERACTIVE,LOGON32_PROVIDER_DEFAULT,ref tokenHandle);
// tokenHandle - new security token
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
// ---- Step - 2
WindowsIdentity newId = new WindowsIdentity(tokenHandle);
// ---- Step - 3
impersonatedUser = newId.Impersonate();
}
}
// Stops impersonation
public void Undo()
{
impersonatedUser.Undo();
// Free the tokens.
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
}
Timer code:
void polling_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
StopPolling();
//doing some stuff some impersonation
StartPolling();
}
catch (Exception ex)
{
retries++;
if (retries < 10)
{
StartPolling();
}
}
}
I am using the code to impersonate a user account to get access to a file share.
public class Impersonator :
IDisposable
{
#region Public methods.
// ------------------------------------------------------------------
/// <summary>
/// Constructor. Starts the impersonation with the given credentials.
/// Please note that the account that instantiates the Impersonator class
/// needs to have the 'Act as part of operating system' privilege set.
/// </summary>
/// <param name="userName">The name of the user to act as.</param>
/// <param name="domainName">The domain name of the user to act as.</param>
/// <param name="password">The password of the user to act as.</param>
public Impersonator(
string userName,
string domainName,
string password )
{
ImpersonateValidUser( userName, domainName, password );
}
// ------------------------------------------------------------------
#endregion
#region IDisposable member.
// ------------------------------------------------------------------
public void Dispose()
{
UndoImpersonation();
}
// ------------------------------------------------------------------
#endregion
#region P/Invoke.
// ------------------------------------------------------------------
[DllImport("advapi32.dll", SetLastError=true)]
private static extern int LogonUser(
string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern int DuplicateToken(
IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern bool CloseHandle(
IntPtr handle);
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
// ------------------------------------------------------------------
#endregion
#region Private member.
// ------------------------------------------------------------------
/// <summary>
/// Does the actual impersonation.
/// </summary>
/// <param name="userName">The name of the user to act as.</param>
/// <param name="domainName">The domain name of the user to act as.</param>
/// <param name="password">The password of the user to act as.</param>
private void ImpersonateValidUser(
string userName,
string domain,
string password )
{
WindowsIdentity tempWindowsIdentity = null;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
try
{
if ( RevertToSelf() )
{
if ( LogonUser(
userName,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref token ) != 0 )
{
if ( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 )
{
tempWindowsIdentity = new WindowsIdentity( tokenDuplicate );
impersonationContext = tempWindowsIdentity.Impersonate();
}
else
{
throw new Win32Exception( Marshal.GetLastWin32Error() );
}
}
else
{
throw new Win32Exception( Marshal.GetLastWin32Error() );
}
}
else
{
throw new Win32Exception( Marshal.GetLastWin32Error() );
}
}
finally
{
if ( token!= IntPtr.Zero )
{
CloseHandle( token );
}
if ( tokenDuplicate!=IntPtr.Zero )
{
CloseHandle( tokenDuplicate );
}
}
}
/// <summary>
/// Reverts the impersonation.
/// </summary>
private void UndoImpersonation()
{
if ( impersonationContext!=null )
{
impersonationContext.Undo();
}
}
private WindowsImpersonationContext impersonationContext = null;
// ------------------------------------------------------------------
#endregion
}
Then using:
using (new Impersonator("username", "domain", "password"))
{
Process.Start("explorer.exe", #"/root,\\server01-Prod\abc");
}
I get an "Access Denied" error.
This user supposely has access to this share. I can map a drive, use "net use" but this code will not work. Now I am thinking it is the code. Does anyone see anything? Is there a better way of doing this?
try this :
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken);
Usage :
IntPtr userToken = IntPtr.Zero;
bool success = External.LogonUser(
"john.doe",
"domain.com",
"MyPassword",
(int) AdvApi32Utility.LogonType.LOGON32_LOGON_INTERACTIVE, //2
(int) AdvApi32Utility.LogonProvider.LOGON32_PROVIDER_DEFAULT, //0
out userToken);
if (!success)
{
throw new SecurityException("Logon user failed");
}
using (WindowsIdentity.Impersonate(userToken))
{
Process.Start("explorer.exe", #"/root,\\server01-Prod\abc");
}
If I'm understanding correctly, your intention is to run the process in the impersonation context.
The doc from CreateProcess (which is used by Process.Start) says:
If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token. To run the new process in the security context of the user represented by the impersonation token, use the CreateProcessAsUser or CreateProcessWithLogonW function.
So, you're using the wrong API for doing that.
Instead of using your Impersonator class, what happens when you call Process.Start and pass in a ProcessStartInfo instance that contains the username, password and domain that you want to run the process as?
Perhaps, if that works, then your Impersonator class should create a ProcessStartInfo instance and use that to create new processes (encapsulate that within the class itself).
var psi = new ProcessStartInfo("explorer.exe", #"/root,\\server01-Prod\abc");
psi.Domain = domain;
psi.UserName = username;
psi.Password = password;
psi.WorkingDirectory = workingDir;
Process.Start(psi);
Also, per the MSDN docs...
Setting the Domain, UserName, and the Password properties in a
ProcessStartInfo object is the recommended practice for starting a
process with user credentials.
You should also set the working directory when starting a process with different user creds.
I have a network shared drive ("\serveur\folder") on which I would like to copy file.
I can write on the drive with a specific user ("user"/"pass").
How can I access the shared drived with write privilege using C#?
Untested code, but it will be similiar to:
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
// http://pinvoke.net/default.aspx/advapi32/LogonUser.html
IntPtr token;
LogonUser("username", "domain", "password", LogonType.LOGON32_LOGON_BATCH, LogonProvider.LOGON32_PROVIDER_DEFAULT);
WindowsIdentity identity = new WindowsIdentity(token);
WindowsImpersonationContext context = identity.Impersonate();
try
{
File.Copy(#"c:\temp\MyFile.txt", #"\\server\folder\Myfile.txt", true);
}
finally
{
context.Undo();
}
Here's a working example for ASP.NET applications. Original source
private void SendToFileShare(byte[] pdfData, string fileName)
{
if(pdfData == null)
{
throw new ArgumentNullException("pdfData");
}
if (string.IsNullOrWhiteSpace(fileName))
{
//Assign a unique name because the programmer failed to specify one.
fileName = Guid.NewGuid().ToString();
}
else
{
//Should probably replace special characters (windows filenames) with something.
}
string networkShareLocation = #"\\your\network\share\";
var path = $"{networkShareLocation}{fileName}.pdf";
//Credentials for the account that has write-access. Probably best to store these in a web.config file.
var domain = "AB";
var userID = "Mr";
var password = "C";
if (ImpersonateUser(domain, userID, password) == true)
{
//write the PDF to the share:
System.IO.File.WriteAllBytes(path, report);
undoImpersonation();
}
else
{
//Could not authenticate account. Something is up.
//Log or something.
}
}
/// <summary>
/// Impersonates the given user during the session.
/// </summary>
/// <param name="domain">The domain.</param>
/// <param name="userName">Name of the user.</param>
/// <param name="password">The password.</param>
/// <returns></returns>
private bool ImpersonateUser(string domain, string userName, string password)
{
WindowsIdentity tempWindowsIdentity;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
if (token != IntPtr.Zero)
CloseHandle(token);
if (tokenDuplicate != IntPtr.Zero)
CloseHandle(tokenDuplicate);
return false;
}
/// <summary>
/// Undoes the current impersonation.
/// </summary>
private void undoImpersonation()
{
impersonationContext.Undo();
}
#region Impersionation global variables
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
WindowsImpersonationContext impersonationContext;
[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
#endregion
More simple and modern approach. Works for me in an enterprise network.
try
{
bool validLogin = false;
using (PrincipalContext tempcontext = new PrincipalContext(ContextType.Domain, "domain.company.com", null, ContextOptions.Negotiate))
{
try
{
validLogin = tempcontext.ValidateCredentials("USERNAME", "PASSWORD", ContextOptions.Negotiate);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
if (validLogin)
{
File.Copy(#"C:\folder\filename.txt", #"\\domain\folder\filename.txt", true);
return true;
}
else
{
MessageBox.Show("Username or Password is incorrect...", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
Create a user with privileges to write to the network drive and use impersonation within the c# to use that user when accessing the drive.