mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1919558
- Part 6: Add RemotePermissionService to get default permissions from remote settings r=pbz,permissions-reviewers
This is adding the RemotePermissionService xpcom js service for the purpose of importing default permission manager entries from remote settings. This service will be initialized by the permission manager after it has read all its permissions from disk and is fully initialized. When being initialized, the service will at first get all the current default remote permissions from the remote settings client, and add them as default permissions through the `AddDefaultFromPrincipal` method added in D222650. An event listener is then also set up to keep the default entries in the permission manager in sync with remote settings. All of this is guarded behind a whitelist in the the `ALLOWED_PERMISSION_VALUES` variable, ensuring only specific permission types and values can be imported through this mechanism. Differential Revision: https://phabricator.services.mozilla.com/D222649
This commit is contained in:
parent
fe82d16c24
commit
4dfb0aa60b
@ -6,6 +6,9 @@
|
||||
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/AppShutdown.h"
|
||||
#ifdef MOZ_BACKGROUNDTASKS
|
||||
# include "mozilla/BackgroundTasks.h"
|
||||
#endif
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/ContentPrincipal.h"
|
||||
@ -2795,10 +2798,13 @@ NS_IMETHODIMP PermissionManager::Observe(nsISupports* aSubject,
|
||||
const char16_t* someData) {
|
||||
ENSURE_NOT_CHILD_PROCESS;
|
||||
|
||||
if (!nsCRT::strcmp(aTopic, "profile-do-change") && !mPermissionsFile) {
|
||||
// profile startup is complete, and we didn't have the permissions file
|
||||
// before; init the db from the new location
|
||||
InitDB(false);
|
||||
if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
|
||||
if (!mPermissionsFile) {
|
||||
// profile startup is complete, and we didn't have the permissions file
|
||||
// before; init the db from the new location
|
||||
InitDB(false);
|
||||
}
|
||||
InitRemotePermissionService();
|
||||
} else if (!nsCRT::strcmp(aTopic, "testonly-reload-permissions-from-disk")) {
|
||||
// Testing mechanism to reload all permissions from disk. Because the
|
||||
// permission manager automatically initializes itself at startup, tests
|
||||
@ -2810,6 +2816,7 @@ NS_IMETHODIMP PermissionManager::Observe(nsISupports* aSubject,
|
||||
RemoveAllFromMemory();
|
||||
CloseDB(eNone);
|
||||
InitDB(false);
|
||||
InitRemotePermissionService();
|
||||
} else if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
|
||||
PerformIdleDailyMaintenance();
|
||||
}
|
||||
@ -3256,6 +3263,33 @@ void PermissionManager::CompleteRead() {
|
||||
}
|
||||
}
|
||||
|
||||
void PermissionManager::InitRemotePermissionService() {
|
||||
// Check if this service is disabled by pref, and abort if it is.
|
||||
if (!StaticPrefs::permissions_manager_remote_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Also abort if we are in a background task. We do not want to call remote
|
||||
// settings there, because we do not want to pollute the background task
|
||||
// profile, and because we don't need the remote permissions there anyways.
|
||||
#ifdef MOZ_BACKGROUNDTASKS
|
||||
if (BackgroundTasks::IsBackgroundTaskMode()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
NS_DispatchToCurrentThreadQueue(
|
||||
NS_NewRunnableFunction(
|
||||
"RemotePermissionService::Init",
|
||||
[&] {
|
||||
nsCOMPtr<nsIRemotePermissionService> remotePermissionService =
|
||||
do_GetService(NS_REMOTEPERMISSIONSERVICE_CONTRACTID);
|
||||
NS_ENSURE_TRUE_VOID(remotePermissionService);
|
||||
remotePermissionService->Init();
|
||||
}),
|
||||
EventQueuePriority::Idle);
|
||||
}
|
||||
|
||||
void PermissionManager::MaybeAddReadEntryFromMigration(
|
||||
const nsACString& aOrigin, const nsCString& aType, uint32_t aPermission,
|
||||
uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsIAsyncShutdown.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIRemotePermissionService.h"
|
||||
#include "nsWeakReference.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIURI.h"
|
||||
@ -645,6 +646,10 @@ class PermissionManager final : public nsIPermissionManager,
|
||||
|
||||
void CompleteMigrations();
|
||||
|
||||
// Initialize service used for importing default permissions from remote
|
||||
// settings
|
||||
void InitRemotePermissionService();
|
||||
|
||||
bool mMemoryOnlyDB;
|
||||
|
||||
nsTHashtable<PermissionHashKey> mPermissionTable;
|
||||
|
187
extensions/permissions/RemotePermissionService.sys.mjs
Normal file
187
extensions/permissions/RemotePermissionService.sys.mjs
Normal file
@ -0,0 +1,187 @@
|
||||
/* 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/. */
|
||||
|
||||
import { RemoteSettings } from "resource://services-settings/remote-settings.sys.mjs";
|
||||
|
||||
const COLLECTION_NAME = "remote-permissions";
|
||||
|
||||
/**
|
||||
* Allowlist of permission types and values allowed to be set through remote
|
||||
* settings. In this map, the key is the permission type, while the value is an
|
||||
* array of allowed permission values/capabilities allowed to be set. Possible
|
||||
* values for most permissions are:
|
||||
*
|
||||
* - Ci.nsIPermissionManager.ALLOW_ACTION
|
||||
* - Ci.nsIPermissionManager.DENY_ACTION
|
||||
* - Ci.nsIPermissionManager.PROMPT_ACTION
|
||||
* - "*" (Allows all values)
|
||||
*
|
||||
* Permission types with custom permission values (like
|
||||
* https-only-load-insecure) may include different values. Only change this
|
||||
* value with a review from #permissions-reviewers.
|
||||
*/
|
||||
const ALLOWED_PERMISSION_VALUES = {};
|
||||
|
||||
/**
|
||||
* See nsIRemotePermissionService.idl
|
||||
*/
|
||||
export class RemotePermissionService {
|
||||
classId = Components.ID("{a4b1b3b1-b68a-4129-aa2f-eb086162a8c7}");
|
||||
QueryInterface = ChromeUtils.generateQI(["nsIRemotePermissionService"]);
|
||||
|
||||
#rs = RemoteSettings(COLLECTION_NAME);
|
||||
#onSyncCallback = null;
|
||||
#initialized = Promise.withResolvers();
|
||||
#allowedPermissionValues = ALLOWED_PERMISSION_VALUES;
|
||||
|
||||
/**
|
||||
* Asynchonously import all default permissions from remote settings into the
|
||||
* permission manager. Also, if not already done, set up remote settings event
|
||||
* listener to keep remote permissions in sync.
|
||||
*/
|
||||
async init() {
|
||||
try {
|
||||
if (Services.startup.shuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!Services.prefs.getBoolPref("permissions.manager.remote.enabled", false)
|
||||
) {
|
||||
throw Error(
|
||||
"Tried to initialize remote permission service despite being disabled by pref"
|
||||
);
|
||||
}
|
||||
|
||||
let remotePermissions = await this.#rs.get();
|
||||
for (const permission of remotePermissions) {
|
||||
this.#addDefaultPermission(permission);
|
||||
}
|
||||
|
||||
// Init could be called multiple times if the permission manager is
|
||||
// reinitializing itself due to "testonly-reload-permissions-from-disk"
|
||||
// being emitted. In that case, we don't shouldn't set up the RS listener
|
||||
// again. We may also land in that situtation when "profile-do-change" is
|
||||
// emitted.
|
||||
if (!this.#onSyncCallback) {
|
||||
this.#onSyncCallback = this.#onSync.bind(this);
|
||||
this.#rs.on("sync", this.#onSyncCallback);
|
||||
}
|
||||
|
||||
this.#initialized.resolve();
|
||||
} catch (e) {
|
||||
this.#initialized.reject(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
get isInitialized() {
|
||||
return this.#initialized.promise;
|
||||
}
|
||||
|
||||
get testAllowedPermissionValues() {
|
||||
return this.#allowedPermissionValues;
|
||||
}
|
||||
|
||||
set testAllowedPermissionValues(allowedPermissionValues) {
|
||||
Cu.crashIfNotInAutomation();
|
||||
this.#allowedPermissionValues = allowedPermissionValues;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jsdoc/require-param
|
||||
/**
|
||||
* Callback for the "sync" event from remote settings. This function will
|
||||
* receive the created, updated and deleted permissions from remote settings,
|
||||
* and will update the permission manager accordingly.
|
||||
*/
|
||||
#onSync({ data: { created = [], updated = [], deleted = [] } }) {
|
||||
const toBeDeletedPermissions = [
|
||||
// Delete permissions that got deleted in remote settings.
|
||||
...deleted,
|
||||
// If an existing entry got updated in remote settings, but the origin or
|
||||
// type changed, we can not just update it, as permissions are identified
|
||||
// by origin and type in the permission manager. Instead, we need to
|
||||
// remove the old permission and add a new one.
|
||||
...updated
|
||||
.filter(
|
||||
({
|
||||
old: { origin: oldOrigin, type: oldType },
|
||||
new: { origin: newOrigin, type: newType },
|
||||
}) => oldOrigin != newOrigin || oldType != newType
|
||||
)
|
||||
.map(({ old }) => old),
|
||||
];
|
||||
|
||||
const toBeAddedPermissions = [
|
||||
// Add newly created permissions.
|
||||
...created,
|
||||
// "Add" permissions updated in remote settings (the permission manager
|
||||
// will automatically update the existing default permission instead of
|
||||
// creating a new one if the permission origin and type match).
|
||||
...updated.map(({ new: newPermission }) => newPermission),
|
||||
// Delete permissions by "adding" them with value UNKNOWN_ACTION.
|
||||
...toBeDeletedPermissions.map(({ origin, type }) => ({
|
||||
origin,
|
||||
type,
|
||||
capability: Ci.nsIPermissionManager.UNKNOWN_ACTION,
|
||||
})),
|
||||
];
|
||||
|
||||
for (const permission of toBeAddedPermissions) {
|
||||
this.#addDefaultPermission(permission);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a permission type and value is allowed to be set through remote
|
||||
* settings, based on the ALLOWED_PERMISSION_VALUES allowlist.
|
||||
*
|
||||
* @param {string} type Permission type to check
|
||||
* @param {string} capability Permission capability to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
#isAllowed(type, capability) {
|
||||
if (!this.#allowedPermissionValues[type]) {
|
||||
if (this.#allowedPermissionValues["*"]) {
|
||||
this.#allowedPermissionValues[type] =
|
||||
this.#allowedPermissionValues["*"];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
this.#allowedPermissionValues[type].includes("*") ||
|
||||
this.#allowedPermissionValues[type].includes(capability) ||
|
||||
capability === Ci.nsIPermissionManager.UNKNOWN_ACTION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default permission to the permission manager.
|
||||
*
|
||||
* @param {object} permission The permission to add
|
||||
* @param {string} permission.origin Origin string of the permission
|
||||
* @param {string} permission.type Type of the permission
|
||||
* @param {number} permission.capability Capability of the permission
|
||||
*/
|
||||
#addDefaultPermission({ origin, type, capability }) {
|
||||
if (!this.#isAllowed(type, capability)) {
|
||||
console.error(
|
||||
`Remote Settings contain default permission of disallowed type '${type}' with value '${capability}' for origin '${origin}', skipping import`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let principal = Services.scriptSecurityManager.createContentPrincipal(
|
||||
Services.io.newURI(origin),
|
||||
{}
|
||||
);
|
||||
Services.perms.addDefaultFromPrincipal(principal, type, capability);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -22,4 +22,12 @@ Classes = [
|
||||
'type': 'PermissionDelegateHandler',
|
||||
'headers': ['/extensions/permissions/PermissionDelegateHandler.h'],
|
||||
},
|
||||
{
|
||||
'cid': '{a4b1b3b1-b68a-4129-aa2f-eb086162a8c7}',
|
||||
'contract_ids': ['@mozilla.org/remote-permission-service;1'],
|
||||
'esModule': 'resource://gre/modules/RemotePermissionService.sys.mjs',
|
||||
'constructor': 'RemotePermissionService',
|
||||
'singleton': True,
|
||||
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
|
||||
},
|
||||
]
|
||||
|
@ -27,6 +27,16 @@ XPCOM_MANIFESTS += [
|
||||
"components.conf",
|
||||
]
|
||||
|
||||
XPIDL_MODULE = "permissions"
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
"nsIRemotePermissionService.idl",
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
"RemotePermissionService.sys.mjs",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"/caps",
|
||||
]
|
||||
|
41
extensions/permissions/nsIRemotePermissionService.idl
Normal file
41
extensions/permissions/nsIRemotePermissionService.idl
Normal file
@ -0,0 +1,41 @@
|
||||
/* 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"
|
||||
|
||||
interface nsIPrincipal;
|
||||
|
||||
/**
|
||||
* Service to import default permissions from Remote Settings. Will be
|
||||
* initialized by permission manager after it itself has completed its
|
||||
* initialization, and will then import default permissions from Remote Settings
|
||||
* asynchronously. This also means default permissions aren't guranteed to be
|
||||
* available directly after startup.
|
||||
*/
|
||||
[scriptable, uuid(a4b1b3b1-b68a-4129-aa2f-eb086162a8c7)]
|
||||
interface nsIRemotePermissionService : nsISupports {
|
||||
/**
|
||||
* Asynchonously import all default permissions from remote settings into
|
||||
* the permission manager. Also, if not already done, set up remote settings
|
||||
* event listener to keep remote permissions in sync.
|
||||
*/
|
||||
void init();
|
||||
/**
|
||||
* Promise that is resolved when the remote permission service has been
|
||||
* fully initialized, meaning all intial permissions have been imported and
|
||||
* the remote settings sync event listener has been set up. If any errors
|
||||
* are encountered during inizialization, this promise will be rejected.
|
||||
*/
|
||||
readonly attribute Promise isInitialized;
|
||||
/**
|
||||
* Allowed permission types and values to be set through remote settings.
|
||||
* See RemotePermissionService.sys.mjs for further documentation. Exposed
|
||||
* only for testing purposes.
|
||||
*/
|
||||
attribute jsval testAllowedPermissionValues;
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_REMOTEPERMISSIONSERVICE_CONTRACTID "@mozilla.org/remote-permission-service;1"
|
||||
%}
|
@ -14565,6 +14565,13 @@
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Whether default permissions should be imported from remote settings in
|
||||
# addition to importing them from browser/app/permissions.
|
||||
- name: permissions.manager.remote.enabled
|
||||
type: bool
|
||||
value: @IS_EARLY_BETA_OR_EARLIER@
|
||||
mirror: always
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Prefs starting with "places."
|
||||
#---------------------------------------------------------------------------
|
||||
|
4
services/settings/dumps/main/remote-permissions.json
Normal file
4
services/settings/dumps/main/remote-permissions.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"data": [],
|
||||
"timestamp": 1730278443662
|
||||
}
|
Loading…
Reference in New Issue
Block a user