Impersonation using C#
-
Hi Guys, I'm working on a windows service which runs under a given service account. It writes files to shared areas on other machines, but needs to change the owner of these files to a different account. I can do this using the SetAccessControl as below:
//call SetOwner(@"\\servername\share\filename.ext",@"domain\username");
public static void SetOwner(string uri, string account)
{
System.Security.AccessControl.FileSecurity security = File.GetAccessControl(uri);
IdentityReference owner = new NTAccount(account);
security.SetOwner(owner);
File.SetAccessControl(uri, security);
}However, this only works if the service account which I'm using is an administrator or backup operator on the remote machine (unless it's setting the owner to itself). I'd like to avoid giving this service account rights to too many machines, since that puts too much power in the hands of one account. Instead, I'd like to provide a username and password for the account which will be the owner of the file, and have the service impersonate this account when assigning ownership (e.g. in a similar way to how you could have credentials in a connection string for a database connection which didn't use trusted authentication). I'm looking for something like the code below, but have so far been unable to find an understandable example to work from.
//call SetOwner(@"\\servername\share\filename.ext",@"domain\username");
public static void SetOwner(string uri, string account)
{
StartImpersonating(account);
System.Security.AccessControl.FileSecurity security = File.GetAccessControl(uri);
IdentityReference owner = new NTAccount(account);
security.SetOwner(owner);
File.SetAccessControl(uri, security);
StopImpersonating();
}
static void StartImpersonating(string account, string password)
{
System.Security.Principal.WindowsIdentity.Impersonate(account, password);
}
static void StartImpersonating(string account)
{
string password = GetPasswordFromSecureStore(account);
StartImpersonating(account, password);
}
static void StartImpersonating(string domain, string username)
{
StartImpersonating(string.Format("{0}\\{1}",domain,username));
}
static void StopImpersonating()
{
StartImpersonating(System.Environment.UserDomainName,System.Environment.UserName);
}NB: the above code is just for illustrative purposes to show the sort of thing I'm looking for; I've no idea whether what's there is even close to w
-
Hi Guys, I'm working on a windows service which runs under a given service account. It writes files to shared areas on other machines, but needs to change the owner of these files to a different account. I can do this using the SetAccessControl as below:
//call SetOwner(@"\\servername\share\filename.ext",@"domain\username");
public static void SetOwner(string uri, string account)
{
System.Security.AccessControl.FileSecurity security = File.GetAccessControl(uri);
IdentityReference owner = new NTAccount(account);
security.SetOwner(owner);
File.SetAccessControl(uri, security);
}However, this only works if the service account which I'm using is an administrator or backup operator on the remote machine (unless it's setting the owner to itself). I'd like to avoid giving this service account rights to too many machines, since that puts too much power in the hands of one account. Instead, I'd like to provide a username and password for the account which will be the owner of the file, and have the service impersonate this account when assigning ownership (e.g. in a similar way to how you could have credentials in a connection string for a database connection which didn't use trusted authentication). I'm looking for something like the code below, but have so far been unable to find an understandable example to work from.
//call SetOwner(@"\\servername\share\filename.ext",@"domain\username");
public static void SetOwner(string uri, string account)
{
StartImpersonating(account);
System.Security.AccessControl.FileSecurity security = File.GetAccessControl(uri);
IdentityReference owner = new NTAccount(account);
security.SetOwner(owner);
File.SetAccessControl(uri, security);
StopImpersonating();
}
static void StartImpersonating(string account, string password)
{
System.Security.Principal.WindowsIdentity.Impersonate(account, password);
}
static void StartImpersonating(string account)
{
string password = GetPasswordFromSecureStore(account);
StartImpersonating(account, password);
}
static void StartImpersonating(string domain, string username)
{
StartImpersonating(string.Format("{0}\\{1}",domain,username));
}
static void StopImpersonating()
{
StartImpersonating(System.Environment.UserDomainName,System.Environment.UserName);
}NB: the above code is just for illustrative purposes to show the sort of thing I'm looking for; I've no idea whether what's there is even close to w
Seek and ye shall find Windows Impersonation using C#[^]. It's from '03 but im sure the pricipals are still the same, you are going to have to call the Windows API to get done but the above link should help you out.
Don't comment your code - it was hard to write, it should be hard to read!
-
Hi Guys, I'm working on a windows service which runs under a given service account. It writes files to shared areas on other machines, but needs to change the owner of these files to a different account. I can do this using the SetAccessControl as below:
//call SetOwner(@"\\servername\share\filename.ext",@"domain\username");
public static void SetOwner(string uri, string account)
{
System.Security.AccessControl.FileSecurity security = File.GetAccessControl(uri);
IdentityReference owner = new NTAccount(account);
security.SetOwner(owner);
File.SetAccessControl(uri, security);
}However, this only works if the service account which I'm using is an administrator or backup operator on the remote machine (unless it's setting the owner to itself). I'd like to avoid giving this service account rights to too many machines, since that puts too much power in the hands of one account. Instead, I'd like to provide a username and password for the account which will be the owner of the file, and have the service impersonate this account when assigning ownership (e.g. in a similar way to how you could have credentials in a connection string for a database connection which didn't use trusted authentication). I'm looking for something like the code below, but have so far been unable to find an understandable example to work from.
//call SetOwner(@"\\servername\share\filename.ext",@"domain\username");
public static void SetOwner(string uri, string account)
{
StartImpersonating(account);
System.Security.AccessControl.FileSecurity security = File.GetAccessControl(uri);
IdentityReference owner = new NTAccount(account);
security.SetOwner(owner);
File.SetAccessControl(uri, security);
StopImpersonating();
}
static void StartImpersonating(string account, string password)
{
System.Security.Principal.WindowsIdentity.Impersonate(account, password);
}
static void StartImpersonating(string account)
{
string password = GetPasswordFromSecureStore(account);
StartImpersonating(account, password);
}
static void StartImpersonating(string domain, string username)
{
StartImpersonating(string.Format("{0}\\{1}",domain,username));
}
static void StopImpersonating()
{
StartImpersonating(System.Environment.UserDomainName,System.Environment.UserName);
}NB: the above code is just for illustrative purposes to show the sort of thing I'm looking for; I've no idea whether what's there is even close to w
Hi JB, I used this in a small test I composed on impersonation:
static void TestProcessLogs(string path) { AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); IntPtr token; #region User / Password String user = "xxxxxx"; String domain = "xxxxx"; String pass = "xxxxxxx"; #endregion if (LogonUser(user, domain, pass, 9, 0, out token)) { WindowsIdentity fromIdentity = new WindowsIdentity(token); WindowsImpersonationContext context = fromIdentity.Impersonate(); ProcessLog(@"\\\\xxxxxxx.xxxxxxx.xxxxxx\\f$\\Connector\\LNWorkerDir\\705-SORL9002-PGBU-WEC-US-productn-octtic\\peqdLN02\\all.log"); //ProcessLog(path); context.Undo(); } }
and here comes the definition of LogonUser. Place this within a class definition:
\[DllImport("advapi32.dll", SetLastError = true)\] public static extern bool LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
Best regards Manfred
-
Hi JB, I used this in a small test I composed on impersonation:
static void TestProcessLogs(string path) { AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); IntPtr token; #region User / Password String user = "xxxxxx"; String domain = "xxxxx"; String pass = "xxxxxxx"; #endregion if (LogonUser(user, domain, pass, 9, 0, out token)) { WindowsIdentity fromIdentity = new WindowsIdentity(token); WindowsImpersonationContext context = fromIdentity.Impersonate(); ProcessLog(@"\\\\xxxxxxx.xxxxxxx.xxxxxx\\f$\\Connector\\LNWorkerDir\\705-SORL9002-PGBU-WEC-US-productn-octtic\\peqdLN02\\all.log"); //ProcessLog(path); context.Undo(); } }
and here comes the definition of LogonUser. Place this within a class definition:
\[DllImport("advapi32.dll", SetLastError = true)\] public static extern bool LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
Best regards Manfred
That's perfect. Thanks to both of you for the link & code sample; very much appreciated. I've put this code into a disposable object, so that you can easily swap contexts, using the using statement to control when you impersonate another account. Hopefully this will be of use to anyone following this thread in future. e.g.
class Program { static void Main(string\[\] args) { XmlDocument doc = new XmlDocument(); doc.LoadXml("<demo />"); doc.Save(@"\\\\server\\share\\demo1.xml"); //created with owner set to Administrators (assuming current account has access) using (Impersonator impersonate = new Impersonator(@"domain\\user")) { doc.Save(@"\\\\server\\share\\xjbdemo2.xml"); //created with owner set to domain\\user (assuming domain\\user has access) } doc.Save(@"\\\\server\\share\\xjbdemo3.xml"); //created with owner set to Administrators (assuming current account has access) Console.WriteLine("Done"); Console.ReadKey(); } } /// <summary> /// Allows impersonation to be done easily via the code below /// using (Impersonator impersonate = new Impersonator("domain\\user", "pass") /// { /// //do stuff as other user /// } /// </summary> public class Impersonator: IDisposable { #region windows api reference #region enums //thanks to http://www.pinvoke.net/default.aspx/advapi32.logonuser public enum LogonType { /// <summary> /// This logon type is intended for users who will be interactively using the computer, such as a user being logged on /// by a terminal server, remote shell, or similar process. /// This logon type has the additional expense of caching logon information for disconnected operations; /// therefore, it is inappropriate for some client/server applications, /// such as a mail server. /// </summary> LOGON32\_LOGON\_INTERACTIVE = 2, /// <summary> /// This logon type is intended for high performance servers to authenticate plaintext passwords. /// The LogonUser function does not cache credentials for this logon type. /// </summary> LOGON32\_LOGON\_NETWORK = 3,
-
That's perfect. Thanks to both of you for the link & code sample; very much appreciated. I've put this code into a disposable object, so that you can easily swap contexts, using the using statement to control when you impersonate another account. Hopefully this will be of use to anyone following this thread in future. e.g.
class Program { static void Main(string\[\] args) { XmlDocument doc = new XmlDocument(); doc.LoadXml("<demo />"); doc.Save(@"\\\\server\\share\\demo1.xml"); //created with owner set to Administrators (assuming current account has access) using (Impersonator impersonate = new Impersonator(@"domain\\user")) { doc.Save(@"\\\\server\\share\\xjbdemo2.xml"); //created with owner set to domain\\user (assuming domain\\user has access) } doc.Save(@"\\\\server\\share\\xjbdemo3.xml"); //created with owner set to Administrators (assuming current account has access) Console.WriteLine("Done"); Console.ReadKey(); } } /// <summary> /// Allows impersonation to be done easily via the code below /// using (Impersonator impersonate = new Impersonator("domain\\user", "pass") /// { /// //do stuff as other user /// } /// </summary> public class Impersonator: IDisposable { #region windows api reference #region enums //thanks to http://www.pinvoke.net/default.aspx/advapi32.logonuser public enum LogonType { /// <summary> /// This logon type is intended for users who will be interactively using the computer, such as a user being logged on /// by a terminal server, remote shell, or similar process. /// This logon type has the additional expense of caching logon information for disconnected operations; /// therefore, it is inappropriate for some client/server applications, /// such as a mail server. /// </summary> LOGON32\_LOGON\_INTERACTIVE = 2, /// <summary> /// This logon type is intended for high performance servers to authenticate plaintext passwords. /// The LogonUser function does not cache credentials for this logon type. /// </summary> LOGON32\_LOGON\_NETWORK = 3,
Hi JB, thanks for sharing your class with us. I have some issues with your design though: 1. In the constructor I would not call Impersonate(...) straight away. There are two reasons for this: a.) Code in a constructor that will likely cause an exception is a real pain in the a**e. b.) Let the user of your library decide when Impersonation starts and ends. Store user account and domain in instance variables. Password should not be stored in the class (as you have already done) but could be retrieved via GetPassword() (see 2.). I would make an additional overload Impersonate() a public function and add another function like Revert() that will call context.Undo() and set context = null. bool IsImpersonating() would be nifty (context != null) too. 2. Move the function GetPassword to a separate class that implements an interface say IStringPasswordProvider. Have a property that allows the user of your library to inject an instance of his or her own implementation of IPasswordProvider.
public interface IStringPasswordProvider
{
String GetPassword(String account);
String GetPassword(String account, String domain);
}public class DefaultPasswordProvider : IStringPasswordProvider
{
string GetPassword(string account)
{
string[] accountSplit = SplitAccount(account);
return GetPassword(accountSplit[0], accountSplit[1]);
}
string GetPassword(string domain, string user)
{
string pass = string.Empty;
pass = "todo"; //todo:add code here to return the password for the given user from the secure store.
return pass;
}
}A password provider might read passwords from a DB or may be the app.config (web.config) file. Please let me know what you think of these changes. Best Regards Manfred