Bug 1915355 - Part 4: Implement the third-party cookie blocking excpetion in CookieService. r=cookie-reviewers,baku,valentin

The patch integrates the third-party cookie blocking excpetion in
CookieService.

Depends on D223381

Differential Revision: https://phabricator.services.mozilla.com/D223383
This commit is contained in:
Tim Huang
2025-01-08 19:25:59 +00:00
parent 9dbd73f6a6
commit 93bfe3a6c1
10 changed files with 420 additions and 3 deletions

View File

@@ -61,7 +61,7 @@ static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
bool aForeignByAncestorContext,
bool aIsFirstPartyEnabled, bool aForced,
bool aUseSite,
nsString OriginAttributes::*aTarget,
nsString OriginAttributes::* aTarget,
OriginAttributes& aOriginAttributes) {
nsresult rv;
@@ -514,4 +514,23 @@ bool OriginAttributes::ParsePartitionKey(const nsAString& aPartitionKey,
return fieldIndex > 1;
}
/* static */
bool OriginAttributes::ExtractSiteFromPartitionKey(
const nsAString& aPartitionKey, nsAString& aOutSite) {
nsAutoString scheme, host;
int32_t port;
bool unused;
if (!ParsePartitionKey(aPartitionKey, scheme, host, port, unused)) {
return false;
}
if (port == -1) {
aOutSite.Assign(scheme + u"://"_ns + host);
} else {
aOutSite.Assign(scheme + u"://"_ns + host + u":"_ns);
aOutSite.AppendInt(port);
}
return true;
}
} // namespace mozilla

View File

@@ -141,6 +141,11 @@ class OriginAttributes : public dom::OriginAttributesDictionary {
nsAString& outScheme, nsAString& outBaseDomain,
int32_t& outPort,
bool& outForeignByAncestorContext);
// Parse a partitionKey and extract the site from it. Returns false if the
// partitionKey cannot be parsed because the format is invalid.
static bool ExtractSiteFromPartitionKey(const nsAString& aPartitionKey,
nsAString& aOutSite);
};
class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary {

View File

@@ -7,6 +7,7 @@
#include "CookieCommons.h"
#include "CookieLogging.h"
#include "CookieParser.h"
#include "CookieService.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Components.h"
@@ -16,6 +17,7 @@
#include "mozilla/dom/Document.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/net/CookiePersistentStorage.h"
#include "mozilla/net/CookiePrivateStorage.h"
@@ -259,6 +261,10 @@ nsresult CookieService::Init() {
os->AddObserver(this, "profile-do-change", true);
os->AddObserver(this, "last-pb-context-exited", true);
RunOnShutdown([self = RefPtr{this}] {
self->mThirdPartyCookieBlockingExceptions.Shutdown();
});
return NS_OK;
}
@@ -1723,5 +1729,77 @@ void CookieService::AddCookieFromDocument(
aThirdParty, aDocument->GetBrowsingContext());
}
/* static */
void CookieService::Update3PCBExceptionInfo(nsIChannel* aChannel) {
MOZ_ASSERT(aChannel);
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
RefPtr<CookieService> csSingleton = CookieService::GetSingleton();
// If the channel is a top-level loading, we start initiating the exception
// list service.
if (loadInfo->GetExternalContentPolicyType() ==
ExtContentPolicy::TYPE_DOCUMENT) {
Unused
<< csSingleton->mThirdPartyCookieBlockingExceptions.EnsureInitialized();
return;
}
// If the channel is triggered by a system principal, we don't need to do
// anything because the channel is for loading system resources.
if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
return;
}
// Suspend the channel here. We will resume it after we check the exception
// list.
aChannel->Suspend();
// It would be better to do this check with other checks that also suspend
// the channel, such as the URLClassifier.
csSingleton->mThirdPartyCookieBlockingExceptions.EnsureInitialized()->Then(
GetMainThreadSerialEventTarget(), __func__,
[channel = nsCOMPtr{aChannel}, csSingleton, loadInfo](
const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) {
// We check the 3PCB exception list here. We will check both the
// wildcard exception and the specific exception. If any of them is in
// the exception list, we will set the channel's isOn3PCBExceptionList
// to true.
bool isInExceptionList =
csSingleton->mThirdPartyCookieBlockingExceptions
.CheckExceptionForChannel(channel);
Unused << loadInfo->SetIsOn3PCBExceptionList(isInExceptionList);
channel->Resume();
return NS_OK;
});
}
NS_IMETHODIMP
CookieService::AddThirdPartyCookieBlockingExceptions(
const nsTArray<RefPtr<nsIThirdPartyCookieExceptionEntry>>& aExceptions) {
for (const auto& ex : aExceptions) {
nsAutoCString exception;
MOZ_ALWAYS_SUCCEEDS(ex->Serialize(exception));
mThirdPartyCookieBlockingExceptions.Insert(exception);
}
return NS_OK;
}
NS_IMETHODIMP
CookieService::RemoveThirdPartyCookieBlockingExceptions(
const nsTArray<RefPtr<nsIThirdPartyCookieExceptionEntry>>& aExceptions) {
for (const auto& ex : aExceptions) {
nsAutoCString exception;
MOZ_ALWAYS_SUCCEEDS(ex->Serialize(exception));
mThirdPartyCookieBlockingExceptions.Remove(exception);
}
return NS_OK;
}
} // namespace net
} // namespace mozilla

View File

@@ -13,10 +13,12 @@
#include "Cookie.h"
#include "CookieCommons.h"
#include "ThirdPartyCookieBlockingExceptions.h"
#include "nsString.h"
#include "nsIMemoryReporter.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/MozPromise.h"
class nsIConsoleReportCollector;
class nsICookieJarSettings;
@@ -58,6 +60,12 @@ class CookieService final : public nsICookieService,
static already_AddRefed<nsICookieService> GetXPCOMSingleton();
nsresult Init();
static void Update3PCBExceptionInfo(nsIChannel* aChannel);
ThirdPartyCookieBlockingExceptions& ThirdPartyCookieBlockingExceptionsRef() {
return mThirdPartyCookieBlockingExceptions;
}
/**
* Start watching the observer service for messages indicating that an app has
* been uninstalled. When an app is uninstalled, we get the cookie service
@@ -126,6 +134,8 @@ class CookieService final : public nsICookieService,
nsCOMPtr<mozIThirdPartyUtil> mThirdPartyUtil;
nsCOMPtr<nsIEffectiveTLDService> mTLDService;
ThirdPartyCookieBlockingExceptions mThirdPartyCookieBlockingExceptions;
// we have two separate Cookie Storages: one for normal browsing and one for
// private browsing.
RefPtr<CookieStorage> mPersistentStorage;

View File

@@ -57,10 +57,10 @@ export class ThirdPartyCookieBlockingExceptionListService {
#handleExceptionChange(created = [], deleted = []) {
if (created.length) {
// TODO: Calling CookieService API to remove exception sites.
Services.cookies.addThirdPartyCookieBlockingExceptions(created);
}
if (deleted.length) {
// TODO: Calling CookieService API to add exception sites.
Services.cookies.removeThirdPartyCookieBlockingExceptions(deleted);
}
}

View File

@@ -0,0 +1,208 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ThirdPartyCookieBlockingExceptions.h"
#include "mozilla/Components.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsIChannel.h"
namespace mozilla {
namespace net {
RefPtr<GenericNonExclusivePromise>
ThirdPartyCookieBlockingExceptions::EnsureInitialized() {
if (mInitPromise) {
return mInitPromise;
}
// Get the remote third-party cookie blocking exception list service instance.
nsresult rv;
m3PCBExceptionService = do_GetService(
NS_NSITHIRDPARTYCOOKIEBLOCKINGEXCEPTIONLISTSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv,
GenericNonExclusivePromise::CreateAndReject(rv, __func__));
RefPtr<mozilla::dom::Promise> initPromise;
rv = m3PCBExceptionService->Init(getter_AddRefs(initPromise));
NS_ENSURE_SUCCESS(rv,
GenericNonExclusivePromise::CreateAndReject(rv, __func__));
// Bail out earlier if we don't have a init promise.
if (!initPromise) {
return GenericNonExclusivePromise::CreateAndReject(rv, __func__);
}
mInitPromise = new GenericNonExclusivePromise::Private(__func__);
initPromise->AddCallbacksWithCycleCollectedArgs(
[&self = *this](JSContext*, JS::Handle<JS::Value>,
mozilla::ErrorResult&) {
self.mInitPromise->Resolve(true, __func__);
},
[&self = *this](JSContext*, JS::Handle<JS::Value>,
mozilla::ErrorResult& error) {
nsresult rv = error.StealNSResult();
self.mInitPromise->Reject(rv, __func__);
return;
});
return mInitPromise;
}
void ThirdPartyCookieBlockingExceptions::Shutdown() {
if (m3PCBExceptionService) {
Unused << m3PCBExceptionService->Shutdown();
m3PCBExceptionService = nullptr;
}
// Reject the init promise during the shutdown.
if (mInitPromise) {
mInitPromise->Reject(NS_ERROR_ABORT, __func__);
mInitPromise = nullptr;
}
}
void ThirdPartyCookieBlockingExceptions::Insert(const nsACString& aException) {
m3PCBExceptionsSet.Insert(aException);
}
void ThirdPartyCookieBlockingExceptions::Remove(const nsACString& aException) {
m3PCBExceptionsSet.Remove(aException);
}
bool ThirdPartyCookieBlockingExceptions::CheckWildcardException(
const nsACString& aThirdPartySite) {
nsAutoCString key;
Create3PCBExceptionKey("*"_ns, aThirdPartySite, key);
return m3PCBExceptionsSet.Contains(key);
}
bool ThirdPartyCookieBlockingExceptions::CheckException(
const nsACString& aFirstPartySite, const nsACString& aThirdPartySite) {
nsAutoCString key;
Create3PCBExceptionKey(aFirstPartySite, aThirdPartySite, key);
return m3PCBExceptionsSet.Contains(key);
}
bool ThirdPartyCookieBlockingExceptions::CheckExceptionForURIs(
nsIURI* aFirstPartyURI, nsIURI* aThirdPartyURI) {
MOZ_ASSERT(XRE_IsParentProcess());
NS_ENSURE_TRUE(aFirstPartyURI, false);
NS_ENSURE_TRUE(aThirdPartyURI, false);
RefPtr<nsEffectiveTLDService> eTLDService =
nsEffectiveTLDService::GetInstance();
NS_ENSURE_TRUE(eTLDService, false);
nsAutoCString thirdPartySite;
nsresult rv = eTLDService->GetSite(aThirdPartyURI, thirdPartySite);
NS_ENSURE_SUCCESS(rv, false);
bool isInExceptionList = CheckWildcardException(thirdPartySite);
if (isInExceptionList) {
return true;
}
nsAutoCString firstPartySite;
rv = eTLDService->GetSite(aFirstPartyURI, firstPartySite);
NS_ENSURE_SUCCESS(rv, false);
return CheckException(firstPartySite, thirdPartySite);
}
bool ThirdPartyCookieBlockingExceptions::CheckExceptionForChannel(
nsIChannel* aChannel) {
MOZ_ASSERT(XRE_IsParentProcess());
NS_ENSURE_TRUE(aChannel, false);
RefPtr<nsEffectiveTLDService> eTLDService =
nsEffectiveTLDService::GetInstance();
NS_ENSURE_TRUE(eTLDService, false);
nsCOMPtr<nsIURI> uri;
nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, false);
nsAutoCString thirdPartySite;
rv = eTLDService->GetSite(uri, thirdPartySite);
NS_ENSURE_SUCCESS(rv, false);
bool isInExceptionList = CheckWildcardException(thirdPartySite);
if (isInExceptionList) {
return true;
}
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
RefPtr<dom::BrowsingContext> bc;
loadInfo->GetBrowsingContext(getter_AddRefs(bc));
if (!bc) {
bc = loadInfo->GetWorkerAssociatedBrowsingContext();
}
nsAutoCString firstPartySite;
// If the channel is not associated with a browsing context, we will try to
// get the first party site from the partition key.
if (!bc) {
nsCOMPtr<nsICookieJarSettings> cjs;
nsresult rv = loadInfo->GetCookieJarSettings(getter_AddRefs(cjs));
NS_ENSURE_SUCCESS(rv, false);
nsAutoString partitionKey;
rv = cjs->GetPartitionKey(partitionKey);
NS_ENSURE_SUCCESS(rv, false);
nsAutoString site;
if (!OriginAttributes::ExtractSiteFromPartitionKey(partitionKey, site)) {
return false;
}
firstPartySite.Assign(NS_ConvertUTF16toUTF8(site));
} else {
RefPtr<dom::WindowGlobalParent> topWGP =
bc->Top()->Canonical()->GetCurrentWindowGlobal();
if (!topWGP) {
return false;
}
nsCOMPtr<nsIPrincipal> topPrincipal = topWGP->DocumentPrincipal();
// If the top window is an about page, we don't need to do anything. This
// could happen when fetching system resources, such as pocket's images
if (topPrincipal->SchemeIs("about")) {
return false;
}
nsCOMPtr<nsIURI> topURI = topPrincipal->GetURI();
nsAutoCString site;
nsresult rv = eTLDService->GetSite(topURI, firstPartySite);
NS_ENSURE_SUCCESS(rv, false);
}
return CheckException(firstPartySite, thirdPartySite);
}
void ThirdPartyCookieBlockingExceptions::GetExceptions(
nsTArray<nsCString>& aExceptions) {
aExceptions.Clear();
for (const auto& host : m3PCBExceptionsSet) {
aExceptions.AppendElement(host);
}
}
} // namespace net
} // namespace mozilla

View File

@@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_ThirdPartyCookieBlockingExceptions_h
#define mozilla_net_ThirdPartyCookieBlockingExceptions_h
#include "mozilla/MozPromise.h"
#include "nsEffectiveTLDService.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTHashSet.h"
#include "nsIThirdPartyCookieBlockingExceptionListService.h"
class nsIEffectiveTLDService;
class nsIURI;
class nsIChannel;
namespace mozilla {
namespace net {
class ThirdPartyCookieBlockingExceptions final {
public:
// Lazily initializes the foreign cookie blocking exception list. The function
// returns the promise that resolves when the list is initialized.
RefPtr<GenericNonExclusivePromise> EnsureInitialized();
// Check if the given top-level and third-party URIs are in the exception
// list.
bool CheckExceptionForURIs(nsIURI* aFirstPartyURI, nsIURI* aThirdPartyURI);
// Check if the given channel is in the exception list.
bool CheckExceptionForChannel(nsIChannel* aChannel);
// Check if the given third-party site is in the wildcard exception list.
// The wildcard exception list is used to allow third-party cookies under
// every top-level site.
bool CheckWildcardException(const nsACString& aThirdPartySite);
// Check if the given first-party and third-party sites are in the exception
// list.
bool CheckException(const nsACString& aFirstPartySite,
const nsACString& aThirdPartySite);
void Insert(const nsACString& aException);
void Remove(const nsACString& aException);
void GetExceptions(nsTArray<nsCString>& aExceptions);
void Shutdown();
private:
nsCOMPtr<nsIThirdPartyCookieBlockingExceptionListService>
m3PCBExceptionService;
// A helper function to create a key for the exception list.
static void Create3PCBExceptionKey(const nsACString& aFirstPartySite,
const nsACString& aThirdPartySite,
nsACString& aKey) {
aKey.Truncate();
aKey.Append(aFirstPartySite);
aKey.AppendLiteral(",");
aKey.Append(aThirdPartySite);
}
// The promise that resolves when the 3PCB exception service is initialized.
RefPtr<GenericNonExclusivePromise::Private> mInitPromise;
nsTHashSet<nsCStringHashKey> m3PCBExceptionsSet;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_net_ThirdPartyCookieBlockingExceptions_h

View File

@@ -34,6 +34,7 @@ EXPORTS.mozilla.net = [
"CookieServiceChild.h",
"CookieServiceParent.h",
"CookieStorage.h",
"ThirdPartyCookieBlockingExceptions.h",
]
UNIFIED_SOURCES += [
"Cookie.cpp",
@@ -48,6 +49,7 @@ UNIFIED_SOURCES += [
"CookieServiceChild.cpp",
"CookieServiceParent.cpp",
"CookieStorage.cpp",
"ThirdPartyCookieBlockingExceptions.cpp",
]
XPCSHELL_TESTS_MANIFESTS += [
"test/unit/xpcshell.toml",

View File

@@ -5,6 +5,7 @@
#include "nsISupports.idl"
#include "nsICookie.idl"
#include "nsIThirdPartyCookieBlockingExceptionListService.idl"
%{ C++
namespace mozilla {
@@ -295,4 +296,19 @@ interface nsICookieManager : nsISupports
* Retrieves all the cookies that were created on or after aSinceWhen, order
* by creation time */
Array<nsICookie> getCookiesSince(in int64_t aSinceWhen);
/**
* Adds a list of exceptions to the third party cookie blocking exception
* list.
*/
void addThirdPartyCookieBlockingExceptions(
in Array<nsIThirdPartyCookieExceptionEntry> aExcpetions);
/**
* Removes a list of exceptions from the third party cookie blocking
* exception list.
*/
void removeThirdPartyCookieBlockingExceptions(
in Array<nsIThirdPartyCookieExceptionEntry> aExceptions);
};

View File

@@ -72,6 +72,7 @@
#include "nsContentUtils.h"
#include "nsContentSecurityManager.h"
#include "nsIClassOfService.h"
#include "CookieService.h"
#include "nsIPrincipal.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
@@ -7176,6 +7177,10 @@ nsresult nsHttpChannel::BeginConnect() {
MaybeStartDNSPrefetch();
// Update whether the channel is on the third-party cookie blocking exception
// list.
CookieService::Update3PCBExceptionInfo(this);
rv = CallOrWaitForResume(
[](nsHttpChannel* self) { return self->PrepareToConnect(); });
if (NS_FAILED(rv)) {