PermissionDelegateHandler.cpp (13276B)
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/. */ 6 7 #include "mozilla/PermissionDelegateHandler.h" 8 9 #include "nsPIDOMWindow.h" 10 #include "nsIPrincipal.h" 11 #include "nsContentPermissionHelper.h" 12 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" 20 21 using namespace mozilla::dom; 22 23 namespace mozilla { 24 25 typedef PermissionDelegateHandler::PermissionDelegatePolicy DelegatePolicy; 26 typedef PermissionDelegateHandler::PermissionDelegateInfo DelegateInfo; 27 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 {"localhost", u"localhost", DelegatePolicy::eDelegateUseFeaturePolicy}, 49 {"local-network", u"local-network", 50 DelegatePolicy::eDelegateUseFeaturePolicy}, 51 52 {"screen-wake-lock", u"screen-wake-lock", 53 DelegatePolicy::eDelegateUseFeaturePolicy}}; 54 55 static_assert(PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT == 56 (sizeof(sPermissionsMap) / sizeof(DelegateInfo)), 57 "The PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT must " 58 "match to the " 59 "length of sPermissionsMap. Please update it."); 60 61 NS_IMPL_CYCLE_COLLECTION(PermissionDelegateHandler) 62 NS_IMPL_CYCLE_COLLECTING_ADDREF(PermissionDelegateHandler) 63 NS_IMPL_CYCLE_COLLECTING_RELEASE(PermissionDelegateHandler) 64 65 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PermissionDelegateHandler) 66 NS_INTERFACE_MAP_ENTRY(nsIPermissionDelegateHandler) 67 NS_INTERFACE_MAP_ENTRY(nsISupports) 68 NS_INTERFACE_MAP_END 69 70 PermissionDelegateHandler::PermissionDelegateHandler(dom::Document* aDocument) 71 : mDocument(aDocument) { 72 MOZ_ASSERT(aDocument); 73 } 74 75 /* static */ 76 const DelegateInfo* PermissionDelegateHandler::GetPermissionDelegateInfo( 77 const nsAString& aPermissionName) { 78 nsAutoString lowerContent(aPermissionName); 79 ToLowerCase(lowerContent); 80 81 for (const auto& perm : sPermissionsMap) { 82 if (lowerContent.EqualsASCII(perm.mPermissionName)) { 83 return &perm; 84 } 85 } 86 87 return nullptr; 88 } 89 90 NS_IMETHODIMP 91 PermissionDelegateHandler::MaybeUnsafePermissionDelegate( 92 const nsTArray<nsCString>& aTypes, bool* aMaybeUnsafe) { 93 *aMaybeUnsafe = false; 94 for (auto& type : aTypes) { 95 const DelegateInfo* info = 96 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(type)); 97 if (!info) { 98 continue; 99 } 100 101 nsAutoString featureName(info->mFeatureName); 102 if (FeaturePolicyUtils::IsFeatureUnsafeAllowedAll(mDocument, featureName)) { 103 *aMaybeUnsafe = true; 104 return NS_OK; 105 } 106 } 107 108 return NS_OK; 109 } 110 111 /* static */ 112 nsresult PermissionDelegateHandler::GetDelegatePrincipal( 113 const nsACString& aType, nsIContentPermissionRequest* aRequest, 114 nsIPrincipal** aResult) { 115 MOZ_ASSERT(aRequest); 116 117 const DelegateInfo* info = 118 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType)); 119 if (!info) { 120 *aResult = nullptr; 121 return NS_OK; 122 } 123 124 if (info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin || 125 info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) { 126 return aRequest->GetTopLevelPrincipal(aResult); 127 } 128 129 return aRequest->GetPrincipal(aResult); 130 } 131 132 bool PermissionDelegateHandler::Initialize() { 133 MOZ_ASSERT(mDocument); 134 135 mPermissionManager = PermissionManager::GetInstance(); 136 if (!mPermissionManager) { 137 return false; 138 } 139 140 mPrincipal = mDocument->NodePrincipal(); 141 return true; 142 } 143 144 static bool IsCrossOriginContentToTop(Document* aDocument) { 145 MOZ_ASSERT(aDocument); 146 147 RefPtr<BrowsingContext> bc = aDocument->GetBrowsingContext(); 148 if (!bc) { 149 return true; 150 } 151 RefPtr<BrowsingContext> topBC = bc->Top(); 152 153 // In Fission, we can know if it is cross-origin by checking whether both 154 // contexts in the same process. So, If they are not in the same process, we 155 // can say that it's cross-origin. 156 if (!topBC->IsInProcess()) { 157 return true; 158 } 159 160 RefPtr<Document> topDoc = topBC->GetDocument(); 161 if (!topDoc) { 162 return true; 163 } 164 165 nsCOMPtr<nsIPrincipal> topLevelPrincipal = topDoc->NodePrincipal(); 166 167 return !aDocument->NodePrincipal()->Subsumes(topLevelPrincipal); 168 } 169 170 bool PermissionDelegateHandler::HasFeaturePolicyAllowed( 171 const DelegateInfo* info) const { 172 if (info->mPolicy != DelegatePolicy::eDelegateUseFeaturePolicy || 173 !info->mFeatureName) { 174 return true; 175 } 176 177 nsAutoString featureName(info->mFeatureName); 178 return FeaturePolicyUtils::IsFeatureAllowed(mDocument, featureName); 179 } 180 181 bool PermissionDelegateHandler::HasPermissionDelegated( 182 const nsACString& aType) const { 183 MOZ_ASSERT(mDocument); 184 185 // System principal should have right to make permission request 186 if (mPrincipal->IsSystemPrincipal()) { 187 return true; 188 } 189 190 const DelegateInfo* info = 191 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType)); 192 if (!info || !HasFeaturePolicyAllowed(info)) { 193 return false; 194 } 195 196 if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin && 197 !mDocument->IsTopLevelContentDocument() && 198 IsCrossOriginContentToTop(mDocument)) { 199 return false; 200 } 201 202 return true; 203 } 204 205 nsresult PermissionDelegateHandler::GetPermission(const nsACString& aType, 206 uint32_t* aPermission, 207 bool aExactHostMatch) { 208 MOZ_ASSERT(mDocument); 209 MOZ_ASSERT(mPrincipal); 210 211 if (mPrincipal->IsSystemPrincipal()) { 212 *aPermission = nsIPermissionManager::ALLOW_ACTION; 213 return NS_OK; 214 } 215 216 const DelegateInfo* info = 217 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType)); 218 if (!info || !HasFeaturePolicyAllowed(info)) { 219 *aPermission = nsIPermissionManager::DENY_ACTION; 220 return NS_OK; 221 } 222 223 nsresult (NS_STDCALL nsIPermissionManager::*testPermission)( 224 nsIPrincipal*, const nsACString&, uint32_t*) = 225 aExactHostMatch ? &nsIPermissionManager::TestExactPermissionFromPrincipal 226 : &nsIPermissionManager::TestPermissionFromPrincipal; 227 228 if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin && 229 !mDocument->IsTopLevelContentDocument() && 230 IsCrossOriginContentToTop(mDocument)) { 231 *aPermission = nsIPermissionManager::DENY_ACTION; 232 return NS_OK; 233 } 234 235 nsIPrincipal* principal = mPrincipal; 236 // If we cannot get the browsing context from the document, we fallback to use 237 // the prinicpal of the document to test the permission. 238 RefPtr<BrowsingContext> bc = mDocument->GetBrowsingContext(); 239 240 if ((info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin || 241 info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) && 242 bc) { 243 RefPtr<WindowContext> topWC = bc->GetTopWindowContext(); 244 245 if (topWC && topWC->IsInProcess()) { 246 // If the top-level window context is in the same process, we directly get 247 // the node principal from the top-level document to test the permission. 248 // We cannot check the lists in the window context in this case since the 249 // 'perm-changed' could be notified in the iframe before the top-level in 250 // certain cases, for example, request permissions in first-party iframes. 251 // In this case, the list in window context hasn't gotten updated, so it 252 // would has an out-dated value until the top-level window get the 253 // observer. So, we have to test permission manager directly if we can. 254 RefPtr<Document> topDoc = topWC->GetBrowsingContext()->GetDocument(); 255 256 if (topDoc) { 257 principal = topDoc->NodePrincipal(); 258 } 259 } else if (topWC) { 260 // Get the delegated permissions from the top-level window context. 261 DelegatedPermissionList list = 262 aExactHostMatch ? topWC->GetDelegatedExactHostMatchPermissions() 263 : topWC->GetDelegatedPermissions(); 264 size_t idx = std::distance(sPermissionsMap, info); 265 *aPermission = list.mPermissions[idx]; 266 return NS_OK; 267 } 268 } 269 270 return (mPermissionManager->*testPermission)(principal, aType, aPermission); 271 } 272 273 nsresult PermissionDelegateHandler::GetPermissionForPermissionsAPI( 274 const nsACString& aType, uint32_t* aPermission) { 275 return GetPermission(aType, aPermission, false); 276 } 277 278 void PermissionDelegateHandler::PopulateAllDelegatedPermissions() { 279 MOZ_ASSERT(mDocument); 280 MOZ_ASSERT(mPermissionManager); 281 282 // We only populate the delegated permissions for the top-level content. 283 if (!mDocument->IsTopLevelContentDocument()) { 284 return; 285 } 286 287 RefPtr<WindowContext> wc = mDocument->GetWindowContext(); 288 NS_ENSURE_TRUE_VOID(wc && !wc->IsDiscarded()); 289 290 DelegatedPermissionList list; 291 DelegatedPermissionList exactHostMatchList; 292 293 for (const auto& perm : sPermissionsMap) { 294 size_t idx = std::distance(sPermissionsMap, &perm); 295 296 nsDependentCString type(perm.mPermissionName); 297 // Populate the permission. 298 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; 299 (void)mPermissionManager->TestPermissionFromPrincipal(mPrincipal, type, 300 &permission); 301 list.mPermissions[idx] = permission; 302 303 // Populate the exact-host-match permission. 304 permission = nsIPermissionManager::UNKNOWN_ACTION; 305 (void)mPermissionManager->TestExactPermissionFromPrincipal(mPrincipal, type, 306 &permission); 307 exactHostMatchList.mPermissions[idx] = permission; 308 } 309 310 WindowContext::Transaction txn; 311 txn.SetDelegatedPermissions(list); 312 txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList); 313 MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc)); 314 } 315 316 void PermissionDelegateHandler::UpdateDelegatedPermission( 317 const nsACString& aType) { 318 MOZ_ASSERT(mDocument); 319 MOZ_ASSERT(mPermissionManager); 320 321 // We only update the delegated permission for the top-level content. 322 if (!mDocument->IsTopLevelContentDocument()) { 323 return; 324 } 325 326 RefPtr<WindowContext> wc = mDocument->GetWindowContext(); 327 NS_ENSURE_TRUE_VOID(wc); 328 329 const DelegateInfo* info = 330 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType)); 331 if (!info) { 332 return; 333 } 334 size_t idx = std::distance(sPermissionsMap, info); 335 336 WindowContext::Transaction txn; 337 bool changed = false; 338 DelegatedPermissionList list = wc->GetDelegatedPermissions(); 339 340 if (UpdateDelegatePermissionInternal( 341 list, aType, idx, 342 &nsIPermissionManager::TestPermissionFromPrincipal)) { 343 txn.SetDelegatedPermissions(list); 344 changed = true; 345 } 346 347 DelegatedPermissionList exactHostMatchList = 348 wc->GetDelegatedExactHostMatchPermissions(); 349 350 if (UpdateDelegatePermissionInternal( 351 exactHostMatchList, aType, idx, 352 &nsIPermissionManager::TestExactPermissionFromPrincipal)) { 353 txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList); 354 changed = true; 355 } 356 357 // We only commit if there is any change of permissions. 358 if (changed) { 359 MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc)); 360 } 361 } 362 363 bool PermissionDelegateHandler::UpdateDelegatePermissionInternal( 364 PermissionDelegateHandler::DelegatedPermissionList& aList, 365 const nsACString& aType, size_t aIdx, 366 nsresult (NS_STDCALL nsIPermissionManager::*aTestFunc)(nsIPrincipal*, 367 const nsACString&, 368 uint32_t*)) { 369 MOZ_ASSERT(aTestFunc); 370 MOZ_ASSERT(mPermissionManager); 371 MOZ_ASSERT(mPrincipal); 372 373 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; 374 (void)(mPermissionManager->*aTestFunc)(mPrincipal, aType, &permission); 375 376 if (aList.mPermissions[aIdx] != permission) { 377 aList.mPermissions[aIdx] = permission; 378 return true; 379 } 380 381 return false; 382 } 383 384 } // namespace mozilla