Bug 1483631 - Prompt with both first party and third party origin if we are delegating permission with allows all feature policy. r=baku

Differential Revision: https://phabricator.services.mozilla.com/D51839

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Thomas Nguyen 2019-12-04 15:39:26 +00:00
Родитель a0b817ac65
Коммит 1e82cdc161
28 изменённых файлов: 517 добавлений и 112 удалений

Просмотреть файл

@ -2,7 +2,8 @@
support-files=
head.js
permissions.html
temporary_permissions_subframe.html
temporary_permissions_frame.html
[browser_canvas_fingerprinting_resistance.js]
skip-if = debug || os == "linux" && asan # Bug 1522069
[browser_permissions.js]
@ -21,7 +22,6 @@ support-files=
[browser_reservedkey.js]
[browser_temporary_permissions.js]
support-files =
temporary_permissions_subframe.html
../webrtc/get_user_media.html
[browser_autoplay_blocked.js]
support-files =

Просмотреть файл

@ -8,6 +8,10 @@ const CROSS_SUBFRAME_PAGE =
getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
"temporary_permissions_subframe.html";
const CROSS_FRAME_PAGE =
getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
"temporary_permissions_frame.html";
const PromptResult = {
ALLOW: "allow",
DENY: "deny",
@ -18,6 +22,93 @@ var Perms = Services.perms;
var uri = NetUtil.newURI(ORIGIN);
var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});
async function checkNotificationBothOrigins(
firstPartyOrigin,
thirdPartyOrigin
) {
// Notification is shown, check label and deny to clean
let popuphidden = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphidden"
);
let notification = PopupNotifications.panel.firstElementChild;
// Check the label of the notificaiton should be the first party
is(
PopupNotifications.getNotification("geolocation").options.name,
firstPartyOrigin,
"Use first party's origin"
);
// Check the second name of the notificaiton should be the third party
is(
PopupNotifications.getNotification("geolocation").options.secondName,
thirdPartyOrigin,
"Use third party's origin"
);
// Check remember checkbox is hidden
let checkbox = notification.checkbox;
ok(!!checkbox, "checkbox is present");
ok(checkbox.hidden, "checkbox is not visible");
ok(!checkbox.checked, "checkbox not checked");
EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
await popuphidden;
}
async function checkGeolocation(browser, frameId, expect) {
let waitForPrompt = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
let isPrompt = expect == PromptResult.PROMPT;
await SpecialPowers.spawn(
browser,
[{ frameId, expect, isPrompt }],
async args => {
let frame = content.document.getElementById(args.frameId);
let waitForNoPrompt = new Promise(resolve => {
function onMessage(event) {
// Check the result right here because there's no notification
Assert.equal(
event.data,
args.expect,
"Correct expectation for third party"
);
content.window.removeEventListener("message", onMessage);
resolve();
}
if (!args.isPrompt) {
content.window.addEventListener("message", onMessage);
}
});
await content.SpecialPowers.spawn(frame, [], async () => {
const { E10SUtils } = ChromeUtils.import(
"resource://gre/modules/E10SUtils.jsm"
);
E10SUtils.wrapHandlingUserInput(this.content, true, function() {
let frameDoc = this.content.document;
frameDoc.getElementById("geo").click();
});
});
if (!args.isPrompt) {
await waitForNoPrompt;
}
}
);
if (isPrompt) {
await waitForPrompt;
}
}
add_task(async function setup() {
await new Promise(r => {
SpecialPowers.pushPrefEnv(
@ -48,28 +139,7 @@ add_task(async function testUseTempPermissionsFirstParty() {
browser
);
// Request a permission.
await ContentTask.spawn(browser, uri.host, async function(host0) {
let frame = content.document.getElementById("frame");
function onMessage(event) {
// Check the result right here because there's no notification
is(event.data, "deny", "Expected deny for third party");
content.window.removeEventListener("message", onMessage);
}
content.window.addEventListener("message", onMessage);
await content.SpecialPowers.spawn(frame, [host0], async function(host) {
const { E10SUtils } = ChromeUtils.import(
"resource://gre/modules/E10SUtils.jsm"
);
E10SUtils.wrapHandlingUserInput(this.content, true, function() {
let frameDoc = this.content.document;
frameDoc.getElementById("geo").click();
});
});
});
await checkGeolocation(browser, "frame", PromptResult.DENY);
SitePermissions.removeFromPrincipal(principal, "geo", browser);
});
@ -83,48 +153,9 @@ add_task(async function testUsePersistentPermissionsFirstParty() {
) {
async function checkPermission(aPermission, aExpect) {
PermissionTestUtils.add(uri, "geo", aPermission);
let waitForPrompt = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
// Request a permission.
await ContentTask.spawn(
browser,
{ host: uri.host, expect: aExpect },
async function(args) {
let frame = content.document.getElementById("frame");
if (args.expect != "prompt") {
function onMessage(event) {
// Check the result right here because there's no notification
is(
event.data,
args.expect,
"Expected correct permission for third party"
);
content.window.removeEventListener("message", onMessage);
}
content.window.addEventListener("message", onMessage);
}
await content.SpecialPowers.spawn(frame, [args.host], async function(
host
) {
const { E10SUtils } = ChromeUtils.import(
"resource://gre/modules/E10SUtils.jsm"
);
E10SUtils.wrapHandlingUserInput(this.content, true, function() {
let frameDoc = this.content.document;
frameDoc.getElementById("geo").click();
});
});
}
);
await checkGeolocation(browser, "frame", aExpect);
if (aExpect == PromptResult.PROMPT) {
await waitForPrompt;
// Notification is shown, check label and deny to clean
let popuphidden = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
@ -153,3 +184,74 @@ add_task(async function testUsePersistentPermissionsFirstParty() {
await checkPermission(Perms.ALLOW_ACTION, PromptResult.ALLOW);
});
});
// Test that we should prompt if we are in unsafe permission delegation. The
// prompt popup should include both first and third party origin.
add_task(async function testPromptInMaybeUnsafePermissionDelegation() {
await BrowserTestUtils.withNewTab(CROSS_SUBFRAME_PAGE, async function(
browser
) {
// Persistent allow top level origin
PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);
await checkGeolocation(browser, "frameAllowsAll", PromptResult.PROMPT);
await checkNotificationBothOrigins(uri.host, "example.org");
SitePermissions.removeFromPrincipal(null, "geo", browser);
PermissionTestUtils.remove(uri, "geo");
});
});
// Test that we should prompt if we are in unsafe permission delegation and
// change location to origin which is not explicitly trusted. The prompt popup
// should include both first and third party origin.
add_task(async function testPromptChangeLocatioUnsafePermissionDelegation() {
await BrowserTestUtils.withNewTab(CROSS_SUBFRAME_PAGE, async function(
browser
) {
// Persistent allow top level origin
PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);
// Request change location.
await ContentTask.spawn(browser, { host: uri.host }, async function(args) {
let frame = content.document.getElementById("frameAllowsAll");
await new Promise(resolve => {
function listener() {
frame.removeEventListener("load", listener, true);
resolve();
}
frame.addEventListener("load", listener, true);
frame.contentWindow.location =
"https://test1.example.com/browser/browser/base/content/test/permissions/permissions.html";
});
});
await checkGeolocation(browser, "frameAllowsAll", PromptResult.PROMPT);
await checkNotificationBothOrigins(uri.host, "test1.example.com");
SitePermissions.removeFromPrincipal(null, "geo", browser);
PermissionTestUtils.remove(uri, "geo");
});
});
// If we are in unsafe permission delegation and the origin is explicitly
// trusted in ancestor chain. Do not need prompt
add_task(async function testExplicitlyAllowedInChain() {
await BrowserTestUtils.withNewTab(CROSS_FRAME_PAGE, async function(browser) {
// Persistent allow top level origin
PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);
const iframeAncestor = await SpecialPowers.spawn(browser, [], () => {
return content.document.getElementById("frameAncestor").browsingContext;
});
await checkGeolocation(
iframeAncestor,
"frameAllowsAll",
PromptResult.ALLOW
);
PermissionTestUtils.remove(uri, "geo");
});
});

Просмотреть файл

@ -21,8 +21,9 @@ function requestPush() {
function requestGeo() {
return navigator.geolocation.getCurrentPosition(() => {
parent.postMessage("allow", "*");
}, () => {
parent.postMessage("deny", "*");
}, error => {
// PERMISSION_DENIED = 1
parent.postMessage(error.code == 1 ? "deny" : "allow", "*");
});
}

Просмотреть файл

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Permissions Subframe Test</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
</head>
<body>
<iframe id="frameAncestor"
src="https://test1.example.com/browser/browser/base/content/test/permissions/temporary_permissions_subframe.html"
allow="geolocation 'src' https://example.org"></iframe>
</body>
</html>

Просмотреть файл

@ -5,6 +5,7 @@
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
</head>
<body>
<iframe id="frame" src="https://example.org/browser/browser/base/content/test/permissions/permissions.html" allow="geolocation"/>
<iframe id="frame" src="https://example.org/browser/browser/base/content/test/permissions/permissions.html" allow="geolocation"></iframe>
<iframe id="frameAllowsAll" src="https://example.org/browser/browser/base/content/test/permissions/permissions.html" allow="geolocation *"></iframe>
</body>
</html>

Просмотреть файл

@ -623,6 +623,9 @@ geolocation.dontAllowLocation=Dont Allow
geolocation.dontAllowLocation.accesskey=n
geolocation.shareWithSite3=Will you allow %S to access your location?
geolocation.shareWithFile3=Will you allow this local file to access your location?
# LOCALIZATION NOTE(geolocation.shareWithSiteUnsafeDelegation):
# %1$S is the first party origin, %2$S is the third party origin.
geolocation.shareWithSiteUnsafeDelegation=Will you allow %1$S to give %2$S permission to access your location?
geolocation.remember=Remember this decision
# Persistent storage UI

Просмотреть файл

@ -148,20 +148,6 @@ var PermissionPromptPrototype = {
throw new Error("Not implemented.");
},
/**
* Provides the preferred name to use in the permission popups,
* based on the principal URI (the URI.hostPort for any URI scheme
* besides the moz-extension one which should default to the
* extension name).
*/
get principalName() {
if (this.principal.addonPolicy) {
return this.principal.addonPolicy.name;
}
return this.principal.URI.hostPort;
},
/**
* Indicates the type of the permission request from content. This type might
* be different from the permission key used in the permissions database.
@ -283,6 +269,20 @@ var PermissionPromptPrototype = {
throw new Error("Not implemented.");
},
/**
* Provides the preferred name to use in the permission popups,
* based on the principal URI (the URI.hostPort for any URI scheme
* besides the moz-extension one which should default to the
* extension name).
*/
getPrincipalName(principal = this.principal) {
if (principal.addonPolicy) {
return principal.addonPolicy.name;
}
return principal.URI.hostPort;
},
/**
* This will be called if the request is to be cancelled.
*
@ -421,7 +421,10 @@ var PermissionPromptPrototype = {
return;
}
if (state == SitePermissions.ALLOW) {
if (
state == SitePermissions.ALLOW &&
!this.request.maybeUnsafePermissionDelegate
) {
this.allow();
return;
}
@ -719,11 +722,8 @@ var PermissionPromptForRequestPrototype = {
},
get principal() {
if (Services.prefs.getBoolPref("permissions.delegate.enable", false)) {
let request = this.request.QueryInterface(Ci.nsIContentPermissionRequest);
return request.getDelegatePrincipal(this.type);
}
return this.request.principal;
let request = this.request.QueryInterface(Ci.nsIContentPermissionRequest);
return request.getDelegatePrincipal(this.type);
},
cancel() {
@ -768,7 +768,7 @@ GeolocationPermissionPrompt.prototype = {
let options = {
learnMoreURL: Services.urlFormatter.formatURLPref(pref),
displayURI: false,
name: this.principalName,
name: this.getPrincipalName(),
};
if (this.principal.schemeIs("file")) {
@ -780,6 +780,12 @@ GeolocationPermissionPrompt.prototype = {
};
}
if (this.request.maybeUnsafePermissionDelegate) {
// Second name should be the third party origin
options.secondName = this.getPrincipalName(this.request.principal);
options.checkbox = { show: false };
}
if (options.checkbox.show) {
options.checkbox.label = gBrowserBundle.GetStringFromName(
"geolocation.remember"
@ -802,6 +808,13 @@ GeolocationPermissionPrompt.prototype = {
return gBrowserBundle.GetStringFromName("geolocation.shareWithFile3");
}
if (this.request.maybeUnsafePermissionDelegate) {
return gBrowserBundle.formatStringFromName(
"geolocation.shareWithSiteUnsafeDelegation",
["<>", "{}"]
);
}
return gBrowserBundle.formatStringFromName("geolocation.shareWithSite3", [
"<>",
]);
@ -923,7 +936,7 @@ DesktopNotificationPermissionPrompt.prototype = {
return {
learnMoreURL,
displayURI: false,
name: this.principalName,
name: this.getPrincipalName(),
};
},
@ -1026,7 +1039,7 @@ PersistentStoragePermissionPrompt.prototype = {
return {
learnMoreURL,
displayURI: false,
name: this.principalName,
name: this.getPrincipalName(),
};
},
@ -1115,7 +1128,7 @@ MIDIPermissionPrompt.prototype = {
// TODO (bug 1433235) We need a security/permissions explanation URL for this
let options = {
displayURI: false,
name: this.principalName,
name: this.getPrincipalName(),
};
if (this.principal.schemeIs("file")) {

Просмотреть файл

@ -8337,6 +8337,10 @@ void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
mSynchronousDOMContentLoaded = true;
}
nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
return GetPermissionDelegateHandler();
}
Document* Document::RequestExternalResource(
nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
ExternalResourceLoad** aPendingLoad) {

Просмотреть файл

@ -134,6 +134,7 @@ class nsIAppWindow;
class nsXULPrototypeDocument;
class nsXULPrototypeElement;
class PermissionDelegateHandler;
class nsIPermissionDelegateHandler;
struct nsFont;
namespace mozilla {
@ -4187,6 +4188,8 @@ class Document : public nsINode,
void SetPrototypeDocument(nsXULPrototypeDocument* aPrototype);
nsIPermissionDelegateHandler* PermDelegateHandler();
// Returns true if we use overlay scrollbars on the system wide or on the
// given document.
static bool UseOverlayScrollbars(const Document* aDocument);

Просмотреть файл

@ -124,6 +124,7 @@ class ContentPermissionRequestParent : public PContentPermissionRequestParent {
const nsTArray<PermissionRequest>& aRequests, Element* aElement,
nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
const bool aIsHandlingUserInput,
const bool aMaybeUnsafePermissionDelegate,
const bool aUserHadInteractedWithDocument,
const DOMTimeStamp aDocumentDOMContentLoadedTimestamp);
virtual ~ContentPermissionRequestParent();
@ -134,6 +135,7 @@ class ContentPermissionRequestParent : public PContentPermissionRequestParent {
nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
nsCOMPtr<Element> mElement;
bool mIsHandlingUserInput;
bool mMaybeUnsafePermissionDelegate;
bool mUserHadInteractedWithDocument;
DOMTimeStamp mDocumentDOMContentLoadedTimestamp;
RefPtr<nsContentPermissionRequestProxy> mProxy;
@ -152,7 +154,8 @@ class ContentPermissionRequestParent : public PContentPermissionRequestParent {
ContentPermissionRequestParent::ContentPermissionRequestParent(
const nsTArray<PermissionRequest>& aRequests, Element* aElement,
nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
const bool aIsHandlingUserInput, const bool aUserHadInteractedWithDocument,
const bool aIsHandlingUserInput, const bool aMaybeUnsafePermissionDelegate,
const bool aUserHadInteractedWithDocument,
const DOMTimeStamp aDocumentDOMContentLoadedTimestamp) {
MOZ_COUNT_CTOR(ContentPermissionRequestParent);
@ -161,6 +164,7 @@ ContentPermissionRequestParent::ContentPermissionRequestParent(
mElement = aElement;
mRequests = aRequests;
mIsHandlingUserInput = aIsHandlingUserInput;
mMaybeUnsafePermissionDelegate = aMaybeUnsafePermissionDelegate;
mUserHadInteractedWithDocument = aUserHadInteractedWithDocument;
mDocumentDOMContentLoadedTimestamp = aDocumentDOMContentLoadedTimestamp;
}
@ -329,12 +333,14 @@ PContentPermissionRequestParent*
nsContentPermissionUtils::CreateContentPermissionRequestParent(
const nsTArray<PermissionRequest>& aRequests, Element* aElement,
nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
const bool aIsHandlingUserInput, const bool aUserHadInteractedWithDocument,
const bool aIsHandlingUserInput, const bool aMaybeUnsafePermissionDelegate,
const bool aUserHadInteractedWithDocument,
const DOMTimeStamp aDocumentDOMContentLoadedTimestamp,
const TabId& aTabId) {
PContentPermissionRequestParent* parent = new ContentPermissionRequestParent(
aRequests, aElement, aPrincipal, aTopLevelPrincipal, aIsHandlingUserInput,
aUserHadInteractedWithDocument, aDocumentDOMContentLoadedTimestamp);
aMaybeUnsafePermissionDelegate, aUserHadInteractedWithDocument,
aDocumentDOMContentLoadedTimestamp);
ContentPermissionRequestParentMap()[parent] = aTabId;
return parent;
@ -374,6 +380,11 @@ nsresult nsContentPermissionUtils::AskPermission(
rv = aRequest->GetIsHandlingUserInput(&isHandlingUserInput);
NS_ENSURE_SUCCESS(rv, rv);
bool maybeUnsafePermissionDelegate;
rv = aRequest->GetMaybeUnsafePermissionDelegate(
&maybeUnsafePermissionDelegate);
NS_ENSURE_SUCCESS(rv, rv);
bool userHadInteractedWithDocument;
rv = aRequest->GetUserHadInteractedWithDocument(
&userHadInteractedWithDocument);
@ -391,8 +402,8 @@ nsresult nsContentPermissionUtils::AskPermission(
ContentChild::GetSingleton()->SendPContentPermissionRequestConstructor(
req, permArray, IPC::Principal(principal),
IPC::Principal(topLevelPrincipal), isHandlingUserInput,
userHadInteractedWithDocument, documentDOMContentLoadedTimestamp,
child->GetTabId());
maybeUnsafePermissionDelegate, userHadInteractedWithDocument,
documentDOMContentLoadedTimestamp, child->GetTabId());
ContentPermissionRequestChildMap()[req.get()] = child->GetTabId();
req->Sendprompt();
@ -553,6 +564,7 @@ ContentPermissionRequestBase::ContentPermissionRequestBase(
mType(aType),
mIsHandlingUserInput(UserActivation::IsHandlingUserInput()),
mUserHadInteractedWithDocument(false),
mMaybeUnsafePermissionDelegate(false),
mDocumentDOMContentLoadedTimestamp(0) {
if (!aWindow) {
return;
@ -564,6 +576,12 @@ ContentPermissionRequestBase::ContentPermissionRequestBase(
}
mPermissionHandler = doc->GetPermissionDelegateHandler();
if (mPermissionHandler) {
nsTArray<nsCString> types;
types.AppendElement(mType);
mPermissionHandler->MaybeUnsafePermissionDelegate(
types, &mMaybeUnsafePermissionDelegate);
}
mUserHadInteractedWithDocument = doc->UserHasInteracted();
@ -588,6 +606,13 @@ ContentPermissionRequestBase::GetDelegatePrincipal(
aRequestingPrincipal);
}
NS_IMETHODIMP
ContentPermissionRequestBase::GetMaybeUnsafePermissionDelegate(
bool* aMaybeUnsafePermissionDelegate) {
*aMaybeUnsafePermissionDelegate = mMaybeUnsafePermissionDelegate;
return NS_OK;
}
NS_IMETHODIMP
ContentPermissionRequestBase::GetTopLevelPrincipal(
nsIPrincipal** aRequestingPrincipal) {
@ -967,6 +992,17 @@ nsContentPermissionRequestProxy::GetIsHandlingUserInput(
return NS_OK;
}
NS_IMETHODIMP
nsContentPermissionRequestProxy::GetMaybeUnsafePermissionDelegate(
bool* aMaybeUnsafePermissionDelegate) {
NS_ENSURE_ARG_POINTER(aMaybeUnsafePermissionDelegate);
if (mParent == nullptr) {
return NS_ERROR_FAILURE;
}
*aMaybeUnsafePermissionDelegate = mParent->mMaybeUnsafePermissionDelegate;
return NS_OK;
}
NS_IMETHODIMP
nsContentPermissionRequestProxy::GetUserHadInteractedWithDocument(
bool* aUserHadInteractedWithDocument) {

Просмотреть файл

@ -73,6 +73,7 @@ class nsContentPermissionUtils {
const nsTArray<PermissionRequest>& aRequests, Element* aElement,
nsIPrincipal* aPrincipal, nsIPrincipal* aTopLevelPrincipal,
const bool aIsHandlingUserInput,
const bool aMaybeUnsafePermissionDelegate,
const bool aUserHadInteractedWithDocument,
const DOMTimeStamp aDocumentDOMContentLoadedTimestamp,
const TabId& aTabId);
@ -126,6 +127,8 @@ class ContentPermissionRequestBase : public nsIContentPermissionRequest {
NS_IMETHOD GetWindow(mozIDOMWindow** aWindow) override;
NS_IMETHOD GetElement(mozilla::dom::Element** aElement) override;
NS_IMETHOD GetIsHandlingUserInput(bool* aIsHandlingUserInput) override;
NS_IMETHOD GetMaybeUnsafePermissionDelegate(
bool* aMaybeUnsafePermissionDelegate) override;
NS_IMETHOD GetUserHadInteractedWithDocument(
bool* aUserHadInteractedWithDocument) override;
NS_IMETHOD GetDocumentDOMContentLoadedTimestamp(
@ -169,6 +172,7 @@ class ContentPermissionRequestBase : public nsIContentPermissionRequest {
nsCString mType;
bool mIsHandlingUserInput;
bool mUserHadInteractedWithDocument;
bool mMaybeUnsafePermissionDelegate;
DOMTimeStamp mDocumentDOMContentLoadedTimestamp;
};

Просмотреть файл

@ -1858,3 +1858,6 @@ addExternalIface('XULTemplateRuleFilter', nativeType='nsIXULTemplateRuleFilter',
notflattened=True)
addExternalIface('nsISHistory', nativeType='nsISHistory', notflattened=True)
addExternalIface('ReferrerInfo', nativeType='nsIReferrerInfo')
addExternalIface('nsIPermissionDelegateHandler',
nativeType='nsIPermissionDelegateHandler',
notflattened=True)

Просмотреть файл

@ -21,6 +21,7 @@ XPIDL_SOURCES += [
'nsIDOMWindow.idl',
'nsIDOMWindowUtils.idl',
'nsIFocusManager.idl',
'nsIPermissionDelegateHandler.idl',
'nsIQueryContentEventResult.idl',
'nsIRemoteTab.idl',
'nsIServiceWorkerManager.idl',

Просмотреть файл

@ -92,6 +92,7 @@ interface nsIContentPermissionRequest : nsISupports {
readonly attribute boolean userHadInteractedWithDocument;
readonly attribute DOMTimeStamp documentDOMContentLoadedTimestamp;
readonly attribute boolean maybeUnsafePermissionDelegate;
/**
* The requester to get the required information of
* the window.

Просмотреть файл

@ -0,0 +1,27 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* This file contains an interface to the Permission Delegate Handler,
*/
#include "nsISupports.idl"
interface nsIPrincipal;
[scriptable, builtinclass, uuid(07611dc6-bf4d-4d8a-a64b-f3a5904dddc7)]
interface nsIPermissionDelegateHandler : nsISupports
{
/*
* Return true if we are delegating permission to a third party which is not
* explicitly trusted. An orgin is not explicitly trusted means it is not
* presented in the Feature Policy ancestor chain, via src, explicitly listed
* in allow, and it is not the top-level origin.
*
* @param aTypes the permission types to check
*/
boolean maybeUnsafePermissionDelegate(in Array<ACString> aTypes);
};

Просмотреть файл

@ -3151,8 +3151,10 @@ PContentPermissionRequestChild*
ContentChild::AllocPContentPermissionRequestChild(
const nsTArray<PermissionRequest>& aRequests,
const IPC::Principal& aPrincipal, const IPC::Principal& aTopLevelPrincipal,
const bool& aIsHandlingUserInput, const bool& aDocumentHasUserInput,
const DOMTimeStamp aPageLoadTimestamp, const TabId& aTabId) {
const bool& aIsHandlingUserInput,
const bool& aMaybeUnsafePermissionDelegate,
const bool& aDocumentHasUserInput, const DOMTimeStamp aPageLoadTimestamp,
const TabId& aTabId) {
MOZ_CRASH("unused");
return nullptr;
}

Просмотреть файл

@ -528,8 +528,10 @@ class ContentChild final
const nsTArray<PermissionRequest>& aRequests,
const IPC::Principal& aPrincipal,
const IPC::Principal& aTopLevelPrincipal,
const bool& aIsHandlingUserInput, const bool& aDocumentHasUserInput,
const DOMTimeStamp aPageLoadTimestamp, const TabId& aTabId);
const bool& aIsHandlingUserInput,
const bool& aMaybeUnsafePermissionDelegate,
const bool& aDocumentHasUserInput, const DOMTimeStamp aPageLoadTimestamp,
const TabId& aTabId);
bool DeallocPContentPermissionRequestChild(
PContentPermissionRequestChild* actor);

Просмотреть файл

@ -4613,8 +4613,10 @@ PContentPermissionRequestParent*
ContentParent::AllocPContentPermissionRequestParent(
const nsTArray<PermissionRequest>& aRequests,
const IPC::Principal& aPrincipal, const IPC::Principal& aTopLevelPrincipal,
const bool& aIsHandlingUserInput, const bool& aDocumentHasUserInput,
const DOMTimeStamp& aPageLoadTimestamp, const TabId& aTabId) {
const bool& aIsHandlingUserInput,
const bool& aMaybeUnsafePermissionDelegate,
const bool& aDocumentHasUserInput, const DOMTimeStamp& aPageLoadTimestamp,
const TabId& aTabId) {
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
RefPtr<BrowserParent> tp =
cpm->GetTopLevelBrowserParentByProcessAndTabId(this->ChildID(), aTabId);
@ -4629,7 +4631,8 @@ ContentParent::AllocPContentPermissionRequestParent(
}
return nsContentPermissionUtils::CreateContentPermissionRequestParent(
aRequests, tp->GetOwnerElement(), aPrincipal, topPrincipal,
aIsHandlingUserInput, aDocumentHasUserInput, aPageLoadTimestamp, aTabId);
aIsHandlingUserInput, aMaybeUnsafePermissionDelegate,
aDocumentHasUserInput, aPageLoadTimestamp, aTabId);
}
bool ContentParent::DeallocPContentPermissionRequestParent(

Просмотреть файл

@ -509,8 +509,10 @@ class ContentParent final
const nsTArray<PermissionRequest>& aRequests,
const IPC::Principal& aPrincipal,
const IPC::Principal& aTopLevelPrincipal,
const bool& aIsHandlingUserInput, const bool& aDocumentHasUserInput,
const DOMTimeStamp& aPageLoadTimestamp, const TabId& aTabId);
const bool& aIsHandlingUserInput,
const bool& aMaybeUnsafePermissionDelegate,
const bool& aDocumentHasUserInput, const DOMTimeStamp& aPageLoadTimestamp,
const TabId& aTabId);
bool DeallocPContentPermissionRequestParent(
PContentPermissionRequestParent* actor);

Просмотреть файл

@ -1168,9 +1168,13 @@ parent:
* principals that can live in the content process should
* provided.
*/
async PContentPermissionRequest(PermissionRequest[] aRequests, Principal aPrincipal,
Principal aTopLevelPrincipal, bool aIsHandlingUserInput,
bool aDocumentHasUserInput, uint64_t aPageLoadTimestamp, TabId tabId);
async PContentPermissionRequest(PermissionRequest[] aRequests,
Principal aPrincipal,
Principal aTopLevelPrincipal,
bool aIsHandlingUserInput,
bool aMaybeUnsafePermissionDelegate,
bool aDocumentHasUserInput,
uint64_t aPageLoadTimestamp, TabId tabId);
async ShutdownProfile(nsCString aProfile);

Просмотреть файл

@ -32,9 +32,20 @@ void FeaturePolicy::InheritPolicy(FeaturePolicy* aParentPolicy) {
RefPtr<FeaturePolicy> dest = this;
RefPtr<FeaturePolicy> src = aParentPolicy;
// Inherit origins which explicitly declared policy in chain
for (const Feature& featureInChain :
aParentPolicy->mDeclaredFeaturesInAncestorChain) {
dest->AppendToDeclaredAllowInAncestorChain(featureInChain);
}
FeaturePolicyUtils::ForEachFeature([dest, src](const char* aFeatureName) {
nsString featureName;
featureName.AppendASCII(aFeatureName);
// Store unsafe allows all (allow=*)
if (src->HasFeatureUnsafeAllowsAll(featureName)) {
dest->mParentAllowedAllFeatures.AppendElement(featureName);
}
// If the destination has a declared feature (via the HTTP header or 'allow'
// attribute) we allow the feature if the destination allows it and the
@ -76,6 +87,38 @@ bool FeaturePolicy::HasDeclaredFeature(const nsAString& aFeatureName) const {
return false;
}
bool FeaturePolicy::HasFeatureUnsafeAllowsAll(
const nsAString& aFeatureName) const {
for (const Feature& feature : mFeatures) {
if (feature.AllowsAll() && feature.Name().Equals(aFeatureName)) {
return true;
}
}
// We should look into parent too (for example, document of iframe which
// allows all, would be unsafe)
return mParentAllowedAllFeatures.Contains(aFeatureName);
}
void FeaturePolicy::AppendToDeclaredAllowInAncestorChain(
const Feature& aFeature) {
for (Feature& featureInChain : mDeclaredFeaturesInAncestorChain) {
if (featureInChain.Name().Equals(aFeature.Name())) {
MOZ_ASSERT(featureInChain.HasAllowList());
nsTArray<nsCOMPtr<nsIPrincipal>> list;
aFeature.GetAllowList(list);
for (nsIPrincipal* principal : list) {
featureInChain.AppendToAllowList(principal);
}
continue;
}
}
mDeclaredFeaturesInAncestorChain.AppendElement(aFeature);
}
void FeaturePolicy::SetDeclaredPolicy(Document* aDocument,
const nsAString& aPolicyString,
nsIPrincipal* aSelfOrigin,
@ -88,6 +131,13 @@ void FeaturePolicy::SetDeclaredPolicy(Document* aDocument,
Unused << NS_WARN_IF(!FeaturePolicyParser::ParseString(
aPolicyString, aDocument, aSelfOrigin, aSrcOrigin, mFeatures));
// Only store explicitly declared allowlist
for (const Feature& feature : mFeatures) {
if (feature.HasAllowList()) {
AppendToDeclaredAllowInAncestorChain(feature);
}
}
}
void FeaturePolicy::ResetDeclaredPolicy() {
@ -95,6 +145,7 @@ void FeaturePolicy::ResetDeclaredPolicy() {
mDeclaredString.Truncate();
mSelfOrigin = nullptr;
mSrcOrigin = nullptr;
mDeclaredFeaturesInAncestorChain.Clear();
}
JSObject* FeaturePolicy::WrapObject(JSContext* aCx,
@ -124,6 +175,19 @@ bool FeaturePolicy::AllowsFeature(const nsAString& aFeatureName,
return AllowsFeatureInternal(aFeatureName, origin);
}
bool FeaturePolicy::AllowsFeatureExplicitlyInAncestorChain(
const nsAString& aFeatureName, nsIPrincipal* aOrigin) const {
MOZ_ASSERT(aOrigin);
for (const Feature& feature : mDeclaredFeaturesInAncestorChain) {
if (feature.Name().Equals(aFeatureName)) {
return feature.AllowListContains(aOrigin);
}
}
return false;
}
bool FeaturePolicy::AllowsFeatureInternal(const nsAString& aFeatureName,
nsIPrincipal* aOrigin) const {
MOZ_ASSERT(aOrigin);

Просмотреть файл

@ -101,6 +101,20 @@ class FeaturePolicy final : public nsISupports, public nsWrapperCache {
// policy.
void ResetDeclaredPolicy();
// This method appends a feature to in-chain declared allowlist. If the name's
// feature existed in the list, we only need to append the allowlist of new
// feature to the existed one.
void AppendToDeclaredAllowInAncestorChain(const Feature& aFeature);
// This method returns true if aFeatureName is declared as "*" (allow all)
// in parent.
bool HasFeatureUnsafeAllowsAll(const nsAString& aFeatureName) const;
// This method returns true if the aFeatureName is allowed for aOrigin
// explicitly in ancestor chain,
bool AllowsFeatureExplicitlyInAncestorChain(const nsAString& aFeatureName,
nsIPrincipal* aOrigin) const;
// WebIDL internal methods.
JSObject* WrapObject(JSContext* aCx,
@ -159,6 +173,14 @@ class FeaturePolicy final : public nsISupports, public nsWrapperCache {
// current context.
nsTArray<nsString> mInheritedDeniedFeatureNames;
// This is set of feature names when the parent allows all for that feature.
nsTArray<nsString> mParentAllowedAllFeatures;
// The explicitly declared policy contains allowlist as a set of origins
// except 'none' and '*'. This set contains all explicitly declared policies
// in ancestor chain
nsTArray<Feature> mDeclaredFeaturesInAncestorChain;
// Feature policy for the current context.
nsTArray<Feature> mFeatures;

Просмотреть файл

@ -123,6 +123,51 @@ FeaturePolicyUtils::DefaultAllowListFeature(const nsAString& aFeatureName) {
return FeaturePolicyValue::eNone;
}
static bool IsSameOriginAsTop(Document* aDocument) {
MOZ_ASSERT(aDocument);
BrowsingContext* browsingContext = aDocument->GetBrowsingContext();
if (!browsingContext) {
return false;
}
nsPIDOMWindowOuter* topWindow = browsingContext->Top()->GetDOMWindow();
if (!topWindow) {
// If we don't have a DOMWindow, We are not in same origin.
return false;
}
Document* topLevelDocument = topWindow->GetExtantDoc();
if (!topLevelDocument) {
return false;
}
return NS_SUCCEEDED(
nsContentUtils::CheckSameOrigin(topLevelDocument, aDocument));
}
/* static */
bool FeaturePolicyUtils::IsFeatureUnsafeAllowedAll(
Document* aDocument, const nsAString& aFeatureName) {
MOZ_ASSERT(aDocument);
if (!StaticPrefs::dom_security_featurePolicy_enabled()) {
return false;
}
if (!aDocument->IsHTMLDocument()) {
return false;
}
FeaturePolicy* policy = aDocument->FeaturePolicy();
MOZ_ASSERT(policy);
return policy->HasFeatureUnsafeAllowsAll(aFeatureName) &&
!policy->AllowsFeatureExplicitlyInAncestorChain(
aFeatureName, policy->DefaultOrigin()) &&
!IsSameOriginAsTop(aDocument);
}
/* static */
bool FeaturePolicyUtils::IsFeatureAllowed(Document* aDocument,
const nsAString& aFeatureName) {

Просмотреть файл

@ -49,6 +49,13 @@ class FeaturePolicyUtils final {
static FeaturePolicyValue DefaultAllowListFeature(
const nsAString& aFeatureName);
// This method returns true if aFeatureName is in unsafe allowed "*" case.
// We are in "unsafe" case when there is 'allow "*"' presents for an origin
// that's not presented in the ancestor feature policy chain, via src, via
// explicitly listed in allow, and not being the top-level origin.
static bool IsFeatureUnsafeAllowedAll(Document* aDocument,
const nsAString& aFeatureName);
private:
static void ReportViolation(Document* aDocument,
const nsAString& aFeatureName);

Просмотреть файл

@ -23,6 +23,7 @@ interface URI;
interface nsIDocShell;
interface nsILoadGroup;
interface nsIReferrerInfo;
interface nsIPermissionDelegateHandler;
interface XULCommandDispatcher;
enum VisibilityState { "hidden", "visible" };
@ -676,3 +677,9 @@ partial interface Document {
[ChromeOnly, BinaryName="setUserHasInteracted"]
void userInteractionForTesting();
};
// Extension for permission delegation.
partial interface Document {
[Pref="permissions.delegation.enabled", ChromeOnly, Pure]
readonly attribute nsIPermissionDelegateHandler permDelegateHandler;
};

Просмотреть файл

@ -44,6 +44,7 @@ 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
@ -67,6 +68,31 @@ const DelegateInfo* PermissionDelegateHandler::GetPermissionDelegateInfo(
return nullptr;
}
NS_IMETHODIMP
PermissionDelegateHandler::MaybeUnsafePermissionDelegate(
const nsTArray<nsCString>& aTypes, bool* aMaybeUnsafe) {
*aMaybeUnsafe = false;
if (!StaticPrefs::permissions_delegation_enabled()) {
return NS_OK;
}
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,

Просмотреть файл

@ -27,6 +27,7 @@
#define PermissionDelegateHandler_h__
#include "nsISupports.h"
#include "nsIPermissionDelegateHandler.h"
class nsIPrincipal;
class nsIContentPermissionRequest;
@ -37,13 +38,16 @@ class Document;
}
} // namespace mozilla
class PermissionDelegateHandler final : nsISupports {
class PermissionDelegateHandler final : public nsIPermissionDelegateHandler {
public:
explicit PermissionDelegateHandler(mozilla::dom::Document* aDocument);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(PermissionDelegateHandler)
NS_DECL_NSIPERMISSIONDELEGATEHANDLER
explicit PermissionDelegateHandler() = default;
explicit PermissionDelegateHandler(mozilla::dom::Document* aDocument);
bool Initialize();
/*
@ -139,7 +143,7 @@ class PermissionDelegateHandler final : nsISupports {
nsIPrincipal** aResult);
private:
virtual ~PermissionDelegateHandler() = default;
~PermissionDelegateHandler() = default;
/*
* Check whether the permission is blocked by FeaturePolicy directive.

Просмотреть файл

@ -21,4 +21,10 @@ Classes = [
'constructor': 'nsPermissionManager::GetXPCOMSingleton',
'headers': ['/extensions/permissions/nsPermissionManager.h'],
},
{
'cid': '{07611dc6-bf4d-4d8a-a64b-f3a5904dddc7}',
'contract_ids': ['@mozilla.org/permissiondelegatehandler;1'],
'type': 'PermissionDelegateHandler',
'headers': ['/extensions/permissions/PermissionDelegateHandler.h'],
},
]