using System;
using System.ComponentModel;
using System.Drawing;
using System.Security;
using System.Text;
using System.Windows.Forms;
using Vanara.Extensions;
using Vanara.PInvoke;
using Vanara.Security;
using static Vanara.PInvoke.AdvApi32;
using static Vanara.PInvoke.CredUI;
using static Vanara.PInvoke.Gdi32;
namespace Vanara.Windows.Forms
{
/// Dialog box which prompts for user credentials using the Win32 CREDUI methods.
[ToolboxItem(true), ToolboxBitmap(typeof(CredentialsDialog), "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 static readonly bool PreVista = Environment.OSVersion.Version.Major <= 5;
private uint authPackage;
private bool saveChecked;
/// 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.
public CredentialsDialog(string caption, string message = null, string userName = null) : this()
{
Caption = caption;
Message = message;
UserName = userName;
}
/// Occurs after the OK button has been clicked for validation of the supplied credentials.
///
/// If handled, the property must be set to true if the password is valid or false if
/// not. If false, the method will return .
///
[Description("Validates the supplied credentials."), Category("Data")]
public event EventHandler ValidatePassword;
/// Gets or sets the Windows Error Code that caused this credential dialog to appear, if applicable.
[DefaultValue(0), Category("Data"), Description("Windows Error Code that caused this credential dialog")]
public int AuthenticationError { get; set; }
///
/// Windows Vista and later: On input, the value of this parameter is used to specify the authentication package for which the credentials are serialized.
///
/// To get the appropriate value to use for this parameter on input, call the LsaLookupAuthenticationPackage function and use the value of the
/// AuthenticationPackage parameter of that function.
///
/// On output, this parameter specifies the authentication package for which the credentials in the ppvOutAuthBuffer buffer are serialized.
///
/// The authentication package.
[Browsable(false), DefaultValue(0u)]
public uint AuthenticationPackage { get => authPackage; set => authPackage = value; }
/// Windows XP and earlier: Gets or sets the image to display as the banner for the dialog.
[DefaultValue(null), Category("Appearance"), Description("Image to display in dialog banner")]
public Bitmap Banner { get; set; }
///
/// Gets or sets the caption for the dialog. If this value is null or an empty string, the name of the application will be shown as the caption.
///
[DefaultValue(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.
[DefaultValue(false), Category("Behavior"), Description("Indicates whether to encrypt password")]
public bool EncryptPassword { get; set; }
/// Gets or sets a value indicating whether to cause the pre-Windows Vista style dialog to be used regardless of the current Windows version.
/// true if older UI will always be used; false to display the UI commensurate for the current Windows version.
[DefaultValue(false), Category("Appearance"), Description("Indicates whether to force the older UI.")]
public bool ForcePreVistaUI { get; set; }
/// Gets or sets the message to display on the dialog
[DefaultValue(null), Category("Appearance"), Description("Message to display in the dialog")]
public string Message { get; set; }
/// Gets the password entered by the user
[DefaultValue(null), Browsable(false)]
public string Password { get; private set; }
/// Gets or sets a boolean indicating if the save check box was checked
[DefaultValue(false), Category("Behavior"), Description("Indicates if the save check box is checked.")]
public bool SaveChecked { get => saveChecked; set => saveChecked = value; }
/// Gets the password entered by the user using an encrypted string
[DefaultValue(null), Browsable(false)]
public SecureString SecurePassword { get; private set; }
/// Gets or sets a value that determines if the combo box is populated with local administrators only.
public bool ShowAdminsOnly { get; set; }
/// Gets or sets a boolean indicating if the save check box should be shown.
[DefaultValue(false), Category("Behavior"), Description("Indicates if the save check box is shown.")]
public bool ShowSaveCheckBox { get; 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
[DefaultValue(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
[DefaultValue(null), Category("Data"), Description("User name displayed in the dialog")]
public string UserName { get; set; }
/// Gets a default value for the target.
/// The default target.
private static string DefaultTarget => Environment.UserDomainName;
/// Validates a password using the LogonUser API function.
/// Name of the user.
/// The password.
/// true if the credentials are validated, otherwise false.
public static bool IsValidPassword(string userName, string password)
{
ParseUserName(userName, out var user, out var domain);
try
{
if (LogonUser(user, domain, password, LogonUserType.LOGON32_LOGON_NETWORK, LogonUserProvider.LOGON32_PROVIDER_DEFAULT, out var obj))
return true;
}
catch { }
return false;
}
/// Wraps the CredUIParseUserName function which extracts the domain and user account name from a fully qualified user name.
/// Contains the user name to be parsed. The name must be in UPN or down-level format, or a certificate.
/// Receives the user account name.
/// Receives the domain name. If specifies a certificate, domain will be NULL.
public static void ParseUserName(string userName, out string user, out string domain)
{
if (userName == null)
throw new ArgumentNullException(nameof(userName));
var sbUser = new StringBuilder(CREDUI_MAX_USERNAME_LENGTH);
var sbDomain = new StringBuilder(CREDUI_MAX_DOMAIN_TARGET_LENGTH);
var ret = CredUIParseUserName(userName, sbUser, CREDUI_MAX_USERNAME_LENGTH, sbDomain, CREDUI_MAX_DOMAIN_TARGET_LENGTH);
ret.ThrowIfFailed();
user = sbUser.ToString();
domain = sbDomain.Length != 0 ? sbDomain.ToString() : null;
}
/// Implements a standard password validator using the LogonUser API function.
/// The sender.
/// The instance containing the event data.
public static void StandardPasswordValidator(object sender, PasswordValidatorEventArgs args)
{
if (args.SecurePassword != null) throw new ArgumentException();
args.Validated = IsValidPassword(args.Username, args.Password);
}
/// Confirms the credentials.
/// If set to true the credentials are stored in the credential manager.
/// true if the credentials were confirmed; otherwise false.
public bool ConfirmCredentials(bool updateCredentials) => CredUIConfirmCredentials(Target, updateCredentials).Succeeded;
/// When overridden in a derived class, resets the properties of a common dialog box to their default values.
public override void Reset()
{
Target = UserName = Caption = Message = Password = null;
Banner = null;
EncryptPassword = ForcePreVistaUI = SaveChecked = ShowSaveCheckBox = false;
}
/// Gets the flags to use in .
/// The flags based on current properties.
protected virtual WindowsCredentialsDialogOptions GetDialogFlags()
{
return WindowsCredentialsDialogOptions.CREDUIWIN_GENERIC |
(ShowAdminsOnly ? WindowsCredentialsDialogOptions.CREDUIWIN_ENUMERATE_ADMINS : 0) |
(ShowSaveCheckBox ? WindowsCredentialsDialogOptions.CREDUIWIN_CHECKBOX : 0);
}
/// Gets the flags to use in .
/// The flags based on current properties.
protected virtual CredentialsDialogOptions GetPreVistaDialogFlags()
{
return CredentialsDialogOptions.CREDUI_FLAGS_DEFAULT |
(ShowAdminsOnly ? CredentialsDialogOptions.CREDUI_FLAGS_REQUEST_ADMINISTRATOR : 0) |
(ShowSaveCheckBox ? CredentialsDialogOptions.CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX : 0);
}
/// 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)
{
var info = new CREDUI_INFO(parentWindowHandle, Caption, Message) { hbmBanner = Banner?.GetHbitmap() ?? IntPtr.Zero };
try
{
if (PreVista || ForcePreVistaUI)
{
var userName = new StringBuilder(UserName, CREDUI_MAX_USERNAME_LENGTH);
var password = new StringBuilder(CREDUI_MAX_PASSWORD_LENGTH);
if (string.IsNullOrEmpty(Target)) Target = DefaultTarget;
var ret = CredUIPromptForCredentials(info, Target, IntPtr.Zero, unchecked((uint)AuthenticationError), userName, CREDUI_MAX_USERNAME_LENGTH,
password, CREDUI_MAX_PASSWORD_LENGTH, ref saveChecked, GetPreVistaDialogFlags());
if (ret == Win32Error.ERROR_CANCELLED)
return false;
if (ret != Win32Error.ERROR_SUCCESS)
throw new InvalidOperationException($"Unknown error in {nameof(CredentialsDialog)}. Error: 0x{(uint)ret:X}");
if (EncryptPassword)
{
// Convert the password to a SecureString
var newPassword = StringBuilderToSecureString(password);
// Clear the old password and set the new one (read-only)
SecurePassword?.Dispose();
newPassword.MakeReadOnly();
SecurePassword = newPassword;
Password = null;
}
else
Password = password.ToString();
if (ValidatePassword != null)
{
var pve = new PasswordValidatorEventArgs(userName.ToString(), Password, SecurePassword);
ValidatePassword.Invoke(this, pve);
if (!pve.Validated)
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;
}
}*/
// Update other properties
UserName = userName.ToString();
return true;
}
else
{
using (var buf = EncryptPassword && SecurePassword != null ? new AuthenticationBuffer(UserName.ToSecureString(), SecurePassword)
: new AuthenticationBuffer(UserName, Password))
{
var retVal = CredUIPromptForWindowsCredentials(info, 0, ref authPackage, buf, (uint)buf.Size, out var outAuthBuffer, out var outAuthBufferSize, ref saveChecked, GetDialogFlags());
if (retVal == Win32Error.ERROR_CANCELLED)
return false;
retVal.ThrowIfFailed();
using var outAuth = new AuthenticationBuffer(outAuthBuffer, (int)outAuthBufferSize);
if (EncryptPassword)
{
outAuth.UnPack(true, out SecureString u, out var d, out var p);
Password = null;
SecurePassword = p;
UserName = d?.Length > 0 ? $"{d.ToInsecureString()}\\{u.ToInsecureString()}" : u.ToInsecureString();
}
else
{
outAuth.UnPack(true, out string u, out var d, out var p);
Password = p;
SecurePassword = null;
UserName = string.IsNullOrEmpty(d) ? u : $"{d}\\{u}";
}
}
if (ValidatePassword != null)
{
var pve = new PasswordValidatorEventArgs(UserName, Password, SecurePassword);
ValidatePassword.Invoke(this, pve);
if (!pve.Validated)
return false;
}
return true;
}
}
finally
{
if (!info.hbmBanner.IsNull)
DeleteObject(info.hbmBanner);
}
}
private static SecureString StringBuilderToSecureString(StringBuilder password)
{
// Copy the password into the secure string, zeroing the original buffer as we go
var newPassword = new SecureString();
for (var i = 0; i < password.Length; i++)
{
newPassword.AppendChar(password[i]);
password[i] = '\0';
}
return newPassword;
}
/// Used by the event.
public class PasswordValidatorEventArgs : EventArgs
{
/// Initializes a new instance of the class.
/// The user name.
/// The password.
/// The secure password.
internal PasswordValidatorEventArgs(string un, string pwd, SecureString sPwd)
{
Username = un;
Password = pwd;
SecurePassword = sPwd;
}
/// Gets the password.
/// The password.
public string Password { get; }
/// Gets the secure password.
/// The secure password.
public SecureString SecurePassword { get; }
/// Gets the username.
/// The username.
public string Username { get; }
/// Gets or sets a value indicating whether this is validated.
/// true if validated; otherwise, false.
public bool Validated { get; set; } = false;
}
}
}