summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/ui/login/login_interstitial_delegate.cc39
-rw-r--r--chrome/browser/ui/login/login_interstitial_delegate.h47
-rw-r--r--chrome/browser/ui/login/login_prompt.cc73
-rw-r--r--chrome/browser/ui/login/login_prompt_browsertest.cc99
-rw-r--r--chrome/chrome_browser_ui.gypi2
-rw-r--r--chrome/test/data/login/cross_origin.html3
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>