From 1e74cd96fe7589e2c9a64f97915c4a0e86d7792a Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Fri, 20 Sep 2024 14:02:28 +0000 Subject: [PATCH] Bug 1914417 - Refactor notification permission checker using principal partition key r=asuth,bvandersloot This patch keeps the current status where Firefox and Chrome both allows notification permission in ABA context although the spec does not cover it. (https://github.com/whatwg/notifications/issues/177, also listed in https://privacycg.github.io/storage-partitioning/#remaining-user-agent-state) The child patch in bug 1914203 will add a telemetry to see whether the usage is low enough so that we can block ABA access, to make it more consistent with storage partitioning. Differential Revision: https://phabricator.services.mozilla.com/D219908 --- .../test/alerts/browser_notification_close.js | 3 +- .../browser_notification_do_not_disturb.js | 3 +- .../browser_notification_open_settings.js | 3 +- .../browser_notification_remove_permission.js | 3 +- .../browser_notification_tab_switching.js | 3 +- dom/notification/Notification.cpp | 200 +++++++----------- dom/notification/Notification.h | 18 +- dom/notification/NotificationUtils.cpp | 113 ++++++++++ dom/notification/NotificationUtils.h | 61 ++++++ dom/notification/moz.build | 1 + .../test/mochitest/mochitest.toml | 8 +- .../notification_openWindow_worker.js | 2 +- .../test/mochitest/openWindow_worker.js | 10 +- .../test/mochitest/open_window/client.sjs | 4 +- .../test/mochitest/test_notification_tag.html | 6 +- ...origin-nested.tentative.https.sub.html.ini | 1 - .../permissions-non-secure.html.ini | 8 - 17 files changed, 275 insertions(+), 172 deletions(-) create mode 100644 dom/notification/NotificationUtils.cpp create mode 100644 dom/notification/NotificationUtils.h delete mode 100644 testing/web-platform/meta/notifications/permissions-non-secure.html.ini diff --git a/browser/base/content/test/alerts/browser_notification_close.js b/browser/base/content/test/alerts/browser_notification_close.js index 3fd50bed5b125..0b483bd0c40b1 100644 --- a/browser/base/content/test/alerts/browser_notification_close.js +++ b/browser/base/content/test/alerts/browser_notification_close.js @@ -9,8 +9,7 @@ const { PermissionTestUtils } = ChromeUtils.importESModule( ); let notificationURL = - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; + "https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; let oldShowFavicons; add_task(async function test_notificationClose() { diff --git a/browser/base/content/test/alerts/browser_notification_do_not_disturb.js b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js index 8fb5a8a52b72a..852e0d1e24091 100644 --- a/browser/base/content/test/alerts/browser_notification_do_not_disturb.js +++ b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js @@ -14,8 +14,7 @@ const ALERT_SERVICE = Cc["@mozilla.org/alerts-service;1"] .QueryInterface(Ci.nsIAlertsDoNotDisturb); const PAGE = - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; + "https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; // The amount of time in seconds that we will wait for a notification // to show up before we decide that it's not coming. diff --git a/browser/base/content/test/alerts/browser_notification_open_settings.js b/browser/base/content/test/alerts/browser_notification_open_settings.js index e7f1c28251841..1ef68d2ae228d 100644 --- a/browser/base/content/test/alerts/browser_notification_open_settings.js +++ b/browser/base/content/test/alerts/browser_notification_open_settings.js @@ -1,8 +1,7 @@ "use strict"; var notificationURL = - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; + "https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; var expectedURL = "about:preferences#privacy"; add_task(async function test_settingsOpen_observer() { diff --git a/browser/base/content/test/alerts/browser_notification_remove_permission.js b/browser/base/content/test/alerts/browser_notification_remove_permission.js index ba198870a39ca..ec6916e1bc1a9 100644 --- a/browser/base/content/test/alerts/browser_notification_remove_permission.js +++ b/browser/base/content/test/alerts/browser_notification_remove_permission.js @@ -6,8 +6,7 @@ const { PermissionTestUtils } = ChromeUtils.importESModule( var tab; var notificationURL = - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; + "https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; var alertWindowClosed = false; var permRemoved = false; diff --git a/browser/base/content/test/alerts/browser_notification_tab_switching.js b/browser/base/content/test/alerts/browser_notification_tab_switching.js index ee675670cb2f7..4e5a23f181081 100644 --- a/browser/base/content/test/alerts/browser_notification_tab_switching.js +++ b/browser/base/content/test/alerts/browser_notification_tab_switching.js @@ -11,8 +11,7 @@ const { PermissionTestUtils } = ChromeUtils.importESModule( var tab; var notification; var notificationURL = - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; + "https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html"; var newWindowOpenedFromTab; add_task(async function test_notificationPreventDefaultAndSwitchTabs() { diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index e76644e627060..573a3d03e9f64 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -15,7 +15,6 @@ #include "mozilla/HoldDropJSObjects.h" #include "mozilla/JSONStringWriteFuncs.h" #include "mozilla/OwningNonNull.h" -#include "mozilla/Preferences.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/Unused.h" #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" @@ -32,6 +31,7 @@ #include "mozilla/dom/WorkerScope.h" #include "mozilla/dom/quota/ResultExtensions.h" #include "Navigator.h" +#include "NotificationUtils.h" #include "nsComponentManagerUtils.h" #include "nsContentPermissionHelper.h" #include "nsContentUtils.h" @@ -55,6 +55,8 @@ namespace mozilla::dom { +using namespace notification; + struct NotificationStrings { const nsString mID; const nsString mTitle; @@ -211,10 +213,12 @@ class NotificationPermissionRequest : public ContentPermissionRequestBase, NS_IMETHOD Allow(JS::Handle choices) override; NotificationPermissionRequest(nsIPrincipal* aPrincipal, + nsIPrincipal* aEffectiveStoragePrincipal, nsPIDOMWindowInner* aWindow, Promise* aPromise, NotificationPermissionCallback* aCallback) : ContentPermissionRequestBase(aPrincipal, aWindow, "notification"_ns, "desktop-notification"_ns), + mEffectiveStoragePrincipal(aEffectiveStoragePrincipal), mPermission(NotificationPermission::Default), mPromise(aPromise), mCallback(aCallback) { @@ -231,6 +235,7 @@ class NotificationPermissionRequest : public ContentPermissionRequestBase, MOZ_CAN_RUN_SCRIPT nsresult ResolvePromise(); nsresult DispatchResolvePromise(); + nsCOMPtr mEffectiveStoragePrincipal; NotificationPermission mPermission; RefPtr mPromise; RefPtr mCallback; @@ -253,22 +258,33 @@ class ReleaseNotificationControlRunnable final }; class GetPermissionRunnable final : public WorkerMainThreadRunnable { - NotificationPermission mPermission; - public: - explicit GetPermissionRunnable(WorkerPrivate* aWorker) + explicit GetPermissionRunnable(WorkerPrivate* aWorker, + bool aUseRegularPrincipal, + PermissionCheckPurpose aPurpose) : WorkerMainThreadRunnable(aWorker, "Notification :: Get Permission"_ns), - mPermission(NotificationPermission::Denied) {} + mUseRegularPrincipal(aUseRegularPrincipal), + mPurpose(aPurpose) {} bool MainThreadRun() override { - ErrorResult result; MOZ_ASSERT(mWorkerRef); - mPermission = Notification::GetPermissionInternal( - mWorkerRef->Private()->GetPrincipal(), result); + WorkerPrivate* workerPrivate = mWorkerRef->Private(); + nsIPrincipal* principal = workerPrivate->GetPrincipal(); + nsIPrincipal* effectiveStoragePrincipal = + mUseRegularPrincipal ? principal + : workerPrivate->GetPartitionedPrincipal(); + mPermission = + GetNotificationPermission(principal, effectiveStoragePrincipal, + workerPrivate->IsSecureContext(), mPurpose); return true; } NotificationPermission GetPermission() { return mPermission; } + + private: + NotificationPermission mPermission = NotificationPermission::Denied; + bool mUseRegularPrincipal; + PermissionCheckPurpose mPurpose; }; class FocusWindowRunnable final : public Runnable { @@ -487,30 +503,14 @@ NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED( NS_IMETHODIMP NotificationPermissionRequest::Run() { - bool isSystem = mPrincipal->IsSystemPrincipal(); - bool blocked = false; - if (isSystem) { + if (IsNotificationAllowedFor(mPrincipal)) { mPermission = NotificationPermission::Granted; - } else if (mPrincipal->GetIsInPrivateBrowsing() && - !StaticPrefs::dom_webnotifications_privateBrowsing_enabled()) { + } else if (IsNotificationForbiddenFor( + mPrincipal, mEffectiveStoragePrincipal, + mWindow->IsSecureContext(), + PermissionCheckPurpose::PermissionRequest, + mWindow->GetExtantDoc())) { mPermission = NotificationPermission::Denied; - blocked = true; - } else { - // File are automatically granted permission. - - if (mPrincipal->SchemeIs("file")) { - mPermission = NotificationPermission::Granted; - } else if (!mWindow->IsSecureContext()) { - mPermission = NotificationPermission::Denied; - blocked = true; - nsCOMPtr doc = mWindow->GetExtantDoc(); - if (doc) { - nsContentUtils::ReportToConsole( - nsIScriptError::errorFlag, "DOM"_ns, doc, - nsContentUtils::eDOM_PROPERTIES, - "NotificationsInsecureRequestIsForbidden"); - } - } } // We can't call ShowPrompt() directly here since our logic for determining @@ -538,22 +538,6 @@ NotificationPermissionRequest::Run() { } } - // Check this after checking the prompt prefs to make sure this pref overrides - // those. We rely on this for testing purposes. - if (!isSystem && !blocked && - !StaticPrefs::dom_webnotifications_allowcrossoriginiframe() && - !mPrincipal->Subsumes(mTopLevelPrincipal)) { - mPermission = NotificationPermission::Denied; - blocked = true; - nsCOMPtr doc = mWindow->GetExtantDoc(); - if (doc) { - nsContentUtils::ReportToConsole( - nsIScriptError::errorFlag, "DOM"_ns, doc, - nsContentUtils::eDOM_PROPERTIES, - "NotificationsCrossOriginIframeRequestIsForbidden"); - } - } - if (mPermission != NotificationPermission::Default) { return DispatchResolvePromise(); } @@ -605,7 +589,7 @@ nsresult NotificationPermissionRequest::ResolvePromise() { } } - mPermission = Notification::TestPermission(mPrincipal); + mPermission = GetRawNotificationPermission(mPrincipal); } if (mCallback) { ErrorResult error; @@ -717,6 +701,7 @@ Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID, if (!NS_IsMainThread()) { mWorkerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(mWorkerPrivate); + mWorkerUseRegularPrincipal = mWorkerPrivate->UseRegularPrincipal(); } } @@ -1353,9 +1338,16 @@ void Notification::ShowInternal() { ErrorResult result; NotificationPermission permission = NotificationPermission::Denied; if (mWorkerPrivate) { - permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result); + nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); + nsIPrincipal* effectiveStoragePrincipal = + mWorkerUseRegularPrincipal ? principal + : mWorkerPrivate->GetPartitionedPrincipal(); + permission = GetNotificationPermission( + principal, effectiveStoragePrincipal, mWorkerPrivate->IsSecureContext(), + PermissionCheckPurpose::NotificationShow); } else { - permission = GetPermissionInternal(GetOwnerWindow(), result); + permission = GetPermissionInternal( + GetOwnerWindow(), PermissionCheckPurpose::NotificationShow, result); } // We rely on GetPermissionInternal returning Denied on all failure codepaths. MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied); @@ -1495,7 +1487,9 @@ already_AddRefed Notification::RequestPermission( } nsCOMPtr principal = sop->GetPrincipal(); - if (!principal) { + nsCOMPtr effectiveStoragePrincipal = + sop->GetEffectiveStoragePrincipal(); + if (!principal || !effectiveStoragePrincipal) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } @@ -1508,8 +1502,9 @@ already_AddRefed Notification::RequestPermission( if (aCallback.WasPassed()) { permissionCallback = &aCallback.Value(); } - nsCOMPtr request = new NotificationPermissionRequest( - principal, window, promise, permissionCallback); + nsCOMPtr request = + new NotificationPermissionRequest(principal, effectiveStoragePrincipal, + window, promise, permissionCallback); window->AsGlobal()->Dispatch(request.forget()); @@ -1520,30 +1515,34 @@ already_AddRefed Notification::RequestPermission( NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); - return GetPermission(global, aRv); + return GetPermission(global, PermissionCheckPurpose::PermissionAttribute, + aRv); } // static -NotificationPermission Notification::GetPermission(nsIGlobalObject* aGlobal, - ErrorResult& aRv) { +NotificationPermission Notification::GetPermission( + nsIGlobalObject* aGlobal, PermissionCheckPurpose aPurpose, + ErrorResult& aRv) { if (NS_IsMainThread()) { - return GetPermissionInternal(aGlobal->GetAsInnerWindow(), aRv); - } else { - WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); - MOZ_ASSERT(worker); - RefPtr r = new GetPermissionRunnable(worker); - r->Dispatch(worker, Canceling, aRv); - if (aRv.Failed()) { - return NotificationPermission::Denied; - } + return GetPermissionInternal(aGlobal->GetAsInnerWindow(), aPurpose, aRv); + } - return r->GetPermission(); + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + RefPtr r = new GetPermissionRunnable( + worker, worker->UseRegularPrincipal(), aPurpose); + r->Dispatch(worker, Canceling, aRv); + if (aRv.Failed()) { + return NotificationPermission::Denied; } + + return r->GetPermission(); } /* static */ NotificationPermission Notification::GetPermissionInternal( - nsPIDOMWindowInner* aWindow, ErrorResult& aRv) { + nsPIDOMWindowInner* aWindow, PermissionCheckPurpose aPurpose, + ErrorResult& aRv) { // Get principal from global to check permission for notifications. nsCOMPtr sop = do_QueryInterface(aWindow); if (!sop) { @@ -1552,71 +1551,15 @@ NotificationPermission Notification::GetPermissionInternal( } nsCOMPtr principal = sop->GetPrincipal(); - if (!principal) { + nsCOMPtr effectiveStoragePrincipal = + sop->GetEffectiveStoragePrincipal(); + if (!principal || !effectiveStoragePrincipal) { aRv.Throw(NS_ERROR_UNEXPECTED); return NotificationPermission::Denied; } - if (principal->GetIsInPrivateBrowsing() && - !StaticPrefs::dom_webnotifications_privateBrowsing_enabled()) { - return NotificationPermission::Denied; - } - // Disallow showing notification if our origin is not the same origin as the - // toplevel one, see https://github.com/whatwg/notifications/issues/177. - if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe()) { - nsCOMPtr topSop = - do_QueryInterface(aWindow->GetBrowsingContext()->Top()->GetDOMWindow()); - nsIPrincipal* topPrincipal = topSop ? topSop->GetPrincipal() : nullptr; - if (!topPrincipal || !principal->Subsumes(topPrincipal)) { - return NotificationPermission::Denied; - } - } - - return GetPermissionInternal(principal, aRv); -} - -/* static */ -NotificationPermission Notification::GetPermissionInternal( - nsIPrincipal* aPrincipal, ErrorResult& aRv) { - AssertIsOnMainThread(); - MOZ_ASSERT(aPrincipal); - - if (aPrincipal->IsSystemPrincipal()) { - return NotificationPermission::Granted; - } else { - // Allow files to show notifications by default. - if (aPrincipal->SchemeIs("file")) { - return NotificationPermission::Granted; - } - } - - return TestPermission(aPrincipal); -} - -/* static */ -NotificationPermission Notification::TestPermission(nsIPrincipal* aPrincipal) { - AssertIsOnMainThread(); - - uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; - - nsCOMPtr permissionManager = - components::PermissionManager::Service(); - if (!permissionManager) { - return NotificationPermission::Default; - } - - permissionManager->TestExactPermissionFromPrincipal( - aPrincipal, "desktop-notification"_ns, &permission); - - // Convert the result to one of the enum types. - switch (permission) { - case nsIPermissionManager::ALLOW_ACTION: - return NotificationPermission::Granted; - case nsIPermissionManager::DENY_ACTION: - return NotificationPermission::Denied; - default: - return NotificationPermission::Default; - } + return GetNotificationPermission(principal, effectiveStoragePrincipal, + aWindow->IsSecureContext(), aPurpose); } nsresult Notification::ResolveIconAndSoundURL(nsIGlobalObject* aGlobal, @@ -2219,7 +2162,8 @@ already_AddRefed Notification::ShowPersistentNotification( // We check permission here rather than pass the Promise to NotificationTask // which leads to uglier code. // XXX: GetPermission is a synchronous blocking function on workers. - NotificationPermission permission = GetPermission(aGlobal, aRv); + NotificationPermission permission = + GetPermission(aGlobal, PermissionCheckPurpose::NotificationShow, aRv); // Step 6.1: If the result of getting the notifications permission state is // not "granted", then queue a global task on the DOM manipulation task source diff --git a/dom/notification/Notification.h b/dom/notification/Notification.h index 251d9a027944c..b6890705f5873 100644 --- a/dom/notification/Notification.h +++ b/dom/notification/Notification.h @@ -30,6 +30,10 @@ class WorkerNotificationObserver; class Promise; class StrongWorkerRef; +namespace notification { +enum class PermissionCheckPurpose : uint8_t; +} + /* * Notifications on workers introduce some lifetime issues. The property we * are trying to satisfy is: @@ -197,6 +201,7 @@ class Notification : public DOMEventTargetHelper, public GlobalFreezeObserver { // Initialized on the worker thread, never unset, and always used in // a read-only capacity. Used on any thread. CheckedUnsafePtr mWorkerPrivate; + bool mWorkerUseRegularPrincipal = false; // Main thread only. WorkerNotificationObserver* mObserver; @@ -214,13 +219,9 @@ class Notification : public DOMEventTargetHelper, public GlobalFreezeObserver { bool AddRefObject(); void ReleaseObject(); - static NotificationPermission GetPermission(nsIGlobalObject* aGlobal, - ErrorResult& aRv); - - static NotificationPermission GetPermissionInternal(nsIPrincipal* aPrincipal, - ErrorResult& rv); - - static NotificationPermission TestPermission(nsIPrincipal* aPrincipal); + static NotificationPermission GetPermission( + nsIGlobalObject* aGlobal, notification::PermissionCheckPurpose aPurpose, + ErrorResult& aRv); bool DispatchClickEvent(); @@ -252,7 +253,8 @@ class Notification : public DOMEventTargetHelper, public GlobalFreezeObserver { void FrozenCallback(nsIGlobalObject* aOwner) override; static NotificationPermission GetPermissionInternal( - nsPIDOMWindowInner* aWindow, ErrorResult& rv); + nsPIDOMWindowInner* aWindow, + notification::PermissionCheckPurpose aPurpose, ErrorResult& rv); static nsresult GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin); diff --git a/dom/notification/NotificationUtils.cpp b/dom/notification/NotificationUtils.cpp new file mode 100644 index 0000000000000..b69fa4cf9a8ca --- /dev/null +++ b/dom/notification/NotificationUtils.cpp @@ -0,0 +1,113 @@ +/* -*- 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 "NotificationUtils.h" + +#include "mozilla/BasePrincipal.h" +#include "mozilla/Components.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/dom/NotificationBinding.h" +#include "nsIPermissionManager.h" + +namespace mozilla::dom::notification { + +bool IsNotificationAllowedFor(nsIPrincipal* aPrincipal) { + if (aPrincipal->IsSystemPrincipal()) { + return true; + } + // Allow files to show notifications by default. + return aPrincipal->SchemeIs("file"); +} + +bool IsNotificationForbiddenFor(nsIPrincipal* aPrincipal, + nsIPrincipal* aEffectiveStoragePrincipal, + bool isSecureContext, + PermissionCheckPurpose aPurpose, + Document* aRequestorDoc) { + if (aPrincipal->GetIsInPrivateBrowsing() && + !StaticPrefs::dom_webnotifications_privateBrowsing_enabled()) { + return true; + } + + if (!isSecureContext) { + if (aRequestorDoc) { + nsContentUtils::ReportToConsole( + nsIScriptError::errorFlag, "DOM"_ns, aRequestorDoc, + nsContentUtils::eDOM_PROPERTIES, + "NotificationsInsecureRequestIsForbidden"); + } + return true; + } + + const nsString& partitionKey = + aEffectiveStoragePrincipal->OriginAttributesRef().mPartitionKey; + + if (aEffectiveStoragePrincipal->OriginAttributesRef() + .mPartitionKey.IsEmpty()) { + // first party + return false; + } + nsString outScheme; + nsString outBaseDomain; + int32_t outPort; + bool outForeignByAncestorContext; + OriginAttributes::ParsePartitionKey(partitionKey, outScheme, outBaseDomain, + outPort, outForeignByAncestorContext); + if (outForeignByAncestorContext) { + // nested first party + return false; + } + + // third party + if (aRequestorDoc) { + nsContentUtils::ReportToConsole( + nsIScriptError::errorFlag, "DOM"_ns, aRequestorDoc, + nsContentUtils::eDOM_PROPERTIES, + "NotificationsCrossOriginIframeRequestIsForbidden"); + } + return !StaticPrefs::dom_webnotifications_allowcrossoriginiframe(); +} + +NotificationPermission GetRawNotificationPermission(nsIPrincipal* aPrincipal) { + AssertIsOnMainThread(); + + uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; + + nsCOMPtr permissionManager = + components::PermissionManager::Service(); + if (!permissionManager) { + return NotificationPermission::Default; + } + + permissionManager->TestExactPermissionFromPrincipal( + aPrincipal, "desktop-notification"_ns, &permission); + + // Convert the result to one of the enum types. + switch (permission) { + case nsIPermissionManager::ALLOW_ACTION: + return NotificationPermission::Granted; + case nsIPermissionManager::DENY_ACTION: + return NotificationPermission::Denied; + default: + return NotificationPermission::Default; + } +} + +NotificationPermission GetNotificationPermission( + nsIPrincipal* aPrincipal, nsIPrincipal* aEffectiveStoragePrincipal, + bool isSecureContext, PermissionCheckPurpose aPurpose) { + if (IsNotificationAllowedFor(aPrincipal)) { + return NotificationPermission::Granted; + } + if (IsNotificationForbiddenFor(aPrincipal, aEffectiveStoragePrincipal, + isSecureContext, aPurpose)) { + return NotificationPermission::Denied; + } + + return GetRawNotificationPermission(aPrincipal); +} + +} // namespace mozilla::dom::notification diff --git a/dom/notification/NotificationUtils.h b/dom/notification/NotificationUtils.h new file mode 100644 index 0000000000000..2dfdbc71a634d --- /dev/null +++ b/dom/notification/NotificationUtils.h @@ -0,0 +1,61 @@ +/* -*- 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/. */ + +#ifndef DOM_NOTIFICATION_NOTIFICATIONUTILS_H_ +#define DOM_NOTIFICATION_NOTIFICATIONUTILS_H_ + +#include + +class nsIPrincipal; + +namespace mozilla::dom { +enum class NotificationPermission : uint8_t; +class Document; +} // namespace mozilla::dom + +namespace mozilla::dom::notification { + +/** + * Retrieves raw notification permission directly from PermissionManager. + */ +NotificationPermission GetRawNotificationPermission(nsIPrincipal* aPrincipal); + +enum class PermissionCheckPurpose : uint8_t { + PermissionRequest, + PermissionAttribute, + NotificationShow, +}; + +/** + * Returns true if the current principal must be given notification + * permission, regardless of the permission status. This one should be dominant + * compared to FobbiddenFor below. + */ +bool IsNotificationAllowedFor(nsIPrincipal* aPrincipal); + +/** + * Returns true if the current principal must not be given notification + * permission, regardless of the permission status. + * + * @param aRequestorDoc The Document object from the page requesting permission. + * Pass only when this is for requestNotification(). + */ +bool IsNotificationForbiddenFor(nsIPrincipal* aPrincipal, + nsIPrincipal* aEffectiveStoragePrincipal, + bool isSecureContext, + PermissionCheckPurpose aPurpose, + Document* aRequestorDoc = nullptr); + +/** + * Retrieves notification permission based on the context. + */ +NotificationPermission GetNotificationPermission( + nsIPrincipal* aPrincipal, nsIPrincipal* aEffectiveStoragePrincipal, + bool isSecureContext, PermissionCheckPurpose aPurpose); + +} // namespace mozilla::dom::notification + +#endif // DOM_NOTIFICATION_NOTIFICATIONUTILS_H_ diff --git a/dom/notification/moz.build b/dom/notification/moz.build index d83ba0613b9d3..d93d18710de34 100644 --- a/dom/notification/moz.build +++ b/dom/notification/moz.build @@ -23,6 +23,7 @@ EXPORTS.mozilla.dom += [ UNIFIED_SOURCES += [ "Notification.cpp", "NotificationEvent.cpp", + "NotificationUtils.cpp", ] include("/ipc/chromium/chromium-config.mozbuild") diff --git a/dom/notification/test/mochitest/mochitest.toml b/dom/notification/test/mochitest/mochitest.toml index 5decae778e37a..11a6e3daa769c 100644 --- a/dom/notification/test/mochitest/mochitest.toml +++ b/dom/notification/test/mochitest/mochitest.toml @@ -1,12 +1,11 @@ [DEFAULT] - +scheme = "https" support-files = [ "MockAlertsService.js", "NotificationTest.js", ] ["test_notification_basics.html"] -scheme = "https" # requestPermission requires HTTPS skip-if = [ "xorigin", # Bug 1792790 "os == 'android'", # Bug 1816427, Notification.onshow/close are broken on Android @@ -14,18 +13,17 @@ skip-if = [ ["test_notification_crossorigin_iframe.html"] support-files = ["blank.html"] -scheme = "https" -# This test needs to be run on HTTP (not HTTPS). +# This test needs to be run on HTTP (not HTTPS). ["test_notification_insecure_context.html"] skip-if = [ "http3", "http2", ] +scheme = "http" ["test_notification_permissions.html"] support-files = ["blank.html"] -scheme = "https" ["test_notification_serviceworker_constructor_error.html"] support-files = ["notification_constructor_error.js"] diff --git a/dom/notification/test/mochitest/notification_openWindow_worker.js b/dom/notification/test/mochitest/notification_openWindow_worker.js index 8cf9a30f89278..cc33c274c99b7 100644 --- a/dom/notification/test/mochitest/notification_openWindow_worker.js +++ b/dom/notification/test/mochitest/notification_openWindow_worker.js @@ -1,6 +1,6 @@ /* eslint-env serviceworker */ -const gRoot = "http://mochi.test:8888/tests/dom/notification/test/mochitest/"; +const gRoot = "https://example.com/tests/dom/notification/test/mochitest/"; const gTestURL = gRoot + "test_notification_serviceworker_openWindow.html"; const gClientURL = gRoot + "file_notification_openWindow.html"; diff --git a/dom/notification/test/mochitest/openWindow_worker.js b/dom/notification/test/mochitest/openWindow_worker.js index 0f9c0f5099da8..3649bb95da67a 100644 --- a/dom/notification/test/mochitest/openWindow_worker.js +++ b/dom/notification/test/mochitest/openWindow_worker.js @@ -116,15 +116,13 @@ onnotificationclick = function (e) { var promises = []; var redirect = - "http://mochi.test:8888/tests/dom/notification/test/mochitest/redirect.sjs?"; + "https://example.com/tests/dom/notification/test/mochitest/redirect.sjs?"; var redirect_xorigin = - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - "http://example.com/tests/dom/notification/test/mochitest/redirect.sjs?"; + "https://example.org/tests/dom/notification/test/mochitest/redirect.sjs?"; var same_origin = - "http://mochi.test:8888/tests/dom/notification/test/mochitest/open_window/client.sjs"; + "https://example.com/tests/dom/notification/test/mochitest/open_window/client.sjs"; var different_origin = - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - "http://example.com/tests/dom/notification/test/mochitest/open_window/client.sjs"; + "https://example.org/tests/dom/notification/test/mochitest/open_window/client.sjs"; promises.push(testForUrl("about:blank", "TypeError", null, results)); promises.push(testForUrl(different_origin, null, null, results)); diff --git a/dom/notification/test/mochitest/open_window/client.sjs b/dom/notification/test/mochitest/open_window/client.sjs index d8f903f0036c7..b4e0e40bac3dd 100644 --- a/dom/notification/test/mochitest/open_window/client.sjs +++ b/dom/notification/test/mochitest/open_window/client.sjs @@ -15,7 +15,7 @@ const RESPONSE = `