1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/command_line.h"
6#include "chrome/browser/signin/signin_promo.h"
7#include "chrome/browser/ui/browser.h"
8#include "chrome/browser/ui/tabs/tab_strip_model.h"
9#include "chrome/browser/ui/webui/signin/inline_login_ui.h"
10#include "chrome/browser/ui/webui/signin/login_ui_service.h"
11#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
12#include "chrome/common/chrome_switches.h"
13#include "chrome/common/url_constants.h"
14#include "chrome/test/base/in_process_browser_test.h"
15#include "chrome/test/base/test_browser_window.h"
16#include "chrome/test/base/test_chrome_web_ui_controller_factory.h"
17#include "chrome/test/base/testing_browser_process.h"
18#include "chrome/test/base/ui_test_utils.h"
19#include "content/public/browser/render_frame_host.h"
20#include "content/public/browser/render_process_host.h"
21#include "content/public/browser/session_storage_namespace.h"
22#include "content/public/browser/storage_partition.h"
23#include "content/public/browser/web_contents.h"
24#include "content/public/browser/web_ui_controller.h"
25#include "content/public/common/url_constants.h"
26#include "content/public/test/browser_test_utils.h"
27#include "content/public/test/test_navigation_observer.h"
28#include "google_apis/gaia/fake_gaia.h"
29#include "google_apis/gaia/gaia_switches.h"
30#include "net/base/url_util.h"
31#include "net/test/embedded_test_server/embedded_test_server.h"
32#include "net/test/embedded_test_server/http_request.h"
33#include "net/test/embedded_test_server/http_response.h"
34#include "testing/gmock/include/gmock/gmock.h"
35#include "testing/gtest/include/gtest/gtest.h"
36
37using ::testing::_;
38using ::testing::Invoke;
39using ::testing::InvokeWithoutArgs;
40
41namespace {
42
43struct ContentInfo {
44  ContentInfo(int pid, content::StoragePartition* storage_partition) {
45    this->pid = pid;
46    this->storage_partition = storage_partition;
47  }
48
49  int pid;
50  content::StoragePartition* storage_partition;
51};
52
53ContentInfo NavigateAndGetInfo(
54    Browser* browser,
55    const GURL& url,
56    WindowOpenDisposition disposition) {
57  ui_test_utils::NavigateToURLWithDisposition(
58      browser, url, disposition,
59      ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
60  content::WebContents* contents =
61      browser->tab_strip_model()->GetActiveWebContents();
62  content::RenderProcessHost* process = contents->GetRenderProcessHost();
63  return ContentInfo(process->GetID(), process->GetStoragePartition());
64}
65
66// Returns a new WebUI object for the WebContents from |arg0|.
67ACTION(ReturnNewWebUI) {
68  return new content::WebUIController(arg0);
69}
70
71// Mock the TestChromeWebUIControllerFactory::WebUIProvider to prove that we are
72// not called as expected.
73class FooWebUIProvider
74    : public TestChromeWebUIControllerFactory::WebUIProvider {
75 public:
76  MOCK_METHOD2(NewWebUI, content::WebUIController*(content::WebUI* web_ui,
77                                                   const GURL& url));
78};
79
80class MockLoginUIObserver : public LoginUIService::Observer {
81 public:
82  MOCK_METHOD0(OnUntrustedLoginUIShown, void());
83};
84
85const char kFooWebUIURL[] = "chrome://foo/";
86
87}  // namespace
88
89class InlineLoginUIBrowserTest : public InProcessBrowserTest {
90 public:
91  InlineLoginUIBrowserTest() {}
92};
93
94IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, DifferentStorageId) {
95  GURL test_url = ui_test_utils::GetTestUrl(
96      base::FilePath(base::FilePath::kCurrentDirectory),
97      base::FilePath(FILE_PATH_LITERAL("title1.html")));
98
99  ContentInfo info1 =
100      NavigateAndGetInfo(browser(), test_url, CURRENT_TAB);
101  ContentInfo info2 =
102      NavigateAndGetInfo(browser(),
103                         signin::GetPromoURL(signin::SOURCE_START_PAGE, false),
104                         CURRENT_TAB);
105  NavigateAndGetInfo(browser(), test_url, CURRENT_TAB);
106  ContentInfo info3 =
107      NavigateAndGetInfo(browser(),
108                         signin::GetPromoURL( signin::SOURCE_START_PAGE, false),
109                         NEW_FOREGROUND_TAB);
110
111  // The info for signin should be the same.
112  ASSERT_EQ(info2.storage_partition, info3.storage_partition);
113  // The info for test_url and signin should be different.
114  ASSERT_NE(info1.storage_partition, info2.storage_partition);
115}
116
117IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, OneProcessLimit) {
118  GURL test_url_1 = ui_test_utils::GetTestUrl(
119      base::FilePath(base::FilePath::kCurrentDirectory),
120      base::FilePath(FILE_PATH_LITERAL("title1.html")));
121  GURL test_url_2 = ui_test_utils::GetTestUrl(
122      base::FilePath(base::FilePath::kCurrentDirectory),
123      base::FilePath(FILE_PATH_LITERAL("data:text/html,Hello world!")));
124
125  // Even when the process limit is set to one, the signin process should
126  // still be given its own process and storage partition.
127  content::RenderProcessHost::SetMaxRendererProcessCount(1);
128
129  ContentInfo info1 =
130      NavigateAndGetInfo(browser(), test_url_1, CURRENT_TAB);
131  ContentInfo info2 =
132      NavigateAndGetInfo(browser(), test_url_2, CURRENT_TAB);
133  ContentInfo info3 =
134      NavigateAndGetInfo(browser(),
135                         signin::GetPromoURL( signin::SOURCE_START_PAGE, false),
136                         CURRENT_TAB);
137
138  ASSERT_EQ(info1.pid, info2.pid);
139  ASSERT_NE(info1.pid, info3.pid);
140}
141
142class InlineLoginUISafeIframeBrowserTest : public InProcessBrowserTest {
143 public:
144  FooWebUIProvider& foo_provider() { return foo_provider_; }
145
146  void WaitUntilUIReady() {
147    content::DOMMessageQueue message_queue;
148    ASSERT_TRUE(content::ExecuteScript(
149        browser()->tab_strip_model()->GetActiveWebContents(),
150        "if (!inline.login.getAuthExtHost())"
151        "  inline.login.initialize();"
152        "var handler = function() {"
153        "  window.domAutomationController.setAutomationId(0);"
154        "  window.domAutomationController.send('ready');"
155        "};"
156        "if (inline.login.isAuthReady())"
157        "  handler();"
158        "else"
159        "  inline.login.getAuthExtHost().addEventListener('ready', handler);"));
160
161    std::string message;
162    do {
163      ASSERT_TRUE(message_queue.WaitForMessage(&message));
164    } while (message != "\"ready\"");
165  }
166
167 // Executes JavaScript code in the auth iframe hosted by gaia_auth extension.
168  void ExecuteJsInSigninFrame(const std::string& js) {
169    content::WebContents* web_contents =
170        browser()->tab_strip_model()->GetActiveWebContents();
171    ASSERT_TRUE(content::ExecuteScript(InlineLoginUI::GetAuthIframe(
172        web_contents, GURL(), "signin-frame"), js));
173  }
174
175 private:
176  virtual void SetUp() OVERRIDE {
177    ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
178
179    // EmbeddedTestServer spawns a thread to initialize socket.
180    // Stop IO thread in preparation for fork and exec.
181    embedded_test_server()->StopThread();
182
183    InProcessBrowserTest::SetUp();
184  }
185
186  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
187    const GURL& base_url = embedded_test_server()->base_url();
188    command_line->AppendSwitchASCII(::switches::kGaiaUrl, base_url.spec());
189    command_line->AppendSwitchASCII(::switches::kLsoUrl, base_url.spec());
190    command_line->AppendSwitchASCII(::switches::kGoogleApisUrl,
191                                    base_url.spec());
192  }
193
194  virtual void SetUpOnMainThread() OVERRIDE {
195    embedded_test_server()->RestartThreadAndListen();
196
197    content::WebUIControllerFactory::UnregisterFactoryForTesting(
198        ChromeWebUIControllerFactory::GetInstance());
199    test_factory_.reset(new TestChromeWebUIControllerFactory);
200    content::WebUIControllerFactory::RegisterFactory(test_factory_.get());
201    test_factory_->AddFactoryOverride(
202        GURL(kFooWebUIURL).host(), &foo_provider_);
203  }
204
205  virtual void TearDownOnMainThread() OVERRIDE {
206    test_factory_->RemoveFactoryOverride(GURL(kFooWebUIURL).host());
207    content::WebUIControllerFactory::UnregisterFactoryForTesting(
208        test_factory_.get());
209    test_factory_.reset();
210    EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
211  }
212
213  FooWebUIProvider foo_provider_;
214  scoped_ptr<TestChromeWebUIControllerFactory> test_factory_;
215};
216
217// Make sure that the foo webui handler is working properly and that it gets
218// created when navigated to normally.
219IN_PROC_BROWSER_TEST_F(InlineLoginUISafeIframeBrowserTest, Basic) {
220  const GURL kUrl(kFooWebUIURL);
221  EXPECT_CALL(foo_provider(), NewWebUI(_, ::testing::Eq(kUrl)))
222      .WillOnce(ReturnNewWebUI());
223  ui_test_utils::NavigateToURL(browser(), GURL(kFooWebUIURL));
224}
225
226// Make sure that the foo webui handler does not get created when we try to
227// load it inside the iframe of the login ui.
228IN_PROC_BROWSER_TEST_F(InlineLoginUISafeIframeBrowserTest, NoWebUIInIframe) {
229  GURL url = signin::GetPromoURL(signin::SOURCE_START_PAGE, false).
230      Resolve("?source=0&frameUrl=chrome://foo");
231  EXPECT_CALL(foo_provider(), NewWebUI(_, _)).Times(0);
232  ui_test_utils::NavigateToURL(browser(), url);
233}
234
235// Flaky on CrOS, http://crbug.com/364759.
236#if defined(OS_CHROMEOS)
237#define MAYBE_TopFrameNavigationDisallowed DISABLED_TopFrameNavigationDisallowed
238#else
239#define MAYBE_TopFrameNavigationDisallowed TopFrameNavigationDisallowed
240#endif
241
242// Make sure that the gaia iframe cannot trigger top-frame navigation.
243// TODO(guohui): flaky on trybot crbug/364759.
244IN_PROC_BROWSER_TEST_F(InlineLoginUISafeIframeBrowserTest,
245    MAYBE_TopFrameNavigationDisallowed) {
246  // Loads into gaia iframe a web page that attempts to deframe on load.
247  GURL deframe_url(embedded_test_server()->GetURL("/login/deframe.html"));
248  GURL url(net::AppendOrReplaceQueryParameter(
249      signin::GetPromoURL(signin::SOURCE_START_PAGE, false),
250      "frameUrl", deframe_url.spec()));
251  ui_test_utils::NavigateToURL(browser(), url);
252  WaitUntilUIReady();
253
254  content::WebContents* contents =
255      browser()->tab_strip_model()->GetActiveWebContents();
256  EXPECT_EQ(url, contents->GetVisibleURL());
257
258  content::NavigationController& controller = contents->GetController();
259  EXPECT_TRUE(controller.GetPendingEntry() == NULL);
260}
261
262// Flaky on CrOS, http://crbug.com/364759.
263#if defined(OS_CHROMEOS)
264#define MAYBE_NavigationToOtherChromeURLDisallowed \
265    DISABLED_NavigationToOtherChromeURLDisallowed
266#else
267#define MAYBE_NavigationToOtherChromeURLDisallowed \
268    NavigationToOtherChromeURLDisallowed
269#endif
270
271IN_PROC_BROWSER_TEST_F(InlineLoginUISafeIframeBrowserTest,
272    MAYBE_NavigationToOtherChromeURLDisallowed) {
273  ui_test_utils::NavigateToURL(
274      browser(), signin::GetPromoURL(signin::SOURCE_START_PAGE, false));
275  WaitUntilUIReady();
276
277  content::WebContents* contents =
278      browser()->tab_strip_model()->GetActiveWebContents();
279  ASSERT_TRUE(content::ExecuteScript(
280      contents, "window.location.href = 'chrome://foo'"));
281
282  content::TestNavigationObserver navigation_observer(contents, 1);
283  navigation_observer.Wait();
284
285  EXPECT_EQ(GURL("about:blank"), contents->GetVisibleURL());
286}
287
288#if !defined(OS_CHROMEOS)
289IN_PROC_BROWSER_TEST_F(InlineLoginUISafeIframeBrowserTest,
290    ConfirmationRequiredForNonsecureSignin) {
291  FakeGaia fake_gaia;
292  fake_gaia.Initialize();
293
294  embedded_test_server()->RegisterRequestHandler(
295      base::Bind(&FakeGaia::HandleRequest,
296                 base::Unretained(&fake_gaia)));
297  fake_gaia.SetFakeMergeSessionParams(
298      "email", "fake-sid-cookie", "fake-lsid-cookie");
299
300  // Navigates to the Chrome signin page which loads the fake gaia auth page.
301  // Since the fake gaia auth page is served over HTTP, thus expects to see an
302  // untrusted signin confirmation dialog upon submitting credentials below.
303  ui_test_utils::NavigateToURL(
304      browser(), signin::GetPromoURL(signin::SOURCE_START_PAGE, false));
305  WaitUntilUIReady();
306
307  MockLoginUIObserver observer;
308  LoginUIServiceFactory::GetForProfile(browser()->profile())
309      ->AddObserver(&observer);
310  base::RunLoop run_loop;
311  EXPECT_CALL(observer, OnUntrustedLoginUIShown())
312      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
313
314  std::string js =
315      "document.getElementById('Email').value = 'email';"
316      "document.getElementById('Passwd').value = 'password';"
317      "document.getElementById('signIn').click();";
318  ExecuteJsInSigninFrame(js);
319
320  run_loop.Run();
321  base::MessageLoop::current()->RunUntilIdle();
322}
323#endif // OS_CHROMEOS
324