Index: chrome/browser/password_manager/password_manager_util_win.cc |
diff --git a/chrome/browser/password_manager/password_manager_util_win.cc b/chrome/browser/password_manager/password_manager_util_win.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..75492abb8cff56b57d182e3196bbc93f25e98de0 |
--- /dev/null |
+++ b/chrome/browser/password_manager/password_manager_util_win.cc |
@@ -0,0 +1,224 @@ |
+// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// windows.h must be first otherwise Win8 SDK breaks. |
+#include <windows.h> |
+#include <wincred.h> |
+#include <LM.h> |
+ |
+// SECURITY_WIN32 must be defined in order to get |
+// EXTENDED_NAME_FORMAT enumeration. |
+#define SECURITY_WIN32 1 |
+#include <security.h> |
+#undef SECURITY_WIN32 |
+ |
+#include "chrome/browser/password_manager/password_manager_util.h" |
+ |
+#include "base/prefs/pref_registry_simple.h" |
+#include "base/prefs/pref_service.h" |
+#include "base/safe_numerics.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "base/time/time.h" |
+#include "chrome/browser/browser_process.h" |
+#include "chrome/browser/password_manager/password_manager.h" |
+#include "chrome/common/pref_names.h" |
+#include "content/public/browser/render_view_host.h" |
+#include "content/public/browser/render_widget_host_view.h" |
+#include "grit/chromium_strings.h" |
+#include "grit/generated_resources.h" |
+#include "ui/base/l10n/l10n_util.h" |
+ |
+#if defined(USE_AURA) |
+#include "ui/aura/root_window.h" |
+#include "ui/aura/window.h" |
+#endif |
+ |
+// static |
+void PasswordManager::RegisterLocalPrefs(PrefRegistrySimple* registry) { |
+ registry->RegisterInt64Pref(prefs::kOsPasswordLastChanged, 0); |
+ registry->RegisterBooleanPref(prefs::kOsPasswordBlank, false); |
+} |
+ |
+namespace password_manager_util { |
+ |
+const unsigned kMaxPasswordRetries = 3; |
+ |
+const unsigned kCredUiDefaultFlags = |
+ CREDUI_FLAGS_GENERIC_CREDENTIALS | |
+ CREDUI_FLAGS_EXCLUDE_CERTIFICATES | |
+ CREDUI_FLAGS_KEEP_USERNAME | |
+ CREDUI_FLAGS_ALWAYS_SHOW_UI | |
+ CREDUI_FLAGS_DO_NOT_PERSIST; |
+ |
+static int64 GetPasswordLastChanged(WCHAR* username) { |
+ LPUSER_INFO_1 user_info = NULL; |
+ DWORD age = 0; |
+ |
+ NET_API_STATUS ret = NetUserGetInfo(NULL, username, 1, (LPBYTE*) &user_info); |
+ |
+ if (ret == NERR_Success) { |
+ // Returns seconds since last password change. |
+ age = user_info->usri1_password_age; |
+ NetApiBufferFree(user_info); |
+ } else { |
+ return -1; |
+ } |
+ |
+ base::Time changed = base::Time::Now() - base::TimeDelta::FromSeconds(age); |
+ |
+ return changed.ToInternalValue(); |
+} |
+ |
+static bool CheckBlankPassword(WCHAR* username) { |
+ PrefService* local_state = g_browser_process->local_state(); |
+ int64 last_changed = GetPasswordLastChanged(username); |
+ bool need_recheck = true; |
+ bool blank_password = false; |
+ |
+ // If we cannot determine when the password was last changed |
+ // then assume the password is not blank |
+ if (last_changed == -1) |
+ return false; |
+ |
+ blank_password = local_state->GetBoolean(prefs::kOsPasswordBlank); |
+ int64 pref_last_changed = |
+ local_state->GetInt64(prefs::kOsPasswordLastChanged); |
+ if (pref_last_changed > 0 && last_changed <= pref_last_changed) { |
+ need_recheck = false; |
+ } |
+ |
+ if (need_recheck) { |
+ HANDLE handle = INVALID_HANDLE_VALUE; |
+ |
+ // Attempt to login using blank password. |
+ DWORD logon_result = LogonUser(username, |
+ L".", |
+ L"", |
+ LOGON32_LOGON_NETWORK, |
+ LOGON32_PROVIDER_DEFAULT, |
+ &handle); |
+ |
+ // Win XP and later return ERROR_ACCOUNT_RESTRICTION for blank password. |
+ if (logon_result) |
+ CloseHandle(handle); |
+ |
+ // In the case the password is blank, then LogonUser returns a failure, |
+ // handle is INVALID_HANDLE_VALUE, and GetLastError() is |
+ // ERROR_ACCOUNT_RESTRICTION. |
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385 |
+ blank_password = (logon_result || |
+ GetLastError() == ERROR_ACCOUNT_RESTRICTION); |
+ } |
+ |
+ // Account for clock skew between pulling the password age and |
+ // writing to the preferences by adding a small skew factor here. |
+ last_changed += base::Time::kMicrosecondsPerSecond; |
+ |
+ // Save the blank password status for later. |
+ local_state->SetBoolean(prefs::kOsPasswordBlank, blank_password); |
+ local_state->SetInt64(prefs::kOsPasswordLastChanged, last_changed); |
+ |
+ return blank_password; |
+} |
+ |
+bool AuthenticateUser(gfx::NativeWindow window) { |
+ bool retval = false; |
+ CREDUI_INFO cui = {}; |
+ WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {}; |
+ WCHAR displayname[CREDUI_MAX_USERNAME_LENGTH+1] = {}; |
+ WCHAR password[CREDUI_MAX_PASSWORD_LENGTH+1] = {}; |
+ DWORD username_length = CREDUI_MAX_USERNAME_LENGTH; |
+ std::wstring product_name = |
+ UTF16ToWide(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); |
+ std::wstring password_prompt = |
+ UTF16ToWide(l10n_util::GetStringUTF16( |
+ IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT)); |
+ HANDLE handle = INVALID_HANDLE_VALUE; |
+ int tries = 0; |
+ bool use_displayname = false; |
+ bool use_principalname = false; |
+ DWORD logon_result = 0; |
+ |
+ // On a domain, we obtain the User Principal Name |
+ // for domain authentication. |
+ if (GetUserNameEx(NameUserPrincipal, username, &username_length)) { |
+ use_principalname = true; |
+ } else { |
+ username_length = CREDUI_MAX_USERNAME_LENGTH; |
+ // Otherwise, we're a workstation, use the plain local username. |
+ if (!GetUserName(username, &username_length)) { |
+ DLOG(ERROR) << "Unable to obtain username " << GetLastError(); |
+ return false; |
+ } else { |
+ // As we are on a workstation, it's possible the user |
+ // has no password, so check here. |
+ if (CheckBlankPassword(username)) |
+ return true; |
+ } |
+ } |
+ |
+ // Try and obtain a friendly display name. |
+ username_length = CREDUI_MAX_USERNAME_LENGTH; |
+ if (GetUserNameEx(NameDisplay, displayname, &username_length)) |
+ use_displayname = true; |
+ |
+ cui.cbSize = sizeof(CREDUI_INFO); |
+ cui.hwndParent = NULL; |
+#if defined(USE_AURA) |
+ cui.hwndParent = window->GetDispatcher()->host()->GetAcceleratedWidget(); |
+#else |
+ cui.hwndParent = window; |
+#endif |
+ |
+ cui.pszMessageText = password_prompt.c_str(); |
+ cui.pszCaptionText = product_name.c_str(); |
+ |
+ cui.hbmBanner = NULL; |
+ BOOL save_password = FALSE; |
+ DWORD credErr = NO_ERROR; |
+ |
+ do { |
+ tries++; |
+ |
+ // TODO(wfh) Make sure we support smart cards here. |
+ credErr = CredUIPromptForCredentials( |
+ &cui, |
+ product_name.c_str(), |
+ NULL, |
+ 0, |
+ use_displayname ? displayname : username, |
+ CREDUI_MAX_USERNAME_LENGTH+1, |
+ password, |
+ CREDUI_MAX_PASSWORD_LENGTH+1, |
+ &save_password, |
+ kCredUiDefaultFlags | |
+ (tries > 1 ? CREDUI_FLAGS_INCORRECT_PASSWORD : 0)); |
+ |
+ if (credErr == NO_ERROR) { |
+ logon_result = LogonUser(username, |
+ use_principalname ? NULL : L".", |
+ password, |
+ LOGON32_LOGON_NETWORK, |
+ LOGON32_PROVIDER_DEFAULT, |
+ &handle); |
+ if (logon_result) { |
+ retval = true; |
+ CloseHandle(handle); |
+ } else { |
+ if (GetLastError() == ERROR_ACCOUNT_RESTRICTION && |
+ wcslen(password) == 0) { |
+ // Password is blank, so permit. |
+ retval = true; |
+ } else { |
+ DLOG(WARNING) << "Unable to authenticate " << GetLastError(); |
+ } |
+ } |
+ SecureZeroMemory(password, sizeof(password)); |
+ } |
+ } while (credErr == NO_ERROR && |
+ (retval == false && tries < kMaxPasswordRetries)); |
+ return retval; |
+} |
+ |
+} // namespace password_manager_util |