//--------------------------------------------------------------------- // private methods //--------------------------------------------------------------------- /// <summary> /// This function calls the OS dialog to prompt user for credential. /// </summary> /// <param name="target"> /// The credential target. It is displayed in the prompt dialog and is /// used for credential storage. /// </param> /// <param name="hwdOwner">The parent for the dialog.</param> /// <param name="userName">The username supplied by the user.</param> /// <param name="password">The password supplied by the user.</param> /// <returns> /// DialogResult.OK = if Successfully prompted user for credentials. /// DialogResult.Cancel = if user cancelled the prompt dialog. /// </returns> private static DialogResult ShowOSCredentialDialog(string target, IntPtr hwdOwner, out string userName, out string password) { DialogResult retValue = DialogResult.Cancel; userName = string.Empty; password = string.Empty; string titleFormat = SR.CredentialDialog_TitleFormat; string descriptionFormat = SR.CredentialDialog_DescriptionTextFormat; // Create the CREDUI_INFO structure. NativeMethods.CREDUI_INFO info = new NativeMethods.CREDUI_INFO(); info.pszCaptionText = string.Format(CultureInfo.CurrentUICulture, titleFormat, target); info.pszMessageText = string.Format(CultureInfo.CurrentUICulture, descriptionFormat, target); info.hwndParentCERParent = hwdOwner; info.hbmBannerCERHandle = IntPtr.Zero; info.cbSize = Marshal.SizeOf(info); // We do not use CREDUI_FLAGS_VALIDATE_USERNAME flag as it doesn't allow plain user // (one with no domain component). Instead we use CREDUI_FLAGS_COMPLETE_USERNAME. // It does some basic username validation (like doesnt allow two "\" in the user name. // It does adds the target to the username. For example, if user entered "foo" for // taget "bar.com", it will return username as "bar.com\foo". We trim out bar.com // while parsing the username. User can input "*****@*****.**" as workaround to provide // "bar.com\foo" as the username. // We specify CRED_TYPE_SERVER_CREDENTIAL flag as the stored credentials appear in the // "Control Panel->Stored Usernames and Password". It is how IE stores and retrieve // credentials. By using the CRED_TYPE_SERVER_CREDENTIAL flag allows IE and VS to // share credentials. // We dont specify the CREDUI_FLAGS_EXPECT_CONFIRMATION as the VS proxy service consumers // dont call back into the service to confirm that the call succeeded. NativeMethods.CREDUI_FLAGS flags = NativeMethods.CREDUI_FLAGS.SERVER_CREDENTIAL | NativeMethods.CREDUI_FLAGS.SHOW_SAVE_CHECK_BOX | NativeMethods.CREDUI_FLAGS.COMPLETE_USERNAME | NativeMethods.CREDUI_FLAGS.EXCLUDE_CERTIFICATES; StringBuilder user = new StringBuilder(Convert.ToInt32(NativeMethods.CREDUI_MAX_USERNAME_LENGTH)); StringBuilder pwd = new StringBuilder(Convert.ToInt32(NativeMethods.CREDUI_MAX_PASSWORD_LENGTH)); int saveCredentials = 0; // Ensures that CredUPPromptForCredentials results in a prompt. int netError = NativeMethods.ERROR_LOGON_FAILURE; // Call the OS API to prompt for credentials. NativeMethods.CredUIReturnCodes result = NativeMethods.CredUIPromptForCredentials( info, target, IntPtr.Zero, netError, user, NativeMethods.CREDUI_MAX_USERNAME_LENGTH, pwd, NativeMethods.CREDUI_MAX_PASSWORD_LENGTH, ref saveCredentials, flags); if (result == NativeMethods.CredUIReturnCodes.NO_ERROR) { userName = user.ToString(); password = pwd.ToString(); try { if (Convert.ToBoolean(saveCredentials)) { // Try reading the credentials back to ensure that we can read the stored credentials. If // the CredUIPromptForCredentials() function is not able successfully call CredGetTargetInfo(), // it will store credentials with credential type as DOMAIN_PASSWORD. For DOMAIN_PASSWORD // credential type we can only retrive the user name. As a workaround, we store the credentials // as credential type as GENERIC. string storedUserName; string storedPassword; bool successfullyReadCredentials = ReadOSCredentials(target, out storedUserName, out storedPassword); if (!successfullyReadCredentials || !string.Equals(userName, storedUserName, StringComparison.Ordinal) || !string.Equals(password, storedPassword, StringComparison.Ordinal)) { // We are not able to retrieve the credentials. Try storing them as GENERIC credetails. // Create the NativeCredential object. NativeMethods.NativeCredential customCredential = new NativeMethods.NativeCredential(); customCredential.userName = userName; customCredential.type = NativeMethods.CRED_TYPE_GENERIC; customCredential.targetName = CreateCustomTarget(target); // Store credentials across sessions. customCredential.persist = (uint)NativeMethods.CRED_PERSIST.LOCAL_MACHINE; if (!string.IsNullOrEmpty(password)) { customCredential.credentialBlobSize = (uint)Marshal.SystemDefaultCharSize * ((uint)password.Length); customCredential.credentialBlob = Marshal.StringToCoTaskMemAuto(password); } try { NativeMethods.CredWrite(ref customCredential, 0); } finally { if (customCredential.credentialBlob != IntPtr.Zero) { Marshal.FreeCoTaskMem(customCredential.credentialBlob); } } } } } catch { // Ignore that failure to read back the credentials. We still have // username and password to use in the current session. } retValue = DialogResult.OK; } else if (result == NativeMethods.CredUIReturnCodes.ERROR_CANCELLED) { retValue = DialogResult.Cancel; } else { Debug.Fail("CredUIPromptForCredentials failed with result = " + result.ToString()); retValue = DialogResult.Cancel; } info.Dispose(); return retValue; }
//--------------------------------------------------------------------- // private methods //--------------------------------------------------------------------- /// <summary> /// This function calls the OS dialog to prompt user for credential. /// </summary> /// <param name="target"> /// The credential target. It is displayed in the prompt dialog and is /// used for credential storage. /// </param> /// <param name="hwdOwner">The parent for the dialog.</param> /// <param name="userName">The username supplied by the user.</param> /// <param name="password">The password supplied by the user.</param> /// <returns> /// DialogResult.OK = if Successfully prompted user for credentials. /// DialogResult.Cancel = if user cancelled the prompt dialog. /// </returns> private static DialogResult ShowOSCredentialDialog(string target, IntPtr hwdOwner, out string userName, out string password) { DialogResult retValue = DialogResult.Cancel; userName = string.Empty; password = string.Empty; string titleFormat = SR.CredentialDialog_TitleFormat; string descriptionFormat = SR.CredentialDialog_DescriptionTextFormat; // Create the CREDUI_INFO structure. NativeMethods.CREDUI_INFO info = new NativeMethods.CREDUI_INFO(); info.pszCaptionText = string.Format(CultureInfo.CurrentUICulture, titleFormat, target); info.pszMessageText = string.Format(CultureInfo.CurrentUICulture, descriptionFormat, target); info.hwndParentCERParent = hwdOwner; info.hbmBannerCERHandle = IntPtr.Zero; info.cbSize = Marshal.SizeOf(info); // We do not use CREDUI_FLAGS_VALIDATE_USERNAME flag as it doesn't allow plain user // (one with no domain component). Instead we use CREDUI_FLAGS_COMPLETE_USERNAME. // It does some basic username validation (like doesnt allow two "\" in the user name. // It does adds the target to the username. For example, if user entered "foo" for // taget "bar.com", it will return username as "bar.com\foo". We trim out bar.com // while parsing the username. User can input "*****@*****.**" as workaround to provide // "bar.com\foo" as the username. // We specify CRED_TYPE_SERVER_CREDENTIAL flag as the stored credentials appear in the // "Control Panel->Stored Usernames and Password". It is how IE stores and retrieve // credentials. By using the CRED_TYPE_SERVER_CREDENTIAL flag allows IE and VS to // share credentials. // We dont specify the CREDUI_FLAGS_EXPECT_CONFIRMATION as the VS proxy service consumers // dont call back into the service to confirm that the call succeeded. NativeMethods.CREDUI_FLAGS flags = NativeMethods.CREDUI_FLAGS.SERVER_CREDENTIAL | NativeMethods.CREDUI_FLAGS.SHOW_SAVE_CHECK_BOX | NativeMethods.CREDUI_FLAGS.COMPLETE_USERNAME | NativeMethods.CREDUI_FLAGS.EXCLUDE_CERTIFICATES; StringBuilder user = new StringBuilder(Convert.ToInt32(NativeMethods.CREDUI_MAX_USERNAME_LENGTH)); StringBuilder pwd = new StringBuilder(Convert.ToInt32(NativeMethods.CREDUI_MAX_PASSWORD_LENGTH)); int saveCredentials = 0; // Ensures that CredUPPromptForCredentials results in a prompt. int netError = NativeMethods.ERROR_LOGON_FAILURE; // Call the OS API to prompt for credentials. NativeMethods.CredUIReturnCodes result = NativeMethods.CredUIPromptForCredentials( info, target, IntPtr.Zero, netError, user, NativeMethods.CREDUI_MAX_USERNAME_LENGTH, pwd, NativeMethods.CREDUI_MAX_PASSWORD_LENGTH, ref saveCredentials, flags); if (result == NativeMethods.CredUIReturnCodes.NO_ERROR) { userName = user.ToString(); password = pwd.ToString(); try { if (Convert.ToBoolean(saveCredentials)) { // Try reading the credentials back to ensure that we can read the stored credentials. If // the CredUIPromptForCredentials() function is not able successfully call CredGetTargetInfo(), // it will store credentials with credential type as DOMAIN_PASSWORD. For DOMAIN_PASSWORD // credential type we can only retrive the user name. As a workaround, we store the credentials // as credential type as GENERIC. string storedUserName; string storedPassword; bool successfullyReadCredentials = ReadOSCredentials(target, out storedUserName, out storedPassword); if (!successfullyReadCredentials || !string.Equals(userName, storedUserName, StringComparison.Ordinal) || !string.Equals(password, storedPassword, StringComparison.Ordinal)) { // We are not able to retrieve the credentials. Try storing them as GENERIC credetails. // Create the NativeCredential object. NativeMethods.NativeCredential customCredential = new NativeMethods.NativeCredential(); customCredential.userName = userName; customCredential.type = NativeMethods.CRED_TYPE_GENERIC; customCredential.targetName = CreateCustomTarget(target); // Store credentials across sessions. customCredential.persist = (uint)NativeMethods.CRED_PERSIST.LOCAL_MACHINE; if (!string.IsNullOrEmpty(password)) { customCredential.credentialBlobSize = (uint)Marshal.SystemDefaultCharSize * ((uint)password.Length); customCredential.credentialBlob = Marshal.StringToCoTaskMemAuto(password); } try { NativeMethods.CredWrite(ref customCredential, 0); } finally { if (customCredential.credentialBlob != IntPtr.Zero) { Marshal.FreeCoTaskMem(customCredential.credentialBlob); } } } } } catch { // Ignore that failure to read back the credentials. We still have // username and password to use in the current session. } retValue = DialogResult.OK; } else if (result == NativeMethods.CredUIReturnCodes.ERROR_CANCELLED) { retValue = DialogResult.Cancel; } else { Debug.Fail("CredUIPromptForCredentials failed with result = " + result.ToString()); retValue = DialogResult.Cancel; } info.Dispose(); return(retValue); }