Commit c5710f6b authored by hjd@google.com's avatar hjd@google.com

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
parent ab4e0392
...@@ -96,6 +96,7 @@ ...@@ -96,6 +96,7 @@
'<(SHARED_INTERMEDIATE_DIR)/android_webview_unittests', '<(SHARED_INTERMEDIATE_DIR)/android_webview_unittests',
], ],
'sources': [ 'sources': [
'browser/aw_cookie_access_policy_unittest.cc',
'browser/aw_form_database_service_unittest.cc', 'browser/aw_form_database_service_unittest.cc',
'browser/net/android_stream_reader_url_request_job_unittest.cc', 'browser/net/android_stream_reader_url_request_job_unittest.cc',
'browser/net/input_stream_reader_unittest.cc', 'browser/net/input_stream_reader_unittest.cc',
......
...@@ -7,9 +7,11 @@ ...@@ -7,9 +7,11 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "net/base/net_errors.h"
using base::AutoLock; using base::AutoLock;
using content::BrowserThread; using content::BrowserThread;
using net::StaticCookiePolicy;
namespace android_webview { namespace android_webview {
...@@ -21,7 +23,8 @@ AwCookieAccessPolicy::~AwCookieAccessPolicy() { ...@@ -21,7 +23,8 @@ AwCookieAccessPolicy::~AwCookieAccessPolicy() {
} }
AwCookieAccessPolicy::AwCookieAccessPolicy() AwCookieAccessPolicy::AwCookieAccessPolicy()
: allow_access_(true) { : allow_access_(true),
allow_third_party_access_(true) {
} }
AwCookieAccessPolicy* AwCookieAccessPolicy::GetInstance() { AwCookieAccessPolicy* AwCookieAccessPolicy::GetInstance() {
...@@ -38,15 +41,25 @@ void AwCookieAccessPolicy::SetGlobalAllowAccess(bool allow) { ...@@ -38,15 +41,25 @@ void AwCookieAccessPolicy::SetGlobalAllowAccess(bool allow) {
allow_access_ = 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, bool AwCookieAccessPolicy::OnCanGetCookies(const net::URLRequest& request,
const net::CookieList& cookie_list) { const net::CookieList& cookie_list) {
return GetGlobalAllowAccess(); return AllowGet(request.url(), request.first_party_for_cookies());
} }
bool AwCookieAccessPolicy::OnCanSetCookie(const net::URLRequest& request, bool AwCookieAccessPolicy::OnCanSetCookie(const net::URLRequest& request,
const std::string& cookie_line, const std::string& cookie_line,
net::CookieOptions* options) { net::CookieOptions* options) {
return GetGlobalAllowAccess(); return AllowSet(request.url(), request.first_party_for_cookies());
} }
bool AwCookieAccessPolicy::AllowGetCookie(const GURL& url, bool AwCookieAccessPolicy::AllowGetCookie(const GURL& url,
...@@ -55,7 +68,7 @@ bool AwCookieAccessPolicy::AllowGetCookie(const GURL& url, ...@@ -55,7 +68,7 @@ bool AwCookieAccessPolicy::AllowGetCookie(const GURL& url,
content::ResourceContext* context, content::ResourceContext* context,
int render_process_id, int render_process_id,
int render_frame_id) { int render_frame_id) {
return GetGlobalAllowAccess(); return AllowGet(url, first_party);
} }
bool AwCookieAccessPolicy::AllowSetCookie(const GURL& url, bool AwCookieAccessPolicy::AllowSetCookie(const GURL& url,
...@@ -65,7 +78,26 @@ bool AwCookieAccessPolicy::AllowSetCookie(const GURL& url, ...@@ -65,7 +78,26 @@ bool AwCookieAccessPolicy::AllowSetCookie(const GURL& url,
int render_process_id, int render_process_id,
int render_frame_id, int render_frame_id,
net::CookieOptions* options) { 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 } // namespace android_webview
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "net/base/static_cookie_policy.h"
#include "net/cookies/canonical_cookie.h" #include "net/cookies/canonical_cookie.h"
#include "net/url_request/url_request.h"
namespace content { namespace content {
class ResourceContext; class ResourceContext;
...@@ -16,7 +18,6 @@ class ResourceContext; ...@@ -16,7 +18,6 @@ class ResourceContext;
namespace net { namespace net {
class CookieOptions; class CookieOptions;
class URLRequest;
} }
class GURL; class GURL;
...@@ -33,6 +34,11 @@ class AwCookieAccessPolicy { ...@@ -33,6 +34,11 @@ class AwCookieAccessPolicy {
bool GetGlobalAllowAccess(); bool GetGlobalAllowAccess();
void SetGlobalAllowAccess(bool allow); 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 // These are the functions called when operating over cookies from the
// network. See NetworkDelegate for further descriptions. // network. See NetworkDelegate for further descriptions.
bool OnCanGetCookies(const net::URLRequest& request, bool OnCanGetCookies(const net::URLRequest& request,
...@@ -63,8 +69,18 @@ class AwCookieAccessPolicy { ...@@ -63,8 +69,18 @@ class AwCookieAccessPolicy {
AwCookieAccessPolicy(); AwCookieAccessPolicy();
~AwCookieAccessPolicy(); ~AwCookieAccessPolicy();
bool allow_access_; bool allow_access_;
bool allow_third_party_access_;
base::Lock lock_; 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); DISALLOW_COPY_AND_ASSIGN(AwCookieAccessPolicy);
}; };
......
// 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);
}
...@@ -29,6 +29,22 @@ public final class AwCookieManager { ...@@ -29,6 +29,22 @@ public final class AwCookieManager {
return nativeAcceptCookie(); return nativeAcceptCookie();
} }
/**
* 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 * 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 * be removed. The new cookie will be added if it is not expired or it does
...@@ -107,6 +123,9 @@ public final class AwCookieManager { ...@@ -107,6 +123,9 @@ public final class AwCookieManager {
private native void nativeSetAcceptCookie(boolean accept); private native void nativeSetAcceptCookie(boolean accept);
private native boolean nativeAcceptCookie(); private native boolean nativeAcceptCookie();
private native void nativeSetAcceptThirdPartyCookie(boolean accept);
private native boolean nativeAcceptThirdPartyCookie();
private native void nativeSetCookie(String url, String value); private native void nativeSetCookie(String url, String value);
private native String nativeGetCookie(String url); private native String nativeGetCookie(String url);
......
...@@ -234,4 +234,167 @@ public class CookieManagerTest extends AwTestBase { ...@@ -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");
}
} }
...@@ -96,6 +96,8 @@ class CookieManager { ...@@ -96,6 +96,8 @@ class CookieManager {
void SetAcceptCookie(bool accept); void SetAcceptCookie(bool accept);
bool AcceptCookie(); bool AcceptCookie();
void SetAcceptThirdPartyCookie(bool accept);
bool AcceptThirdPartyCookie();
void SetCookie(const GURL& host, const std::string& cookie_value); void SetCookie(const GURL& host, const std::string& cookie_value);
std::string GetCookie(const GURL& host); std::string GetCookie(const GURL& host);
void RemoveSessionCookie(); void RemoveSessionCookie();
...@@ -273,6 +275,14 @@ bool CookieManager::AcceptCookie() { ...@@ -273,6 +275,14 @@ bool CookieManager::AcceptCookie() {
return AwCookieAccessPolicy::GetInstance()->GetGlobalAllowAccess(); 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, void CookieManager::SetCookie(const GURL& host,
const std::string& cookie_value) { const std::string& cookie_value) {
ExecCookieTask(base::Bind(&CookieManager::SetCookieAsyncHelper, ExecCookieTask(base::Bind(&CookieManager::SetCookieAsyncHelper,
...@@ -447,6 +457,16 @@ static jboolean AcceptCookie(JNIEnv* env, jobject obj) { ...@@ -447,6 +457,16 @@ static jboolean AcceptCookie(JNIEnv* env, jobject obj) {
return CookieManager::GetInstance()->AcceptCookie(); 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) { static void SetCookie(JNIEnv* env, jobject obj, jstring url, jstring value) {
GURL host(ConvertJavaStringToUTF16(env, url)); GURL host(ConvertJavaStringToUTF16(env, url));
std::string cookie_value(ConvertJavaStringToUTF8(env, value)); std::string cookie_value(ConvertJavaStringToUTF8(env, value));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment