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; } } }