mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-23 02:05:42 +00:00
98960a9c41
Differential Revision: https://phabricator.services.mozilla.com/D32556 --HG-- extra : moz-landing-system : lando
370 lines
13 KiB
C++
370 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 "mozilla/dom/Document.h"
|
|
#include "mozilla/net/CookieSettings.h"
|
|
#include "mozilla/AntiTrackingCommon.h"
|
|
#include "mozilla/StorageAccess.h"
|
|
#include "nsICookieService.h"
|
|
#include "nsICookieSettings.h"
|
|
#include "nsIWebProgressListener.h"
|
|
|
|
/**
|
|
* Gets the cookie lifetime policy for a given cookieSettings and a given
|
|
* principal by checking the permission value.
|
|
*
|
|
* Used in the implementation of InternalStorageAllowedCheck.
|
|
*/
|
|
static void GetCookieLifetimePolicyFromCookieSettings(
|
|
nsICookieSettings* aCookieSettings, nsIPrincipal* aPrincipal,
|
|
uint32_t* aLifetimePolicy) {
|
|
*aLifetimePolicy = StaticPrefs::network_cookie_lifetimePolicy();
|
|
|
|
if (aCookieSettings) {
|
|
uint32_t cookiePermission = 0;
|
|
nsresult rv =
|
|
aCookieSettings->CookiePermission(aPrincipal, &cookiePermission);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
switch (cookiePermission) {
|
|
case nsICookiePermission::ACCESS_ALLOW:
|
|
*aLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
|
|
break;
|
|
case nsICookiePermission::ACCESS_DENY:
|
|
*aLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
|
|
break;
|
|
case nsICookiePermission::ACCESS_SESSION:
|
|
*aLifetimePolicy = nsICookieService::ACCEPT_SESSION;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Checks if storage for a given principal is permitted by the user's
|
|
* preferences. If aWindow is non-null, its principal must be passed as
|
|
* aPrincipal, and the third-party iframe and sandboxing status of the window
|
|
* are also checked. If aURI is non-null, then it is used as the comparison
|
|
* against aWindow to determine if this is a third-party load. We also
|
|
* allow a channel instead of the window reference when determining 3rd party
|
|
* status.
|
|
*
|
|
* Used in the implementation of StorageAllowedForWindow,
|
|
* StorageAllowedForChannel and StorageAllowedForServiceWorker.
|
|
*/
|
|
static StorageAccess InternalStorageAllowedCheck(
|
|
nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow, nsIURI* aURI,
|
|
nsIChannel* aChannel, nsICookieSettings* aCookieSettings,
|
|
uint32_t& aRejectedReason) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
aRejectedReason = 0;
|
|
|
|
StorageAccess access = StorageAccess::eAllow;
|
|
|
|
// We don't allow storage on the null principal, in general. Even if the
|
|
// calling context is chrome.
|
|
if (aPrincipal->GetIsNullPrincipal()) {
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
if (aWindow) {
|
|
// If the document is sandboxed, then it is not permitted to use storage
|
|
Document* document = aWindow->GetExtantDoc();
|
|
if (document && document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
// Check if we are in private browsing, and record that fact
|
|
if (nsContentUtils::IsInPrivateBrowsing(document)) {
|
|
access = StorageAccess::ePrivateBrowsing;
|
|
}
|
|
}
|
|
|
|
uint32_t lifetimePolicy;
|
|
|
|
// WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
|
|
// and ACCEPT_NORMALLY as lifetimePolicy (See Bug 1406675 for rationale).
|
|
auto policy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
|
|
|
|
if (policy) {
|
|
lifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
|
|
} else {
|
|
GetCookieLifetimePolicyFromCookieSettings(aCookieSettings, aPrincipal,
|
|
&lifetimePolicy);
|
|
}
|
|
|
|
// Check if we should only allow storage for the session, and record that fact
|
|
if (lifetimePolicy == nsICookieService::ACCEPT_SESSION) {
|
|
// Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow
|
|
// so perform a std::min comparison to make sure we preserve
|
|
// ePrivateBrowsing if it has been set.
|
|
access = std::min(StorageAccess::eSessionScoped, access);
|
|
}
|
|
|
|
// About URIs are allowed to access storage, even if they don't have chrome
|
|
// privileges. If this is not desired, than the consumer will have to
|
|
// implement their own restriction functionality.
|
|
//
|
|
// This is due to backwards-compatibility and the state of storage access
|
|
// before the introducton of InternalStorageAllowedCheck:
|
|
//
|
|
// BEFORE:
|
|
// localStorage, caches: allowed in 3rd-party iframes always
|
|
// IndexedDB: allowed in 3rd-party iframes only if 3rd party URI is an about:
|
|
// URI within a specific whitelist
|
|
//
|
|
// AFTER:
|
|
// localStorage, caches: allowed in 3rd-party iframes by default. Preference
|
|
// can be set to disable in 3rd-party, which will not disallow in about:
|
|
// URIs.
|
|
// IndexedDB: allowed in 3rd-party iframes by default. Preference can be set
|
|
// to disable in 3rd-party, which will disallow in about: URIs, unless they
|
|
// are within a specific whitelist.
|
|
//
|
|
// This means that behavior for storage with internal about: URIs should not
|
|
// be affected, which is desireable due to the lack of automated testing for
|
|
// about: URIs with these preferences set, and the importance of the correct
|
|
// functioning of these URIs even with custom preferences.
|
|
nsCOMPtr<nsIURI> uri = aURI;
|
|
if (!uri) {
|
|
Unused << aPrincipal->GetURI(getter_AddRefs(uri));
|
|
}
|
|
if (uri) {
|
|
bool isAbout = false;
|
|
MOZ_ALWAYS_SUCCEEDS(uri->SchemeIs("about", &isAbout));
|
|
if (isAbout) {
|
|
return access;
|
|
}
|
|
}
|
|
|
|
if (!StorageDisabledByAntiTracking(aWindow, aChannel, aPrincipal, aURI,
|
|
aRejectedReason)) {
|
|
return access;
|
|
}
|
|
|
|
// We want to have a partitioned storage only for trackers.
|
|
if (aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
|
|
return StorageAccess::ePartitionTrackersOrDeny;
|
|
}
|
|
|
|
// We want to have a partitioned storage for all third parties.
|
|
if (aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) {
|
|
return StorageAccess::ePartitionForeignOrDeny;
|
|
}
|
|
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
static bool StorageDisabledByAntiTrackingInternal(
|
|
nsPIDOMWindowInner* aWindow, nsIChannel* aChannel, nsIPrincipal* aPrincipal,
|
|
nsIURI* aURI, nsICookieSettings* aCookieSettings,
|
|
uint32_t& aRejectedReason) {
|
|
MOZ_ASSERT(aWindow || aChannel || aPrincipal);
|
|
|
|
if (aWindow) {
|
|
nsIURI* documentURI = aURI ? aURI : aWindow->GetDocumentURI();
|
|
return !documentURI ||
|
|
!AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
|
|
aWindow, documentURI, &aRejectedReason);
|
|
}
|
|
|
|
if (aChannel) {
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
|
|
if (!httpChannel) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = httpChannel->GetURI(getter_AddRefs(uri));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
return !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
|
|
httpChannel, uri, &aRejectedReason);
|
|
}
|
|
|
|
MOZ_ASSERT(aPrincipal);
|
|
return !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
|
|
aPrincipal, aCookieSettings);
|
|
}
|
|
|
|
namespace mozilla {
|
|
|
|
StorageAccess StorageAllowedForWindow(nsPIDOMWindowInner* aWindow,
|
|
uint32_t* aRejectedReason) {
|
|
uint32_t rejectedReason;
|
|
if (!aRejectedReason) {
|
|
aRejectedReason = &rejectedReason;
|
|
}
|
|
|
|
*aRejectedReason = 0;
|
|
|
|
if (Document* document = aWindow->GetExtantDoc()) {
|
|
nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal();
|
|
// Note that GetChannel() below may return null, but that's OK, since the
|
|
// callee is able to deal with a null channel argument, and if passed null,
|
|
// will only fail to notify the UI in case storage gets blocked.
|
|
nsIChannel* channel = document->GetChannel();
|
|
return InternalStorageAllowedCheck(principal, aWindow, nullptr, channel,
|
|
document->CookieSettings(),
|
|
*aRejectedReason);
|
|
}
|
|
|
|
// No document? Let's return a generic rejected reason.
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
StorageAccess StorageAllowedForDocument(const Document* aDoc) {
|
|
MOZ_ASSERT(aDoc);
|
|
|
|
if (nsPIDOMWindowInner* inner = aDoc->GetInnerWindow()) {
|
|
nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
|
|
// Note that GetChannel() below may return null, but that's OK, since the
|
|
// callee is able to deal with a null channel argument, and if passed null,
|
|
// will only fail to notify the UI in case storage gets blocked.
|
|
nsIChannel* channel = aDoc->GetChannel();
|
|
|
|
uint32_t rejectedReason = 0;
|
|
return InternalStorageAllowedCheck(
|
|
principal, inner, nullptr, channel,
|
|
const_cast<Document*>(aDoc)->CookieSettings(), rejectedReason);
|
|
}
|
|
|
|
return StorageAccess::eDeny;
|
|
}
|
|
|
|
StorageAccess StorageAllowedForNewWindow(nsIPrincipal* aPrincipal, nsIURI* aURI,
|
|
nsPIDOMWindowInner* aParent) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
MOZ_ASSERT(aURI);
|
|
// parent may be nullptr
|
|
|
|
uint32_t rejectedReason = 0;
|
|
nsCOMPtr<nsICookieSettings> cs;
|
|
if (aParent && aParent->GetExtantDoc()) {
|
|
cs = aParent->GetExtantDoc()->CookieSettings();
|
|
} else {
|
|
cs = net::CookieSettings::Create();
|
|
}
|
|
return InternalStorageAllowedCheck(aPrincipal, aParent, aURI, nullptr, cs,
|
|
rejectedReason);
|
|
}
|
|
|
|
StorageAccess StorageAllowedForChannel(nsIChannel* aChannel) {
|
|
MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::GetSecurityManager());
|
|
MOZ_DIAGNOSTIC_ASSERT(aChannel);
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
Unused << nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
|
|
aChannel, getter_AddRefs(principal));
|
|
NS_ENSURE_TRUE(principal, StorageAccess::eDeny);
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
|
|
nsCOMPtr<nsICookieSettings> cookieSettings;
|
|
nsresult rv = loadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
|
|
NS_ENSURE_SUCCESS(rv, StorageAccess::eDeny);
|
|
|
|
uint32_t rejectedReason = 0;
|
|
StorageAccess result = InternalStorageAllowedCheck(
|
|
principal, nullptr, nullptr, aChannel, cookieSettings, rejectedReason);
|
|
|
|
return result;
|
|
}
|
|
|
|
StorageAccess StorageAllowedForServiceWorker(
|
|
nsIPrincipal* aPrincipal, nsICookieSettings* aCookieSettings) {
|
|
uint32_t rejectedReason = 0;
|
|
return InternalStorageAllowedCheck(aPrincipal, nullptr, nullptr, nullptr,
|
|
aCookieSettings, rejectedReason);
|
|
}
|
|
|
|
bool StorageDisabledByAntiTracking(nsPIDOMWindowInner* aWindow,
|
|
nsIChannel* aChannel,
|
|
nsIPrincipal* aPrincipal, nsIURI* aURI,
|
|
uint32_t& aRejectedReason) {
|
|
nsCOMPtr<nsICookieSettings> cookieSettings;
|
|
if (aWindow) {
|
|
if (aWindow->GetExtantDoc()) {
|
|
cookieSettings = aWindow->GetExtantDoc()->CookieSettings();
|
|
}
|
|
} else if (aChannel) {
|
|
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
|
|
Unused << loadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
|
|
}
|
|
if (!cookieSettings) {
|
|
cookieSettings = net::CookieSettings::Create();
|
|
}
|
|
bool disabled = StorageDisabledByAntiTrackingInternal(
|
|
aWindow, aChannel, aPrincipal, aURI, cookieSettings, aRejectedReason);
|
|
if (StaticPrefs::
|
|
browser_contentblocking_rejecttrackers_control_center_ui_enabled()) {
|
|
if (aWindow) {
|
|
AntiTrackingCommon::NotifyBlockingDecision(
|
|
aWindow,
|
|
disabled ? AntiTrackingCommon::BlockingDecision::eBlock
|
|
: AntiTrackingCommon::BlockingDecision::eAllow,
|
|
aRejectedReason);
|
|
} else if (aChannel) {
|
|
AntiTrackingCommon::NotifyBlockingDecision(
|
|
aChannel,
|
|
disabled ? AntiTrackingCommon::BlockingDecision::eBlock
|
|
: AntiTrackingCommon::BlockingDecision::eAllow,
|
|
aRejectedReason);
|
|
}
|
|
}
|
|
return disabled;
|
|
}
|
|
|
|
bool ShouldPartitionStorage(StorageAccess aAccess) {
|
|
return aAccess == StorageAccess::ePartitionTrackersOrDeny ||
|
|
aAccess == StorageAccess::ePartitionForeignOrDeny;
|
|
}
|
|
|
|
bool ShouldPartitionStorage(uint32_t aRejectedReason) {
|
|
return aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN;
|
|
}
|
|
|
|
bool StoragePartitioningEnabled(StorageAccess aAccess,
|
|
nsICookieSettings* aCookieSettings) {
|
|
if (aAccess == StorageAccess::ePartitionTrackersOrDeny) {
|
|
return aCookieSettings->GetCookieBehavior() ==
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER &&
|
|
StaticPrefs::privacy_storagePrincipal_enabledForTrackers();
|
|
}
|
|
if (aAccess == StorageAccess::ePartitionForeignOrDeny) {
|
|
return aCookieSettings->GetCookieBehavior() ==
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool StoragePartitioningEnabled(uint32_t aRejectedReason,
|
|
nsICookieSettings* aCookieSettings) {
|
|
if (aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
|
|
return aCookieSettings->GetCookieBehavior() ==
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER &&
|
|
StaticPrefs::privacy_storagePrincipal_enabledForTrackers();
|
|
}
|
|
if (aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) {
|
|
return aCookieSettings->GetCookieBehavior() ==
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace mozilla
|