using Microsoft.Win32;
using System.ComponentModel;
using System.Drawing;
using System.Security;
using System.Text;
namespace System.Windows.Forms
{
///
/// Dialog box which prompts for user credentials using the Win32 CREDUI methods.
///
[ToolboxItem(true), System.Drawing.ToolboxBitmap(typeof(Microsoft.Win32.TaskScheduler.TaskEditDialog), "Dialog"), ToolboxItemFilter("System.Windows.Forms.Control.TopLevel"),
DesignTimeVisible(true), Description("Dialog that prompts the user for credentials."),
Designer("System.ComponentModel.Design.ComponentDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
public class CredentialsDialog : CommonDialog
{
private const int maxStringLength = 100;
///
/// Initializes a new instance of the class.
///
public CredentialsDialog()
{
Reset();
}
///
/// Initializes a new instance of the class.
///
/// The caption.
/// The message.
/// Name of the user.
/// The options.
public CredentialsDialog(string caption = null, string message = null, string userName = null, CredentialsDialogOptions options = CredentialsDialogOptions.Default) : this()
{
this.Caption = caption;
this.Message = message;
this.UserName = userName;
this.Options = options;
}
///
/// Gets or sets the Windows Error Code that caused this credential dialog to appear, if applicable.
///
[System.ComponentModel.DefaultValue(0), Category("Data"), Description("Windows Error Code that caused this credential dialog")]
public int AuthenticationError { get; set; }
///
/// Gets or sets the image to display as the banner for the dialog
///
[System.ComponentModel.DefaultValue((string)null), Category("Appearance"), Description("Image to display in dialog banner")]
public Bitmap Banner { get; set; }
///
/// Gets or sets the caption for the dialog
///
[System.ComponentModel.DefaultValue((string)null), Category("Appearance"), Description("Caption to display for dialog")]
public string Caption { get; set; }
///
/// Gets or sets a value indicating whether to encrypt password.
///
/// true if password is to be encrypted; otherwise, false.
[System.ComponentModel.DefaultValue(false), Category("Behavior"), Description("Indicates whether to encrypt password")]
public bool EncryptPassword { get; set; }
///
/// Gets or sets the message to display on the dialog
///
[System.ComponentModel.DefaultValue((string)null), Category("Appearance"), Description("Message to display in the dialog")]
public string Message { get; set; }
///
/// Gets or sets the options for the dialog.
///
/// The options.
[System.ComponentModel.DefaultValue(typeof(CredentialsDialogOptions), "Default"), Category("Behavior"), Description("Options for the dialog")]
public CredentialsDialogOptions Options { get; set; }
///
/// Gets the password entered by the user
///
[System.ComponentModel.DefaultValue((string)null), Browsable(false)]
public string Password { get; private set; }
///
/// Gets or sets a boolean indicating if the save check box was checked
///
///
/// Only valid if has the newDS set.
///
[System.ComponentModel.DefaultValue(false), Category("Behavior"), Description("Indicates if the save check box is checked.")]
public bool SaveChecked { get; set; }
///
/// Gets the password entered by the user using an encrypted string
///
[System.ComponentModel.DefaultValue(null), Browsable(false)]
public SecureString SecurePassword { get; private set; }
///
/// Gets or sets the name of the target for these credentials
///
///
/// This value is used as a key to store the credentials if persisted
///
[System.ComponentModel.DefaultValue((string)null), Category("Data"), Description("Target for the credentials")]
public string Target { get; set; }
///
/// Gets or sets the username entered
///
///
/// If non-empty before calling , this value will be displayed in the dialog
///
[System.ComponentModel.DefaultValue((string)null), Category("Data"), Description("User name displayed in the dialog")]
public string UserName { get; set; }
///
/// Gets or sets a value indicating whether the password should be validated before returning.
///
///
/// true if the password should be validated; otherwise, false.
///
[System.ComponentModel.DefaultValue(false), Category("Behavior"), Description("Indicates if the password should be validated before returning.")]
public bool ValidatePassword { get; set; }
///
/// Gets a default value for the target.
///
/// The default target.
private string DefaultTarget
{
get { return Environment.UserDomainName; }
}
///
/// Confirms the credentials.
///
/// If set to true the credentials are stored in the credential manager.
public void ConfirmCredentials(bool storedCredentials)
{
NativeMethods.CredUIReturnCodes ret = NativeMethods.CredUIConfirmCredentials(this.Target, storedCredentials);
if (ret != NativeMethods.CredUIReturnCodes.NO_ERROR && ret != NativeMethods.CredUIReturnCodes.ERROR_INVALID_PARAMETER)
throw new InvalidOperationException(string.Format("Unable to confirm credentials. Error: 0x{0:X}", ret));
}
///
/// When overridden in a derived class, resets the properties of a common dialog box to their default values.
///
public override void Reset()
{
this.Target = this.UserName = this.Caption = this.Message = this.Password = null;
this.Banner = null;
this.EncryptPassword = this.SaveChecked = false;
this.Options = CredentialsDialogOptions.Default;
}
private bool IsValidPassword(string userName, string password)
{
NativeMethods.SafeTokenHandle token;
string[] udn = userName.Split('\\');
string domain = udn.Length == 2 ? udn[0] : null;
string user = udn.Length == 2 ? udn[1] : udn[0];
try
{
if (NativeMethods.LogonUser(user, domain, password, 3, 0, out token) != 0)
return true;
}
catch { }
return false;
}
///
/// When overridden in a derived class, specifies a common dialog box.
///
/// A value that represents the window handle of the owner window for the common dialog box.
///
/// true if the dialog box was successfully run; otherwise, false.
///
protected override bool RunDialog(IntPtr parentWindowHandle)
{
NativeMethods.CREDUI_INFO info = new NativeMethods.CREDUI_INFO(parentWindowHandle, this.Caption, this.Message, this.Banner);
try
{
StringBuilder userName = new StringBuilder(this.UserName, maxStringLength);
StringBuilder password = new StringBuilder(maxStringLength);
bool save = this.SaveChecked;
if (string.IsNullOrEmpty(this.Target)) this.Target = this.DefaultTarget;
NativeMethods.CredUIReturnCodes ret = NativeMethods.CredUIPromptForCredentials(ref info, this.Target, IntPtr.Zero,
this.AuthenticationError, userName, maxStringLength, password, maxStringLength, ref save, this.Options);
switch (ret)
{
case NativeMethods.CredUIReturnCodes.NO_ERROR:
if (this.ValidatePassword && !IsValidPassword(userName.ToString(), password.ToString()))
return false;
/*if (save)
{
CredUIReturnCodes cret = CredUIConfirmCredentials(this.Target, false);
if (cret != CredUIReturnCodes.NO_ERROR && cret != CredUIReturnCodes.ERROR_INVALID_PARAMETER)
{
this.Options |= CredentialsDialogOptions.IncorrectPassword;
return false;
}
}*/
break;
case NativeMethods.CredUIReturnCodes.ERROR_CANCELLED:
return false;
default:
throw new InvalidOperationException(string.Format("Unknown error in CredentialsDialog. Error: 0x{0:X}", ret));
}
if (this.EncryptPassword)
{
// Convert the password to a SecureString
SecureString newPassword = StringBuilderToSecureString(password);
// Clear the old password and set the new one (read-only)
if (this.SecurePassword != null)
this.SecurePassword.Dispose();
newPassword.MakeReadOnly();
this.SecurePassword = newPassword;
}
else
this.Password = password.ToString();
// Update other properties
this.UserName = userName.ToString();
this.SaveChecked = save;
return true;
}
finally
{
info.Dispose();
}
}
private static SecureString StringBuilderToSecureString(StringBuilder password)
{
// Copy the password into the secure string, zeroing the original buffer as we go
SecureString newPassword = new SecureString();
for (int i = 0; i < password.Length; i++)
{
newPassword.AppendChar(password[i]);
password[i] = '\0';
}
return newPassword;
}
}
}