// 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 <string> #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/values.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/active_tab_permission_granter.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sessions/session_id.h" #include "chrome/common/extensions/features/feature_channel.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/web_contents.h" #include "content/public/common/frame_navigate_params.h" #include "content/public/common/page_transition_types.h" #include "content/public/test/test_browser_thread.h" #include "extensions/common/extension.h" #include "extensions/common/extension_builder.h" #include "extensions/common/features/feature.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/common/value_builder.h" using base::DictionaryValue; using base::ListValue; using content::BrowserThread; using content::NavigationController; namespace extensions { namespace { scoped_refptr<const Extension> CreateTestExtension( const std::string& id, bool has_active_tab_permission, bool has_tab_capture_permission) { ListBuilder permissions; if (has_active_tab_permission) permissions.Append("activeTab"); if (has_tab_capture_permission) permissions.Append("tabCapture"); return ExtensionBuilder() .SetManifest(DictionaryBuilder() .Set("name", "Extension with ID " + id) .Set("version", "1.0") .Set("manifest_version", 2) .Set("permissions", permissions)) .SetID(id) .Build(); } class ActiveTabTest : public ChromeRenderViewHostTestHarness { protected: ActiveTabTest() : current_channel(chrome::VersionInfo::CHANNEL_DEV), extension(CreateTestExtension("deadbeef", true, false)), another_extension(CreateTestExtension("feedbeef", true, false)), extension_without_active_tab(CreateTestExtension("badbeef", false, false)), extension_with_tab_capture(CreateTestExtension("cafebeef", true, true)) {} virtual void SetUp() OVERRIDE { ChromeRenderViewHostTestHarness::SetUp(); TabHelper::CreateForWebContents(web_contents()); } int tab_id() { return SessionID::IdForTab(web_contents()); } ActiveTabPermissionGranter* active_tab_permission_granter() { return extensions::TabHelper::FromWebContents(web_contents())-> active_tab_permission_granter(); } bool IsAllowed(const scoped_refptr<const Extension>& extension, const GURL& url) { return IsAllowed(extension, url, tab_id()); } bool IsAllowed(const scoped_refptr<const Extension>& extension, const GURL& url, int tab_id) { return PermissionsData::CanExecuteScriptOnPage( extension.get(), url, url, tab_id, NULL, -1, NULL) && PermissionsData::CanCaptureVisiblePage( extension.get(), url, tab_id, NULL) && HasTabsPermission(extension, tab_id); } bool IsBlocked(const scoped_refptr<const Extension>& extension, const GURL& url) { return IsBlocked(extension, url, tab_id()); } bool IsBlocked(const scoped_refptr<const Extension>& extension, const GURL& url, int tab_id) { // Note: can't check HasTabsPermission because it isn't URL specific. return !PermissionsData::CanExecuteScriptOnPage( extension.get(), url, url, tab_id, NULL, -1, NULL) && !PermissionsData::CanCaptureVisiblePage( extension.get(), url, tab_id, NULL); } bool HasTabsPermission(const scoped_refptr<const Extension>& extension) { return HasTabsPermission(extension, tab_id()); } bool HasTabsPermission(const scoped_refptr<const Extension>& extension, int tab_id) { return PermissionsData::HasAPIPermissionForTab( extension.get(), tab_id, APIPermission::kTab); } bool IsGrantedForTab(const Extension* extension, const content::WebContents* web_contents) { return PermissionsData::HasAPIPermissionForTab( extension, SessionID::IdForTab(web_contents), APIPermission::kTab); } // TODO(justinlin): Remove when tabCapture is moved to stable. ScopedCurrentChannel current_channel; // An extension with the activeTab permission. scoped_refptr<const Extension> extension; // Another extension with activeTab (for good measure). scoped_refptr<const Extension> another_extension; // An extension without the activeTab permission. scoped_refptr<const Extension> extension_without_active_tab; // An extension with both the activeTab and tabCapture permission. scoped_refptr<const Extension> extension_with_tab_capture; }; TEST_F(ActiveTabTest, GrantToSinglePage) { GURL google("http://www.google.com"); NavigateAndCommit(google); // No access unless it's been granted. EXPECT_TRUE(IsBlocked(extension, google)); EXPECT_TRUE(IsBlocked(another_extension, google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); EXPECT_FALSE(HasTabsPermission(extension)); EXPECT_FALSE(HasTabsPermission(another_extension)); EXPECT_FALSE(HasTabsPermission(extension_without_active_tab)); active_tab_permission_granter()->GrantIfRequested(extension.get()); active_tab_permission_granter()->GrantIfRequested( extension_without_active_tab.get()); // Granted to extension and extension_without_active_tab, but the latter // doesn't have the activeTab permission so not granted. EXPECT_TRUE(IsAllowed(extension, google)); EXPECT_TRUE(IsBlocked(another_extension, google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); // Other subdomains shouldn't be given access. GURL mail_google("http://mail.google.com"); EXPECT_TRUE(IsBlocked(extension, mail_google)); EXPECT_TRUE(IsBlocked(another_extension, mail_google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, mail_google)); // Reloading the page should clear the active permissions. Reload(); EXPECT_TRUE(IsBlocked(extension, google)); EXPECT_TRUE(IsBlocked(another_extension, google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); EXPECT_FALSE(HasTabsPermission(extension)); EXPECT_FALSE(HasTabsPermission(another_extension)); EXPECT_FALSE(HasTabsPermission(extension_without_active_tab)); // But they should still be able to be granted again. active_tab_permission_granter()->GrantIfRequested(extension.get()); EXPECT_TRUE(IsAllowed(extension, google)); EXPECT_TRUE(IsBlocked(another_extension, google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); // And grant a few more times redundantly for good measure. active_tab_permission_granter()->GrantIfRequested(extension.get()); active_tab_permission_granter()->GrantIfRequested(extension.get()); active_tab_permission_granter()->GrantIfRequested(another_extension.get()); active_tab_permission_granter()->GrantIfRequested(another_extension.get()); active_tab_permission_granter()->GrantIfRequested(another_extension.get()); active_tab_permission_granter()->GrantIfRequested(extension.get()); active_tab_permission_granter()->GrantIfRequested(extension.get()); active_tab_permission_granter()->GrantIfRequested(another_extension.get()); active_tab_permission_granter()->GrantIfRequested(another_extension.get()); EXPECT_TRUE(IsAllowed(extension, google)); EXPECT_TRUE(IsAllowed(another_extension, google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); // Navigating to a new URL should clear the active permissions. GURL chromium("http://www.chromium.org"); NavigateAndCommit(chromium); EXPECT_TRUE(IsBlocked(extension, google)); EXPECT_TRUE(IsBlocked(another_extension, google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); EXPECT_TRUE(IsBlocked(extension, chromium)); EXPECT_TRUE(IsBlocked(another_extension, chromium)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium)); EXPECT_FALSE(HasTabsPermission(extension)); EXPECT_FALSE(HasTabsPermission(another_extension)); EXPECT_FALSE(HasTabsPermission(extension_without_active_tab)); // Should be able to grant to multiple extensions at the same time (if they // have the activeTab permission, of course). active_tab_permission_granter()->GrantIfRequested(extension.get()); active_tab_permission_granter()->GrantIfRequested(another_extension.get()); active_tab_permission_granter()->GrantIfRequested( extension_without_active_tab.get()); EXPECT_TRUE(IsBlocked(extension, google)); EXPECT_TRUE(IsBlocked(another_extension, google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); EXPECT_TRUE(IsAllowed(extension, chromium)); EXPECT_TRUE(IsAllowed(another_extension, chromium)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium)); // Should be able to go back to URLs that were previously cleared. NavigateAndCommit(google); active_tab_permission_granter()->GrantIfRequested(extension.get()); active_tab_permission_granter()->GrantIfRequested(another_extension.get()); active_tab_permission_granter()->GrantIfRequested( extension_without_active_tab.get()); EXPECT_TRUE(IsAllowed(extension, google)); EXPECT_TRUE(IsAllowed(another_extension, google)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); EXPECT_TRUE(IsBlocked(extension, chromium)); EXPECT_TRUE(IsBlocked(another_extension, chromium)); EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium)); }; TEST_F(ActiveTabTest, Uninstalling) { // Some semi-arbitrary setup. GURL google("http://www.google.com"); NavigateAndCommit(google); active_tab_permission_granter()->GrantIfRequested(extension.get()); EXPECT_TRUE(IsGrantedForTab(extension.get(), web_contents())); EXPECT_TRUE(IsAllowed(extension, google)); // Uninstalling the extension should clear its tab permissions. UnloadedExtensionInfo details(extension.get(), UnloadedExtensionInfo::REASON_DISABLE); content::NotificationService::current()->Notify( chrome::NOTIFICATION_EXTENSION_UNLOADED, content::Source<Profile>(Profile::FromBrowserContext( web_contents()->GetBrowserContext())), content::Details<UnloadedExtensionInfo>(&details)); // Note: can't EXPECT_FALSE(IsAllowed) here because uninstalled extensions // are just that... considered to be uninstalled, and the manager might // just ignore them from here on. // Granting the extension again should give them back. active_tab_permission_granter()->GrantIfRequested(extension.get()); EXPECT_TRUE(IsGrantedForTab(extension.get(), web_contents())); EXPECT_TRUE(IsAllowed(extension, google)); } TEST_F(ActiveTabTest, OnlyActiveTab) { GURL google("http://www.google.com"); NavigateAndCommit(google); active_tab_permission_granter()->GrantIfRequested(extension.get()); EXPECT_TRUE(IsAllowed(extension, google, tab_id())); EXPECT_TRUE(IsBlocked(extension, google, tab_id() + 1)); EXPECT_FALSE(HasTabsPermission(extension, tab_id() + 1)); } TEST_F(ActiveTabTest, NavigateInPage) { GURL google("http://www.google.com"); NavigateAndCommit(google); active_tab_permission_granter()->GrantIfRequested(extension.get()); // Perform an in-page navigation. The extension should not lose the temporary // permission. GURL google_h1("http://www.google.com#h1"); NavigateAndCommit(google_h1); EXPECT_TRUE(IsAllowed(extension, google, tab_id())); EXPECT_TRUE(IsAllowed(extension, google_h1, tab_id())); GURL chromium("http://www.chromium.org"); NavigateAndCommit(chromium); EXPECT_FALSE(IsAllowed(extension, google, tab_id())); EXPECT_FALSE(IsAllowed(extension, google_h1, tab_id())); EXPECT_FALSE(IsAllowed(extension, chromium, tab_id())); active_tab_permission_granter()->GrantIfRequested(extension.get()); EXPECT_FALSE(IsAllowed(extension, google, tab_id())); EXPECT_FALSE(IsAllowed(extension, google_h1, tab_id())); EXPECT_TRUE(IsAllowed(extension, chromium, tab_id())); GURL chromium_h1("http://www.chromium.org#h1"); NavigateAndCommit(chromium_h1); EXPECT_FALSE(IsAllowed(extension, google, tab_id())); EXPECT_FALSE(IsAllowed(extension, google_h1, tab_id())); EXPECT_TRUE(IsAllowed(extension, chromium, tab_id())); EXPECT_TRUE(IsAllowed(extension, chromium_h1, tab_id())); Reload(); EXPECT_FALSE(IsAllowed(extension, google, tab_id())); EXPECT_FALSE(IsAllowed(extension, google_h1, tab_id())); EXPECT_FALSE(IsAllowed(extension, chromium, tab_id())); EXPECT_FALSE(IsAllowed(extension, chromium_h1, tab_id())); } TEST_F(ActiveTabTest, ChromeUrlGrants) { GURL internal("chrome://version"); NavigateAndCommit(internal); active_tab_permission_granter()->GrantIfRequested( extension_with_tab_capture.get()); // Do not grant tabs/hosts permissions for tab. EXPECT_TRUE(IsBlocked(extension_with_tab_capture, internal, tab_id())); EXPECT_TRUE(PermissionsData::HasAPIPermissionForTab( extension_with_tab_capture.get(), tab_id(), APIPermission::kTabCaptureForTab)); EXPECT_TRUE(IsBlocked(extension_with_tab_capture, internal, tab_id() + 1)); EXPECT_FALSE(PermissionsData::HasAPIPermissionForTab( extension_with_tab_capture.get(), tab_id() + 1, APIPermission::kTabCaptureForTab)); } } // namespace } // namespace extensions