Bug 1644456 - Manage HTTPS-Only Mode permission with SitePermissions interface. r=necko-reviewers,pbz,dragana

Differential Revision: https://phabricator.services.mozilla.com/D79427
This commit is contained in:
julianwels 2020-07-08 12:06:02 +00:00
parent ae8dc932e1
commit c44ea283e8
14 changed files with 190 additions and 11 deletions

View File

@ -1485,7 +1485,8 @@ var gIdentityHandler = {
if (
(aPermission.id == "popup" && !isPolicyPermission) ||
aPermission.id == "autoplay-media"
aPermission.id == "autoplay-media" ||
aPermission.id == "https-only-load-insecure"
) {
let menulist = document.createXULElement("menulist");
let menupopup = document.createXULElement("menupopup");

View File

@ -47,3 +47,4 @@ permission.persistent-storage.label = Store Data in Persistent Storage
permission.canvas.label = Extract Canvas Data
permission.midi.label = Access MIDI Devices
permission.midi-sysex.label = Access MIDI Devices with SysEx Support
permission.https-only-load-insecure.label = Use insecure HTTP

View File

@ -263,6 +263,8 @@ var SitePermissions = {
PROMPT: Services.perms.PROMPT_ACTION,
ALLOW_COOKIES_FOR_SESSION: Ci.nsICookiePermission.ACCESS_SESSION,
AUTOPLAY_BLOCKED_ALL: Ci.nsIAutoplay.BLOCKED_ALL,
ALLOW_INSECURE_LOAD_FOR_SESSION:
Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION,
// Permission scopes.
SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
@ -302,9 +304,17 @@ var SitePermissions = {
if (permission.type == "canvas" && !this.resistFingerprinting) {
continue;
}
// Hide exception permission when HTTPS-Only Mode is disabled.
if (
permission.type == "https-only-load-insecure" &&
!this.httpsOnlyModeEnabled
) {
continue;
}
/* Hide persistent storage permission when extension principal
* have WebExtensions-unlimitedStorage permission. */
if (
permission.type == "persistent-storage" &&
SitePermissions.getForPrincipal(
@ -428,6 +438,12 @@ var SitePermissions = {
if (!this.resistFingerprinting) {
permissions = permissions.filter(permission => permission !== "canvas");
}
// Hide exception permission when HTTPS-Only Mode is disabled.
if (!this.httpsOnlyModeEnabled) {
permissions = permissions.filter(
permission => permission !== "https-only-load-insecure"
);
}
this._permissionsArray = permissions;
}
@ -435,7 +451,7 @@ var SitePermissions = {
},
/**
* Called when the privacy.resistFingerprinting preference changes its value.
* Called when a preference changes its value.
*
* @param {string} data
* The last argument passed to the preference change observer
@ -444,7 +460,7 @@ var SitePermissions = {
* @param {string} latest
* The latest value of the preference
*/
onResistFingerprintingChanged(data, previous, latest) {
invalidatePermissionList(data, previous, latest) {
// Ensure that listPermissions() will reconstruct its return value the next
// time it's called.
this._permissionsArray = null;
@ -651,6 +667,15 @@ var SitePermissions = {
);
}
if (state == this.ALLOW_INSECURE_LOAD_FOR_SESSION) {
if (permissionID !== "https-only-load-insecure") {
throw new Error(
"ALLOW_INSECURE_LOAD_FOR_SESSION can only be set on the https-only-load-insecure permission"
);
}
scope = this.SCOPE_SESSION;
}
// Save temporary permissions.
if (scope == this.SCOPE_TEMPORARY) {
// We do not support setting temp ALLOW for security reasons.
@ -807,6 +832,7 @@ var SitePermissions = {
case this.ALLOW:
return gStringBundle.GetStringFromName("state.multichoice.allow");
case this.ALLOW_COOKIES_FOR_SESSION:
case this.ALLOW_INSECURE_LOAD_FOR_SESSION:
return gStringBundle.GetStringFromName(
"state.multichoice.allowForSession"
);
@ -846,6 +872,7 @@ var SitePermissions = {
}
return gStringBundle.GetStringFromName("state.current.allowed");
case this.ALLOW_COOKIES_FOR_SESSION:
case this.ALLOW_INSECURE_LOAD_FOR_SESSION:
return gStringBundle.GetStringFromName(
"state.current.allowedForSession"
);
@ -1037,6 +1064,19 @@ var gPermissionObject = {
return SitePermissions.UNKNOWN;
},
},
"https-only-load-insecure": {
exactHostMatch: true,
labelID: "https-only-load-insecure",
getDefault() {
return SitePermissions.BLOCK;
},
states: [
SitePermissions.BLOCK,
SitePermissions.ALLOW_INSECURE_LOAD_FOR_SESSION,
SitePermissions.ALLOW,
],
},
};
if (!Services.prefs.getBoolPref("dom.webmidi.enabled")) {
@ -1058,5 +1098,12 @@ XPCOMUtils.defineLazyPreferenceGetter(
"resistFingerprinting",
"privacy.resistFingerprinting",
false,
SitePermissions.onResistFingerprintingChanged.bind(SitePermissions)
SitePermissions.invalidatePermissionList.bind(SitePermissions)
);
XPCOMUtils.defineLazyPreferenceGetter(
SitePermissions,
"httpsOnlyModeEnabled",
"dom.security.https_only_mode",
false,
SitePermissions.invalidatePermissionList.bind(SitePermissions)
);

View File

@ -11,6 +11,9 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const RESIST_FINGERPRINTING_ENABLED = Services.prefs.getBoolPref(
"privacy.resistFingerprinting"
);
const HTTPS_ONLY_MODE_ENABLED = Services.prefs.getBoolPref(
"dom.security.https_only_mode"
);
const MIDI_ENABLED = Services.prefs.getBoolPref("dom.webmidi.enabled");
add_task(async function testPermissionsListing() {
@ -35,6 +38,10 @@ add_task(async function testPermissionsListing() {
// is true.
expectedPermissions.push("canvas");
}
if (HTTPS_ONLY_MODE_ENABLED) {
// Exception permission should be hidden unless HTTPS-Only Mode is enabled
expectedPermissions.push("https-only-load-insecure");
}
if (MIDI_ENABLED) {
// Should remove this checking and add it as default after it is fully pref'd-on.
expectedPermissions.push("midi");
@ -193,6 +200,10 @@ add_task(async function testExactHostMatch() {
// is true.
exactHostMatched.push("canvas");
}
if (HTTPS_ONLY_MODE_ENABLED) {
// Exception permission should be hidden unless HTTPS-Only Mode is enabled
exactHostMatched.push("https-only-load-insecure");
}
if (MIDI_ENABLED) {
// WebMIDI is only pref'd on in nightly.
// Should remove this checking and add it as default after it is fully pref-on.
@ -354,6 +365,52 @@ add_task(async function testCanvasPermission() {
);
});
add_task(async function testHttpsOnlyPermission() {
let originalValue = Services.prefs.getBoolPref(
"dom.security.https_only_mode",
false
);
let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
"https://example.com"
);
SitePermissions.setForPrincipal(
principal,
"https-only-load-insecure",
SitePermissions.ALLOW
);
// Exception permission is hidden when HTTPS-Only Mode is disabled
Services.prefs.setBoolPref("dom.security.https_only_mode", false);
Assert.equal(
SitePermissions.listPermissions().indexOf("https-only-load-insecure"),
-1
);
Assert.equal(
SitePermissions.getAllByPrincipal(principal).filter(
permission => permission.id === "https-only-load-insecure"
).length,
0
);
// Exception permission should show up when HTTPS-Only Mode is enabled
Services.prefs.setBoolPref("dom.security.https_only_mode", true);
Assert.notEqual(
SitePermissions.listPermissions().indexOf("https-only-load-insecure"),
-1
);
Assert.notEqual(
SitePermissions.getAllByPrincipal(principal).filter(
permission => permission.id === "https-only-load-insecure"
).length,
0
);
// Reset everything
SitePermissions.removeFromPrincipal(principal, "https-only-load-insecure");
Services.prefs.setBoolPref("dom.security.https_only_mode", originalValue);
});
add_task(async function testFilePermissions() {
let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
"file:///example.js"

View File

@ -77,6 +77,7 @@
skin/classic/browser/notification-icons/desktop-notification.svg (../shared/notification-icons/desktop-notification.svg)
skin/classic/browser/notification-icons/drag-indicator.svg (../shared/notification-icons/drag-indicator.svg)
skin/classic/browser/notification-icons/focus-tab-by-prompt.svg (../shared/notification-icons/focus-tab-by-prompt.svg)
skin/classic/browser/notification-icons/https-only-load-insecure-blocked.svg (../shared/notification-icons/https-only-load-insecure-blocked.svg)
skin/classic/browser/notification-icons/indexedDB.svg (../shared/notification-icons/indexedDB.svg)
skin/classic/browser/notification-icons/microphone-blocked.svg (../shared/notification-icons/microphone-blocked.svg)
skin/classic/browser/notification-icons/microphone-detailed.svg (../shared/notification-icons/microphone-detailed.svg)

View File

@ -174,6 +174,10 @@
list-style-image: url(chrome://browser/skin/notification-icons/midi.svg);
}
.https-only-load-insecure-icon {
list-style-image: url(chrome://browser/skin/notification-icons/https-only-load-insecure-blocked.svg);
}
#canvas-notification-icon,
.popup-notification-icon[popupid="canvas-permissions-prompt"],
.canvas-icon {

View File

@ -0,0 +1,7 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path d="M14.71,1.32a1,1,0,0,0-1.42,0L11.48,3.1A4,4,0,0,0,4,5V7H3.5A1.5,1.5,0,0,0,2,8.48v4.11l-.71.7A1,1,0,0,0,1,14a1,1,0,0,0,1,1,1,1,0,0,0,.71-.29l12-12A1,1,0,0,0,14.71,1.32ZM6,7V5a2,2,0,0,1,4-.38L7.6,7Z" fill="context-fill" fill-opacity="context-fill-opacity"/>
<path d="M12.5,7H12L4,15h8.5A1.5,1.5,0,0,0,14,13.5v-5A1.5,1.5,0,0,0,12.5,7Z" fill="context-fill" fill-opacity="context-fill-opacity"/>
</svg>

After

Width:  |  Height:  |  Size: 706 B

View File

@ -9064,8 +9064,7 @@ nsIPrincipal* nsDocShell::GetInheritedPrincipal(
nsCOMPtr<nsIPrincipal> permissionPrincipal =
BasePrincipal::CreateContentPrincipal(aLoadState->URI(), attrs);
if (nsContentUtils::IsExactSitePermAllow(permissionPrincipal,
"https-only-mode-exception"_ns)) {
if (nsHTTPSOnlyUtils::TestHttpsOnlySitePermission(permissionPrincipal)) {
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT;
aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);

View File

@ -76,3 +76,9 @@ if CONFIG['FUZZING_INTERFACES']:
'fuzztest'
]
XPIDL_SOURCES += [
'nsIHttpsOnlyModePermission.idl',
]
XPIDL_MODULE = 'dom_security'

View File

@ -9,6 +9,8 @@
#include "nsContentUtils.h"
#include "nsHTTPSOnlyUtils.h"
#include "nsIConsoleService.h"
#include "nsIHttpsOnlyModePermission.h"
#include "nsIPermissionManager.h"
#include "nsIScriptError.h"
#include "prnetdb.h"
@ -136,6 +138,26 @@ bool nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(nsresult aError) {
NS_ERROR_FRAME_CRASHED == aError);
}
/* static */
bool nsHTTPSOnlyUtils::TestHttpsOnlySitePermission(nsIPrincipal* aPrincipal) {
if (!aPrincipal) {
// We always deny the permission if we don't have a principal.
return false;
}
nsCOMPtr<nsIPermissionManager> permMgr =
mozilla::services::GetPermissionManager();
NS_ENSURE_TRUE(permMgr, false);
uint32_t perm;
nsresult rv = permMgr->TestExactPermissionFromPrincipal(
aPrincipal, "https-only-load-insecure"_ns, &perm);
NS_ENSURE_SUCCESS(rv, false);
return perm == nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW ||
perm == nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION;
}
/* ------ Logging ------ */
/* static */

View File

@ -64,6 +64,13 @@ class nsHTTPSOnlyUtils {
bool aFromPrivateWindow,
nsIURI* aURI = nullptr);
/**
* Tests is the HTTPS-Only Mode upgrade exception is set for a given principal
* @param aPrincipal Principal to check permission for
* @return true if exempt from upgrade
*/
static bool TestHttpsOnlySitePermission(nsIPrincipal* aPrincipal);
private:
/**
* Logs localized message to either content console or browser console

View File

@ -0,0 +1,26 @@
/* 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 "nsISupports.idl"
/**
* An interface to test for cookie permissions
*/
[scriptable, uuid(73f4f039-d6ff-41a7-9eb3-00db57b0b7f4)]
interface nsIHttpsOnlyModePermission : nsISupports
{
/**
* nsIPermissionManager permission values
*/
const uint32_t LOAD_INSECURE_DEFAULT = 0;
const uint32_t LOAD_INSECURE_ALLOW = 1;
const uint32_t LOAD_INSECURE_BLOCK = 2;
/**
* additional values which do not match
* nsIPermissionManager. Keep space available to allow nsIPermissionManager to
* add values without colliding. ACCESS_SESSION is not directly returned by
* any methods on this interface.
*/
const uint32_t LOAD_INSECURE_ALLOW_SESSION = 9;
};

View File

@ -20,6 +20,7 @@
#include "mozilla/dom/ClientChannelHelper.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/ipc/IdType.h"
@ -2093,8 +2094,8 @@ DocumentLoadListener::AsyncOnChannelRedirect(
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
mChannel, getter_AddRefs(resultPrincipal));
if (NS_SUCCEEDED(rv)) {
isHttpsOnlyExempt = nsContentUtils::IsExactSitePermAllow(
resultPrincipal, "https-only-mode-exception"_ns);
isHttpsOnlyExempt =
nsHTTPSOnlyUtils::TestHttpsOnlySitePermission(resultPrincipal);
}
}

View File

@ -81,8 +81,8 @@ class AboutHttpsOnlyErrorParent extends JSWindowActorParent {
// Create exception for this website that expires with the session.
Services.perms.addFromPrincipal(
principal,
"https-only-mode-exception",
Ci.nsIPermissionManager.ALLOW_ACTION,
"https-only-load-insecure",
Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION,
Ci.nsIPermissionManager.EXPIRE_SESSION
);