diff options
-rw-r--r-- | chrome/browser/ui/login/login_interstitial_delegate.cc | 39 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_interstitial_delegate.h | 47 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt.cc | 73 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt_browsertest.cc | 99 | ||||
-rw-r--r-- | chrome/chrome_browser_ui.gypi | 2 | ||||
-rw-r--r-- | chrome/test/data/login/cross_origin.html | 3 |
6 files changed, 247 insertions, 16 deletions
diff --git a/chrome/browser/ui/login/login_interstitial_delegate.cc b/chrome/browser/ui/login/login_interstitial_delegate.cc new file mode 100644 index 0000000..b4b8dfb --- /dev/null +++ b/chrome/browser/ui/login/login_interstitial_delegate.cc @@ -0,0 +1,39 @@ +// Copyright 2014 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 "chrome/browser/ui/login/login_interstitial_delegate.h" + +LoginInterstitialDelegate::LoginInterstitialDelegate( + content::WebContents* web_contents, + const GURL& request_url, + base::Closure& callback) + : callback_(callback) { + // The interstitial page owns us. + content::InterstitialPage* interstitial_page = + content::InterstitialPage::Create(web_contents, + true, + request_url, + this); + interstitial_page->Show(); +} + +LoginInterstitialDelegate::~LoginInterstitialDelegate() { +} + +void LoginInterstitialDelegate::CommandReceived(const std::string& command) { + callback_.Run(); +} + +std::string LoginInterstitialDelegate::GetHTMLContents() { + // Showing an interstitial results in a new navigation, and a new navigation + // closes all modal dialogs on the page. Therefore the login prompt must be + // shown after the interstitial is displayed. This is done by sending a + // command from the interstitial page as soon as it is loaded. + return std::string( + "<!DOCTYPE html>" + "<html><body><script>" + "window.domAutomationController.setAutomationId(1);" + "window.domAutomationController.send('1');" + "</script></body></html>"); +} diff --git a/chrome/browser/ui/login/login_interstitial_delegate.h b/chrome/browser/ui/login/login_interstitial_delegate.h new file mode 100644 index 0000000..c55a420 --- /dev/null +++ b/chrome/browser/ui/login/login_interstitial_delegate.h @@ -0,0 +1,47 @@ +// Copyright 2014 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. + +#ifndef CHROME_BROWSER_UI_LOGIN_LOGIN_INTERSTITIAL_DELEGATE_H_ +#define CHROME_BROWSER_UI_LOGIN_LOGIN_INTERSTITIAL_DELEGATE_H_ + +#include <string> + +#include "base/callback.h" +#include "content/public/browser/interstitial_page.h" +#include "content/public/browser/interstitial_page_delegate.h" +#include "url/gurl.h" + +class LoginHandler; + +namespace content { +class WebContents; +} + +namespace net { +class AuthChallengeInfo; +} + +// Placeholder interstitial for HTTP login prompts. This interstitial makes the +// omnibox show the correct url when the login prompt is visible. +class LoginInterstitialDelegate : public content::InterstitialPageDelegate { + public: + LoginInterstitialDelegate(content::WebContents* web_contents, + const GURL& request_url, + base::Closure& callback); + + virtual ~LoginInterstitialDelegate(); + + // content::InterstitialPageDelegate: + virtual void CommandReceived(const std::string& command) OVERRIDE; + + protected: + virtual std::string GetHTMLContents() OVERRIDE; + + private: + base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(LoginInterstitialDelegate); +}; + +#endif // CHROME_BROWSER_UI_LOGIN_LOGIN_INTERSTITIAL_DELEGATE_H_ diff --git a/chrome/browser/ui/login/login_prompt.cc b/chrome/browser/ui/login/login_prompt.cc index c15bd18..53f68a7 100644 --- a/chrome/browser/ui/login/login_prompt.cc +++ b/chrome/browser/ui/login/login_prompt.cc @@ -14,6 +14,7 @@ #include "chrome/browser/password_manager/chrome_password_manager_client.h" #include "chrome/browser/prerender/prerender_contents.h" #include "chrome/browser/tab_contents/tab_util.h" +#include "chrome/browser/ui/login/login_interstitial_delegate.h" #include "components/password_manager/core/browser/browser_save_password_progress_logger.h" #include "components/password_manager/core/browser/password_manager.h" #include "content/public/browser/browser_thread.h" @@ -25,6 +26,7 @@ #include "content/public/browser/web_contents.h" #include "grit/generated_resources.h" #include "net/base/auth.h" +#include "net/base/load_flags.h" #include "net/base/net_util.h" #include "net/http/http_transaction_factory.h" #include "net/url_request/url_request.h" @@ -396,6 +398,16 @@ void LoginHandler::CloseContentsDeferred() { DCHECK_CURRENTLY_ON(BrowserThread::UI); CloseDialog(); + + WebContents* requesting_contents = GetWebContentsForLogin(); + if (!requesting_contents) + return; + // If a (blank) login interstitial was displayed, proceed so that the + // navigation is committed. + content::InterstitialPage* interstitial_page = + requesting_contents->GetInterstitialPage(); + if (interstitial_page) + interstitial_page->Proceed(); } // Helper to create a PasswordForm and stuff it into a vector as input @@ -433,21 +445,13 @@ void MakeInputForPasswordManager( handler->SetPasswordForm(dialog_form); } -// This callback is run on the UI thread and creates a constrained window with -// a LoginView to prompt the user. The response will be sent to LoginHandler, -// which then routes it to the net::URLRequest on the I/O thread. -void LoginDialogCallback(const GURL& request_url, - net::AuthChallengeInfo* auth_info, - LoginHandler* handler) { +void ShowLoginPrompt(const GURL& request_url, + net::AuthChallengeInfo* auth_info, + LoginHandler* handler) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); WebContents* parent_contents = handler->GetWebContentsForLogin(); - if (!parent_contents || handler->WasAuthHandled()) { - // The request may have been cancelled, or it may be for a renderer - // not hosted by a tab (e.g. an extension). Cancel just in case - // (cancelling twice is a no-op). - handler->CancelAuth(); + if (!parent_contents) return; - } - prerender::PrerenderContents* prerender_contents = prerender::PrerenderContents::FromWebContents(parent_contents); if (prerender_contents) { @@ -485,15 +489,56 @@ void LoginDialogCallback(const GURL& request_url, handler->BuildViewForPasswordManager(password_manager, explanation); } +// This callback is run on the UI thread and creates a constrained window with +// a LoginView to prompt the user. If the prompt is triggered because of +// a cross origin navigation in the main frame, a blank interstitial is first +// created which in turn creates the LoginView. Otherwise, a LoginView is +// directly in this callback. In both cases, the response will be sent to +// LoginHandler, which then routes it to the net::URLRequest on the I/O thread. +void LoginDialogCallback(const GURL& request_url, + net::AuthChallengeInfo* auth_info, + LoginHandler* handler, + bool is_main_frame) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + WebContents* parent_contents = handler->GetWebContentsForLogin(); + if (!parent_contents || handler->WasAuthHandled()) { + // The request may have been cancelled, or it may be for a renderer + // not hosted by a tab (e.g. an extension). Cancel just in case + // (cancelling twice is a no-op). + handler->CancelAuth(); + return; + } + + if (is_main_frame && + parent_contents->GetVisibleURL().GetOrigin() != request_url.GetOrigin()) { + // Show a blank interstitial for main-frame, cross origin requests + // so that the correct URL is shown in the omnibox. + base::Closure callback = base::Bind(&ShowLoginPrompt, + request_url, + make_scoped_refptr(auth_info), + make_scoped_refptr(handler)); + // This is owned by the interstitial it creates. + new LoginInterstitialDelegate(parent_contents, + request_url, + callback); + } else { + ShowLoginPrompt(request_url, + auth_info, + handler); + } +} + // ---------------------------------------------------------------------------- // Public API LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info, net::URLRequest* request) { + bool is_main_frame = (request->load_flags() & net::LOAD_MAIN_FRAME) != 0; LoginHandler* handler = LoginHandler::Create(auth_info, request); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&LoginDialogCallback, request->url(), - make_scoped_refptr(auth_info), make_scoped_refptr(handler))); + make_scoped_refptr(auth_info), make_scoped_refptr(handler), + is_main_frame)); return handler; } diff --git a/chrome/browser/ui/login/login_prompt_browsertest.cc b/chrome/browser/ui/login/login_prompt_browsertest.cc index f5c6a8b..b0e0d13 100644 --- a/chrome/browser/ui/login/login_prompt_browsertest.cc +++ b/chrome/browser/ui/login/login_prompt_browsertest.cc @@ -16,10 +16,12 @@ #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/interstitial_page.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" #include "net/base/auth.h" #include "net/dns/mock_host_resolver.h" @@ -195,6 +197,42 @@ void WindowedLoadStopObserver::Observe( WindowedNotificationObserver::Observe(type, source, details); } +class InterstitialObserver : public content::WebContentsObserver { + public: + InterstitialObserver(content::WebContents* web_contents, + const base::Closure& attach_callback, + const base::Closure& detach_callback) + : WebContentsObserver(web_contents), + attach_callback_(attach_callback), + detach_callback_(detach_callback) { + } + + virtual void DidAttachInterstitialPage() OVERRIDE { + attach_callback_.Run(); + } + + virtual void DidDetachInterstitialPage() OVERRIDE { + detach_callback_.Run(); + } + + private: + base::Closure attach_callback_; + base::Closure detach_callback_; + + DISALLOW_COPY_AND_ASSIGN(InterstitialObserver); +}; + +void WaitForInterstitialAttach(content::WebContents* web_contents) { + scoped_refptr<content::MessageLoopRunner> interstitial_attach_loop_runner( + new content::MessageLoopRunner); + InterstitialObserver observer( + web_contents, + interstitial_attach_loop_runner->QuitClosure(), + base::Closure()); + if (!content::InterstitialPage::GetInterstitialPage(web_contents)) + interstitial_attach_loop_runner->Run(); +} + typedef WindowedNavigationObserver<chrome::NOTIFICATION_AUTH_NEEDED> WindowedAuthNeededObserver; @@ -758,7 +796,7 @@ IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, NoLoginPromptForFavicon) { // Block crossdomain image login prompting as a phishing defense. IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, - BlockCrossdomainPrompt) { + BlockCrossdomainPromptForSubresources) { const char* kTestPage = "files/login/load_img_from_b.html"; host_resolver()->AddRule("www.a.com", "127.0.0.1"); @@ -829,7 +867,7 @@ IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, // Allow crossdomain iframe login prompting despite the above. IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, - AllowCrossdomainPrompt) { + AllowCrossdomainPromptForSubframes) { const char* kTestPage = "files/login/load_iframe_from_b.html"; host_resolver()->AddRule("www.a.com", "127.0.0.1"); @@ -866,11 +904,19 @@ IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, LoginHandler* handler = *observer.handlers_.begin(); ASSERT_TRUE(handler); + // When a cross origin iframe displays a login prompt, the blank + // interstitial shouldn't be displayed and the omnibox should show the + // main frame's url, not the iframe's. + EXPECT_EQ(new_host, contents->GetURL().host()); + handler->CancelAuth(); auth_cancelled_waiter.Wait(); } } + // Should stay on the main frame's url once the prompt the iframe is closed. + EXPECT_EQ("www.a.com", contents->GetURL().host()); + EXPECT_EQ(1, observer.auth_needed_count_); EXPECT_TRUE(test_server()->Stop()); } @@ -1255,4 +1301,53 @@ IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, EXPECT_TRUE(test_server()->Stop()); } +// If a cross origin navigation triggers a login prompt, the destination URL +// should be shown in the omnibox. +IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, + ShowCorrectUrlForCrossOriginMainFrameRequests) { + const char* kTestPage = "files/login/cross_origin.html"; + host_resolver()->AddRule("www.a.com", "127.0.0.1"); + ASSERT_TRUE(test_server()->Start()); + + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + NavigationController* controller = &contents->GetController(); + LoginPromptBrowserTestObserver observer; + + observer.Register(content::Source<NavigationController>(controller)); + + // Load a page which navigates to a cross origin page with a login prompt. + { + GURL test_page = test_server()->GetURL(kTestPage); + ASSERT_EQ("127.0.0.1", test_page.host()); + + WindowedAuthNeededObserver auth_needed_waiter(controller); + browser()->OpenURL(OpenURLParams( + test_page, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_TYPED, + false)); + ASSERT_EQ("127.0.0.1", contents->GetURL().host()); + auth_needed_waiter.Wait(); + ASSERT_EQ(1u, observer.handlers_.size()); + WaitForInterstitialAttach(contents); + + // The omnibox should show the correct origin for the new page when the + // login prompt is shown. + EXPECT_EQ("www.a.com", contents->GetURL().host()); + EXPECT_TRUE(contents->ShowingInterstitialPage()); + + // Cancel and wait for the interstitial to detach. + LoginHandler* handler = *observer.handlers_.begin(); + scoped_refptr<content::MessageLoopRunner> loop_runner( + new content::MessageLoopRunner); + InterstitialObserver interstitial_observer(contents, + base::Closure(), + loop_runner->QuitClosure()); + handler->CancelAuth(); + if (content::InterstitialPage::GetInterstitialPage(contents)) + loop_runner->Run(); + EXPECT_EQ("www.a.com", contents->GetURL().host()); + EXPECT_FALSE(contents->ShowingInterstitialPage()); + } +} + } // namespace diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi index 3e32221..eb13e474 100644 --- a/chrome/chrome_browser_ui.gypi +++ b/chrome/chrome_browser_ui.gypi @@ -1192,6 +1192,8 @@ 'browser/ui/host_desktop.h', 'browser/ui/hung_plugin_tab_helper.cc', 'browser/ui/hung_plugin_tab_helper.h', + 'browser/ui/login/login_interstitial_delegate.cc', + 'browser/ui/login/login_interstitial_delegate.h', 'browser/ui/login/login_prompt.cc', 'browser/ui/login/login_prompt.h', 'browser/ui/media_utils.cc', diff --git a/chrome/test/data/login/cross_origin.html b/chrome/test/data/login/cross_origin.html new file mode 100644 index 0000000..7728a97 --- /dev/null +++ b/chrome/test/data/login/cross_origin.html @@ -0,0 +1,3 @@ +<script> + window.location = "http://www.a.com:" + location.port + "/auth-basic/index.html"; +</script> |