Autogenerated with:
cp dom/.clang-format netwerk/ && mach format netwerk/**.{cpp,h,mm}
Manual fixes:
* mozurl/cbindgen.toml (including headers earlier)
* NetlinkService.h (forward-declaring a necessary struct)
* nsIWebSocketChannel.idl (forward-declaring OriginAttributes).
* nsAsyncRedirectVerifyHelper.h (forward-declaring nsIEventTarget)
* CapsuleEncoder.h gets a missing Capsule forward-decl.
* WebSocketConnectionBase.h gets some missing includes.
* HappyEyeballsConnectionAttempt gets some missing includes.
* nsUDPSocket missed nsSocketTransportService2.h for OnSocketThread().
* HttpInfo missed a forward declaration.
* MockHttpAuth missed an nsCOMPtr include.
* nsHttpAuthManager missed a forward declaration for nsIHttpAuthCache.
* WebTransportSessionProxy was using mozilla::Mutex in a member without
including it.
* win32 fixes to keep include order because windows headers suck.
* nsInputStreamPump needs an Atomics.h include.
Differential Revision: https://phabricator.services.mozilla.com/D311696
273 lines
9.4 KiB
C++
273 lines
9.4 KiB
C++
/* 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/FeaturePolicy.h"
|
|
#include "mozilla/dom/WindowGlobalParent.h"
|
|
#include "mozilla/glean/NetwerkMetrics.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsIConsoleService.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<dom::ClientInfo> 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<mozilla::dom::WindowGlobalParent> 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<JS::Value> 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 Feature 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 feature policy checks
|
|
RefPtr<dom::CanonicalBrowsingContext> 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<dom::FeaturePolicyInfo> fpInfo = bc->GetContainerFeaturePolicy();
|
|
// Feature Policy is populated in the canonical browsing context via
|
|
// HTMLIFrameElement::MaybeStoreCrossOriginFeaturePolicy() (for <iframe>)
|
|
// nsObjectLoadingContent::MaybeStoreCrossOriginFeaturePolicy() (for
|
|
// <object>/<embed>)
|
|
// Hence, it's safe to ignore feature policy when it's missing as that
|
|
// would only mean the request is from a top-level document, which should
|
|
// be allowed to request local network access without being blocked by
|
|
// feature policy.
|
|
if (fpInfo.isSome()) {
|
|
nsAutoString featureName;
|
|
if (mType.Equals(LOOPBACK_NETWORK_PERMISSION_KEY)) {
|
|
featureName = u"loopback-network"_ns;
|
|
} else {
|
|
featureName = u"local-network"_ns;
|
|
}
|
|
|
|
if (fpInfo->mInheritedDeniedFeatureNames.Contains(featureName)) {
|
|
NS_WARNING("Feature policy denying the request");
|
|
return Cancel();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if the domain should skip LNA checks
|
|
if (mPrincipal && gIOService) {
|
|
nsAutoCString origin;
|
|
nsresult rv = mPrincipal->GetAsciiHost(origin);
|
|
if (NS_SUCCEEDED(rv) && !origin.IsEmpty()) {
|
|
if (gIOService->ShouldSkipDomainForLNA(origin)) {
|
|
// Domain is in the skip list, grant permission automatically
|
|
return Allow(JS::UndefinedHandleValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
PromptResult pr = CheckPromptPrefs();
|
|
if (pr == PromptResult::Granted) {
|
|
return Allow(JS::UndefinedHandleValue);
|
|
}
|
|
|
|
if (pr == PromptResult::Denied) {
|
|
return Cancel();
|
|
}
|
|
|
|
// For shared and service workers, do not show a permission prompt.
|
|
// Only grant access if the origin already has a persistent LNA permission.
|
|
Maybe<dom::ClientInfo> clientInfo = mLoadInfo->GetClientInfo();
|
|
if (clientInfo.isSome() &&
|
|
(clientInfo->Type() == dom::ClientType::Sharedworker ||
|
|
clientInfo->Type() == dom::ClientType::Serviceworker)) {
|
|
nsCOMPtr<nsIPermissionManager> permMgr =
|
|
mozilla::components::PermissionManager::Service();
|
|
if (!permMgr || !mPrincipal) {
|
|
NS_WARNING(
|
|
"LNA worker permission check failed: no permission manager or "
|
|
"principal");
|
|
return Cancel();
|
|
}
|
|
uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
|
|
nsresult rv =
|
|
permMgr->TestPermissionFromPrincipal(mPrincipal, mType, &permission);
|
|
if (NS_SUCCEEDED(rv) && permission == nsIPermissionManager::ALLOW_ACTION) {
|
|
return Allow(JS::UndefinedHandleValue);
|
|
}
|
|
// Log the denial to the browser console so developers can diagnose why
|
|
// worker fetch requests are being blocked.
|
|
nsCOMPtr<nsIConsoleService> console =
|
|
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
|
if (console && mPrincipal) {
|
|
nsAutoCString origin;
|
|
mPrincipal->GetOrigin(origin);
|
|
nsAutoString msg;
|
|
msg.AppendLiteral("Local Network Access blocked: worker from origin ");
|
|
msg.Append(NS_ConvertUTF8toUTF16(origin));
|
|
msg.AppendLiteral(" attempted ");
|
|
msg.Append(NS_ConvertUTF8toUTF16(mType));
|
|
msg.AppendLiteral(" access but no persistent permission was granted.");
|
|
console->LogStringMessage(msg.get());
|
|
}
|
|
return Cancel();
|
|
}
|
|
|
|
// Ensure we have a top-level principal before showing the permission prompt
|
|
if (!mTopLevelPrincipal) {
|
|
NS_WARNING("Cannot show permission prompt without top-level principal");
|
|
return Cancel();
|
|
}
|
|
|
|
if (NS_SUCCEEDED(
|
|
dom::nsContentPermissionUtils::AskPermission(this, mWindow))) {
|
|
// Here we could be getting synchronous callback from the prompts depending
|
|
// on whether there is already a permission for this or not. If we have a
|
|
// permission, we will get a synchronous callback Allow/Deny and async if
|
|
// we don't have a permission yet and waiting for user permission.
|
|
return NS_OK;
|
|
}
|
|
|
|
return Cancel();
|
|
}
|
|
|
|
} // namespace mozilla::net
|