1// Copyright (c) 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 "base/path_service.h"
7#include "chrome/browser/chrome_notification_types.h"
8#include "chrome/browser/extensions/extension_browsertest.h"
9#include "chrome/browser/extensions/extension_service.h"
10#include "chrome/browser/extensions/unpacked_installer.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/ui/tabs/tab_strip_model.h"
13#include "chrome/common/chrome_paths.h"
14#include "chrome/common/chrome_switches.cc"
15#include "chrome/common/extensions/extension.h"
16#include "chrome/common/extensions/extension_file_util.h"
17#include "chrome/common/extensions/manifest.h"
18#include "chrome/common/pref_names.h"
19#include "chrome/test/base/in_process_browser_test.h"
20#include "chrome/test/base/ui_test_utils.h"
21#include "content/public/browser/navigation_controller.h"
22#include "content/public/browser/navigation_entry.h"
23#include "content/public/browser/notification_service.h"
24#include "content/public/browser/render_view_host.h"
25#include "content/public/browser/web_contents.h"
26#include "content/public/test/browser_test_utils.h"
27#include "content/public/test/test_utils.h"
28#include "net/dns/mock_host_resolver.h"
29
30using extensions::Extension;
31
32namespace {
33// Command line arguments specific to the chromoting browser tests.
34const char kOverrideUserDataDir[] = "override-user-data-dir";
35const char kNoCleanup[] = "no-cleanup";
36const char kNoInstall[] = "no-install";
37const char kWebAppCrx[] = "webapp-crx";
38const char kUsername[] = "username";
39const char kkPassword[] = "password";
40
41// ASSERT_TRUE can only be used in void returning functions.
42void _ASSERT_TRUE(bool condition) {
43  ASSERT_TRUE(condition);
44  return;
45}
46
47}
48
49namespace remoting {
50
51class RemoteDesktopBrowserTest : public ExtensionBrowserTest {
52 public:
53  virtual void SetUp() OVERRIDE {
54    ParseCommandLine();
55    ExtensionBrowserTest::SetUp();
56  }
57
58 protected:
59  // Override InProcessBrowserTest. Change behavior of the default host
60  // resolver to avoid DNS lookup errors, so we can make network calls.
61  virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
62    // The resolver object lifetime is managed by sync_test_setup, not here.
63    EnableDNSLookupForThisTest(
64        new net::RuleBasedHostResolverProc(host_resolver()));
65  }
66
67  // Override InProcessBrowserTest.
68  virtual void TearDownInProcessBrowserTestFixture() OVERRIDE {
69    DisableDNSLookupForThisTest();
70  }
71
72  // Install the chromoting extension from a crx file.
73  void InstallChromotingApp();
74
75  // Uninstall the chromoting extension.
76  void UninstallChromotingApp();
77
78  // Test whether the chromoting extension is installed.
79  void VerifyChromotingLoaded(bool expected);
80
81  // Launch the chromoting app.
82  void LaunchChromotingApp();
83
84  // Verify the test has access to the internet (specifically google.com)
85  void VerifyInternetAccess();
86
87  void Authorize();
88
89  void Authenticate();
90
91  // Whether to perform the cleanup tasks (uninstalling chromoting, etc).
92  // This is useful for diagnostic purposes.
93  bool NoCleanup() { return no_cleanup_; }
94
95  // Whether to install the chromoting extension before running the test cases.
96  // This is useful for diagnostic purposes.
97  bool NoInstall() { return no_install_; }
98
99 private:
100  void ParseCommandLine();
101
102  // Change behavior of the default host resolver to allow DNS lookup
103  // to proceed instead of being blocked by the test infrastructure.
104  void EnableDNSLookupForThisTest(
105    net::RuleBasedHostResolverProc* host_resolver);
106
107  // We need to reset the DNS lookup when we finish, or the test will fail.
108  void DisableDNSLookupForThisTest();
109
110  // Helper to get the path to the crx file of the webapp to be tested.
111  base::FilePath WebAppCrxPath() { return webapp_crx_; }
112
113  // Helper to get the extension ID of the installed chromoting webapp.
114  std::string ChromotingID() { return chromoting_id_; }
115
116  // Helper to retrieve the current URL of the active tab in the browser.
117  GURL GetCurrentURL() {
118    return browser()->tab_strip_model()->GetActiveWebContents()->GetURL();
119  }
120
121  // Helper to execute a javascript code snippet on the current page.
122  void ExecuteScript(const std::string& script) {
123    ASSERT_TRUE(content::ExecuteScript(
124        browser()->tab_strip_model()->GetActiveWebContents(), script));
125  }
126
127  // Helper to execute a javascript code snippet on the current page and
128  // wait for page load to complete.
129  void ExecuteScriptAndWait(const std::string& script) {
130    content::WindowedNotificationObserver observer(
131        content::NOTIFICATION_LOAD_STOP,
132        content::Source<content::NavigationController>(
133            &browser()->tab_strip_model()->GetActiveWebContents()->
134                GetController()));
135
136    ExecuteScript(script);
137
138    observer.Wait();
139  }
140
141  // Helper to execute a javascript code snippet on the current page and
142  // extract the boolean result.
143  bool ExecuteScriptAndExtractBool(const std::string& script) {
144    bool result;
145    // Using a private assert function because ASSERT_TRUE can only be used in
146    // void returning functions.
147    _ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
148        browser()->tab_strip_model()->GetActiveWebContents(),
149        "window.domAutomationController.send(" + script + ");",
150        &result));
151
152    return result;
153  }
154
155  // Helper to check whether a html element with the given name exists on
156  // the current page.
157  bool HtmlElementExists(const std::string& name) {
158    return ExecuteScriptAndExtractBool(
159        "document.getElementById(\"" + name + "\") != null");
160  }
161
162  // Helper to navigate to a given url.
163  void NavigateToURLAndWait(const GURL& url) {
164    content::WindowedNotificationObserver observer(
165        content::NOTIFICATION_LOAD_STOP,
166        content::Source<content::NavigationController>(
167            &browser()->tab_strip_model()->GetActiveWebContents()->
168                GetController()));
169
170    ui_test_utils::NavigateToURL(browser(), url);
171    observer.Wait();
172  }
173
174  // This test needs to make live DNS requests for access to
175  // GAIA and sync server URLs under google.com. We use a scoped version
176  // to override the default resolver while the test is active.
177  scoped_ptr<net::ScopedDefaultHostResolverProc> mock_host_resolver_override_;
178
179  bool no_cleanup_;
180  bool no_install_;
181  std::string chromoting_id_;
182  base::FilePath webapp_crx_;
183  std::string username_;
184  std::string password_;
185};
186
187void RemoteDesktopBrowserTest::ParseCommandLine() {
188  CommandLine* command_line = CommandLine::ForCurrentProcess();
189
190  // The test framework overrides any command line user-data-dir
191  // argument with a /tmp/.org.chromium.Chromium.XXXXXX directory.
192  // That happens in the ChromeTestLauncherDelegate, and affects
193  // all unit tests (no opt out available). It intentionally erases
194  // any --user-data-dir switch if present and appends a new one.
195  // Re-override the default data dir if override-user-data-dir
196  // is specified.
197  if (command_line->HasSwitch(kOverrideUserDataDir)) {
198    const base::FilePath& override_user_data_dir =
199        command_line->GetSwitchValuePath(kOverrideUserDataDir);
200
201    ASSERT_FALSE(override_user_data_dir.empty());
202
203    command_line->AppendSwitchPath(switches::kUserDataDir,
204                                   override_user_data_dir);
205  }
206
207  username_ = command_line->GetSwitchValueASCII(kUsername);
208  password_ = command_line->GetSwitchValueASCII(kkPassword);
209
210  no_cleanup_ = command_line->HasSwitch(kNoCleanup);
211  no_install_ = command_line->HasSwitch(kNoInstall);
212
213  if (!no_install_) {
214    webapp_crx_ = command_line->GetSwitchValuePath(kWebAppCrx);
215    ASSERT_FALSE(webapp_crx_.empty());
216  }
217}
218
219void RemoteDesktopBrowserTest::EnableDNSLookupForThisTest(
220    net::RuleBasedHostResolverProc* host_resolver) {
221  // mock_host_resolver_override_ takes ownership of the resolver.
222  scoped_refptr<net::RuleBasedHostResolverProc> resolver =
223      new net::RuleBasedHostResolverProc(host_resolver);
224  resolver->AllowDirectLookup("*.google.com");
225  // On Linux, we use Chromium's NSS implementation which uses the following
226  // hosts for certificate verification. Without these overrides, running the
227  // integration tests on Linux causes errors as we make external DNS lookups.
228  resolver->AllowDirectLookup("*.thawte.com");
229  resolver->AllowDirectLookup("*.geotrust.com");
230  resolver->AllowDirectLookup("*.gstatic.com");
231  resolver->AllowDirectLookup("*.googleapis.com");
232  mock_host_resolver_override_.reset(
233      new net::ScopedDefaultHostResolverProc(resolver.get()));
234}
235
236void RemoteDesktopBrowserTest::DisableDNSLookupForThisTest() {
237  mock_host_resolver_override_.reset();
238}
239
240void RemoteDesktopBrowserTest::VerifyInternetAccess() {
241  GURL google_url("http://www.google.com");
242  NavigateToURLAndWait(google_url);
243
244  EXPECT_EQ(GetCurrentURL().host(), "www.google.com");
245}
246
247void RemoteDesktopBrowserTest::InstallChromotingApp() {
248  base::FilePath install_dir(WebAppCrxPath());
249  scoped_refptr<const Extension> extension(InstallExtensionWithUIAutoConfirm(
250      install_dir, 1, browser()));
251
252  EXPECT_FALSE(extension.get() == NULL);
253}
254
255void RemoteDesktopBrowserTest::UninstallChromotingApp() {
256  UninstallExtension(ChromotingID());
257  chromoting_id_.clear();
258}
259
260void RemoteDesktopBrowserTest::LaunchChromotingApp() {
261  ASSERT_FALSE(ChromotingID().empty());
262
263  std::string url = "chrome-extension://" + ChromotingID() + "/main.html";
264  const GURL chromoting_main(url);
265  NavigateToURLAndWait(chromoting_main);
266
267  EXPECT_EQ(GetCurrentURL(), chromoting_main);
268}
269
270void RemoteDesktopBrowserTest::VerifyChromotingLoaded(bool expected) {
271  const ExtensionSet* extensions = extension_service()->extensions();
272  scoped_refptr<const extensions::Extension> extension;
273  ExtensionSet::const_iterator iter;
274  bool installed = false;
275
276  for (iter = extensions->begin(); iter != extensions->end(); ++iter) {
277    extension = *iter;
278    // Is there a better way to recognize the chromoting extension
279    // than name comparison?
280    if (extension->name() == "Chromoting" ||
281        extension->name() == "Chrome Remote Desktop") {
282      installed = true;
283      break;
284    }
285  }
286
287  if (installed) {
288    chromoting_id_ = extension->id();
289
290    EXPECT_EQ(extension->GetType(),
291        extensions::Manifest::TYPE_LEGACY_PACKAGED_APP);
292
293    EXPECT_TRUE(extension->ShouldDisplayInAppLauncher());
294  }
295
296  EXPECT_EQ(installed, expected);
297}
298
299void RemoteDesktopBrowserTest::Authorize() {
300  // The chromoting extension should be installed.
301  ASSERT_FALSE(ChromotingID().empty());
302
303  // The chromoting main page should be loaded in the current tab
304  // and isAuthenticated() should be false (auth dialog visible).
305  std::string url = "chrome-extension://" + ChromotingID() + "/main.html";
306  ASSERT_EQ(GetCurrentURL().spec(), url);
307  ASSERT_FALSE(ExecuteScriptAndExtractBool(
308      "remoting.OAuth2.prototype.isAuthenticated()"));
309
310  ExecuteScriptAndWait("remoting.OAuth2.prototype.doAuthRedirect();");
311
312  // Verify the active tab is at the "Google Accounts" login page.
313  EXPECT_EQ(GetCurrentURL().host(), "accounts.google.com");
314  EXPECT_TRUE(HtmlElementExists("Email"));
315  EXPECT_TRUE(HtmlElementExists("Passwd"));
316}
317
318void RemoteDesktopBrowserTest::Authenticate() {
319  // The chromoting extension should be installed.
320  ASSERT_FALSE(ChromotingID().empty());
321
322  // The active tab should have the "Google Accounts" login page loaded.
323  ASSERT_EQ(GetCurrentURL().host(), "accounts.google.com");
324  ASSERT_TRUE(HtmlElementExists("Email"));
325  ASSERT_TRUE(HtmlElementExists("Passwd"));
326
327  // Now log in using the username and password passed in from the command line.
328  ExecuteScriptAndWait(
329      "document.getElementById(\"Email\").value = \"" + username_ + "\";" +
330      "document.getElementById(\"Passwd\").value = \"" + password_ +"\";" +
331      "document.forms[\"gaia_loginform\"].submit();");
332
333  EXPECT_EQ(GetCurrentURL().host(), "accounts.google.com");
334
335  // TODO: Is there a better way to verify we are on the
336  // "Request for Permission" page?
337  EXPECT_TRUE(HtmlElementExists("submit_approve_access"));
338}
339
340IN_PROC_BROWSER_TEST_F(RemoteDesktopBrowserTest, MANUAL_Launch) {
341  VerifyInternetAccess();
342
343  if (!NoInstall()) {
344    VerifyChromotingLoaded(false);
345    InstallChromotingApp();
346  }
347
348  VerifyChromotingLoaded(true);
349
350  LaunchChromotingApp();
351
352  // TODO: Remove this hack by blocking on the appropriate notification.
353  // The browser may still be loading images embedded in the webapp. If we
354  // uinstall it now those load will fail. Navigating away to avoid the load
355  // failures.
356  ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
357
358  if (!NoCleanup()) {
359    UninstallChromotingApp();
360    VerifyChromotingLoaded(false);
361  }
362}
363
364IN_PROC_BROWSER_TEST_F(RemoteDesktopBrowserTest, MANUAL_Auth) {
365  VerifyInternetAccess();
366
367  if (!NoInstall()) {
368    VerifyChromotingLoaded(false);
369    InstallChromotingApp();
370  }
371
372  VerifyChromotingLoaded(true);
373
374  LaunchChromotingApp();
375
376  Authorize();
377
378  Authenticate();
379
380  if (!NoCleanup()) {
381    UninstallChromotingApp();
382    VerifyChromotingLoaded(false);
383  }
384}
385
386}  // namespace remoting
387