1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/PermissionDelegateHandler.h"
9 #include "nsPIDOMWindow.h"
10 #include "nsIPrincipal.h"
11 #include "nsContentPermissionHelper.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/StaticPrefs_permissions.h"
15 #include "mozilla/dom/BrowsingContext.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/FeaturePolicyUtils.h"
18 #include "mozilla/dom/WindowContext.h"
19 #include "mozilla/PermissionManager.h"
21 using namespace mozilla::dom
;
25 typedef PermissionDelegateHandler::PermissionDelegatePolicy DelegatePolicy
;
26 typedef PermissionDelegateHandler::PermissionDelegateInfo DelegateInfo
;
28 // Particular type of permissions to care about. We decide cases by case and
29 // give various types of controls over each of these.
30 static const DelegateInfo sPermissionsMap
[] = {
31 // Permissions API map. All permission names have to be in lowercase.
32 {"geo", u
"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy
},
33 // The same with geo, but we support both to save some conversions between
34 // "geo" and "geolocation"
35 {"geolocation", u
"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy
},
36 {"desktop-notification", nullptr,
37 DelegatePolicy::ePersistDeniedCrossOrigin
},
38 {"persistent-storage", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin
},
39 {"vibration", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin
},
40 {"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin
},
41 // Like "midi" but with sysex support.
42 {"midi-sysex", nullptr, DelegatePolicy::eDelegateUseIframeOrigin
},
43 {"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin
},
44 {"camera", u
"camera", DelegatePolicy::eDelegateUseFeaturePolicy
},
45 {"microphone", u
"microphone", DelegatePolicy::eDelegateUseFeaturePolicy
},
46 {"screen", u
"display-capture", DelegatePolicy::eDelegateUseFeaturePolicy
},
47 {"xr", u
"xr-spatial-tracking", DelegatePolicy::eDelegateUseFeaturePolicy
},
48 {"screen-wake-lock", u
"screen-wake-lock",
49 DelegatePolicy::eDelegateUseFeaturePolicy
}};
51 static_assert(PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT
==
52 (sizeof(sPermissionsMap
) / sizeof(DelegateInfo
)),
53 "The PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT must "
55 "length of sPermissionsMap. Please update it.");
57 NS_IMPL_CYCLE_COLLECTION(PermissionDelegateHandler
)
58 NS_IMPL_CYCLE_COLLECTING_ADDREF(PermissionDelegateHandler
)
59 NS_IMPL_CYCLE_COLLECTING_RELEASE(PermissionDelegateHandler
)
61 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PermissionDelegateHandler
)
62 NS_INTERFACE_MAP_ENTRY(nsIPermissionDelegateHandler
)
63 NS_INTERFACE_MAP_ENTRY(nsISupports
)
66 PermissionDelegateHandler::PermissionDelegateHandler(dom::Document
* aDocument
)
67 : mDocument(aDocument
) {
68 MOZ_ASSERT(aDocument
);
72 const DelegateInfo
* PermissionDelegateHandler::GetPermissionDelegateInfo(
73 const nsAString
& aPermissionName
) {
74 nsAutoString
lowerContent(aPermissionName
);
75 ToLowerCase(lowerContent
);
77 for (const auto& perm
: sPermissionsMap
) {
78 if (lowerContent
.EqualsASCII(perm
.mPermissionName
)) {
87 PermissionDelegateHandler::MaybeUnsafePermissionDelegate(
88 const nsTArray
<nsCString
>& aTypes
, bool* aMaybeUnsafe
) {
89 *aMaybeUnsafe
= false;
90 for (auto& type
: aTypes
) {
91 const DelegateInfo
* info
=
92 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(type
));
97 nsAutoString
featureName(info
->mFeatureName
);
98 if (FeaturePolicyUtils::IsFeatureUnsafeAllowedAll(mDocument
, featureName
)) {
108 nsresult
PermissionDelegateHandler::GetDelegatePrincipal(
109 const nsACString
& aType
, nsIContentPermissionRequest
* aRequest
,
110 nsIPrincipal
** aResult
) {
111 MOZ_ASSERT(aRequest
);
113 const DelegateInfo
* info
=
114 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType
));
120 if (info
->mPolicy
== DelegatePolicy::eDelegateUseTopOrigin
||
121 info
->mPolicy
== DelegatePolicy::eDelegateUseFeaturePolicy
) {
122 return aRequest
->GetTopLevelPrincipal(aResult
);
125 return aRequest
->GetPrincipal(aResult
);
128 bool PermissionDelegateHandler::Initialize() {
129 MOZ_ASSERT(mDocument
);
131 mPermissionManager
= PermissionManager::GetInstance();
132 if (!mPermissionManager
) {
136 mPrincipal
= mDocument
->NodePrincipal();
140 static bool IsCrossOriginContentToTop(Document
* aDocument
) {
141 MOZ_ASSERT(aDocument
);
143 RefPtr
<BrowsingContext
> bc
= aDocument
->GetBrowsingContext();
147 RefPtr
<BrowsingContext
> topBC
= bc
->Top();
149 // In Fission, we can know if it is cross-origin by checking whether both
150 // contexts in the same process. So, If they are not in the same process, we
151 // can say that it's cross-origin.
152 if (!topBC
->IsInProcess()) {
156 RefPtr
<Document
> topDoc
= topBC
->GetDocument();
161 nsCOMPtr
<nsIPrincipal
> topLevelPrincipal
= topDoc
->NodePrincipal();
163 return !aDocument
->NodePrincipal()->Subsumes(topLevelPrincipal
);
166 bool PermissionDelegateHandler::HasFeaturePolicyAllowed(
167 const DelegateInfo
* info
) const {
168 if (info
->mPolicy
!= DelegatePolicy::eDelegateUseFeaturePolicy
||
169 !info
->mFeatureName
) {
173 nsAutoString
featureName(info
->mFeatureName
);
174 return FeaturePolicyUtils::IsFeatureAllowed(mDocument
, featureName
);
177 bool PermissionDelegateHandler::HasPermissionDelegated(
178 const nsACString
& aType
) const {
179 MOZ_ASSERT(mDocument
);
181 // System principal should have right to make permission request
182 if (mPrincipal
->IsSystemPrincipal()) {
186 const DelegateInfo
* info
=
187 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType
));
188 if (!info
|| !HasFeaturePolicyAllowed(info
)) {
192 if (info
->mPolicy
== DelegatePolicy::ePersistDeniedCrossOrigin
&&
193 !mDocument
->IsTopLevelContentDocument() &&
194 IsCrossOriginContentToTop(mDocument
)) {
201 nsresult
PermissionDelegateHandler::GetPermission(const nsACString
& aType
,
202 uint32_t* aPermission
,
203 bool aExactHostMatch
) {
204 MOZ_ASSERT(mDocument
);
205 MOZ_ASSERT(mPrincipal
);
207 if (mPrincipal
->IsSystemPrincipal()) {
208 *aPermission
= nsIPermissionManager::ALLOW_ACTION
;
212 const DelegateInfo
* info
=
213 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType
));
214 if (!info
|| !HasFeaturePolicyAllowed(info
)) {
215 *aPermission
= nsIPermissionManager::DENY_ACTION
;
219 nsresult (NS_STDCALL
nsIPermissionManager::*testPermission
)(
220 nsIPrincipal
*, const nsACString
&, uint32_t*) =
221 aExactHostMatch
? &nsIPermissionManager::TestExactPermissionFromPrincipal
222 : &nsIPermissionManager::TestPermissionFromPrincipal
;
224 if (info
->mPolicy
== DelegatePolicy::ePersistDeniedCrossOrigin
&&
225 !mDocument
->IsTopLevelContentDocument() &&
226 IsCrossOriginContentToTop(mDocument
)) {
227 *aPermission
= nsIPermissionManager::DENY_ACTION
;
231 nsIPrincipal
* principal
= mPrincipal
;
232 // If we cannot get the browsing context from the document, we fallback to use
233 // the prinicpal of the document to test the permission.
234 RefPtr
<BrowsingContext
> bc
= mDocument
->GetBrowsingContext();
236 if ((info
->mPolicy
== DelegatePolicy::eDelegateUseTopOrigin
||
237 info
->mPolicy
== DelegatePolicy::eDelegateUseFeaturePolicy
) &&
239 RefPtr
<WindowContext
> topWC
= bc
->GetTopWindowContext();
241 if (topWC
&& topWC
->IsInProcess()) {
242 // If the top-level window context is in the same process, we directly get
243 // the node principal from the top-level document to test the permission.
244 // We cannot check the lists in the window context in this case since the
245 // 'perm-changed' could be notified in the iframe before the top-level in
246 // certain cases, for example, request permissions in first-party iframes.
247 // In this case, the list in window context hasn't gotten updated, so it
248 // would has an out-dated value until the top-level window get the
249 // observer. So, we have to test permission manager directly if we can.
250 RefPtr
<Document
> topDoc
= topWC
->GetBrowsingContext()->GetDocument();
253 principal
= topDoc
->NodePrincipal();
256 // Get the delegated permissions from the top-level window context.
257 DelegatedPermissionList list
=
258 aExactHostMatch
? topWC
->GetDelegatedExactHostMatchPermissions()
259 : topWC
->GetDelegatedPermissions();
260 size_t idx
= std::distance(sPermissionsMap
, info
);
261 *aPermission
= list
.mPermissions
[idx
];
266 return (mPermissionManager
->*testPermission
)(principal
, aType
, aPermission
);
269 nsresult
PermissionDelegateHandler::GetPermissionForPermissionsAPI(
270 const nsACString
& aType
, uint32_t* aPermission
) {
271 return GetPermission(aType
, aPermission
, false);
274 void PermissionDelegateHandler::PopulateAllDelegatedPermissions() {
275 MOZ_ASSERT(mDocument
);
276 MOZ_ASSERT(mPermissionManager
);
278 // We only populate the delegated permissions for the top-level content.
279 if (!mDocument
->IsTopLevelContentDocument()) {
283 RefPtr
<WindowContext
> wc
= mDocument
->GetWindowContext();
284 NS_ENSURE_TRUE_VOID(wc
&& !wc
->IsDiscarded());
286 DelegatedPermissionList list
;
287 DelegatedPermissionList exactHostMatchList
;
289 for (const auto& perm
: sPermissionsMap
) {
290 size_t idx
= std::distance(sPermissionsMap
, &perm
);
292 nsDependentCString
type(perm
.mPermissionName
);
293 // Populate the permission.
294 uint32_t permission
= nsIPermissionManager::UNKNOWN_ACTION
;
295 Unused
<< mPermissionManager
->TestPermissionFromPrincipal(mPrincipal
, type
,
297 list
.mPermissions
[idx
] = permission
;
299 // Populate the exact-host-match permission.
300 permission
= nsIPermissionManager::UNKNOWN_ACTION
;
301 Unused
<< mPermissionManager
->TestExactPermissionFromPrincipal(
302 mPrincipal
, type
, &permission
);
303 exactHostMatchList
.mPermissions
[idx
] = permission
;
306 WindowContext::Transaction txn
;
307 txn
.SetDelegatedPermissions(list
);
308 txn
.SetDelegatedExactHostMatchPermissions(exactHostMatchList
);
309 MOZ_ALWAYS_SUCCEEDS(txn
.Commit(wc
));
312 void PermissionDelegateHandler::UpdateDelegatedPermission(
313 const nsACString
& aType
) {
314 MOZ_ASSERT(mDocument
);
315 MOZ_ASSERT(mPermissionManager
);
317 // We only update the delegated permission for the top-level content.
318 if (!mDocument
->IsTopLevelContentDocument()) {
322 RefPtr
<WindowContext
> wc
= mDocument
->GetWindowContext();
323 NS_ENSURE_TRUE_VOID(wc
);
325 const DelegateInfo
* info
=
326 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType
));
330 size_t idx
= std::distance(sPermissionsMap
, info
);
332 WindowContext::Transaction txn
;
333 bool changed
= false;
334 DelegatedPermissionList list
= wc
->GetDelegatedPermissions();
336 if (UpdateDelegatePermissionInternal(
338 &nsIPermissionManager::TestPermissionFromPrincipal
)) {
339 txn
.SetDelegatedPermissions(list
);
343 DelegatedPermissionList exactHostMatchList
=
344 wc
->GetDelegatedExactHostMatchPermissions();
346 if (UpdateDelegatePermissionInternal(
347 exactHostMatchList
, aType
, idx
,
348 &nsIPermissionManager::TestExactPermissionFromPrincipal
)) {
349 txn
.SetDelegatedExactHostMatchPermissions(exactHostMatchList
);
353 // We only commit if there is any change of permissions.
355 MOZ_ALWAYS_SUCCEEDS(txn
.Commit(wc
));
359 bool PermissionDelegateHandler::UpdateDelegatePermissionInternal(
360 PermissionDelegateHandler::DelegatedPermissionList
& aList
,
361 const nsACString
& aType
, size_t aIdx
,
362 nsresult (NS_STDCALL
nsIPermissionManager::*aTestFunc
)(nsIPrincipal
*,
365 MOZ_ASSERT(aTestFunc
);
366 MOZ_ASSERT(mPermissionManager
);
367 MOZ_ASSERT(mPrincipal
);
369 uint32_t permission
= nsIPermissionManager::UNKNOWN_ACTION
;
370 Unused
<< (mPermissionManager
->*aTestFunc
)(mPrincipal
, aType
, &permission
);
372 if (aList
.mPermissions
[aIdx
] != permission
) {
373 aList
.mPermissions
[aIdx
] = permission
;
380 } // namespace mozilla