/* 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 "LNAPermissionRequest.h" #include "mozilla/Components.h" #include "mozilla/Preferences.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/ClientInfo.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/PermissionsPolicy.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/glean/NetwerkMetrics.h" #include "nsContentUtils.h" #include "nsGlobalWindowInner.h" #include "nsIConsoleService.h" #include "nsIContentPolicy.h" #include "nsIIOService.h" #include "nsIOService.h" #include "nsIPermissionManager.h" #include "nsPIDOMWindow.h" #include "xpcpublic.h" namespace mozilla::net { //------------------------------------------------- // LNA Permission Requests //------------------------------------------------- NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(LNAPermissionRequest, ContentPermissionRequestBase) NS_IMPL_CYCLE_COLLECTION_INHERITED(LNAPermissionRequest, ContentPermissionRequestBase, mBrowsingContext) LNAPermissionRequest::LNAPermissionRequest(PermissionPromptCallback&& aCallback, nsILoadInfo* aLoadInfo, const nsACString& aType) : dom::ContentPermissionRequestBase( aLoadInfo->GetLoadingPrincipal(), nullptr, (aType.Equals(LOOPBACK_NETWORK_PERMISSION_KEY) ? "network.loopback-network"_ns : "network.localnetwork"_ns), aType), mPermissionPromptCallback(std::move(aCallback)) { MOZ_ASSERT(aLoadInfo); aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(mPrincipal)); aLoadInfo->GetBrowsingContext(getter_AddRefs(mBrowsingContext)); // For worker-initiated requests, the browsing context is null. // Fall back to the associated browsing context which points to the // worker's parent document's BC. if (!mBrowsingContext) { Maybe clientInfo = aLoadInfo->GetClientInfo(); if (clientInfo.isSome() && clientInfo->Type() != dom::ClientType::Window) { aLoadInfo->GetAssociatedBrowsingContext(getter_AddRefs(mBrowsingContext)); } } if (mBrowsingContext && mBrowsingContext->Top()) { if (mBrowsingContext->Top()->Canonical()) { RefPtr topWindowGlobal = mBrowsingContext->Top()->Canonical()->GetCurrentWindowGlobal(); if (topWindowGlobal) { mTopLevelPrincipal = topWindowGlobal->DocumentPrincipal(); } } } if (!mTopLevelPrincipal && xpc::IsInAutomation()) { // In test environments, the browsing context may not be fully set up. // Fall back to triggering principal to allow tests to proceed. mTopLevelPrincipal = mPrincipal; } mLoadInfo = aLoadInfo; MOZ_ASSERT(mPrincipal); } NS_IMETHODIMP LNAPermissionRequest::GetElement(mozilla::dom::Element** aElement) { NS_ENSURE_ARG_POINTER(aElement); if (!mBrowsingContext) { return NS_ERROR_FAILURE; } return mBrowsingContext->GetTopFrameElement(aElement); } // callback when the permission request is denied NS_IMETHODIMP LNAPermissionRequest::Cancel() { // callback to the http channel on the prompt failure result mPermissionPromptCallback(false, mType, mPromptWasShown); return NS_OK; } // callback when the permission request is allowed NS_IMETHODIMP LNAPermissionRequest::Allow(JS::Handle aChoices) { // callback to the http channel on the prompt success result mPermissionPromptCallback(true, mType, mPromptWasShown); return NS_OK; } // callback when the permission prompt is shown NS_IMETHODIMP LNAPermissionRequest::NotifyShown() { // Mark that the prompt was shown to the user mPromptWasShown = true; // Record telemetry for permission prompts shown to users // Skip telemetry if we don't have both principals (e.g., in test // environments) if (!mPrincipal || !mTopLevelPrincipal) { return NS_OK; } // Check if this is a cross-origin request bool isCrossOrigin = !mPrincipal->Equals(mTopLevelPrincipal); if (mType.Equals(LOOPBACK_NETWORK_PERMISSION_KEY)) { if (isCrossOrigin) { mozilla::glean::networking::local_network_access_prompts_shown .Get("localhost_cross_site"_ns) .Add(1); } else { mozilla::glean::networking::local_network_access_prompts_shown .Get("localhost"_ns) .Add(1); } } else if (mType.Equals(LOCAL_NETWORK_PERMISSION_KEY)) { if (isCrossOrigin) { mozilla::glean::networking::local_network_access_prompts_shown .Get("local_network_cross_site"_ns) .Add(1); } else { mozilla::glean::networking::local_network_access_prompts_shown .Get("local_network"_ns) .Add(1); } } return NS_OK; } nsresult LNAPermissionRequest::RequestPermission() { MOZ_ASSERT(NS_IsMainThread()); // Enforce Permissions Policy for Local Network Access (Bug 1978550) if (!mLoadInfo) { NS_WARNING("LNA permission request without load info"); return Cancel(); } // Retrieve the canonical browsing context for permissions policy checks RefPtr bc; if (mBrowsingContext) { bc = mBrowsingContext->Canonical(); } if (!bc) { // for unit tests, we may not have a browsing context, so we dont treat this // as hard error for automation if (!xpc::IsInAutomation()) { NS_WARNING("local network access without browsing context"); return Cancel(); } } else { Maybe fpInfo = bc->GetContainerPermissionsPolicy(); // Permissions Policy is populated in the canonical browsing context via // HTMLIFrameElement::MaybeStoreCrossOriginPermissionsPolicy() (for //