diff options
Diffstat (limited to 'extensions/permissions/PermissionDelegateHandler.cpp')
-rw-r--r-- | extensions/permissions/PermissionDelegateHandler.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/extensions/permissions/PermissionDelegateHandler.cpp b/extensions/permissions/PermissionDelegateHandler.cpp new file mode 100644 index 0000000000..098d8386e8 --- /dev/null +++ b/extensions/permissions/PermissionDelegateHandler.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 2; 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/PermissionDelegateHandler.h" + +#include "nsGlobalWindowInner.h" +#include "nsPIDOMWindow.h" +#include "nsIPrincipal.h" +#include "nsContentPermissionHelper.h" + +#include "mozilla/BasePrincipal.h" +#include "mozilla/StaticPrefs_permissions.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/FeaturePolicyUtils.h" +#include "mozilla/dom/WindowContext.h" +#include "mozilla/PermissionManager.h" + +using namespace mozilla::dom; + +namespace mozilla { + +typedef PermissionDelegateHandler::PermissionDelegatePolicy DelegatePolicy; +typedef PermissionDelegateHandler::PermissionDelegateInfo DelegateInfo; + +// Particular type of permissions to care about. We decide cases by case and +// give various types of controls over each of these. +static const DelegateInfo sPermissionsMap[] = { + // Permissions API map + {"geo", u"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy}, + // The same with geo, but we support both to save some conversions between + // "geo" and "geolocation" + {"geolocation", u"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy}, + {"desktop-notification", nullptr, + DelegatePolicy::ePersistDeniedCrossOrigin}, + {"persistent-storage", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin}, + {"vibration", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin}, + {"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin}, + {"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin}, + {"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy}, + {"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy}, + {"screen", u"display-capture", DelegatePolicy::eDelegateUseFeaturePolicy}, + {"xr", u"xr-spatial-tracking", DelegatePolicy::eDelegateUseFeaturePolicy}, +}; + +static_assert(PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT == + (sizeof(sPermissionsMap) / sizeof(DelegateInfo)), + "The PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT must " + "match to the " + "length of sPermissionsMap. Please update it."); + +NS_IMPL_CYCLE_COLLECTION(PermissionDelegateHandler) +NS_IMPL_CYCLE_COLLECTING_ADDREF(PermissionDelegateHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PermissionDelegateHandler) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PermissionDelegateHandler) + NS_INTERFACE_MAP_ENTRY(nsIPermissionDelegateHandler) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +PermissionDelegateHandler::PermissionDelegateHandler(dom::Document* aDocument) + : mDocument(aDocument) { + MOZ_ASSERT(aDocument); +} + +/* static */ +const DelegateInfo* PermissionDelegateHandler::GetPermissionDelegateInfo( + const nsAString& aPermissionName) { + nsAutoString lowerContent(aPermissionName); + ToLowerCase(lowerContent); + + for (const auto& perm : sPermissionsMap) { + if (lowerContent.EqualsASCII(perm.mPermissionName)) { + return &perm; + } + } + + return nullptr; +} + +NS_IMETHODIMP +PermissionDelegateHandler::MaybeUnsafePermissionDelegate( + const nsTArray<nsCString>& aTypes, bool* aMaybeUnsafe) { + *aMaybeUnsafe = false; + if (!StaticPrefs::permissions_delegation_enabled()) { + return NS_OK; + } + + for (auto& type : aTypes) { + const DelegateInfo* info = + GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(type)); + if (!info) { + continue; + } + + nsAutoString featureName(info->mFeatureName); + if (FeaturePolicyUtils::IsFeatureUnsafeAllowedAll(mDocument, featureName)) { + *aMaybeUnsafe = true; + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +PermissionDelegateHandler::GetPermissionDelegateFPEnabled(bool* aEnabled) { + MOZ_ASSERT(NS_IsMainThread()); + *aEnabled = StaticPrefs::permissions_delegation_enabled(); + return NS_OK; +} + +/* static */ +nsresult PermissionDelegateHandler::GetDelegatePrincipal( + const nsACString& aType, nsIContentPermissionRequest* aRequest, + nsIPrincipal** aResult) { + MOZ_ASSERT(aRequest); + + if (!StaticPrefs::permissions_delegation_enabled()) { + return aRequest->GetPrincipal(aResult); + } + + const DelegateInfo* info = + GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType)); + if (!info) { + *aResult = nullptr; + return NS_OK; + } + + if (info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin || + info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) { + return aRequest->GetTopLevelPrincipal(aResult); + } + + return aRequest->GetPrincipal(aResult); +} + +bool PermissionDelegateHandler::Initialize() { + MOZ_ASSERT(mDocument); + + mPermissionManager = PermissionManager::GetInstance(); + if (!mPermissionManager) { + return false; + } + + mPrincipal = mDocument->NodePrincipal(); + return true; +} + +static bool IsCrossOriginContentToTop(Document* aDocument) { + MOZ_ASSERT(aDocument); + + RefPtr<BrowsingContext> bc = aDocument->GetBrowsingContext(); + if (!bc) { + return true; + } + RefPtr<BrowsingContext> topBC = bc->Top(); + + // In Fission, we can know if it is cross-origin by checking whether both + // contexts in the same process. So, If they are not in the same process, we + // can say that it's cross-origin. + if (!topBC->IsInProcess()) { + return true; + } + + RefPtr<Document> topDoc = topBC->GetDocument(); + if (!topDoc) { + return true; + } + + nsCOMPtr<nsIPrincipal> topLevelPrincipal = topDoc->NodePrincipal(); + + return !aDocument->NodePrincipal()->Subsumes(topLevelPrincipal); +} + +bool PermissionDelegateHandler::HasFeaturePolicyAllowed( + const DelegateInfo* info) const { + if (info->mPolicy != DelegatePolicy::eDelegateUseFeaturePolicy || + !info->mFeatureName) { + return true; + } + + nsAutoString featureName(info->mFeatureName); + return FeaturePolicyUtils::IsFeatureAllowed(mDocument, featureName); +} + +bool PermissionDelegateHandler::HasPermissionDelegated( + const nsACString& aType) { + MOZ_ASSERT(mDocument); + + // System principal should have right to make permission request + if (mPrincipal->IsSystemPrincipal()) { + return true; + } + + const DelegateInfo* info = + GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType)); + if (!info || !HasFeaturePolicyAllowed(info)) { + return false; + } + + if (!StaticPrefs::permissions_delegation_enabled()) { + return true; + } + + if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin && + !mDocument->IsTopLevelContentDocument() && + IsCrossOriginContentToTop(mDocument)) { + return false; + } + + return true; +} + +nsresult PermissionDelegateHandler::GetPermission(const nsACString& aType, + uint32_t* aPermission, + bool aExactHostMatch) { + MOZ_ASSERT(mDocument); + MOZ_ASSERT(mPrincipal); + + if (mPrincipal->IsSystemPrincipal()) { + *aPermission = nsIPermissionManager::ALLOW_ACTION; + return NS_OK; + } + + const DelegateInfo* info = + GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType)); + if (!info || !HasFeaturePolicyAllowed(info)) { + *aPermission = nsIPermissionManager::DENY_ACTION; + return NS_OK; + } + + nsresult (NS_STDCALL nsIPermissionManager::*testPermission)( + nsIPrincipal*, const nsACString&, uint32_t*) = + aExactHostMatch ? &nsIPermissionManager::TestExactPermissionFromPrincipal + : &nsIPermissionManager::TestPermissionFromPrincipal; + + if (!StaticPrefs::permissions_delegation_enabled()) { + return (mPermissionManager->*testPermission)(mPrincipal, aType, + aPermission); + } + + if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin && + !mDocument->IsTopLevelContentDocument() && + IsCrossOriginContentToTop(mDocument)) { + *aPermission = nsIPermissionManager::DENY_ACTION; + return NS_OK; + } + + nsIPrincipal* principal = mPrincipal; + // If we cannot get the browsing context from the document, we fallback to use + // the prinicpal of the document to test the permission. + RefPtr<BrowsingContext> bc = mDocument->GetBrowsingContext(); + + if ((info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin || + info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) && + bc) { + RefPtr<WindowContext> topWC = bc->GetTopWindowContext(); + + if (topWC && topWC->IsInProcess()) { + // If the top-level window context is in the same process, we directly get + // the node principal from the top-level document to test the permission. + // We cannot check the lists in the window context in this case since the + // 'perm-changed' could be notified in the iframe before the top-level in + // certain cases, for example, request permissions in first-party iframes. + // In this case, the list in window context hasn't gotten updated, so it + // would has an out-dated value until the top-level window get the + // observer. So, we have to test permission manager directly if we can. + RefPtr<Document> topDoc = topWC->GetBrowsingContext()->GetDocument(); + + if (topDoc) { + principal = topDoc->NodePrincipal(); + } + } else if (topWC) { + // Get the delegated permissions from the top-level window context. + DelegatedPermissionList list = + aExactHostMatch ? topWC->GetDelegatedExactHostMatchPermissions() + : topWC->GetDelegatedPermissions(); + size_t idx = std::distance(sPermissionsMap, info); + *aPermission = list.mPermissions[idx]; + return NS_OK; + } + } + + return (mPermissionManager->*testPermission)(principal, aType, aPermission); +} + +nsresult PermissionDelegateHandler::GetPermissionForPermissionsAPI( + const nsACString& aType, uint32_t* aPermission) { + return GetPermission(aType, aPermission, false); +} + +void PermissionDelegateHandler::PopulateAllDelegatedPermissions() { + MOZ_ASSERT(mDocument); + MOZ_ASSERT(mPermissionManager); + + // We only populate the delegated permissions for the top-level content. + if (!mDocument->IsTopLevelContentDocument()) { + return; + } + + RefPtr<WindowContext> wc = mDocument->GetWindowContext(); + NS_ENSURE_TRUE_VOID(wc && !wc->IsDiscarded()); + + DelegatedPermissionList list; + DelegatedPermissionList exactHostMatchList; + + for (const auto& perm : sPermissionsMap) { + size_t idx = std::distance(sPermissionsMap, &perm); + + nsDependentCString type(perm.mPermissionName); + // Populate the permission. + uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; + Unused << mPermissionManager->TestPermissionFromPrincipal(mPrincipal, type, + &permission); + list.mPermissions[idx] = permission; + + // Populate the exact-host-match permission. + permission = nsIPermissionManager::UNKNOWN_ACTION; + Unused << mPermissionManager->TestExactPermissionFromPrincipal( + mPrincipal, type, &permission); + exactHostMatchList.mPermissions[idx] = permission; + } + + WindowContext::Transaction txn; + txn.SetDelegatedPermissions(list); + txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList); + MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc)); +} + +void PermissionDelegateHandler::UpdateDelegatedPermission( + const nsACString& aType) { + MOZ_ASSERT(mDocument); + MOZ_ASSERT(mPermissionManager); + + // We only update the delegated permission for the top-level content. + if (!mDocument->IsTopLevelContentDocument()) { + return; + } + + RefPtr<WindowContext> wc = mDocument->GetWindowContext(); + NS_ENSURE_TRUE_VOID(wc); + + const DelegateInfo* info = + GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType)); + NS_ENSURE_TRUE_VOID(info); + size_t idx = std::distance(sPermissionsMap, info); + + WindowContext::Transaction txn; + bool changed = false; + DelegatedPermissionList list = wc->GetDelegatedPermissions(); + + if (UpdateDelegatePermissionInternal( + list, aType, idx, + &nsIPermissionManager::TestPermissionFromPrincipal)) { + txn.SetDelegatedPermissions(list); + changed = true; + } + + DelegatedPermissionList exactHostMatchList = + wc->GetDelegatedExactHostMatchPermissions(); + + if (UpdateDelegatePermissionInternal( + exactHostMatchList, aType, idx, + &nsIPermissionManager::TestExactPermissionFromPrincipal)) { + txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList); + changed = true; + } + + // We only commit if there is any change of permissions. + if (changed) { + MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc)); + } +} + +bool PermissionDelegateHandler::UpdateDelegatePermissionInternal( + PermissionDelegateHandler::DelegatedPermissionList& aList, + const nsACString& aType, size_t aIdx, + nsresult (NS_STDCALL nsIPermissionManager::*aTestFunc)(nsIPrincipal*, + const nsACString&, + uint32_t*)) { + MOZ_ASSERT(aTestFunc); + MOZ_ASSERT(mPermissionManager); + MOZ_ASSERT(mPrincipal); + + uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; + Unused << (mPermissionManager->*aTestFunc)(mPrincipal, aType, &permission); + + if (aList.mPermissions[aIdx] != permission) { + aList.mPermissions[aIdx] = permission; + return true; + } + + return false; +} + +} // namespace mozilla |