From ede0ddfc6bc3fa09b58677b0a16b5ce736579755 Mon Sep 17 00:00:00 2001 From: RKrom Date: Tue, 21 Sep 2010 15:13:46 +0000 Subject: [PATCH] Added a CredentialsDialog which can be used for different kind of plugins which need a login. The credentials supplied over this dialog can be stored inside the window itself. This way the plugins don't need to store the password themselves! The credentials will be visible in the "Stored user names and passwords", which can be found under "Manage Passwords" of the user accounts. git-svn-id: http://svn.code.sf.net/p/greenshot/code/trunk@882 7dccd23d-a4a3-4e1f-8c07-b4c1b4018ab4 --- GreenshotCore/Helpers/CredentialsHelper.cs | 593 +++++++++++++++++++++ 1 file changed, 593 insertions(+) create mode 100644 GreenshotCore/Helpers/CredentialsHelper.cs diff --git a/GreenshotCore/Helpers/CredentialsHelper.cs b/GreenshotCore/Helpers/CredentialsHelper.cs new file mode 100644 index 000000000..256fd1cc3 --- /dev/null +++ b/GreenshotCore/Helpers/CredentialsHelper.cs @@ -0,0 +1,593 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2010 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +/// +/// The following code comes from: http://www.developerfusion.com/code/4693/using-the-credential-management-api/ +/// and is slightly modified so it works for us. +/// The following code is an example for a login, it will call the Authenticate with user/password +/// which should return true if the login worked, false if not. +/// private static bool Login(string system, string name) { +/// try { +/// CredentialsDialog dialog = new CredentialsDialog(system); +/// dialog.Name = name; +/// while (dialog.Show(dialog.Name) == DialogResult.OK) { +/// if (Authenticate(dialog.Name, dialog.Password)) { +/// if (dialog.SaveChecked) dialog.Confirm(true); +/// return true; +/// } else { +/// try { +/// dialog.Confirm(false); +/// } catch (ApplicationException) { +/// // exception handling ... +/// } +/// } +/// } +/// } catch (ApplicationException) { +/// // exception handling ... +/// } +/// return false; +/// } +/// +namespace Greenshot.Helpers { + /// Encapsulates dialog functionality from the Credential Management API. + public sealed class CredentialsDialog { + /// The only valid bitmap height (in pixels) of a user-defined banner. + private const int ValidBannerHeight = 60; + + /// The only valid bitmap width (in pixels) of a user-defined banner. + private const int ValidBannerWidth = 320; + + /// Initializes a new instance of the class + /// with the specified target. + /// The name of the target for the credentials, typically a server name. + public CredentialsDialog(string target) : this(target, null) { + } + + /// Initializes a new instance of the class + /// with the specified target and caption. + /// The name of the target for the credentials, typically a server name. + /// The caption of the dialog (null will cause a system default title to be used). + public CredentialsDialog(string target, string caption) : this(target, caption, null) { + } + + /// Initializes a new instance of the class + /// with the specified target, caption and message. + /// The name of the target for the credentials, typically a server name. + /// The caption of the dialog (null will cause a system default title to be used). + /// The message of the dialog (null will cause a system default message to be used). + public CredentialsDialog(string target, string caption, string message) : this(target, caption, message, null) { + } + + /// Initializes a new instance of the class + /// with the specified target, caption, message and banner. + /// The name of the target for the credentials, typically a server name. + /// The caption of the dialog (null will cause a system default title to be used). + /// The message of the dialog (null will cause a system default message to be used). + /// The image to display on the dialog (null will cause a system default image to be used). + public CredentialsDialog(string target, string caption, string message, Image banner) { + this.Target = target; + this.Caption = caption; + this.Message = message; + this.Banner = banner; + } + + private bool _alwaysDisplay = false; + /// + /// Gets or sets if the dialog will be shown even if the credentials + /// can be returned from an existing credential in the credential manager. + /// + public bool AlwaysDisplay { + get { + return _alwaysDisplay; + } + set { + _alwaysDisplay = value; + } + } + + private bool _excludeCertificates = true; + /// Gets or sets if the dialog is populated with name/password only. + public bool ExcludeCertificates { + get { + return _excludeCertificates; + } + set { + _excludeCertificates = value; + } + } + + private bool _persist = true; + /// Gets or sets if the credentials are to be persisted in the credential manager. + public bool Persist { + get { + return _persist; + } + set { + _persist = value; + } + } + + private bool _keepName = false; + /// Gets or sets if the name is read-only. + public bool KeepName { + get { + return _keepName; + } + set { + _keepName = value; + } + } + + private string _name = String.Empty; + /// Gets or sets the name for the credentials. + public string Name { + get { + return _name; + } + set { + if (value != null) { + if (value.Length > CREDUI.MAX_USERNAME_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The name has a maximum length of {0} characters.", + CREDUI.MAX_USERNAME_LENGTH); + throw new ArgumentException(message, "Name"); + } + } + _name = value; + } + } + + private string _password = String.Empty; + /// Gets or sets the password for the credentials. + public string Password { + get { + return _password; + } + set { + if (value != null) { + if (value.Length > CREDUI.MAX_PASSWORD_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The password has a maximum length of {0} characters.", + CREDUI.MAX_PASSWORD_LENGTH); + throw new ArgumentException(message, "Password"); + } + } + _password = value; + } + } + + private bool _saveChecked = false; + /// Gets or sets if the save checkbox status. + public bool SaveChecked { + get { + return _saveChecked; + } + set { + _saveChecked = value; + } + } + + private bool _saveDisplayed = true; + /// Gets or sets if the save checkbox is displayed. + /// This value only has effect if Persist is true. + public bool SaveDisplayed { + get { + return _saveDisplayed; + } + set { + _saveDisplayed = value; + } + } + + private string _target = String.Empty; + /// Gets or sets the name of the target for the credentials, typically a server name. + public string Target { + get { + return _target; + } + set { + if (value == null) { + throw new ArgumentException("The target cannot be a null value.", "Target"); + } else if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The target has a maximum length of {0} characters.", + CREDUI.MAX_GENERIC_TARGET_LENGTH); + throw new ArgumentException(message, "Target"); + } + _target = value; + } + } + + private string _caption = String.Empty; + /// Gets or sets the caption of the dialog. + /// A null value will cause a system default caption to be used. + public string Caption { + get { + return _caption; + } + set { + if (value != null) { + if (value.Length > CREDUI.MAX_CAPTION_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The caption has a maximum length of {0} characters.", + CREDUI.MAX_CAPTION_LENGTH); + throw new ArgumentException(message, "Caption"); + } + } + _caption = value; + } + } + + private string _message = String.Empty; + /// Gets or sets the message of the dialog. + /// A null value will cause a system default message to be used. + public string Message { + get { + return _message; + } + set { + if (value != null) { + if (value.Length > CREDUI.MAX_MESSAGE_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The message has a maximum length of {0} characters.", + CREDUI.MAX_MESSAGE_LENGTH); + throw new ArgumentException(message, "Message"); + } + } + _message = value; + } + } + + private Image _banner = null; + /// Gets or sets the image to display on the dialog. + /// A null value will cause a system default image to be used. + public Image Banner { + get { + return _banner; + } + set { + if (value != null) { + if (value.Width != ValidBannerWidth) { + throw new ArgumentException("The banner image width must be 320 pixels.", "Banner"); + } + if (value.Height != ValidBannerHeight) { + throw new ArgumentException("The banner image height must be 60 pixels.", "Banner"); + } + } + _banner = value; + } + } + + /// Shows the credentials dialog. + /// Returns a DialogResult indicating the user action. + public DialogResult Show() { + return Show(null, this.Name, this.Password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified save checkbox status. + /// True if the save checkbox is checked. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(bool saveChecked) { + return Show(null, this.Name, this.Password, saveChecked); + } + + /// Shows the credentials dialog with the specified name. + /// The name for the credentials. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(string name) { + return Show(null, name, this.Password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified name and password. + /// The name for the credentials. + /// The password for the credentials. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(string name, string password) { + return Show(null, name, password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified name, password and save checkbox status. + /// The name for the credentials. + /// The password for the credentials. + /// True if the save checkbox is checked. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(string name, string password, bool saveChecked) { + return Show(null, name, password, saveChecked); + } + + /// Shows the credentials dialog with the specified owner. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(IWin32Window owner) { + return Show(owner, this.Name, this.Password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified owner and save checkbox status. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// True if the save checkbox is checked. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(IWin32Window owner, bool saveChecked) { + return Show(owner, this.Name, this.Password, saveChecked); + } + + /// Shows the credentials dialog with the specified owner, name and password. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// The name for the credentials. + /// The password for the credentials. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(IWin32Window owner, string name, string password) { + return Show(owner, name, password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified owner, name, password and save checkbox status. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// The name for the credentials. + /// The password for the credentials. + /// True if the save checkbox is checked. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(IWin32Window owner, string name, string password, bool saveChecked) { + if ((Environment.OSVersion.Version.Major < 5) || ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor < 1))) { + throw new ApplicationException("The Credential Management API requires Windows XP / Windows Server 2003 or later."); + } + this.Name = name; + this.Password = password; + this.SaveChecked = saveChecked; + + return ShowDialog(owner); + } + + /// Confirmation action to be applied. + /// True if the credentials should be persisted. + public void Confirm(bool value) { + switch (CREDUI.ConfirmCredentials(this.Target, value)) { + case CREDUI.ReturnCodes.NO_ERROR: + break; + + case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER: + // for some reason, this is encountered when credentials are overwritten + break; + + default: + throw new ApplicationException("Credential confirmation failed."); + } + } + + /// Returns a DialogResult indicating the user action. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// + /// Sets the name, password and SaveChecked accessors to the state of the dialog as it was dismissed by the user. + /// + private DialogResult ShowDialog(IWin32Window owner) { + // set the api call parameters + StringBuilder name = new StringBuilder(CREDUI.MAX_USERNAME_LENGTH); + name.Append(this.Name); + + StringBuilder password = new StringBuilder(CREDUI.MAX_PASSWORD_LENGTH); + password.Append(this.Password); + + int saveChecked = Convert.ToInt32(this.SaveChecked); + + CREDUI.INFO info = GetInfo(owner); + CREDUI.FLAGS flags = GetFlags(); + + // make the api call + CREDUI.ReturnCodes code = CREDUI.PromptForCredentials( + ref info, + this.Target, + IntPtr.Zero, 0, + name, CREDUI.MAX_USERNAME_LENGTH, + password, CREDUI.MAX_PASSWORD_LENGTH, + ref saveChecked, + flags + ); + + // clean up resources + if (this.Banner != null) GDI32.DeleteObject(info.hbmBanner); + + // set the accessors from the api call parameters + this.Name = name.ToString(); + this.Password = password.ToString(); + this.SaveChecked = Convert.ToBoolean(saveChecked); + + return GetDialogResult(code); + } + + /// Returns the info structure for dialog display settings. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + private CREDUI.INFO GetInfo(IWin32Window owner) { + CREDUI.INFO info = new CREDUI.INFO(); + if (owner != null) info.hwndParent = owner.Handle; + info.pszCaptionText = this.Caption; + info.pszMessageText = this.Message; + if (this.Banner != null) { + info.hbmBanner = new Bitmap(this.Banner, ValidBannerWidth, ValidBannerHeight).GetHbitmap(); + } + info.cbSize = Marshal.SizeOf(info); + return info; + } + + /// Returns the flags for dialog display options. + private CREDUI.FLAGS GetFlags() { + CREDUI.FLAGS flags = CREDUI.FLAGS.GENERIC_CREDENTIALS; + + if (this.AlwaysDisplay) { + flags = flags | CREDUI.FLAGS.ALWAYS_SHOW_UI; + } + + if (this.ExcludeCertificates) { + flags = flags | CREDUI.FLAGS.EXCLUDE_CERTIFICATES; + } + + if (this.Persist) { + flags = flags | CREDUI.FLAGS.EXPECT_CONFIRMATION; + if (this.SaveDisplayed) { + flags = flags | CREDUI.FLAGS.SHOW_SAVE_CHECK_BOX; + } else { + flags = flags | CREDUI.FLAGS.PERSIST; + } + } else { + flags = flags | CREDUI.FLAGS.DO_NOT_PERSIST; + } + + if (this.KeepName) { + flags = flags | CREDUI.FLAGS.KEEP_USERNAME; + } + + return flags; + } + + /// Returns a DialogResult from the specified code. + /// The credential return code. + private DialogResult GetDialogResult(CREDUI.ReturnCodes code) { + DialogResult result; + switch (code) { + case CREDUI.ReturnCodes.NO_ERROR: + result = DialogResult.OK; + break; + case CREDUI.ReturnCodes.ERROR_CANCELLED: + result = DialogResult.Cancel; + break; + case CREDUI.ReturnCodes.ERROR_NO_SUCH_LOGON_SESSION: + throw new ApplicationException("No such logon session."); + case CREDUI.ReturnCodes.ERROR_NOT_FOUND: + throw new ApplicationException("Not found."); + case CREDUI.ReturnCodes.ERROR_INVALID_ACCOUNT_NAME: + throw new ApplicationException("Invalid account name."); + case CREDUI.ReturnCodes.ERROR_INSUFFICIENT_BUFFER: + throw new ApplicationException("Insufficient buffer."); + case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER: + throw new ApplicationException("Invalid parameter."); + case CREDUI.ReturnCodes.ERROR_INVALID_FLAGS: + throw new ApplicationException("Invalid flags."); + default: + throw new ApplicationException("Unknown credential result encountered."); + } + return result; + } + } + + public sealed class GDI32 { + private GDI32() { + } + + [DllImport("gdi32.dll", EntryPoint="DeleteObject")] + public static extern bool DeleteObject(IntPtr hObject); + } + + public sealed class CREDUI { + private CREDUI() { + } + + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/authentication_constants.asp + public const int MAX_MESSAGE_LENGTH = 100; + public const int MAX_CAPTION_LENGTH = 100; + public const int MAX_GENERIC_TARGET_LENGTH = 100; + public const int MAX_DOMAIN_TARGET_LENGTH = 100; + public const int MAX_USERNAME_LENGTH = 100; + public const int MAX_PASSWORD_LENGTH = 100; + + /// + /// http://www.pinvoke.net/default.aspx/Enums.CREDUI_FLAGS + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/dpapiusercredentials.asp + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp + /// + [Flags] public enum FLAGS { + INCORRECT_PASSWORD = 0x1, + DO_NOT_PERSIST = 0x2, + REQUEST_ADMINISTRATOR = 0x4, + EXCLUDE_CERTIFICATES = 0x8, + REQUIRE_CERTIFICATE = 0x10, + SHOW_SAVE_CHECK_BOX = 0x40, + ALWAYS_SHOW_UI = 0x80, + REQUIRE_SMARTCARD = 0x100, + PASSWORD_ONLY_OK = 0x200, + VALIDATE_USERNAME = 0x400, + COMPLETE_USERNAME = 0x800, + PERSIST = 0x1000, + SERVER_CREDENTIAL = 0x4000, + EXPECT_CONFIRMATION = 0x20000, + GENERIC_CREDENTIALS = 0x40000, + USERNAME_TARGET_CREDENTIALS = 0x80000, + KEEP_USERNAME = 0x100000, + } + + /// http://www.pinvoke.net/default.aspx/Enums.CredUIReturnCodes + public enum ReturnCodes { + NO_ERROR = 0, + ERROR_INVALID_PARAMETER = 87, + ERROR_INSUFFICIENT_BUFFER = 122, + ERROR_INVALID_FLAGS = 1004, + ERROR_NOT_FOUND = 1168, + ERROR_CANCELLED = 1223, + ERROR_NO_SUCH_LOGON_SESSION = 1312, + ERROR_INVALID_ACCOUNT_NAME = 1315 + } + + /// + /// http://www.pinvoke.net/default.aspx/Structures.CREDUI_INFO + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/credui_info.asp + /// + public struct INFO { + public int cbSize; + public IntPtr hwndParent; + [MarshalAs(UnmanagedType.LPWStr)] public string pszMessageText; + [MarshalAs(UnmanagedType.LPWStr)] public string pszCaptionText; + public IntPtr hbmBanner; + } + + /// + /// http://www.pinvoke.net/default.aspx/credui.CredUIPromptForCredentialsW + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp + /// + [DllImport("credui", EntryPoint="CredUIPromptForCredentialsW", CharSet=CharSet.Unicode)] + public static extern ReturnCodes PromptForCredentials( + ref INFO creditUR, + string targetName, + IntPtr reserved1, + int iError, + StringBuilder userName, + int maxUserName, + StringBuilder password, + int maxPassword, + ref int iSave, + FLAGS flags + ); + + /// + /// http://www.pinvoke.net/default.aspx/credui.CredUIConfirmCredentials + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduiconfirmcredentials.asp + /// + [DllImport("credui.dll", EntryPoint="CredUIConfirmCredentialsW", CharSet=CharSet.Unicode)] + public static extern ReturnCodes ConfirmCredentials(string targetName, bool confirm); + } +} \ No newline at end of file