diff options
author | hjd@google.com <hjd@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-24 22:36:36 +0000 |
---|---|---|
committer | hjd@google.com <hjd@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-24 22:36:36 +0000 |
commit | c5710f6bcdf5a14fa8e305658ad267750137d017 (patch) | |
tree | f6f89e7f0594cf0d38f0c8d7dff3f9641d8ba318 | |
parent | ab4e0392e8b715230045f87ff75947e993eca5af (diff) | |
download | chromium_src-c5710f6bcdf5a14fa8e305658ad267750137d017.zip chromium_src-c5710f6bcdf5a14fa8e305658ad267750137d017.tar.gz chromium_src-c5710f6bcdf5a14fa8e305658ad267750137d017.tar.bz2 |
Allows AwCookieManager to block ThirdParty cookies
BUG=11678084
TEST=AndroidWebviewTest
Review URL: https://codereview.chromium.org/241143002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@266013 0039d316-1c4b-4281-b951-d872f2087c98
7 files changed, 408 insertions, 6 deletions
diff --git a/android_webview/android_webview_tests.gypi b/android_webview/android_webview_tests.gypi index f3e8cbb5..e6f1df3 100644 --- a/android_webview/android_webview_tests.gypi +++ b/android_webview/android_webview_tests.gypi @@ -96,6 +96,7 @@ '<(SHARED_INTERMEDIATE_DIR)/android_webview_unittests', ], 'sources': [ + 'browser/aw_cookie_access_policy_unittest.cc', 'browser/aw_form_database_service_unittest.cc', 'browser/net/android_stream_reader_url_request_job_unittest.cc', 'browser/net/input_stream_reader_unittest.cc', diff --git a/android_webview/browser/aw_cookie_access_policy.cc b/android_webview/browser/aw_cookie_access_policy.cc index 15158b9..06e30605 100644 --- a/android_webview/browser/aw_cookie_access_policy.cc +++ b/android_webview/browser/aw_cookie_access_policy.cc @@ -7,9 +7,11 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" using base::AutoLock; using content::BrowserThread; +using net::StaticCookiePolicy; namespace android_webview { @@ -21,7 +23,8 @@ AwCookieAccessPolicy::~AwCookieAccessPolicy() { } AwCookieAccessPolicy::AwCookieAccessPolicy() - : allow_access_(true) { + : allow_access_(true), + allow_third_party_access_(true) { } AwCookieAccessPolicy* AwCookieAccessPolicy::GetInstance() { @@ -38,15 +41,25 @@ void AwCookieAccessPolicy::SetGlobalAllowAccess(bool allow) { allow_access_ = allow; } +bool AwCookieAccessPolicy::GetThirdPartyAllowAccess() { + AutoLock lock(lock_); + return allow_third_party_access_; +} + +void AwCookieAccessPolicy::SetThirdPartyAllowAccess(bool allow) { + AutoLock lock(lock_); + allow_third_party_access_ = allow; +} + bool AwCookieAccessPolicy::OnCanGetCookies(const net::URLRequest& request, const net::CookieList& cookie_list) { - return GetGlobalAllowAccess(); + return AllowGet(request.url(), request.first_party_for_cookies()); } bool AwCookieAccessPolicy::OnCanSetCookie(const net::URLRequest& request, const std::string& cookie_line, net::CookieOptions* options) { - return GetGlobalAllowAccess(); + return AllowSet(request.url(), request.first_party_for_cookies()); } bool AwCookieAccessPolicy::AllowGetCookie(const GURL& url, @@ -55,7 +68,7 @@ bool AwCookieAccessPolicy::AllowGetCookie(const GURL& url, content::ResourceContext* context, int render_process_id, int render_frame_id) { - return GetGlobalAllowAccess(); + return AllowGet(url, first_party); } bool AwCookieAccessPolicy::AllowSetCookie(const GURL& url, @@ -65,7 +78,26 @@ bool AwCookieAccessPolicy::AllowSetCookie(const GURL& url, int render_process_id, int render_frame_id, net::CookieOptions* options) { - return GetGlobalAllowAccess(); + return AllowSet(url, first_party); +} + +StaticCookiePolicy::Type AwCookieAccessPolicy::GetPolicy() { + if (!GetGlobalAllowAccess()) { + return StaticCookiePolicy::BLOCK_ALL_COOKIES; + } else if (!GetThirdPartyAllowAccess()) { + return StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES; + } + return StaticCookiePolicy::ALLOW_ALL_COOKIES; +} + +bool AwCookieAccessPolicy::AllowSet(const GURL& url, const GURL& first_party) { + return StaticCookiePolicy(GetPolicy()).CanSetCookie(url, first_party) + == net::OK; +} + +bool AwCookieAccessPolicy::AllowGet(const GURL& url, const GURL& first_party) { + return StaticCookiePolicy(GetPolicy()).CanGetCookies(url, first_party) + == net::OK; } } // namespace android_webview diff --git a/android_webview/browser/aw_cookie_access_policy.h b/android_webview/browser/aw_cookie_access_policy.h index 7014521..7fd7b59 100644 --- a/android_webview/browser/aw_cookie_access_policy.h +++ b/android_webview/browser/aw_cookie_access_policy.h @@ -8,7 +8,9 @@ #include "base/basictypes.h" #include "base/lazy_instance.h" #include "base/synchronization/lock.h" +#include "net/base/static_cookie_policy.h" #include "net/cookies/canonical_cookie.h" +#include "net/url_request/url_request.h" namespace content { class ResourceContext; @@ -16,7 +18,6 @@ class ResourceContext; namespace net { class CookieOptions; -class URLRequest; } class GURL; @@ -33,6 +34,11 @@ class AwCookieAccessPolicy { bool GetGlobalAllowAccess(); void SetGlobalAllowAccess(bool allow); + // These allow more fine grained control over requests depending on whether + // the cookie is third party or not. + bool GetThirdPartyAllowAccess(); + void SetThirdPartyAllowAccess(bool allow); + // These are the functions called when operating over cookies from the // network. See NetworkDelegate for further descriptions. bool OnCanGetCookies(const net::URLRequest& request, @@ -63,8 +69,18 @@ class AwCookieAccessPolicy { AwCookieAccessPolicy(); ~AwCookieAccessPolicy(); bool allow_access_; + bool allow_third_party_access_; base::Lock lock_; + // We have two bits of state but only three different cases: + // If !GlobalAllowAccess then reject all cookies. + // If GlobalAllowAccess and !ThirdPartyAllowAccess then reject third party. + // If GlobalAllowAccess and ThirdPartyAllowAccess then allow all cookies. + net::StaticCookiePolicy::Type GetPolicy(void); + + bool AllowGet(const GURL& url, const GURL& first_party); + bool AllowSet(const GURL& url, const GURL& first_party); + DISALLOW_COPY_AND_ASSIGN(AwCookieAccessPolicy); }; diff --git a/android_webview/browser/aw_cookie_access_policy_unittest.cc b/android_webview/browser/aw_cookie_access_policy_unittest.cc new file mode 100644 index 0000000..6f8a1f1 --- /dev/null +++ b/android_webview/browser/aw_cookie_access_policy_unittest.cc @@ -0,0 +1,151 @@ +// 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 "android_webview/browser/aw_cookie_access_policy.h" + +#include "base/run_loop.h" +#include "net/base/request_priority.h" +#include "net/cookies/canonical_cookie.h" +#include "net/url_request/url_request_test_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { +class ResourceContext; +} + +namespace net { +class CookieOptions; +} + +class GURL; + +using android_webview::AwCookieAccessPolicy; +using net::CookieList; +using net::TestDelegate; +using net::TestJobInterceptor; +using net::TestNetworkDelegate; +using net::TestURLRequestContext; +using net::TestURLRequest; +using testing::Test; + +class AwCookieAccessPolicyTest : public Test { + public: + static const GURL kUrlFirstParty; + static const GURL kUrlThirdParty; + + AwCookieAccessPolicyTest() + : loop_(), + context_(), + url_request_delegate_(), + network_delegate_(), + first_party_request_() {} + + virtual void SetUp() { + context_.set_network_delegate(&network_delegate_); + first_party_request_.reset(new TestURLRequest(kUrlFirstParty, + net::DEFAULT_PRIORITY, + &url_request_delegate_, + &context_)); + first_party_request_->set_method("GET"); + + third_party_request_.reset(new TestURLRequest(kUrlThirdParty, + net::DEFAULT_PRIORITY, + &url_request_delegate_, + &context_)); + third_party_request_->set_method("GET"); + third_party_request_->set_first_party_for_cookies(kUrlFirstParty); + } + + AwCookieAccessPolicy* policy() { + return AwCookieAccessPolicy::GetInstance(); + } + + bool GetGlobalAllowAccess() { + return policy()->GetGlobalAllowAccess(); + } + + void SetGlobalAllowAccess(bool allow) { + policy()->SetGlobalAllowAccess(allow); + } + + bool GetThirdPartyAllowAccess() { + return policy()->GetThirdPartyAllowAccess(); + } + + void SetThirdPartyAllowAccess(bool allow) { + policy()->SetThirdPartyAllowAccess(allow); + } + + bool OnCanGetCookies(const net::URLRequest& request) { + return policy()->OnCanGetCookies(request, CookieList()); + } + + bool OnCanSetCookie(const net::URLRequest& request) { + return policy()->OnCanSetCookie(request, "", NULL); + } + + bool AllowGetCookie(const GURL& url, const GURL& first_party) { + return policy()->AllowGetCookie(url, first_party, CookieList(), NULL, 0, 0); + } + + bool AllowSetCookie(const GURL& url, const GURL& first_party) { + return policy()->AllowSetCookie(url, first_party, "", NULL, 0, 0, NULL); + } + + void expectFirstPartyAccess(bool expectedResult) { + EXPECT_EQ(expectedResult, AllowSetCookie(kUrlFirstParty, kUrlFirstParty)); + EXPECT_EQ(expectedResult, AllowGetCookie(kUrlFirstParty, kUrlFirstParty)); + EXPECT_EQ(expectedResult, OnCanGetCookies(*first_party_request_)); + EXPECT_EQ(expectedResult, OnCanSetCookie(*first_party_request_)); + } + + void expectThirdPartyAccess(bool expectedResult) { + EXPECT_EQ(expectedResult, AllowSetCookie(kUrlFirstParty, kUrlThirdParty)); + EXPECT_EQ(expectedResult, AllowGetCookie(kUrlFirstParty, kUrlThirdParty)); + EXPECT_EQ(expectedResult, OnCanGetCookies(*third_party_request_)); + EXPECT_EQ(expectedResult, OnCanSetCookie(*third_party_request_)); + } + + protected: + base::MessageLoopForIO loop_; + TestURLRequestContext context_; + TestDelegate url_request_delegate_; + TestNetworkDelegate network_delegate_; + scoped_ptr<TestURLRequest> first_party_request_; + scoped_ptr<TestURLRequest> third_party_request_; +}; + +const GURL AwCookieAccessPolicyTest::kUrlFirstParty = + GURL("http://first.example"); +const GURL AwCookieAccessPolicyTest::kUrlThirdParty = + GURL("http://third.example"); + +TEST_F(AwCookieAccessPolicyTest, BlockAllCookies) { + SetGlobalAllowAccess(false); + SetThirdPartyAllowAccess(false); + expectFirstPartyAccess(false); + expectThirdPartyAccess(false); +} + +TEST_F(AwCookieAccessPolicyTest, BlockAllCookiesWithThirdPartySet) { + SetGlobalAllowAccess(false); + SetThirdPartyAllowAccess(true); + expectFirstPartyAccess(false); + expectThirdPartyAccess(false); +} + +TEST_F(AwCookieAccessPolicyTest, FirstPartyCookiesOnly) { + SetGlobalAllowAccess(true); + SetThirdPartyAllowAccess(false); + expectFirstPartyAccess(true); + expectThirdPartyAccess(false); +} + +TEST_F(AwCookieAccessPolicyTest, AllowAllCookies) { + SetGlobalAllowAccess(true); + SetThirdPartyAllowAccess(true); + expectFirstPartyAccess(true); + expectThirdPartyAccess(true); +} diff --git a/android_webview/java/src/org/chromium/android_webview/AwCookieManager.java b/android_webview/java/src/org/chromium/android_webview/AwCookieManager.java index c1bae36..ab91e5d 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwCookieManager.java +++ b/android_webview/java/src/org/chromium/android_webview/AwCookieManager.java @@ -30,6 +30,22 @@ public final class AwCookieManager { } /** + * Control whether third party cookies are enabled or disabled + * @param accept TRUE if accept third party cookies + */ + public void setAcceptThirdPartyCookie(boolean accept) { + nativeSetAcceptThirdPartyCookie(accept); + } + + /** + * Return whether third party cookies are enabled + * @return TRUE if accept third party cookies + */ + public boolean acceptThirdPartyCookie() { + return nativeAcceptThirdPartyCookie(); + } + + /** * Set cookie for a given url. The old cookie with same host/path/name will * be removed. The new cookie will be added if it is not expired or it does * not have expiration which implies it is session cookie. @@ -107,6 +123,9 @@ public final class AwCookieManager { private native void nativeSetAcceptCookie(boolean accept); private native boolean nativeAcceptCookie(); + private native void nativeSetAcceptThirdPartyCookie(boolean accept); + private native boolean nativeAcceptThirdPartyCookie(); + private native void nativeSetCookie(String url, String value); private native String nativeGetCookie(String url); diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java index 18cde68..d105966 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java @@ -234,4 +234,167 @@ public class CookieManagerTest extends AwTestBase { } }); } + + @MediumTest + @Feature({"AndroidWebView", "Privacy"}) + public void testThirdPartyCookie() throws Throwable { + TestWebServer webServer = null; + try { + // In theory we need two servers to test this, one server ('the first party') + // which returns a response with a link to a second server ('the third party') + // at different origin. This second server attempts to set a cookie which should + // fail if AcceptThirdPartyCookie() is false. + // Strictly according to the letter of RFC6454 it should be possible to set this + // situation up with two TestServers on different ports (these count as having + // different origins) but Chrome is not strict about this and does not check the + // port. Instead we cheat making some of the urls come from localhost and some + // from 127.0.0.1 which count (both in theory and pratice) as having different + // origins. + webServer = new TestWebServer(false); + + // Turn global allow on. + mCookieManager.setAcceptCookie(true); + mCookieManager.removeAllCookie(); + assertTrue(mCookieManager.acceptCookie()); + assertFalse(mCookieManager.hasCookies()); + + // When third party cookies are disabled... + mCookieManager.setAcceptThirdPartyCookie(false); + assertFalse(mCookieManager.acceptThirdPartyCookie()); + + // ...we can't set third party cookies. + // First on the third party server we create a url which tries to set a cookie. + String cookieUrl = toThirdPartyUrl( + makeCookieUrl(webServer, "/cookie_1.js", "test1", "value1")); + // Then we create a url on the first party server which links to the first url. + String url = makeScriptLinkUrl(webServer, "/content_1.html", cookieUrl); + loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url); + assertNull(mCookieManager.getCookie(cookieUrl)); + + // When third party cookies are enabled... + mCookieManager.setAcceptThirdPartyCookie(true); + assertTrue(mCookieManager.acceptThirdPartyCookie()); + + // ...we can set third party cookies. + cookieUrl = toThirdPartyUrl( + makeCookieUrl(webServer, "/cookie_2.js", "test2", "value2")); + url = makeScriptLinkUrl(webServer, "/content_2.html", cookieUrl); + loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url); + waitForCookie(cookieUrl); + String cookie = mCookieManager.getCookie(cookieUrl); + assertNotNull(cookie); + validateCookies(cookie, "test2"); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + /** + * Creates a response on the TestWebServer which attempts to set a cookie when fetched. + * @param webServer the webServer on which to create the response + * @param path the path component of the url (e.g "/cookie_test.html") + * @param key the key of the cookie + * @param value the value of the cookie + * @return the url which gets the response + */ + private String makeCookieUrl(TestWebServer webServer, String path, String key, String value) { + String response = ""; + List<Pair<String, String>> responseHeaders = new ArrayList<Pair<String, String>>(); + responseHeaders.add( + Pair.create("Set-Cookie", key + "=" + value + "; path=" + path)); + return webServer.setResponse(path, response, responseHeaders); + } + + /** + * Creates a response on the TestWebServer which contains a script tag with an external src. + * @param webServer the webServer on which to create the response + * @param path the path component of the url (e.g "/my_thing_with_script.html") + * @param url the url which which should appear as the src of the script tag. + * @return the url which gets the response + */ + private String makeScriptLinkUrl(TestWebServer webServer, String path, String url) { + String responseStr = "<html><head><title>Content!</title></head>" + + "<body><script src=" + url + "></script></body></html>"; + return webServer.setResponse(path, responseStr, null); + } + + @MediumTest + @Feature({"AndroidWebView", "Privacy"}) + public void testThirdPartyJavascriptCookie() throws Throwable { + TestWebServer webServer = null; + try { + // This test again uses 127.0.0.1/localhost trick to simulate a third party. + webServer = new TestWebServer(false); + + mCookieManager.setAcceptCookie(true); + mCookieManager.removeAllCookie(); + assertTrue(mCookieManager.acceptCookie()); + assertFalse(mCookieManager.hasCookies()); + + // When third party cookies are disabled... + mCookieManager.setAcceptThirdPartyCookie(false); + assertFalse(mCookieManager.acceptThirdPartyCookie()); + + // ...we can't set third party cookies. + // We create a script which tries to set a cookie on a third party. + String cookieUrl = toThirdPartyUrl( + makeCookieScriptUrl(webServer, "/cookie_1.html", "test1", "value1")); + // Then we load it as an iframe. + String url = makeIframeUrl(webServer, "/content_1.html", cookieUrl); + loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url); + assertNull(mCookieManager.getCookie(cookieUrl)); + + // When third party cookies are enabled... + mCookieManager.setAcceptThirdPartyCookie(true); + assertTrue(mCookieManager.acceptThirdPartyCookie()); + + // ...we can set third party cookies. + cookieUrl = toThirdPartyUrl( + makeCookieScriptUrl(webServer, "/cookie_2.html", "test2", "value2")); + url = makeIframeUrl(webServer, "/content_2.html", cookieUrl); + loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url); + String cookie = mCookieManager.getCookie(cookieUrl); + assertNotNull(cookie); + validateCookies(cookie, "test2"); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + /** + * Creates a response on the TestWebServer which attempts to set a cookie when fetched. + * @param webServer the webServer on which to create the response + * @param path the path component of the url (e.g "/my_thing_with_iframe.html") + * @param url the url which which should appear as the src of the iframe. + * @return the url which gets the response + */ + private String makeIframeUrl(TestWebServer webServer, String path, String url) { + String responseStr = "<html><head><title>Content!</title></head>" + + "<body><iframe src=" + url + "></iframe></body></html>"; + return webServer.setResponse(path, responseStr, null); + } + + /** + * Creates a response on the TestWebServer with a script that attempts to set a cookie. + * @param webServer the webServer on which to create the response + * @param path the path component of the url (e.g "/cookie_test.html") + * @param key the key of the cookie + * @param value the value of the cookie + * @return the url which gets the response + */ + private String makeCookieScriptUrl(TestWebServer webServer, String path, String key, + String value) { + String response = "<html><head></head><body>" + + "<script>document.cookie = \"" + key + "=" + value + "\";</script></body></html>"; + return webServer.setResponse(path, response, null); + } + + /** + * Makes a url look as if it comes from a different host. + * @param url the url to fake. + * @return the resulting after faking. + */ + private String toThirdPartyUrl(String url) { + return url.replace("localhost", "127.0.0.1"); + } } diff --git a/android_webview/native/cookie_manager.cc b/android_webview/native/cookie_manager.cc index 310cb43..adb0ec8 100644 --- a/android_webview/native/cookie_manager.cc +++ b/android_webview/native/cookie_manager.cc @@ -96,6 +96,8 @@ class CookieManager { void SetAcceptCookie(bool accept); bool AcceptCookie(); + void SetAcceptThirdPartyCookie(bool accept); + bool AcceptThirdPartyCookie(); void SetCookie(const GURL& host, const std::string& cookie_value); std::string GetCookie(const GURL& host); void RemoveSessionCookie(); @@ -273,6 +275,14 @@ bool CookieManager::AcceptCookie() { return AwCookieAccessPolicy::GetInstance()->GetGlobalAllowAccess(); } +void CookieManager::SetAcceptThirdPartyCookie(bool accept) { + AwCookieAccessPolicy::GetInstance()->SetThirdPartyAllowAccess(accept); +} + +bool CookieManager::AcceptThirdPartyCookie() { + return AwCookieAccessPolicy::GetInstance()->GetThirdPartyAllowAccess(); +} + void CookieManager::SetCookie(const GURL& host, const std::string& cookie_value) { ExecCookieTask(base::Bind(&CookieManager::SetCookieAsyncHelper, @@ -447,6 +457,16 @@ static jboolean AcceptCookie(JNIEnv* env, jobject obj) { return CookieManager::GetInstance()->AcceptCookie(); } +static void SetAcceptThirdPartyCookie(JNIEnv* env, + jobject obj, + jboolean accept) { + CookieManager::GetInstance()->SetAcceptThirdPartyCookie(accept); +} + +static jboolean AcceptThirdPartyCookie(JNIEnv* env, jobject obj) { + return CookieManager::GetInstance()->AcceptThirdPartyCookie(); +} + static void SetCookie(JNIEnv* env, jobject obj, jstring url, jstring value) { GURL host(ConvertJavaStringToUTF16(env, url)); std::string cookie_value(ConvertJavaStringToUTF8(env, value)); |