in src/XmlNotepad/proxy.cs [359:483]
private static DialogResult ShowOSCredentialDialog(string target, IntPtr hwdOwner, out string userName, out string password)
{
DialogResult retValue;
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 "foo@bar.com" 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.
bool successfullyReadCredentials = ReadOSCredentials(target, out string storedUserName, out string 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;
}