diff options
author | vabr <vabr@chromium.org> | 2015-10-07 04:05:48 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-07 11:07:05 +0000 |
commit | e0e1e99f39349e912612e58249393f2281de4d00 (patch) | |
tree | c9ab79671cb5c8bc49f50ca432258e19bd0429d7 | |
parent | c8a0c23e2e7b5fc52b7d2bd911016289b8b25ebc (diff) | |
download | chromium_src-e0e1e99f39349e912612e58249393f2281de4d00.zip chromium_src-e0e1e99f39349e912612e58249393f2281de4d00.tar.gz chromium_src-e0e1e99f39349e912612e58249393f2281de4d00.tar.bz2 |
Do not involve PasswordManagerDriver in filling HTTP-auth forms; also check realm
The HTTP authentication is not tied to a renderer and happens completely browser-side.
This CL simplifies the password manager API for filling these forms to avoid referencing the renderer. It also enhances LoginModelObserver to filter irrelevant credentials by realm.
BUG=537823
Review URL: https://codereview.chromium.org/1384283003
Cr-Commit-Position: refs/heads/master@{#352816}
20 files changed, 398 insertions, 120 deletions
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc index 7688c10..1d7ae87 100644 --- a/chrome/browser/password_manager/password_manager_browsertest.cc +++ b/chrome/browser/password_manager/password_manager_browsertest.cc @@ -35,6 +35,7 @@ #include "components/autofill/core/common/password_form.h" #include "components/password_manager/content/browser/content_password_manager_driver.h" #include "components/password_manager/content/browser/content_password_manager_driver_factory.h" +#include "components/password_manager/core/browser/login_model.h" #include "components/password_manager/core/browser/test_password_store.h" #include "components/password_manager/core/common/password_manager_switches.h" #include "components/version_info/version_info.h" @@ -61,8 +62,19 @@ #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/geometry/point.h" +using testing::_; + namespace { +class MockLoginModelObserver : public password_manager::LoginModelObserver { + public: + MOCK_METHOD2(OnAutofillDataAvailableInternal, + void(const base::string16&, const base::string16&)); + + private: + void OnLoginModelDestroying() override {} +}; + GURL GetFileURL(const char* filename) { base::FilePath path; PathService::Get(chrome::DIR_TEST_DATA, &path); @@ -2485,4 +2497,60 @@ IN_PROC_BROWSER_TEST_F( EXPECT_EQ("", retyped_password); } +// When there are multiple LoginModelObservers (e.g., multiple HTTP auth dialogs +// as in http://crbug.com/537823), ensure that credentials from PasswordStore +// distributed to them are filtered by the realm. +IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTestBase, + BasicAuthSeparateRealms) { + embedded_test_server()->RegisterRequestHandler( + base::Bind(&HandleTestAuthRequest)); + + // Save credentials for "test realm" in the store. + scoped_refptr<password_manager::TestPasswordStore> password_store = + static_cast<password_manager::TestPasswordStore*>( + PasswordStoreFactory::GetForProfile( + browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS) + .get()); + autofill::PasswordForm creds; + creds.scheme = autofill::PasswordForm::SCHEME_BASIC; + creds.signon_realm = embedded_test_server()->base_url().spec() + "test realm"; + creds.password_value = base::ASCIIToUTF16("pw"); + creds.username_value = base::ASCIIToUTF16("temp"); + password_store->AddLogin(creds); + base::RunLoop run_loop; + run_loop.RunUntilIdle(); + ASSERT_FALSE(password_store->IsEmpty()); + + // In addition to the LoginModelObserver created automatically for the HTTP + // auth dialog, also create a mock observer, for a different realm. + MockLoginModelObserver mock_login_model_observer; + PasswordManager* password_manager = + ChromePasswordManagerClient::FromWebContents(WebContents()) + ->GetPasswordManager(); + autofill::PasswordForm other_form(creds); + other_form.signon_realm = "https://example.com/other realm"; + password_manager->AddObserverAndDeliverCredentials(&mock_login_model_observer, + other_form); + // The mock observer should not receive the stored credentials. + EXPECT_CALL(mock_login_model_observer, OnAutofillDataAvailableInternal(_, _)) + .Times(0); + + // Now wait until the navigation to the test server causes a HTTP auth dialog + // to appear. + content::NavigationController* nav_controller = + &WebContents()->GetController(); + WindowedAuthNeededObserver auth_needed_observer(nav_controller); + ui_test_utils::NavigateToURLWithDisposition( + browser(), embedded_test_server()->GetURL("/basic_auth"), CURRENT_TAB, + ui_test_utils::BROWSER_TEST_NONE); + auth_needed_observer.Wait(); + + // The auth dialog caused a query to PasswordStore, make sure it was + // processed. + base::RunLoop run_loop2; + run_loop2.RunUntilIdle(); + + password_manager->RemoveObserver(&mock_login_model_observer); +} + } // namespace password_manager diff --git a/chrome/browser/ui/android/login_prompt_android.cc b/chrome/browser/ui/android/login_prompt_android.cc index 7f478bf..367cb2c 100644 --- a/chrome/browser/ui/android/login_prompt_android.cc +++ b/chrome/browser/ui/android/login_prompt_android.cc @@ -10,7 +10,6 @@ #include "base/strings/utf_string_conversions.h" #include "chrome/browser/ui/android/chrome_http_auth_handler.h" #include "chrome/browser/ui/android/window_android_helper.h" -#include "chrome/browser/ui/login/login_prompt.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" #include "net/base/auth.h" @@ -28,8 +27,9 @@ class LoginHandlerAndroid : public LoginHandler { // LoginHandler methods: - void OnAutofillDataAvailable(const base::string16& username, - const base::string16& password) override { + void OnAutofillDataAvailableInternal( + const base::string16& username, + const base::string16& password) override { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(chrome_http_auth_handler_.get() != NULL); chrome_http_auth_handler_->OnAutofillDataAvailable( @@ -37,8 +37,8 @@ class LoginHandlerAndroid : public LoginHandler { } void OnLoginModelDestroying() override {} - void BuildViewForPasswordManager(password_manager::PasswordManager* manager, - const base::string16& explanation) override { + void BuildView(const base::string16& explanation, + LoginModelData* login_model_data) override { DCHECK_CURRENTLY_ON(BrowserThread::UI); // Get pointer to TabAndroid @@ -55,8 +55,11 @@ class LoginHandlerAndroid : public LoginHandler { chrome_http_auth_handler_->ShowDialog( window_helper->GetWindowAndroid()->GetJavaObject().obj()); - // Register to receive a callback to OnAutofillDataAvailable(). - SetModel(manager); + if (login_model_data) + SetModel(*login_model_data); + else + ResetModel(); + NotifyAuthNeeded(); } else { CancelAuth(); diff --git a/chrome/browser/ui/cocoa/login_prompt_cocoa.mm b/chrome/browser/ui/cocoa/login_prompt_cocoa.mm index c2a6a8e..daa4f9b 100644 --- a/chrome/browser/ui/cocoa/login_prompt_cocoa.mm +++ b/chrome/browser/ui/cocoa/login_prompt_cocoa.mm @@ -41,8 +41,9 @@ class LoginHandlerMac : public LoginHandler, } // LoginModelObserver implementation. - void OnAutofillDataAvailable(const base::string16& username, - const base::string16& password) override { + void OnAutofillDataAvailableInternal( + const base::string16& username, + const base::string16& password) override { DCHECK_CURRENTLY_ON(BrowserThread::UI); [sheet_controller_ autofillLogin:base::SysUTF16ToNSString(username) @@ -51,14 +52,17 @@ class LoginHandlerMac : public LoginHandler, void OnLoginModelDestroying() override {} // LoginHandler: - void BuildViewForPasswordManager(password_manager::PasswordManager* manager, - const base::string16& explanation) override { + void BuildView(const base::string16& explanation, + LoginModelData* login_model_data) override { DCHECK_CURRENTLY_ON(BrowserThread::UI); sheet_controller_.reset( [[LoginHandlerSheet alloc] initWithLoginHandler:this]); - SetModel(manager); + if (login_model_data) + SetModel(*login_model_data); + else + ResetModel(); [sheet_controller_ setExplanation:base::SysUTF16ToNSString(explanation)]; @@ -88,7 +92,7 @@ class LoginHandlerMac : public LoginHandler, // Overridden from ConstrainedWindowMacDelegate: void OnConstrainedWindowClosed(ConstrainedWindowMac* window) override { DCHECK_CURRENTLY_ON(BrowserThread::UI); - SetModel(NULL); + ResetModel(); ReleaseSoon(); constrained_window_.reset(); diff --git a/chrome/browser/ui/login/login_prompt.cc b/chrome/browser/ui/login/login_prompt.cc index 8051d83..6ee6149 100644 --- a/chrome/browser/ui/login/login_prompt.cc +++ b/chrome/browser/ui/login/login_prompt.cc @@ -21,7 +21,6 @@ #include "chrome/browser/ui/login/login_interstitial_delegate.h" #include "chrome/common/pref_names.h" #include "chrome/grit/generated_resources.h" -#include "components/password_manager/content/browser/content_password_manager_driver.h" #include "components/password_manager/core/browser/browser_save_password_progress_logger.h" #include "components/password_manager/core/browser/password_manager.h" #include "components/url_formatter/elide_url.h" @@ -90,6 +89,13 @@ std::string GetSignonRealm(const GURL& url, // ---------------------------------------------------------------------------- // LoginHandler +LoginHandler::LoginModelData::LoginModelData( + password_manager::LoginModel* login_model, + const autofill::PasswordForm& observed_form) + : model(login_model), form(observed_form) { + DCHECK(model); +} + LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info, net::URLRequest* request) : handled_auth_(false), @@ -100,8 +106,8 @@ LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info, password_manager_(NULL), login_model_(NULL) { // This constructor is called on the I/O thread, so we cannot load the nib - // here. BuildViewForPasswordManager() will be invoked on the UI thread - // later, so wait with loading the nib until then. + // here. BuildView() will be invoked on the UI thread later, so wait with + // loading the nib until then. DCHECK(request_) << "LoginHandler constructed with NULL request"; DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info"; @@ -145,12 +151,10 @@ WebContents* LoginHandler::GetWebContentsForLogin() const { return WebContents::FromRenderFrameHost(rfh); } -password_manager::ContentPasswordManagerDriver* -LoginHandler::GetPasswordManagerDriverForLogin() { - content::RenderFrameHost* rfh = content::RenderFrameHost::FromID( - render_process_host_id_, render_frame_id_); - return password_manager::ContentPasswordManagerDriver::GetForRenderFrameHost( - rfh); +password_manager::PasswordManager* LoginHandler::GetPasswordManagerForLogin() { + ChromePasswordManagerClient* client = + ChromePasswordManagerClient::FromWebContents(GetWebContentsForLogin()); + return client ? client->GetPasswordManager() : nullptr; } void LoginHandler::SetAuth(const base::string16& username, @@ -274,15 +278,20 @@ bool LoginHandler::WasAuthHandled() const { } LoginHandler::~LoginHandler() { - SetModel(NULL); + ResetModel(); } -void LoginHandler::SetModel(password_manager::LoginModel* model) { +void LoginHandler::SetModel(LoginModelData model_data) { if (login_model_) login_model_->RemoveObserver(this); - login_model_ = model; + login_model_ = model_data.model; + login_model_->AddObserverAndDeliverCredentials(this, model_data.form); +} + +void LoginHandler::ResetModel() { if (login_model_) - login_model_->AddObserver(this); + login_model_->RemoveObserver(this); + login_model_ = nullptr; } void LoginHandler::NotifyAuthNeeded() { @@ -429,13 +438,11 @@ void LoginHandler::CloseContentsDeferred() { interstitial_page->Proceed(); } -// Helper to create a PasswordForm and stuff it into a vector as input -// for PasswordManager::PasswordFormsParsed, the hook into PasswordManager. -void MakeInputForPasswordManager( - const GURL& request_url, - net::AuthChallengeInfo* auth_info, - LoginHandler* handler, - std::vector<PasswordForm>* password_manager_input) { +// Helper to create a PasswordForm for PasswordManager to start looking for +// saved credentials. +PasswordForm MakeInputForPasswordManager(const GURL& request_url, + net::AuthChallengeInfo* auth_info, + LoginHandler* handler) { PasswordForm dialog_form; if (base::LowerCaseEqualsASCII(auth_info->scheme, "basic")) { dialog_form.scheme = PasswordForm::SCHEME_BASIC; @@ -459,9 +466,9 @@ void MakeInputForPasswordManager( dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port); } dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info); - password_manager_input->push_back(dialog_form); // Set the password form for the handler (by copy). handler->SetPasswordForm(dialog_form); + return dialog_form; } void ShowLoginPrompt(const GURL& request_url, @@ -478,9 +485,6 @@ void ShowLoginPrompt(const GURL& request_url, return; } - password_manager::ContentPasswordManagerDriver* driver = - handler->GetPasswordManagerDriverForLogin(); - // The realm is controlled by the remote server, so there is no reason // to believe it is of a reasonable length. base::string16 elided_realm; @@ -504,12 +508,15 @@ void ShowLoginPrompt(const GURL& request_url, : l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION, authority, elided_realm); - if (!driver) { + password_manager::PasswordManager* password_manager = + handler->GetPasswordManagerForLogin(); + + if (!password_manager) { #if defined(ENABLE_EXTENSIONS) // A WebContents in a <webview> (a GuestView type) does not have a password // manager, but still needs to be able to show login prompts. if (guest_view::GuestViewBase::FromWebContents(parent_contents)) { - handler->BuildViewForPasswordManager(nullptr, explanation); + handler->BuildView(explanation, nullptr); return; } #endif @@ -517,8 +524,6 @@ void ShowLoginPrompt(const GURL& request_url, return; } - password_manager::PasswordManager* password_manager = - driver->GetPasswordManager(); if (password_manager && password_manager->client()->IsLoggingActive()) { password_manager::BrowserSavePasswordProgressLogger logger( password_manager->client()); @@ -526,14 +531,12 @@ void ShowLoginPrompt(const GURL& request_url, autofill::SavePasswordProgressLogger::STRING_SHOW_LOGIN_PROMPT_METHOD); } - // Tell the password manager to look for saved passwords. - std::vector<PasswordForm> v; - MakeInputForPasswordManager(request_url, auth_info, handler, &v); - driver->OnPasswordFormsParsedNoRenderCheck(v); - handler->SetPasswordManager(driver->GetPasswordManager()); + handler->SetPasswordManager(password_manager); - handler->BuildViewForPasswordManager(driver->GetPasswordManager(), - explanation); + PasswordForm observed_form( + MakeInputForPasswordManager(request_url, auth_info, handler)); + LoginHandler::LoginModelData model_data(password_manager, observed_form); + handler->BuildView(explanation, &model_data); } // This callback is run on the UI thread and creates a constrained window with diff --git a/chrome/browser/ui/login/login_prompt.h b/chrome/browser/ui/login/login_prompt.h index 9f58dcd..de5c12c 100644 --- a/chrome/browser/ui/login/login_prompt.h +++ b/chrome/browser/ui/login/login_prompt.h @@ -38,6 +38,21 @@ class LoginHandler : public content::ResourceDispatcherHostLoginDelegate, public password_manager::LoginModelObserver, public content::NotificationObserver { public: + // The purpose of this struct is to enforce that BuildView receives either + // both the login model and the observed form, or none. That is a bit spoiled + // by the fact that the model is a pointer to LoginModel, as opposed to a + // reference. Having it as a reference would go against the style guide, which + // forbids non-const references in arguments, presumably also inside passed + // structs, because the guide's rationale still applies. Therefore at least + // the constructor DCHECKs that |login_model| is not null. + struct LoginModelData { + LoginModelData(password_manager::LoginModel* login_model, + const autofill::PasswordForm& observed_form); + + password_manager::LoginModel* const model; + const autofill::PasswordForm& form; + }; + LoginHandler(net::AuthChallengeInfo* auth_info, net::URLRequest* request); // Builds the platform specific LoginHandler. Used from within @@ -48,10 +63,11 @@ class LoginHandler : public content::ResourceDispatcherHostLoginDelegate, // ResourceDispatcherHostLoginDelegate implementation: void OnRequestCancelled() override; - // Initializes the underlying platform specific view. - virtual void BuildViewForPasswordManager( - password_manager::PasswordManager* manager, - const base::string16& explanation) = 0; + // Implement this to initialize the underlying platform specific view. If + // |login_model_data| is not null, the contained LoginModel and PasswordForm + // can be used to register the view. + virtual void BuildView(const base::string16& explanation, + LoginModelData* login_model_data) = 0; // Sets information about the authentication type (|form|) and the // |password_manager| for this profile. @@ -61,9 +77,8 @@ class LoginHandler : public content::ResourceDispatcherHostLoginDelegate, // Returns the WebContents that needs authentication. content::WebContents* GetWebContentsForLogin() const; - // Returns the PasswordManager for the render frame that needs login. - password_manager::ContentPasswordManagerDriver* - GetPasswordManagerDriverForLogin(); + // Returns the PasswordManager for the web contents that needs login. + password_manager::PasswordManager* GetPasswordManagerForLogin(); // Resend the request with authentication credentials. // This function can be called from either thread. @@ -90,7 +105,12 @@ class LoginHandler : public content::ResourceDispatcherHostLoginDelegate, protected: ~LoginHandler() override; - void SetModel(password_manager::LoginModel* model); + // Sets |model_data.model| as |login_model_| and registers |this| as an + // observer for |model_data.form|-related events. + void SetModel(LoginModelData model_data); + + // Clear |login_model_| and remove |this| as an observer. + void ResetModel(); // Notify observers that authentication is needed. void NotifyAuthNeeded(); diff --git a/chrome/browser/ui/views/login_prompt_views.cc b/chrome/browser/ui/views/login_prompt_views.cc index cde26d5..02402fd 100644 --- a/chrome/browser/ui/views/login_prompt_views.cc +++ b/chrome/browser/ui/views/login_prompt_views.cc @@ -34,8 +34,9 @@ class LoginHandlerViews : public LoginHandler, public views::DialogDelegate { } // LoginModelObserver: - void OnAutofillDataAvailable(const base::string16& username, - const base::string16& password) override { + void OnAutofillDataAvailableInternal( + const base::string16& username, + const base::string16& password) override { // Nothing to do here since LoginView takes care of autofill for win. } void OnLoginModelDestroying() override {} @@ -67,7 +68,7 @@ class LoginHandlerViews : public LoginHandler, public views::DialogDelegate { // The widget is going to delete itself; clear our pointer. dialog_ = NULL; - SetModel(NULL); + ResetModel(); ReleaseSoon(); } @@ -97,8 +98,8 @@ class LoginHandlerViews : public LoginHandler, public views::DialogDelegate { } // LoginHandler: - void BuildViewForPasswordManager(password_manager::PasswordManager* manager, - const base::string16& explanation) override { + void BuildView(const base::string16& explanation, + LoginModelData* login_model_data) override { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Create a new LoginView and set the model for it. The model (password @@ -106,7 +107,7 @@ class LoginHandlerViews : public LoginHandler, public views::DialogDelegate { // browser window, so the view may be destroyed after the password // manager. The view listens for model destruction and unobserves // accordingly. - login_view_ = new LoginView(explanation, manager); + login_view_ = new LoginView(explanation, login_model_data); // Scary thread safety note: This can potentially be called *after* SetAuth // or CancelAuth (say, if the request was cancelled before the UI thread got diff --git a/chrome/browser/ui/views/login_view.cc b/chrome/browser/ui/views/login_view.cc index 50f697a..78768e7 100644 --- a/chrome/browser/ui/views/login_view.cc +++ b/chrome/browser/ui/views/login_view.cc @@ -21,7 +21,7 @@ using views::GridLayout; // LoginView, public: LoginView::LoginView(const base::string16& explanation, - LoginModel* model) + LoginHandler::LoginModelData* login_model_data) : username_field_(new views::Textfield()), password_field_(new views::Textfield()), username_label_(new views::Label( @@ -29,7 +29,7 @@ LoginView::LoginView(const base::string16& explanation, password_label_(new views::Label( l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_PASSWORD_FIELD))), message_label_(new views::Label(explanation)), - login_model_(model) { + login_model_(login_model_data ? login_model_data->model : nullptr) { password_field_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); message_label_->SetMultiLine(true); message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); @@ -75,8 +75,10 @@ LoginView::LoginView(const base::string16& explanation, layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); - if (login_model_) - login_model_->AddObserver(this); + if (login_model_data) { + login_model_->AddObserverAndDeliverCredentials(this, + login_model_data->form); + } } LoginView::~LoginView() { @@ -99,8 +101,9 @@ views::View* LoginView::GetInitiallyFocusedView() { /////////////////////////////////////////////////////////////////////////////// // LoginView, views::View, password_manager::LoginModelObserver overrides: -void LoginView::OnAutofillDataAvailable(const base::string16& username, - const base::string16& password) { +void LoginView::OnAutofillDataAvailableInternal( + const base::string16& username, + const base::string16& password) { if (username_field_->text().empty()) { username_field_->SetText(username); password_field_->SetText(password); diff --git a/chrome/browser/ui/views/login_view.h b/chrome/browser/ui/views/login_view.h index eab866d..b2cb06f 100644 --- a/chrome/browser/ui/views/login_view.h +++ b/chrome/browser/ui/views/login_view.h @@ -6,6 +6,8 @@ #define CHROME_BROWSER_UI_VIEWS_LOGIN_VIEW_H_ #include "base/compiler_specific.h" +#include "chrome/browser/ui/login/login_prompt.h" +#include "components/autofill/core/common/password_form.h" #include "components/password_manager/core/browser/login_model.h" #include "ui/views/view.h" @@ -19,10 +21,11 @@ class Textfield; class LoginView : public views::View, public password_manager::LoginModelObserver { public: - // |model| is observed for the entire lifetime of the LoginView. - // Therefore |model| should not be destroyed before the LoginView object. + // |login_model_data->model| is observed for the entire lifetime of the + // LoginView. Therefore |login_model_data->model| should not be destroyed + // before the LoginView object. |login_model_data| may be null. LoginView(const base::string16& explanation, - password_manager::LoginModel* model); + LoginHandler::LoginModelData* login_model_data); ~LoginView() override; // Access the data in the username/password text fields. @@ -30,8 +33,8 @@ class LoginView : public views::View, const base::string16& GetPassword() const; // password_manager::LoginModelObserver: - void OnAutofillDataAvailable(const base::string16& username, - const base::string16& password) override; + void OnAutofillDataAvailableInternal(const base::string16& username, + const base::string16& password) override; void OnLoginModelDestroying() override; // Used by LoginHandlerWin to set the initial focus. diff --git a/components/autofill/core/common/save_password_progress_logger.cc b/components/autofill/core/common/save_password_progress_logger.cc index 58e1c2d..57ffd1d 100644 --- a/components/autofill/core/common/save_password_progress_logger.cc +++ b/components/autofill/core/common/save_password_progress_logger.cc @@ -299,6 +299,8 @@ std::string SavePasswordProgressLogger::GetStringFromID( return "Show password prompt"; case SavePasswordProgressLogger::STRING_PASSWORDMANAGER_AUTOFILL: return "PasswordManager::Autofill"; + case SavePasswordProgressLogger::STRING_PASSWORDMANAGER_AUTOFILLHTTPAUTH: + return "PasswordManager::AutofillHttpAuth"; case SavePasswordProgressLogger::STRING_WAIT_FOR_USERNAME: return "wait_for_username"; case SavePasswordProgressLogger::STRING_LOGINMODELOBSERVER_PRESENT: diff --git a/components/autofill/core/common/save_password_progress_logger.h b/components/autofill/core/common/save_password_progress_logger.h index ff43e90..ccfa6cc 100644 --- a/components/autofill/core/common/save_password_progress_logger.h +++ b/components/autofill/core/common/save_password_progress_logger.h @@ -102,6 +102,7 @@ class SavePasswordProgressLogger { STRING_ONLY_VISIBLE, STRING_SHOW_PASSWORD_PROMPT, STRING_PASSWORDMANAGER_AUTOFILL, + STRING_PASSWORDMANAGER_AUTOFILLHTTPAUTH, STRING_WAIT_FOR_USERNAME, STRING_LOGINMODELOBSERVER_PRESENT, STRING_WAS_LAST_NAVIGATION_HTTP_ERROR_METHOD, diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 7bfe6af..4f43573 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -420,6 +420,7 @@ 'password_manager/core/browser/import/csv_reader_unittest.cc', 'password_manager/core/browser/log_router_unittest.cc', 'password_manager/core/browser/login_database_unittest.cc', + 'password_manager/core/browser/login_model_unittest.cc', 'password_manager/core/browser/mock_affiliated_match_helper.cc', 'password_manager/core/browser/mock_affiliated_match_helper.h', 'password_manager/core/browser/password_autofill_manager_unittest.cc', diff --git a/components/password_manager.gypi b/components/password_manager.gypi index a541d0a..f73a9e8 100644 --- a/components/password_manager.gypi +++ b/components/password_manager.gypi @@ -70,6 +70,7 @@ 'password_manager/core/browser/login_database_mac.cc', 'password_manager/core/browser/login_database_posix.cc', 'password_manager/core/browser/login_database_win.cc', + 'password_manager/core/browser/login_model.cc', 'password_manager/core/browser/login_model.h', 'password_manager/core/browser/password_autofill_manager.cc', 'password_manager/core/browser/password_autofill_manager.h', diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn index 5668e64..764d291 100644 --- a/components/password_manager/core/browser/BUILD.gn +++ b/components/password_manager/core/browser/BUILD.gn @@ -51,6 +51,7 @@ source_set("browser") { "login_database_mac.cc", "login_database_posix.cc", "login_database_win.cc", + "login_model.cc", "login_model.h", "password_autofill_manager.cc", "password_autofill_manager.h", @@ -181,6 +182,7 @@ source_set("unit_tests") { "import/csv_reader_unittest.cc", "log_router_unittest.cc", "login_database_unittest.cc", + "login_model_unittest.cc", "mock_affiliated_match_helper.cc", "mock_affiliated_match_helper.h", "password_autofill_manager_unittest.cc", diff --git a/components/password_manager/core/browser/login_model.cc b/components/password_manager/core/browser/login_model.cc new file mode 100644 index 0000000..a2bf712 --- /dev/null +++ b/components/password_manager/core/browser/login_model.cc @@ -0,0 +1,27 @@ +// Copyright 2015 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. + +#include "components/password_manager/core/browser/login_model.h" + +namespace password_manager { + +LoginModelObserver::LoginModelObserver() {} + +LoginModelObserver::~LoginModelObserver() {} + +void LoginModelObserver::OnAutofillDataAvailable( + const autofill::PasswordForm& credentials) { + DCHECK(!signon_realm_.empty()) + << "The model did not call set_signon_realm properly."; + + if (credentials.signon_realm == signon_realm_) { + OnAutofillDataAvailableInternal(credentials.username_value, + credentials.password_value); + } +} + +LoginModel::LoginModel() {} +LoginModel::~LoginModel() {} + +} // namespace password_manager diff --git a/components/password_manager/core/browser/login_model.h b/components/password_manager/core/browser/login_model.h index 0401926..612d45a 100644 --- a/components/password_manager/core/browser/login_model.h +++ b/components/password_manager/core/browser/login_model.h @@ -5,34 +5,67 @@ #ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_LOGIN_MODEL_H_ #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_LOGIN_MODEL_H_ +#include <string> + +#include "base/macros.h" #include "base/strings/string16.h" +#include "components/autofill/core/common/password_form.h" namespace password_manager { -// Simple Model & Observer interfaces for a LoginView to facilitate exchanging -// information. +// A base class for the http-auth UI to communicate with the provider of stored +// credentials. class LoginModelObserver { public: - // Called by the model when a username,password pair has been identified - // as a match for the pending login prompt. - virtual void OnAutofillDataAvailable(const base::string16& username, - const base::string16& password) = 0; + LoginModelObserver(); + + // Called by the model when |credentials| has been identified as a match for + // the pending login prompt. Checks that the realm matches, and passes + // |credentials| to OnAutofillDataAvailableInternal. + void OnAutofillDataAvailable(const autofill::PasswordForm& credentials); virtual void OnLoginModelDestroying() = 0; + // To be called by the model during AddObserverAndDeliverCredentials. + void set_signon_realm(const std::string& signon_realm) { + signon_realm_ = signon_realm; + } + protected: - virtual ~LoginModelObserver() {} + virtual ~LoginModelObserver(); + + virtual void OnAutofillDataAvailableInternal( + const base::string16& username, + const base::string16& password) = 0; + + private: + // Signon realm for which this observer wishes to receive credentials. + std::string signon_realm_; + + DISALLOW_COPY_AND_ASSIGN(LoginModelObserver); }; +// Corresponding Model interface to be inherited by the provider of stored +// passwords. class LoginModel { public: - // Add an observer interested in the data from the model. - virtual void AddObserver(LoginModelObserver* observer) = 0; + LoginModel(); + + // Add an observer interested in the data from the model. Also, requests that + // the model sends a callback to |observer| with stored credentials for + // |observed_form|. + virtual void AddObserverAndDeliverCredentials( + LoginModelObserver* observer, + const autofill::PasswordForm& observed_form) = 0; + // Remove an observer from the model. virtual void RemoveObserver(LoginModelObserver* observer) = 0; protected: - virtual ~LoginModel() {} + virtual ~LoginModel(); + + private: + DISALLOW_COPY_AND_ASSIGN(LoginModel); }; } // namespace password_manager diff --git a/components/password_manager/core/browser/login_model_unittest.cc b/components/password_manager/core/browser/login_model_unittest.cc new file mode 100644 index 0000000..b9915a0 --- /dev/null +++ b/components/password_manager/core/browser/login_model_unittest.cc @@ -0,0 +1,66 @@ +// Copyright 2015 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. + +#include "components/password_manager/core/browser/login_model.h" + +#include "base/strings/utf_string_conversions.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using autofill::PasswordForm; +using base::ASCIIToUTF16; +using testing::_; + +namespace password_manager { + +namespace { + +// Mocking the class under test is fine, because the mocked methods and the +// tested methods are disjoint. In particular, the virtual methods of +// LoginModelObserver are all pure, so what can be tested are only non-virtual +// (and hence non-mockable) methods. +class MockLoginModelObserver : public LoginModelObserver { + public: + MOCK_METHOD2(OnAutofillDataAvailableInternal, + void(const base::string16& username, + const base::string16& password)); + + private: + void OnLoginModelDestroying() override {} +}; + +PasswordForm CreateFormWithRealm(const std::string& realm) { + PasswordForm form; + form.origin = GURL("http://accounts.google.com/a/LoginAuth"); + form.action = GURL("http://accounts.google.com/a/Login"); + form.username_element = ASCIIToUTF16("Email"); + form.password_element = ASCIIToUTF16("Passwd"); + form.username_value = ASCIIToUTF16("expected username"); + form.password_value = ASCIIToUTF16("expected password"); + form.signon_realm = realm; + return form; +} + +} // namespace + +TEST(LoginModelObserverTest, FilterNotMatchingRealm) { + MockLoginModelObserver observer; + observer.set_signon_realm("http://example.com/"); + EXPECT_CALL(observer, OnAutofillDataAvailableInternal(_, _)).Times(0); + PasswordForm form = CreateFormWithRealm("http://test.org/"); + observer.OnAutofillDataAvailable(form); +} + +TEST(LoginModelObserverTest, NotFilterMatchingRealm) { + MockLoginModelObserver observer; + observer.set_signon_realm("http://example.com/"); + EXPECT_CALL(observer, OnAutofillDataAvailableInternal( + ASCIIToUTF16("expected username"), + ASCIIToUTF16("expected password"))); + PasswordForm form = CreateFormWithRealm("http://example.com/"); + observer.OnAutofillDataAvailable(form); +} + +} // namespace password_manager diff --git a/components/password_manager/core/browser/password_form_manager.cc b/components/password_manager/core/browser/password_form_manager.cc index 6ba4ba2..1d5dd83 100644 --- a/components/password_manager/core/browser/password_form_manager.cc +++ b/components/password_manager/core/browser/password_form_manager.cc @@ -113,7 +113,10 @@ PasswordFormManager::PasswordFormManager( user_action_(kUserActionNone), submit_result_(kSubmitResultNotSubmitted), form_type_(kFormTypeUnspecified) { - drivers_.push_back(driver); + DCHECK_EQ(observed_form.scheme == PasswordForm::SCHEME_HTML, + driver != nullptr); + if (driver) + drivers_.push_back(driver); } PasswordFormManager::~PasswordFormManager() { @@ -489,6 +492,7 @@ void PasswordFormManager::OnRequestDone( void PasswordFormManager::ProcessFrame( const base::WeakPtr<PasswordManagerDriver>& driver) { + DCHECK_EQ(PasswordForm::SCHEME_HTML, observed_form_.scheme); if (state_ == POST_MATCHING_PHASE) ProcessFrameInternal(driver); @@ -504,6 +508,7 @@ void PasswordFormManager::ProcessFrame( void PasswordFormManager::ProcessFrameInternal( const base::WeakPtr<PasswordManagerDriver>& driver) { + DCHECK_EQ(PasswordForm::SCHEME_HTML, observed_form_.scheme); if (!driver || manager_action_ == kManagerActionBlacklisted) return; @@ -536,6 +541,15 @@ void PasswordFormManager::ProcessFrameInternal( *preferred_match_, wait_for_username); } +void PasswordFormManager::ProcessLoginPrompt() { + DCHECK_NE(PasswordForm::SCHEME_HTML, observed_form_.scheme); + if (!preferred_match_) + return; + + manager_action_ = kManagerActionAutofilled; + password_manager_->AutofillHttpAuth(best_matches_, *preferred_match_); +} + void PasswordFormManager::OnGetPasswordStoreResults( ScopedVector<PasswordForm> results) { DCHECK_EQ(state_, MATCHING_PHASE); @@ -567,6 +581,8 @@ void PasswordFormManager::OnGetPasswordStoreResults( if (manager_action_ != kManagerActionBlacklisted) { for (auto const& driver : drivers_) ProcessFrameInternal(driver); + if (observed_form_.scheme != PasswordForm::SCHEME_HTML) + ProcessLoginPrompt(); } } diff --git a/components/password_manager/core/browser/password_form_manager.h b/components/password_manager/core/browser/password_form_manager.h index 1740d89..d37bcd8 100644 --- a/components/password_manager/core/browser/password_form_manager.h +++ b/components/password_manager/core/browser/password_form_manager.h @@ -287,6 +287,9 @@ class PasswordFormManager : public PasswordStoreConsumer { // (fill data, whether to allow password generation, etc.). void ProcessFrameInternal(const base::WeakPtr<PasswordManagerDriver>& driver); + // Trigger filling of HTTP auth dialog and update |manager_action_|. + void ProcessLoginPrompt(); + // Determines if we need to autofill given the results of the query. // Takes ownership of the elements in |result|. void OnRequestDone(ScopedVector<autofill::PasswordForm> result); diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc index d30b10a..3529e88 100644 --- a/components/password_manager/core/browser/password_manager.cc +++ b/components/password_manager/core/browser/password_manager.cc @@ -4,6 +4,8 @@ #include "components/password_manager/core/browser/password_manager.h" +#include <map> + #include "base/command_line.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram_macros.h" @@ -395,8 +397,16 @@ void PasswordManager::AddSubmissionCallback( submission_callbacks_.push_back(callback); } -void PasswordManager::AddObserver(LoginModelObserver* observer) { +void PasswordManager::AddObserverAndDeliverCredentials( + LoginModelObserver* observer, + const PasswordForm& observed_form) { observers_.AddObserver(observer); + + observer->set_signon_realm(observed_form.signon_realm); + + std::vector<PasswordForm> observed_forms; + observed_forms.push_back(observed_form); + OnPasswordFormsParsed(nullptr, observed_forms); } void PasswordManager::RemoveObserver(LoginModelObserver* observer) { @@ -470,7 +480,8 @@ void PasswordManager::CreatePendingLoginManagers( continue; } old_manager_found = true; - old_manager->ProcessFrame(driver->AsWeakPtr()); + if (driver) + old_manager->ProcessFrame(driver->AsWeakPtr()); break; } if (old_manager_found) @@ -492,7 +503,9 @@ void PasswordManager::CreatePendingLoginManagers( logger->LogFormSignatures(Logger::STRING_ADDING_SIGNATURE, *iter); bool ssl_valid = iter->origin.SchemeIsCryptographic(); PasswordFormManager* manager = new PasswordFormManager( - this, client_, driver->AsWeakPtr(), *iter, ssl_valid); + this, client_, + (driver ? driver->AsWeakPtr() : base::WeakPtr<PasswordManagerDriver>()), + *iter, ssl_valid); pending_login_managers_.push_back(manager); PasswordStore::AuthorizationPromptPolicy prompt_policy = @@ -703,45 +716,46 @@ void PasswordManager::Autofill(password_manager::PasswordManagerDriver* driver, const PasswordFormMap& best_matches, const PasswordForm& preferred_match, bool wait_for_username) const { + DCHECK_EQ(PasswordForm::SCHEME_HTML, preferred_match.scheme); + scoped_ptr<BrowserSavePasswordProgressLogger> logger; if (client_->IsLoggingActive()) { logger.reset(new BrowserSavePasswordProgressLogger(client_)); logger->LogMessage(Logger::STRING_PASSWORDMANAGER_AUTOFILL); } - switch (form_for_autofill.scheme) { - case PasswordForm::SCHEME_HTML: { - // Note the check above is required because the observers_ for a non-HTML - // schemed password form may have been freed, so we need to distinguish. - autofill::PasswordFormFillData fill_data; - InitPasswordFormFillData(form_for_autofill, - best_matches, - &preferred_match, - wait_for_username, - OtherPossibleUsernamesEnabled(), - &fill_data); - if (logger) - logger->LogBoolean(Logger::STRING_WAIT_FOR_USERNAME, wait_for_username); - UMA_HISTOGRAM_BOOLEAN( - "PasswordManager.FillSuggestionsIncludeAndroidAppCredentials", - ContainsAndroidCredentials(fill_data)); - metrics_util::LogFilledCredentialIsFromAndroidApp( - PreferredRealmIsFromAndroid(fill_data)); - driver->FillPasswordForm(fill_data); - break; - } - default: - if (logger) { - logger->LogBoolean(Logger::STRING_LOGINMODELOBSERVER_PRESENT, - observers_.might_have_observers()); - } - FOR_EACH_OBSERVER( - LoginModelObserver, - observers_, - OnAutofillDataAvailable(preferred_match.username_value, - preferred_match.password_value)); - break; + + autofill::PasswordFormFillData fill_data; + InitPasswordFormFillData(form_for_autofill, best_matches, &preferred_match, + wait_for_username, OtherPossibleUsernamesEnabled(), + &fill_data); + if (logger) + logger->LogBoolean(Logger::STRING_WAIT_FOR_USERNAME, wait_for_username); + UMA_HISTOGRAM_BOOLEAN( + "PasswordManager.FillSuggestionsIncludeAndroidAppCredentials", + ContainsAndroidCredentials(fill_data)); + metrics_util::LogFilledCredentialIsFromAndroidApp( + PreferredRealmIsFromAndroid(fill_data)); + driver->FillPasswordForm(fill_data); + + client_->PasswordWasAutofilled(best_matches); +} + +void PasswordManager::AutofillHttpAuth( + const PasswordFormMap& best_matches, + const PasswordForm& preferred_match) const { + DCHECK_NE(PasswordForm::SCHEME_HTML, preferred_match.scheme); + + scoped_ptr<BrowserSavePasswordProgressLogger> logger; + if (client_->IsLoggingActive()) { + logger.reset(new BrowserSavePasswordProgressLogger(client_)); + logger->LogMessage(Logger::STRING_PASSWORDMANAGER_AUTOFILLHTTPAUTH); + logger->LogBoolean(Logger::STRING_LOGINMODELOBSERVER_PRESENT, + observers_.might_have_observers()); } + FOR_EACH_OBSERVER(LoginModelObserver, observers_, + OnAutofillDataAvailable(preferred_match)); + client_->PasswordWasAutofilled(best_matches); } diff --git a/components/password_manager/core/browser/password_manager.h b/components/password_manager/core/browser/password_manager.h index 16bde65..410bf33 100644 --- a/components/password_manager/core/browser/password_manager.h +++ b/components/password_manager/core/browser/password_manager.h @@ -67,8 +67,15 @@ class PasswordManager : public LoginModel { const autofill::PasswordForm& preferred_match, bool wait_for_username) const; + // Called by a PasswordFormManager when it decides a HTTP auth dialog can be + // autofilled. + void AutofillHttpAuth(const autofill::PasswordFormMap& best_matches, + const autofill::PasswordForm& preferred_match) const; + // LoginModel implementation. - void AddObserver(LoginModelObserver* observer) override; + void AddObserverAndDeliverCredentials( + LoginModelObserver* observer, + const autofill::PasswordForm& observed_form) override; void RemoveObserver(LoginModelObserver* observer) override; void GenerationAvailableForForm(const autofill::PasswordForm& form); |