We are developing a web application that uses forms authentication and the ActiveDirectoryMembershipProvider to authenticate users against the Active Directory. We soon found out that the provider does not allow a blank/empty password to be specified, even though this is perfectly legal in the Active Directory (provided a preventative password policy is not in place).
Courtesy of reflector:
private void CheckPassword(string password, int maxSize, string paramName)
{
if (password == null)
{
throw new ArgumentNullException(paramName);
}
if (password.Trim().Length < 1)
{
throw new ArgumentException(SR.GetString("Parameter_can_not_be_empty", new object[] { paramName }), paramName);
}
if ((maxSize > 0) && (password.Length > maxSize))
{
throw new ArgumentException(SR.GetString("Parameter_too_long", new object[] { paramName, maxSize.ToString(CultureInfo.InvariantCulture) }), paramName);
}
}
Short of writing our own custom Provider, is there any way to override this functionality using the magic of .NET?
I don't beleive you could change this behaviour without creating a derived class and overiding every method that calls the private CheckPassword method. I would not recomend this option however, i would recomend that you review your design and question whether it is approriate to allow blank passwords in your application. Whilst they are valid in AD it is unusual for this to be allowed in practice and it does impact other things in a windows network, e.g. i think the default settings for network file shares disallow any user with a blank password from connecting to the share.
You could perhaps look at using impersonation but i don't know if you will have the same issue. If it's to authorise a user, then you could use impersonation to try and "impersonate" the user on the machine. I don't know if it helps but I was doing something similar to this the other week. Have put the code below if any of this helps.. :)
using System;
using System.Runtime.InteropServices;
public partial class Test_Index : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e)
{
IntPtr ptr = IntPtr.Zero;
if (LogonUser("USERNAME", "", "LEAVE-THIS-BLANK", LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, ref ptr))
{
using (System.Security.Principal.WindowsImpersonationContext context = new System.Security.Principal.WindowsIdentity(ptr).Impersonate())
{
try
{
// Do do something
}
catch (UnauthorizedAccessException ex)
{
// failed to do something
}
// un-impersonate user out
context.Undo();
}
}
else
{
Response.Write("login fail");
}
}
#region imports
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr existingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr duplicateTokenHandle);
#endregion
#region logon consts
// logon types
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NETWORK = 3;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
// logon providers
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_PROVIDER_WINNT50 = 3;
const int LOGON32_PROVIDER_WINNT40 = 2;
const int LOGON32_PROVIDER_WINNT35 = 1;
#endregion }
Related
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 need to be able to programmatically authenticate when trying to read and write files on a remote computer in a non-domain environment.
When you type a command into the Windows RUN prompt that is similar to \\targetComputer\C$\targetFolder or \\targetComputer\admin$, where the targetComputer is NOT on a domain, you will be prompted to enter a username and password. Once you enter the username and password, you have full access to the remote folder.
How can I accomplish this authentication programmatically in C#?
I've tried..
--Impersonation, but it appears to only work in a domain environment.
--CMDKEY.exe, but it also seems to only work in a domain environment.
There must be a way to do this, but I have searched high and low with no luck so far. Maybe I'm just looking for the wrong thing? I'm sure I'm not the first to have this question. Any help would be greatly appreciated.
Thanks!
EDIT :
I think I just found a different SO posting that answers my question: Accessing a Shared File (UNC) From a Remote, Non-Trusted Domain With Credentials
I will work with that for now and see where it gets me.
Thanks!
Impersonation works with Peer/LAN network as well. I got your typical home network with some machines on default "Workgroup" and some on a named one if I remembered doing it on the install.
Here is the code I use from my IIS server app to access files on my other computer (without having to have the same user and password on both machines involved, copied from somewhere and modified for my use):
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.ComponentModel;
/// <summary>
/// Class to impersonate another user. Requires user, pass and domain/computername
/// All code run after impersonationuser has been run will run as this user.
/// Remember to Dispose() afterwards.
/// </summary>
public class ImpersonateUser:IDisposable {
private WindowsImpersonationContext LastContext = null;
private IntPtr LastUserHandle = IntPtr.Zero;
#region User Impersonation api
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ImpersonateLoggedOnUser(int Token);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DuplicateToken(IntPtr token, int impersonationLevel, ref IntPtr duplication);
[DllImport("kernel32.dll")]
public static extern Boolean CloseHandle(IntPtr hObject);
public const int LOGON32_PROVIDER_DEFAULT = 0;
public const int LOGON32_PROVIDER_WINNT35 = 1;
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_LOGON_NETWORK = 3;
public const int LOGON32_LOGON_BATCH = 4;
public const int LOGON32_LOGON_SERVICE = 5;
public const int LOGON32_LOGON_UNLOCK = 7;
public const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;// Win2K or higher
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;// Win2K or higher
#endregion
public ImpersonateUser(string username, string domainOrComputerName, string password, int nm = LOGON32_LOGON_NETWORK) {
IntPtr userToken = IntPtr.Zero;
IntPtr userTokenDuplication = IntPtr.Zero;
bool loggedOn = false;
if (domainOrComputerName == null) domainOrComputerName = Environment.UserDomainName;
if (domainOrComputerName.ToLower() == "nt authority") {
loggedOn = LogonUser(username, domainOrComputerName, password, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, out userToken);
} else {
loggedOn = LogonUser(username, domainOrComputerName, password, nm, LOGON32_PROVIDER_DEFAULT, out userToken);
}
WindowsImpersonationContext _impersonationContext = null;
if (loggedOn) {
try {
// Create a duplication of the usertoken, this is a solution
// for the known bug that is published under KB article Q319615.
if (DuplicateToken(userToken, 2, ref userTokenDuplication)) {
// Create windows identity from the token and impersonate the user.
WindowsIdentity identity = new WindowsIdentity(userTokenDuplication);
_impersonationContext = identity.Impersonate();
} else {
// Token duplication failed!
// Use the default ctor overload
// that will use Mashal.GetLastWin32Error();
// to create the exceptions details.
throw new Win32Exception();
}
} finally {
// Close usertoken handle duplication when created.
if (!userTokenDuplication.Equals(IntPtr.Zero)) {
// Closes the handle of the user.
CloseHandle(userTokenDuplication);
userTokenDuplication = IntPtr.Zero;
}
// Close usertoken handle when created.
if (!userToken.Equals(IntPtr.Zero)) {
// Closes the handle of the user.
CloseHandle(userToken);
userToken = IntPtr.Zero;
}
}
} else {
// Logon failed!
// Use the default ctor overload that
// will use Mashal.GetLastWin32Error();
// to create the exceptions details.
throw new Win32Exception();
}
if (LastContext == null) LastContext = _impersonationContext;
}
public void Dispose() {
LastContext.Undo();
LastContext.Dispose();
}
}
The specific code I found out worked after a bit of trying was this:
using (var impersonation = new ImpersonateUser("OtherMachineUser", "OtherMachineName", "Password", LOGON32_LOGON_NEW_CREDENTIALS))
{
var files = System.IO.Directory.GetFiles("\\OtherMachineName\fileshare");
}
I am having problems trying to read a file that is stored on a network drive when hosting my website/request on a azure website.
The code works testing on a local machine and it also works on a Dedicated server that we have with another client and have full control of but it gets the following error when we try to read the file.
Access to the path '\xxx.xxx.xxx.xxx\abc\abc.xml' is denied.
I wanted to know if Azure was locked down to prevent this behaviour that I didn't know about. Maybe ports closed or something.
The code I am using
if (impersonate.impersonateValidUser("username", "domain.com", "password"))
{
message = "impersonate ok";
if (!System.IO.File.Exists(file))
{
message = "no file";
impersonate.undoImpersonation();
}
using (StreamReader reader = File.OpenText(file))
{
message = "";
XmlSerializer deserializer = new XmlSerializer(typeof(TextModeSchema));
schema = (TextModeSchema)deserializer.Deserialize(reader);
reader.Close();
}
}
which is using a basic Impersonate class that we use in alot of places
public class Impersonate : IDisposable
{
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_LOGON_NETWORK = 3;
public const int LOGON32_LOGON_BATCH = 4;
public const int LOGON32_LOGON_SERVICE = 5;
public const int LOGON32_LOGON_UNLOCK = 7;
public const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
public const int LOGON32_PROVIDER_DEFAULT =0;
public const int LOGON32_PROVIDER_WINNT35 =1;
public const int LOGON32_PROVIDER_WINNT40 =2;
public const int LOGON32_PROVIDER_WINNT50 = 3;
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);
public bool impersonateValidUser(String userName, String domain, String password)
{
WindowsIdentity tempWindowsIdentity;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_NEW_CREDENTIALS,
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;
}
public void undoImpersonation()
{
impersonationContext.Undo();
}
public void Dispose()
{
impersonationContext.Undo();
GC.Collect();
}
}
If this is not allowed on azure, does anyone know of a way we can get around it without having to set up a full Virtual machine? I would like to keep with a azure website/webapp ideally.
Is this on a web role, worker role or a web application? I know that in case of a role, the remote host in your scenario has to be on the same private network than you role (It may also be true for a web app). Also, we had a similar scenario where we decided to use Azure Files which allows mounting shares on a role which you might want to try. http://blogs.msdn.com/b/windowsazurestorage/archive/2014/05/12/introducing-microsoft-azure-file-service.aspx
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();