mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
f0dc3526ed
Depends on D189509 Differential Revision: https://phabricator.services.mozilla.com/D189510
381 lines
13 KiB
C++
381 lines
13 KiB
C++
/* -*- 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 "nsPIDOMWindow.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsContentPermissionHelper.h"
|
|
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/StaticPrefs_permissions.h"
|
|
#include "mozilla/dom/BrowsingContext.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. All permission names have to be in lowercase.
|
|
{"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},
|
|
// Like "midi" but with sysex support.
|
|
{"midi-sysex", 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},
|
|
{"screen-wake-lock", u"screen-wake-lock",
|
|
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;
|
|
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;
|
|
}
|
|
|
|
/* static */
|
|
nsresult PermissionDelegateHandler::GetDelegatePrincipal(
|
|
const nsACString& aType, nsIContentPermissionRequest* aRequest,
|
|
nsIPrincipal** aResult) {
|
|
MOZ_ASSERT(aRequest);
|
|
|
|
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) const {
|
|
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 (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 (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));
|
|
if (!info) {
|
|
return;
|
|
}
|
|
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
|