// Copyright (c) 2012 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 "base/base64.h" #include "base/files/file_path.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/synchronization/waitable_event.h" #include "base/values.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/api/messaging/incognito_connectability.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/test_extension_dir.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/base/ui_test_utils.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" #include "content/public/test/browser_test_utils.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_system.h" #include "extensions/common/api/runtime.h" #include "extensions/common/extension_builder.h" #include "extensions/common/value_builder.h" #include "net/cert/asn1_util.h" #include "net/cert/jwk_serializer.h" #include "net/dns/mock_host_resolver.h" #include "net/ssl/server_bound_cert_service.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" #include "url/gurl.h" namespace extensions { namespace { class MessageSender : public content::NotificationObserver { public: MessageSender() { registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, content::NotificationService::AllSources()); } private: static scoped_ptr<base::ListValue> BuildEventArguments( const bool last_message, const std::string& data) { base::DictionaryValue* event = new base::DictionaryValue(); event->SetBoolean("lastMessage", last_message); event->SetString("data", data); scoped_ptr<base::ListValue> arguments(new base::ListValue()); arguments->Append(event); return arguments.Pass(); } static scoped_ptr<Event> BuildEvent(scoped_ptr<base::ListValue> event_args, Profile* profile, GURL event_url) { scoped_ptr<Event> event(new Event("test.onMessage", event_args.Pass())); event->restrict_to_browser_context = profile; event->event_url = event_url; return event.Pass(); } virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE { EventRouter* event_router = EventRouter::Get(content::Source<Profile>(source).ptr()); // Sends four messages to the extension. All but the third message sent // from the origin http://b.com/ are supposed to arrive. event_router->BroadcastEvent(BuildEvent( BuildEventArguments(false, "no restriction"), content::Source<Profile>(source).ptr(), GURL())); event_router->BroadcastEvent(BuildEvent( BuildEventArguments(false, "http://a.com/"), content::Source<Profile>(source).ptr(), GURL("http://a.com/"))); event_router->BroadcastEvent(BuildEvent( BuildEventArguments(false, "http://b.com/"), content::Source<Profile>(source).ptr(), GURL("http://b.com/"))); event_router->BroadcastEvent(BuildEvent( BuildEventArguments(true, "last message"), content::Source<Profile>(source).ptr(), GURL())); } content::NotificationRegistrar registrar_; }; // Tests that message passing between extensions and content scripts works. IN_PROC_BROWSER_TEST_F(ExtensionApiTest, Messaging) { ASSERT_TRUE(StartEmbeddedTestServer()); ASSERT_TRUE(RunExtensionTest("messaging/connect")) << message_; } // Tests that message passing from one extension to another works. IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MessagingExternal) { ASSERT_TRUE(LoadExtension( test_data_dir_.AppendASCII("..").AppendASCII("good") .AppendASCII("Extensions") .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa") .AppendASCII("1.0"))); ASSERT_TRUE(RunExtensionTest("messaging/connect_external")) << message_; } // Tests that messages with event_urls are only passed to extensions with // appropriate permissions. IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MessagingEventURL) { MessageSender sender; ASSERT_TRUE(RunExtensionTest("messaging/event_url")) << message_; } // Tests connecting from a panel to its extension. class PanelMessagingTest : public ExtensionApiTest { virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { ExtensionApiTest::SetUpCommandLine(command_line); command_line->AppendSwitch(switches::kEnablePanels); } }; IN_PROC_BROWSER_TEST_F(PanelMessagingTest, MessagingPanel) { ASSERT_TRUE(RunExtensionTest("messaging/connect_panel")) << message_; } // XXX(kalman): All web messaging tests disabled on windows due to extreme // flakiness. See http://crbug.com/350517. #if !defined(OS_WIN) // Tests externally_connectable between a web page and an extension. // // TODO(kalman): Test between extensions. This is already tested in this file, // but not with externally_connectable set in the manifest. // // TODO(kalman): Test with host permissions. class ExternallyConnectableMessagingTest : public ExtensionApiTest { protected: // Result codes from the test. These must match up with |results| in // c/t/d/extensions/api_test/externally_connectable/assertions.json. enum Result { OK = 0, NAMESPACE_NOT_DEFINED = 1, FUNCTION_NOT_DEFINED = 2, COULD_NOT_ESTABLISH_CONNECTION_ERROR = 3, OTHER_ERROR = 4, INCORRECT_RESPONSE_SENDER = 5, INCORRECT_RESPONSE_MESSAGE = 6, }; bool AppendIframe(const GURL& src) { bool result; CHECK(content::ExecuteScriptAndExtractBool( browser()->tab_strip_model()->GetActiveWebContents(), "actions.appendIframe('" + src.spec() + "');", &result)); return result; } Result CanConnectAndSendMessagesToMainFrame(const Extension* extension, const char* message = NULL) { return CanConnectAndSendMessagesToFrame( browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame(), extension, message); } Result CanConnectAndSendMessagesToIFrame(const Extension* extension, const char* message = NULL) { content::RenderFrameHost* frame = content::FrameMatchingPredicate( browser()->tab_strip_model()->GetActiveWebContents(), base::Bind(&content::FrameIsChildOfMainFrame)); return CanConnectAndSendMessagesToFrame(frame, extension, message); } Result CanConnectAndSendMessagesToFrame(content::RenderFrameHost* frame, const Extension* extension, const char* message) { int result; std::string command = base::StringPrintf( "assertions.canConnectAndSendMessages('%s', %s, %s)", extension->id().c_str(), extension->is_platform_app() ? "true" : "false", message ? base::StringPrintf("'%s'", message).c_str() : "undefined"); CHECK(content::ExecuteScriptAndExtractInt(frame, command, &result)); return static_cast<Result>(result); } testing::AssertionResult AreAnyNonWebApisDefinedForMainFrame() { return AreAnyNonWebApisDefinedForFrame( browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame()); } testing::AssertionResult AreAnyNonWebApisDefinedForIFrame() { content::RenderFrameHost* frame = content::FrameMatchingPredicate( browser()->tab_strip_model()->GetActiveWebContents(), base::Bind(&content::FrameIsChildOfMainFrame)); return AreAnyNonWebApisDefinedForFrame(frame); } testing::AssertionResult AreAnyNonWebApisDefinedForFrame( content::RenderFrameHost* frame) { // All runtime API methods are non-web except for sendRequest and connect. const char* non_messaging_apis[] = { "getBackgroundPage", "getManifest", "getURL", "reload", "requestUpdateCheck", "restart", "connectNative", "sendNativeMessage", "onStartup", "onInstalled", "onSuspend", "onSuspendCanceled", "onUpdateAvailable", "onBrowserUpdateAvailable", "onConnect", "onConnectExternal", "onMessage", "onMessageExternal", "onRestartRequired", // Note: no "id" here because this test method is used for hosted apps, // which do have access to runtime.id. }; // Turn the array into a JS array, which effectively gets eval()ed. std::string as_js_array; for (size_t i = 0; i < arraysize(non_messaging_apis); ++i) { as_js_array += as_js_array.empty() ? "[" : ","; as_js_array += base::StringPrintf("'%s'", non_messaging_apis[i]); } as_js_array += "]"; bool any_defined; CHECK(content::ExecuteScriptAndExtractBool( frame, "assertions.areAnyRuntimePropertiesDefined(" + as_js_array + ")", &any_defined)); return any_defined ? testing::AssertionSuccess() : testing::AssertionFailure(); } std::string GetTlsChannelIdFromPortConnect(const Extension* extension, bool include_tls_channel_id, const char* message = NULL) { return GetTlsChannelIdFromAssertion("getTlsChannelIdFromPortConnect", extension, include_tls_channel_id, message); } std::string GetTlsChannelIdFromSendMessage(const Extension* extension, bool include_tls_channel_id, const char* message = NULL) { return GetTlsChannelIdFromAssertion("getTlsChannelIdFromSendMessage", extension, include_tls_channel_id, message); } GURL GetURLForPath(const std::string& host, const std::string& path) { std::string port = base::IntToString(embedded_test_server()->port()); GURL::Replacements replacements; replacements.SetHostStr(host); replacements.SetPortStr(port); return embedded_test_server()->GetURL(path).ReplaceComponents(replacements); } GURL chromium_org_url() { return GetURLForPath("www.chromium.org", "/chromium.org.html"); } GURL google_com_url() { return GetURLForPath("www.google.com", "/google.com.html"); } scoped_refptr<const Extension> LoadChromiumConnectableExtension() { scoped_refptr<const Extension> extension = LoadExtensionIntoDir(&web_connectable_dir_, base::StringPrintf( "{" " \"name\": \"chromium_connectable\"," " %s," " \"externally_connectable\": {" " \"matches\": [\"*://*.chromium.org:*/*\"]" " }" "}", common_manifest())); CHECK(extension.get()); return extension; } scoped_refptr<const Extension> LoadChromiumConnectableApp() { scoped_refptr<const Extension> extension = LoadExtensionIntoDir(&web_connectable_dir_, "{" " \"app\": {" " \"background\": {" " \"scripts\": [\"background.js\"]" " }" " }," " \"externally_connectable\": {" " \"matches\": [\"*://*.chromium.org:*/*\"]" " }," " \"manifest_version\": 2," " \"name\": \"app_connectable\"," " \"version\": \"1.0\"" "}"); CHECK(extension.get()); return extension; } scoped_refptr<const Extension> LoadNotConnectableExtension() { scoped_refptr<const Extension> extension = LoadExtensionIntoDir(¬_connectable_dir_, base::StringPrintf( "{" " \"name\": \"not_connectable\"," " %s" "}", common_manifest())); CHECK(extension.get()); return extension; } scoped_refptr<const Extension> LoadChromiumConnectableExtensionWithTlsChannelId() { return LoadExtensionIntoDir(&tls_channel_id_connectable_dir_, connectable_with_tls_channel_id_manifest()); } scoped_refptr<const Extension> LoadChromiumHostedApp() { scoped_refptr<const Extension> hosted_app = LoadExtensionIntoDir(&hosted_app_dir_, base::StringPrintf( "{" " \"name\": \"chromium_hosted_app\"," " \"version\": \"1.0\"," " \"manifest_version\": 2," " \"app\": {" " \"urls\": [\"%s\"]," " \"launch\": {" " \"web_url\": \"%s\"" " }\n" " }\n" "}", chromium_org_url().spec().c_str(), chromium_org_url().spec().c_str())); CHECK(hosted_app.get()); return hosted_app; } void InitializeTestServer() { base::FilePath test_data; EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data)); embedded_test_server()->ServeFilesFromDirectory(test_data.AppendASCII( "extensions/api_test/messaging/externally_connectable/sites")); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); host_resolver()->AddRule("*", embedded_test_server()->base_url().host()); } const char* close_background_message() { return "closeBackgroundPage"; } private: scoped_refptr<const Extension> LoadExtensionIntoDir( TestExtensionDir* dir, const std::string& manifest) { dir->WriteManifest(manifest); dir->WriteFile(FILE_PATH_LITERAL("background.js"), base::StringPrintf( "function maybeClose(message) {\n" " if (message.indexOf('%s') >= 0)\n" " window.setTimeout(function() { window.close() }, 0);\n" "}\n" "chrome.runtime.onMessageExternal.addListener(\n" " function(message, sender, reply) {\n" " reply({ message: message, sender: sender });\n" " maybeClose(message);\n" "});\n" "chrome.runtime.onConnectExternal.addListener(function(port) {\n" " port.onMessage.addListener(function(message) {\n" " port.postMessage({ message: message, sender: port.sender });\n" " maybeClose(message);\n" " });\n" "});\n", close_background_message())); return LoadExtension(dir->unpacked_path()); } const char* common_manifest() { return "\"version\": \"1.0\"," "\"background\": {" " \"scripts\": [\"background.js\"]," " \"persistent\": false" "}," "\"manifest_version\": 2"; } std::string connectable_with_tls_channel_id_manifest() { return base::StringPrintf( "{" " \"name\": \"chromium_connectable_with_tls_channel_id\"," " %s," " \"externally_connectable\": {" " \"matches\": [\"*://*.chromium.org:*/*\"]," " \"accepts_tls_channel_id\": true" " }" "}", common_manifest()); } std::string GetTlsChannelIdFromAssertion(const char* method, const Extension* extension, bool include_tls_channel_id, const char* message) { std::string result; std::string args = "'" + extension->id() + "', "; args += include_tls_channel_id ? "true" : "false"; if (message) args += std::string(", '") + message + "'"; CHECK(content::ExecuteScriptAndExtractString( browser()->tab_strip_model()->GetActiveWebContents(), base::StringPrintf("assertions.%s(%s)", method, args.c_str()), &result)); return result; } TestExtensionDir web_connectable_dir_; TestExtensionDir not_connectable_dir_; TestExtensionDir tls_channel_id_connectable_dir_; TestExtensionDir hosted_app_dir_; }; IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, NotInstalled) { InitializeTestServer(); scoped_refptr<const Extension> extension = ExtensionBuilder() .SetID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") .SetManifest(DictionaryBuilder() .Set("name", "Fake extension") .Set("version", "1") .Set("manifest_version", 2)) .Build(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessagesToMainFrame(extension)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); ui_test_utils::NavigateToURL(browser(), google_com_url()); EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessagesToMainFrame(extension)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); } // Tests two extensions on the same sites: one web connectable, one not. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, WebConnectableAndNotConnectable) { InitializeTestServer(); // Install the web connectable extension. chromium.org can connect to it, // google.com can't. const Extension* chromium_connectable = LoadChromiumConnectableExtension(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); EXPECT_EQ(OK, CanConnectAndSendMessagesToMainFrame(chromium_connectable)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); ui_test_utils::NavigateToURL(browser(), google_com_url()); EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessagesToMainFrame(chromium_connectable)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); // Install the non-connectable extension. Nothing can connect to it. const Extension* not_connectable = LoadNotConnectableExtension(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); // Namespace will be defined here because |chromium_connectable| can connect // to it - so this will be the "cannot establish connection" error. EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToMainFrame(not_connectable)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); ui_test_utils::NavigateToURL(browser(), google_com_url()); EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessagesToMainFrame(not_connectable)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); } // See http://crbug.com/297866 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, DISABLED_BackgroundPageClosesOnMessageReceipt) { InitializeTestServer(); // Install the web connectable extension. const Extension* chromium_connectable = LoadChromiumConnectableExtension(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); // If the background page closes after receipt of the message, it will still // reply to this message... EXPECT_EQ(OK, CanConnectAndSendMessagesToMainFrame(chromium_connectable, close_background_message())); // and be re-opened by receipt of a subsequent message. EXPECT_EQ(OK, CanConnectAndSendMessagesToMainFrame(chromium_connectable)); } // Tests a web connectable extension that doesn't receive TLS channel id. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, WebConnectableWithoutTlsChannelId) { InitializeTestServer(); // Install the web connectable extension. chromium.org can connect to it, // google.com can't. const Extension* chromium_connectable = LoadChromiumConnectableExtension(); ASSERT_TRUE(chromium_connectable); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); // The web connectable extension doesn't request the TLS channel ID, so it // doesn't get it, whether or not the page asks for it. EXPECT_EQ(std::string(), GetTlsChannelIdFromPortConnect(chromium_connectable, false)); EXPECT_EQ(std::string(), GetTlsChannelIdFromSendMessage(chromium_connectable, true)); EXPECT_EQ(std::string(), GetTlsChannelIdFromPortConnect(chromium_connectable, false)); EXPECT_EQ(std::string(), GetTlsChannelIdFromSendMessage(chromium_connectable, true)); } // Tests a web connectable extension that receives TLS channel id with a site // that can't connect to it. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, WebConnectableWithTlsChannelIdWithNonMatchingSite) { InitializeTestServer(); const Extension* chromium_connectable = LoadChromiumConnectableExtensionWithTlsChannelId(); ASSERT_TRUE(chromium_connectable); ui_test_utils::NavigateToURL(browser(), google_com_url()); // The extension requests the TLS channel ID, but it doesn't get it for a // site that can't connect to it, regardless of whether the page asks for it. EXPECT_EQ(base::StringPrintf("%d", NAMESPACE_NOT_DEFINED), GetTlsChannelIdFromPortConnect(chromium_connectable, false)); EXPECT_EQ(base::StringPrintf("%d", NAMESPACE_NOT_DEFINED), GetTlsChannelIdFromSendMessage(chromium_connectable, true)); EXPECT_EQ(base::StringPrintf("%d", NAMESPACE_NOT_DEFINED), GetTlsChannelIdFromPortConnect(chromium_connectable, false)); EXPECT_EQ(base::StringPrintf("%d", NAMESPACE_NOT_DEFINED), GetTlsChannelIdFromSendMessage(chromium_connectable, true)); } // Tests a web connectable extension that receives TLS channel id on a site // that can connect to it, but with no TLS channel ID having been generated. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, WebConnectableWithTlsChannelIdWithEmptyTlsChannelId) { InitializeTestServer(); const Extension* chromium_connectable = LoadChromiumConnectableExtensionWithTlsChannelId(); ASSERT_TRUE(chromium_connectable); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); // Since the extension requests the TLS channel ID, it gets it for a site that // can connect to it, but only if the page also asks to include it. EXPECT_EQ(std::string(), GetTlsChannelIdFromPortConnect(chromium_connectable, false)); EXPECT_EQ(std::string(), GetTlsChannelIdFromSendMessage(chromium_connectable, false)); // If the page does ask for it, it isn't empty. std::string tls_channel_id = GetTlsChannelIdFromPortConnect(chromium_connectable, true); // Because the TLS channel ID has never been generated for this domain, // no TLS channel ID is reported. EXPECT_EQ(std::string(), tls_channel_id); } // Flaky on Linux and Windows. http://crbug.com/315264 // Tests a web connectable extension that receives TLS channel id, but // immediately closes its background page upon receipt of a message. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, DISABLED_WebConnectableWithEmptyTlsChannelIdAndClosedBackgroundPage) { InitializeTestServer(); const Extension* chromium_connectable = LoadChromiumConnectableExtensionWithTlsChannelId(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); // If the page does ask for it, it isn't empty, even if the background page // closes upon receipt of the connect. std::string tls_channel_id = GetTlsChannelIdFromPortConnect( chromium_connectable, true, close_background_message()); // Because the TLS channel ID has never been generated for this domain, // no TLS channel ID is reported. EXPECT_EQ(std::string(), tls_channel_id); // A subsequent connect will still succeed, even if the background page was // previously closed. tls_channel_id = GetTlsChannelIdFromPortConnect(chromium_connectable, true); // And the empty value is still retrieved. EXPECT_EQ(std::string(), tls_channel_id); } // Tests that enabling and disabling an extension makes the runtime bindings // appear and disappear. // // TODO(kalman): Test with multiple extensions that can be accessed by the same // host. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, EnablingAndDisabling) { InitializeTestServer(); const Extension* chromium_connectable = LoadChromiumConnectableExtension(); const Extension* not_connectable = LoadNotConnectableExtension(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); EXPECT_EQ(OK, CanConnectAndSendMessagesToMainFrame(chromium_connectable)); EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToMainFrame(not_connectable)); DisableExtension(chromium_connectable->id()); EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToMainFrame(chromium_connectable)); EnableExtension(chromium_connectable->id()); EXPECT_EQ(OK, CanConnectAndSendMessagesToMainFrame(chromium_connectable)); EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToMainFrame(not_connectable)); } // Tests connection from incognito tabs when the user denies the connection // request. Spanning mode only. A separate test for apps and extensions. // // TODO(kalman): ensure that we exercise split vs spanning incognito logic // somewhere. This is a test that should be shared with the content script logic // so it's not really our specific concern for web connectable. // // TODO(kalman): test messages from incognito extensions too. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, FromIncognitoDenyApp) { InitializeTestServer(); scoped_refptr<const Extension> app = LoadChromiumConnectableApp(); ASSERT_TRUE(app->is_platform_app()); Browser* incognito_browser = ui_test_utils::OpenURLOffTheRecord( profile()->GetOffTheRecordProfile(), chromium_org_url()); content::RenderFrameHost* incognito_frame = incognito_browser-> tab_strip_model()->GetActiveWebContents()->GetMainFrame(); { IncognitoConnectability::ScopedAlertTracker alert_tracker( IncognitoConnectability::ScopedAlertTracker::ALWAYS_DENY); // No connection because incognito-enabled hasn't been set for the app, and // the user denied our interactive request. EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToFrame(incognito_frame, app, NULL)); EXPECT_EQ(1, alert_tracker.GetAndResetAlertCount()); // Try again. User has already denied so alert not shown. EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToFrame(incognito_frame, app, NULL)); EXPECT_EQ(0, alert_tracker.GetAndResetAlertCount()); } // It's not possible to allow an app in incognito. ExtensionPrefs::Get(profile())->SetIsIncognitoEnabled(app->id(), true); EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToFrame(incognito_frame, app, NULL)); } IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, FromIncognitoDenyExtension) { InitializeTestServer(); scoped_refptr<const Extension> extension = LoadChromiumConnectableExtension(); Browser* incognito_browser = ui_test_utils::OpenURLOffTheRecord( profile()->GetOffTheRecordProfile(), chromium_org_url()); content::RenderFrameHost* incognito_frame = incognito_browser->tab_strip_model() ->GetActiveWebContents() ->GetMainFrame(); { IncognitoConnectability::ScopedAlertTracker alert_tracker( IncognitoConnectability::ScopedAlertTracker::ALWAYS_DENY); // The alert doesn't show for extensions. EXPECT_EQ( COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToFrame(incognito_frame, extension, NULL)); EXPECT_EQ(0, alert_tracker.GetAndResetAlertCount()); } // Allowing the extension in incognito mode will bypass the deny. ExtensionPrefs::Get(profile())->SetIsIncognitoEnabled(extension->id(), true); EXPECT_EQ(OK, CanConnectAndSendMessagesToFrame(incognito_frame, extension, NULL)); } // Tests connection from incognito tabs when the user accepts the connection // request. Spanning mode only. Separate tests for apps and extensions. // // TODO(kalman): see comment above about split mode. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, FromIncognitoAllowApp) { InitializeTestServer(); scoped_refptr<const Extension> app = LoadChromiumConnectableApp(); ASSERT_TRUE(app->is_platform_app()); Browser* incognito_browser = ui_test_utils::OpenURLOffTheRecord( profile()->GetOffTheRecordProfile(), chromium_org_url()); content::RenderFrameHost* incognito_frame = incognito_browser-> tab_strip_model()->GetActiveWebContents()->GetMainFrame(); { IncognitoConnectability::ScopedAlertTracker alert_tracker( IncognitoConnectability::ScopedAlertTracker::ALWAYS_ALLOW); // Connection allowed even with incognito disabled, because the user // accepted the interactive request. EXPECT_EQ(OK, CanConnectAndSendMessagesToFrame(incognito_frame, app, NULL)); EXPECT_EQ(1, alert_tracker.GetAndResetAlertCount()); // Try again. User has already allowed. EXPECT_EQ(OK, CanConnectAndSendMessagesToFrame(incognito_frame, app, NULL)); EXPECT_EQ(0, alert_tracker.GetAndResetAlertCount()); } // Apps can't be allowed in incognito mode, but it's moot because it's // already allowed. ExtensionPrefs::Get(profile())->SetIsIncognitoEnabled(app->id(), true); EXPECT_EQ(OK, CanConnectAndSendMessagesToFrame(incognito_frame, app, NULL)); } IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, FromIncognitoAllowExtension) { InitializeTestServer(); scoped_refptr<const Extension> extension = LoadChromiumConnectableExtension(); Browser* incognito_browser = ui_test_utils::OpenURLOffTheRecord( profile()->GetOffTheRecordProfile(), chromium_org_url()); content::RenderFrameHost* incognito_frame = incognito_browser->tab_strip_model() ->GetActiveWebContents() ->GetMainFrame(); { IncognitoConnectability::ScopedAlertTracker alert_tracker( IncognitoConnectability::ScopedAlertTracker::ALWAYS_ALLOW); // No alert is shown. EXPECT_EQ( COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToFrame(incognito_frame, extension, NULL)); EXPECT_EQ(0, alert_tracker.GetAndResetAlertCount()); } // Allowing the extension in incognito mode is what allows connections. ExtensionPrefs::Get(profile())->SetIsIncognitoEnabled(extension->id(), true); EXPECT_EQ(OK, CanConnectAndSendMessagesToFrame(incognito_frame, extension, NULL)); } // Tests a connection from an iframe within a tab which doesn't have // permission. Iframe should work. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, FromIframeWithPermission) { InitializeTestServer(); const Extension* extension = LoadChromiumConnectableExtension(); ui_test_utils::NavigateToURL(browser(), google_com_url()); EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessagesToMainFrame(extension)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); ASSERT_TRUE(AppendIframe(chromium_org_url())); EXPECT_EQ(OK, CanConnectAndSendMessagesToIFrame(extension)); EXPECT_FALSE(AreAnyNonWebApisDefinedForIFrame()); } // Tests connection from an iframe without permission within a tab that does. // Iframe shouldn't work. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, FromIframeWithoutPermission) { InitializeTestServer(); const Extension* extension = LoadChromiumConnectableExtension(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); EXPECT_EQ(OK, CanConnectAndSendMessagesToMainFrame(extension)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); ASSERT_TRUE(AppendIframe(google_com_url())); EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessagesToIFrame(extension)); EXPECT_FALSE(AreAnyNonWebApisDefinedForIFrame()); } // Tests externally_connectable between a web page and an extension with a // TLS channel ID created for the origin. class ExternallyConnectableMessagingWithTlsChannelIdTest : public ExternallyConnectableMessagingTest { public: ExternallyConnectableMessagingWithTlsChannelIdTest() : tls_channel_id_created_(false, false) { } std::string CreateTlsChannelId() { scoped_refptr<net::URLRequestContextGetter> request_context_getter( profile()->GetRequestContext()); std::string domain_bound_private_key; std::string domain_bound_cert; net::ServerBoundCertService::RequestHandle request_handle; content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind( &ExternallyConnectableMessagingWithTlsChannelIdTest:: CreateDomainBoundCertOnIOThread, base::Unretained(this), base::Unretained(&domain_bound_private_key), base::Unretained(&domain_bound_cert), base::Unretained(&request_handle), request_context_getter)); tls_channel_id_created_.Wait(); // Create the expected value. base::StringPiece spki; net::asn1::ExtractSPKIFromDERCert(domain_bound_cert, &spki); base::DictionaryValue jwk_value; net::JwkSerializer::ConvertSpkiFromDerToJwk(spki, &jwk_value); std::string tls_channel_id_value; base::JSONWriter::Write(&jwk_value, &tls_channel_id_value); return tls_channel_id_value; } private: void CreateDomainBoundCertOnIOThread( std::string* domain_bound_private_key, std::string* domain_bound_cert, net::ServerBoundCertService::RequestHandle* request_handle, scoped_refptr<net::URLRequestContextGetter> request_context_getter) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); net::ServerBoundCertService* server_bound_cert_service = request_context_getter->GetURLRequestContext()-> server_bound_cert_service(); int status = server_bound_cert_service->GetOrCreateDomainBoundCert( chromium_org_url().host(), domain_bound_private_key, domain_bound_cert, base::Bind(&ExternallyConnectableMessagingWithTlsChannelIdTest:: GotDomainBoundCert, base::Unretained(this)), request_handle); if (status == net::ERR_IO_PENDING) return; GotDomainBoundCert(status); } void GotDomainBoundCert(int status) { ASSERT_TRUE(status == net::OK); tls_channel_id_created_.Signal(); } base::WaitableEvent tls_channel_id_created_; }; // Tests a web connectable extension that receives TLS channel id on a site // that can connect to it, with a TLS channel ID having been generated. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingWithTlsChannelIdTest, WebConnectableWithNonEmptyTlsChannelId) { InitializeTestServer(); std::string expected_tls_channel_id_value = CreateTlsChannelId(); const Extension* chromium_connectable = LoadChromiumConnectableExtensionWithTlsChannelId(); ASSERT_TRUE(chromium_connectable); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); // Since the extension requests the TLS channel ID, it gets it for a site that // can connect to it, but only if the page also asks to send it. EXPECT_EQ(std::string(), GetTlsChannelIdFromPortConnect(chromium_connectable, false)); EXPECT_EQ(std::string(), GetTlsChannelIdFromSendMessage(chromium_connectable, false)); // If the page does ask to send the TLS channel ID, it's sent and non-empty. std::string tls_channel_id_from_port_connect = GetTlsChannelIdFromPortConnect(chromium_connectable, true); EXPECT_NE(0u, tls_channel_id_from_port_connect.size()); // The same value is received by both connect and sendMessage. std::string tls_channel_id_from_send_message = GetTlsChannelIdFromSendMessage(chromium_connectable, true); EXPECT_EQ(tls_channel_id_from_port_connect, tls_channel_id_from_send_message); // And since a TLS channel ID exists for the domain, the value received is // parseable as a JWK. (In particular, it has the same value we created by // converting the public key to JWK with net::ConvertSpkiFromDerToJwk.) std::string tls_channel_id(tls_channel_id_from_port_connect); EXPECT_EQ(expected_tls_channel_id_value, tls_channel_id); // The TLS channel ID shouldn't change from one connection to the next... std::string tls_channel_id2 = GetTlsChannelIdFromPortConnect(chromium_connectable, true); EXPECT_EQ(tls_channel_id, tls_channel_id2); tls_channel_id2 = GetTlsChannelIdFromSendMessage(chromium_connectable, true); EXPECT_EQ(tls_channel_id, tls_channel_id2); // nor should it change when navigating away, revisiting the page and // requesting it again. ui_test_utils::NavigateToURL(browser(), google_com_url()); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); tls_channel_id2 = GetTlsChannelIdFromPortConnect(chromium_connectable, true); EXPECT_EQ(tls_channel_id, tls_channel_id2); tls_channel_id2 = GetTlsChannelIdFromSendMessage(chromium_connectable, true); EXPECT_EQ(tls_channel_id, tls_channel_id2); } // Tests a web connectable extension that receives TLS channel id, but // immediately closes its background page upon receipt of a message. // Same flakiness seen in http://crbug.com/297866 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingWithTlsChannelIdTest, DISABLED_WebConnectableWithNonEmptyTlsChannelIdAndClosedBackgroundPage) { InitializeTestServer(); std::string expected_tls_channel_id_value = CreateTlsChannelId(); const Extension* chromium_connectable = LoadChromiumConnectableExtensionWithTlsChannelId(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); // If the page does ask for it, it isn't empty, even if the background page // closes upon receipt of the connect. std::string tls_channel_id = GetTlsChannelIdFromPortConnect( chromium_connectable, true, close_background_message()); EXPECT_EQ(expected_tls_channel_id_value, tls_channel_id); // A subsequent connect will still succeed, even if the background page was // previously closed. tls_channel_id = GetTlsChannelIdFromPortConnect(chromium_connectable, true); // And the expected value is still retrieved. EXPECT_EQ(expected_tls_channel_id_value, tls_channel_id); } IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MessagingUserGesture) { const char kManifest[] = "{" " \"name\": \"user_gesture\"," " \"version\": \"1.0\"," " \"background\": {" " \"scripts\": [\"background.js\"]" " }," " \"manifest_version\": 2" "}"; TestExtensionDir receiver_dir; receiver_dir.WriteManifest(kManifest); receiver_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "chrome.runtime.onMessageExternal.addListener(\n" " function(msg, sender, reply) {\n" " reply({result:chrome.test.isProcessingUserGesture()});\n" " });"); const Extension* receiver = LoadExtension(receiver_dir.unpacked_path()); ASSERT_TRUE(receiver); TestExtensionDir sender_dir; sender_dir.WriteManifest(kManifest); sender_dir.WriteFile(FILE_PATH_LITERAL("background.js"), ""); const Extension* sender = LoadExtension(sender_dir.unpacked_path()); ASSERT_TRUE(sender); EXPECT_EQ("false", ExecuteScriptInBackgroundPage(sender->id(), base::StringPrintf( "chrome.test.runWithoutUserGesture(function() {\n" " chrome.runtime.sendMessage('%s', {}, function(response) {\n" " window.domAutomationController.send('' + response.result);\n" " });\n" "});", receiver->id().c_str()))); EXPECT_EQ("true", ExecuteScriptInBackgroundPage(sender->id(), base::StringPrintf( "chrome.test.runWithUserGesture(function() {\n" " chrome.runtime.sendMessage('%s', {}, function(response) {\n" " window.domAutomationController.send('' + response.result);\n" " });\n" "});", receiver->id().c_str()))); } // Tests that a hosted app on a connectable site doesn't interfere with the // connectability of that site. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, HostedAppOnWebsite) { InitializeTestServer(); scoped_refptr<const Extension> app = LoadChromiumHostedApp(); // The presence of the hosted app shouldn't give the ability to send messages. ui_test_utils::NavigateToURL(browser(), chromium_org_url()); EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessagesToMainFrame(app)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); // Once a connectable extension is installed, it should. const Extension* extension = LoadChromiumConnectableExtension(); EXPECT_EQ(OK, CanConnectAndSendMessagesToMainFrame(extension)); EXPECT_FALSE(AreAnyNonWebApisDefinedForMainFrame()); } // Tests that an invalid extension ID specified in a hosted app does not crash // the hosted app's renderer. // // This is a regression test for http://crbug.com/326250#c12. IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, InvalidExtensionIDFromHostedApp) { InitializeTestServer(); // The presence of the chromium hosted app triggers this bug. The chromium // connectable extension needs to be installed to set up the runtime bindings. LoadChromiumHostedApp(); LoadChromiumConnectableExtension(); scoped_refptr<const Extension> invalid = ExtensionBuilder() // A bit scary that this works... .SetID("invalid") .SetManifest(DictionaryBuilder() .Set("name", "Fake extension") .Set("version", "1") .Set("manifest_version", 2)) .Build(); ui_test_utils::NavigateToURL(browser(), chromium_org_url()); EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR, CanConnectAndSendMessagesToMainFrame(invalid)); } #endif // !defined(OS_WIN) - http://crbug.com/350517. } // namespace }; // namespace extensions